qubes: rework qubes.xml handling

Now it more closely aligns with old core2 semantics. New semantics will
have to wait for qubesd.
This commit is contained in:
Wojtek Porczyk 2015-06-23 22:27:20 +02:00
parent 7c8cc14bec
commit d5f2e6ab74
2 changed files with 62 additions and 72 deletions

View File

@ -55,6 +55,8 @@ import docutils.core
import docutils.io
import lxml.etree
import qubes.config
import qubes.ext
@ -1110,7 +1112,7 @@ class Qubes(PropertyHolder):
doc='Which kernel to use when not overriden in VM')
def __init__(self, store='/var/lib/qubes/qubes.xml'):
def __init__(self, store=None, load=True):
super(Qubes, self).__init__(xml=None)
#: logger instance for logging global messages
@ -1132,61 +1134,36 @@ class Qubes(PropertyHolder):
#: Information about host system
self.host = QubesHost(self)
self._store = store
self._storefd = None
self.load()
self._store = store if store is not None else os.path.join(
qubes.config.system_path['qubes_base_dir'],
qubes.config.system_path['qubes_store_filename'])
if load:
self.load()
def _open_store(self):
def load(self):
'''Open qubes.xml
This method takes care of creation of the store when it does not exist.
:raises OSError: on failure
:throws EnvironmentError: failure on parsing store
:throws xml.parsers.expat.ExpatError: failure on parsing store
:raises lxml.etree.XMLSyntaxError: on syntax error in qubes.xml
'''
if self._storefd is not None:
return
try:
fd = os.open(self._store,
os.O_RDWR | os.O_CREAT | os.O_EXCL | 0o660)
parsexml = False
except OSError as e:
if e.errno != errno.EEXIST:
raise
# file does exist
fd = os.open(self._store, os.O_RDWR)
parsexml = True
self._storefd = os.fdopen(fd, 'r+b')
fd = os.open(self._store, os.O_RDWR) # no O_CREAT
fh = os.fdopen(fd, 'rb')
if os.name == 'posix':
fcntl.lockf(self._storefd, fcntl.LOCK_EX)
fcntl.lockf(fh, fcntl.LOCK_EX)
elif os.name == 'nt':
# pylint: disable=protected-access
win32file.LockFileEx(
win32file._get_osfhandle(self._storefd.fileno()),
win32file._get_osfhandle(fh.fileno()),
win32con.LOCKFILE_EXCLUSIVE_LOCK,
0, -0x10000,
pywintypes.OVERLAPPED())
if parsexml:
self.xml = lxml.etree.parse(self._storefd)
# else: it will remain None, as set by PropertyHolder
def load(self):
'''
:throws EnvironmentError: failure on parsing store
:throws xml.parsers.expat.ExpatError: failure on parsing store
'''
self._open_store()
if self.xml is None:
self._init()
return
self.xml = lxml.etree.parse(fh)
# stage 1: load labels
for node in self.xml.xpath('./labels/label'):
@ -1235,31 +1212,9 @@ class Qubes(PropertyHolder):
vm.events_enabled = True
vm.fire_event('domain-loaded')
def _init(self):
self._open_store()
self.labels = {
1: Label(1, '0xcc0000', 'red'),
2: Label(2, '0xf57900', 'orange'),
3: Label(3, '0xedd400', 'yellow'),
4: Label(4, '0x73d216', 'green'),
5: Label(5, '0x555753', 'gray'),
6: Label(6, '0x3465a4', 'blue'),
7: Label(7, '0x75507b', 'purple'),
8: Label(8, '0x000000', 'black'),
}
self.domains.add(qubes.vm.adminvm.AdminVM(
self, None, qid=0, name='dom0'))
def __del__(self):
# intentionally do not call explicit unlock to not unlock the file
# before all buffers are flushed
if self._storefd is not None:
self._storefd.close()
self._storefd = None
# intentionally do not call explicit unlock
fh.close()
del fh
def __xml__(self):
@ -1278,14 +1233,49 @@ class Qubes(PropertyHolder):
def save(self):
'''Save all data to qubes.xml
:throws EnvironmentError: failure on saving
'''
self._storefd.seek(0)
self._storefd.truncate()
fh = tempfile.NamedTemporaryFile(prefix=self._store, delete=False)
if os.name == 'posix':
fcntl.lockf(fh, fcntl.LOCK_EX)
elif os.name == 'nt':
overlapped = pywintypes.OVERLAPPED()
win32file.LockFileEx(
win32file._get_osfhandle(fh.fileno()),
win32con.LOCKFILE_EXCLUSIVE_LOCK, 0, -0x10000, overlapped)
lxml.etree.ElementTree(self.__xml__()).write(
self._storefd, encoding='utf-8', pretty_print=True)
self._storefd.sync()
os.chmod(self._store, 0o660)
os.chown(self._store, -1, grp.getgrnam('qubes').gr_gid)
fh, encoding='utf-8', pretty_print=True)
fh.flush()
os.chmod(fh.name, 0660)
os.chown(fh.name, -1, grp.getgrnam('qubes').gr_gid)
os.rename(fh.name, self._store)
# intentionally do not call explicit unlock to not unlock the file
# before all buffers are flushed
fh.close()
del fh
@classmethod
def create_empty_store(cls, *args, **kwargs):
self = cls(*args, load=False, **kwargs)
self.labels = {
1: Label(1, '0xcc0000', 'red'),
2: Label(2, '0xf57900', 'orange'),
3: Label(3, '0xedd400', 'yellow'),
4: Label(4, '0x73d216', 'green'),
5: Label(5, '0x555753', 'gray'),
6: Label(6, '0x3465a4', 'blue'),
7: Label(7, '0x75507b', 'purple'),
8: Label(8, '0x000000', 'black'),
}
self.domains.add(
qubes.vm.adminvm.AdminVM(self, None, qid=0, name='dom0'))
self.save()
return self
def xml_labels(self):

View File

@ -354,7 +354,7 @@ class TC_90_Qubes(qubes.tests.QubesTestCase):
os.unlink('/tmp/qubestest.xml')
except:
pass
app = qubes.Qubes('/tmp/qubestest.xml')
app = qubes.Qubes.create_empty_store('/tmp/qubestest.xml')
@qubes.tests.skipUnlessGit
def test_900_example_xml_in_doc(self):