From fb7bd6823a434c81fe7f9913db05124dfba770b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Thu, 9 Mar 2017 02:42:05 +0100 Subject: [PATCH] mgmt: implement storage-related methods QubesOS/qubes-issues#2622 --- qubes/mgmt.py | 152 ++++++++++++++++++++++++++++++++++++++ qubes/storage/__init__.py | 9 +++ 2 files changed, 161 insertions(+) diff --git a/qubes/mgmt.py b/qubes/mgmt.py index ae617787..b7631a43 100644 --- a/qubes/mgmt.py +++ b/qubes/mgmt.py @@ -24,8 +24,10 @@ Qubes OS Management API import asyncio import reprlib +import string import qubes.vm.qubesvm +import qubes.storage class ProtocolRepr(reprlib.Repr): @@ -194,3 +196,153 @@ class QubesMgmt(object): self.fire_event_for_permission() delattr(self.dest, self.arg) + + @asyncio.coroutine + def vm_volume_list(self, untrusted_payload): + assert not self.arg + assert not untrusted_payload + del untrusted_payload + + volume_names = self.fire_event_for_filter(self.dest.volumes.keys()) + return ''.join('{}\n'.format(name) for name in volume_names) + + @asyncio.coroutine + def vm_volume_info(self, untrusted_payload): + assert self.arg in self.dest.volumes.keys() + assert not untrusted_payload + del untrusted_payload + + self.fire_event_for_permission() + + volume = self.dest.volumes[self.arg] + # properties defined in API + volume_properties = [ + 'pool', 'vid', 'size', 'usage', 'rw', 'internal', 'source', + 'save_on_stop', 'snap_on_start'] + return ''.join('{}={}\n'.format(key, getattr(volume, key)) for key in + volume_properties) + + @asyncio.coroutine + def vm_volume_listsnapshots(self, untrusted_payload): + assert self.arg in self.dest.volumes.keys() + assert not untrusted_payload + del untrusted_payload + + self.fire_event_for_permission() + + volume = self.dest.volumes[self.arg] + return ''.join('{}\n'.format(revision) for revision in volume.revisions) + + @asyncio.coroutine + def vm_volume_revert(self, untrusted_payload): + assert self.arg in self.dest.volumes.keys() + untrusted_revision = untrusted_payload.decode('ascii').strip() + del untrusted_payload + + volume = self.dest.volumes[self.arg] + snapshots = volume.revisions + assert untrusted_revision in snapshots + revision = untrusted_revision + + self.fire_event_for_permission(revision=revision) + + self.dest.storage.get_pool(volume).revert(revision) + + @asyncio.coroutine + def vm_volume_resize(self, untrusted_payload): + assert self.arg in self.dest.volumes.keys() + untrusted_size = untrusted_payload.decode('ascii').strip() + del untrusted_payload + assert untrusted_size.isdigit() # only digits, forbid '-' too + assert len(untrusted_size) <= 20 # limit to about 2^64 + + size = int(untrusted_size) + + self.fire_event_for_permission(size=size) + + self.dest.storage.resize(self.arg, size) + + @asyncio.coroutine + def pool_list(self, untrusted_payload): + assert not self.arg + assert self.dest.name == 'dom0' + assert not untrusted_payload + del untrusted_payload + + pools = self.fire_event_for_filter(self.app.pools) + + return ''.join('{}\n'.format(pool) for pool in pools) + + @asyncio.coroutine + def pool_listdrivers(self, untrusted_payload): + assert self.dest.name == 'dom0' + assert not self.arg + assert not untrusted_payload + del untrusted_payload + + drivers = self.fire_event_for_filter(qubes.storage.pool_drivers()) + + return ''.join('{} {}\n'.format( + driver, + ' '.join(qubes.storage.driver_parameters(driver))) + for driver in drivers) + + @asyncio.coroutine + def pool_info(self, untrusted_payload): + assert self.dest.name == 'dom0' + assert self.arg in self.app.pools.keys() + assert not untrusted_payload + del untrusted_payload + + pool = self.app.pools[self.arg] + + self.fire_event_for_permission(pool=pool) + + return ''.join('{}={}\n'.format(prop, val) + for prop, val in sorted(pool.config.items())) + + @asyncio.coroutine + def pool_add(self, untrusted_payload): + assert self.dest.name == 'dom0' + drivers = qubes.storage.pool_drivers() + assert self.arg in drivers + untrusted_pool_config = untrusted_payload.decode('ascii').splitlines() + del untrusted_payload + assert all(('=' in line) for line in untrusted_pool_config) + # pairs of (option, value) + untrusted_pool_config = [line.split('=', 1) + for line in untrusted_pool_config] + # reject duplicated options + assert len(set(x[0] for x in untrusted_pool_config)) == \ + len([x[0] for x in untrusted_pool_config]) + # and convert to dict + untrusted_pool_config = dict(untrusted_pool_config) + + assert 'name' in untrusted_pool_config + untrusted_pool_name = untrusted_pool_config.pop('name') + allowed_chars = string.ascii_letters + string.digits + '-_.' + assert all(c in allowed_chars for c in untrusted_pool_name) + pool_name = untrusted_pool_name + assert pool_name not in self.app.pools + + driver_parameters = qubes.storage.driver_parameters(self.arg) + assert all(key in driver_parameters for key in untrusted_pool_config) + + # option names validated, validation of option values is delegated to + # extension (through events mechanism) + self.fire_event_for_permission(name=pool_name, + untrusted_pool_config=untrusted_pool_config) + pool_config = untrusted_pool_config + + self.app.add_pool(name=pool_name, driver=self.arg, **pool_config) + + @asyncio.coroutine + def pool_remove(self, untrusted_payload): + assert self.dest.name == 'dom0' + assert self.arg in self.app.pools.keys() + assert not untrusted_payload + del untrusted_payload + + self.fire_event_for_permission() + + self.app.remove_pool(self.arg) \ No newline at end of file diff --git a/qubes/storage/__init__.py b/qubes/storage/__init__.py index da57401b..1f8aa44a 100644 --- a/qubes/storage/__init__.py +++ b/qubes/storage/__init__.py @@ -635,6 +635,15 @@ def pool_drivers(): for ep in pkg_resources.iter_entry_points(STORAGE_ENTRY_POINT)] +def driver_parameters(name): + ''' Get __init__ parameters from a driver with out `self` & `name`. ''' + init_function = qubes.utils.get_entry_point_one( + qubes.storage.STORAGE_ENTRY_POINT, name).__init__ + params = init_function.func_code.co_varnames + ignored_params = ['self', 'name'] + return [p for p in params if p not in ignored_params] + + def isodate(seconds=time.time()): ''' Helper method which returns an iso date ''' return datetime.utcfromtimestamp(seconds).isoformat("T")