From 48f78dfbc8c6ecacc7413a18d12385bb08328b43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Wed, 30 Nov 2016 23:35:44 +0100 Subject: [PATCH 01/12] tests: check if snap_on_start=True volumes are not persistent Content should be reset back to base volume at each VM startup. Disposable VMs depend on this behaviour. QubesOS/qubes-issues#2256 --- qubes/tests/integ/storage.py | 46 ++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/qubes/tests/integ/storage.py b/qubes/tests/integ/storage.py index e7f1c7c9..4fbb92fd 100644 --- a/qubes/tests/integ/storage.py +++ b/qubes/tests/integ/storage.py @@ -211,6 +211,52 @@ class StorageTestMixin(qubes.tests.SystemTestsMixin): self.assertNotEqual(p.returncode, 0, 'origin changes not visible in snapshot: {}'.format(stdout)) + def test_004_snapshot_non_persistent(self): + '''Test snapshot volume non-persistence''' + size = 128 * 1024 * 1024 + volume_config = { + 'pool': self.pool.name, + 'size': size, + 'internal': False, + 'save_on_stop': True, + 'rw': True, + } + testvol = self.vm1.storage.init_volume('testvol', volume_config) + self.vm1.storage.get_pool(testvol).create(testvol) + volume_config = { + 'pool': self.pool.name, + 'size': size, + 'internal': False, + 'snap_on_start': True, + 'source': testvol.vid, + 'rw': True, + } + testvol_snap = self.vm2.storage.init_volume('testvol', volume_config) + self.vm2.storage.get_pool(testvol_snap).create(testvol_snap) + self.app.save() + self.vm2.start() + + p = self.vm2.run( + 'head -c {} /dev/zero | diff -q /dev/xvde -'.format(size), + user='root', passio_popen=True) + stdout, _ = p.communicate() + self.assertEqual(p.returncode, 0, + 'snapshot image not clean: {}'.format(stdout)) + + self.vm2.run('echo test123 > /dev/xvde && sync', user='root', wait=True) + p.wait() + self.assertEqual(p.returncode, 0, + 'Write to read-write snapshot volume failed') + self.vm2.shutdown(wait=True) + self.vm2.start() + p = self.vm2.run( + 'head -c {} /dev/zero 2>&1 | diff -q /dev/xvde - 2>&1'.format(size), + user='root', passio_popen=True) + stdout, _ = p.communicate() + self.assertEqual(p.returncode, 0, + 'changes on snapshot survived VM restart: {}'.format( + stdout)) + class StorageFile(StorageTestMixin, qubes.tests.QubesTestCase): def init_pool(self): From b89689e27847dd6de2c917788bd37bc10a58185c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Wed, 30 Nov 2016 23:39:04 +0100 Subject: [PATCH 02/12] storage: implement two-layers of dm-snapshot in block-snapshot script Have dm-snapshot of dm-snapshot. The first layer is to "cache" changes done by base volume holder (TemplateVM in case of root.img), the second layer is to hold changes do by snapshot volume holder (AppVM in case of root.img). In case of Linux VMs the second layer is normally done inside of VM (original volume is exposed read-only). But this does not work for non-Linux VMs, orr even Linux but without qubes-specific startup scripts. This is first part of the change - actual construction of two layers of dm-snapshot, not plugged in to core scripts yet. QubesOS/qubes-issues#2256 --- linux/system-config/block-snapshot | 69 +++++++++++++++++++++++++----- 1 file changed, 58 insertions(+), 11 deletions(-) diff --git a/linux/system-config/block-snapshot b/linux/system-config/block-snapshot index 840753e6..d35eed2f 100755 --- a/linux/system-config/block-snapshot +++ b/linux/system-config/block-snapshot @@ -53,14 +53,21 @@ get_dev() { } get_dm_snapshot_name() { + local base cow cow2 + base=$1 cow=$2 + cow2=$3 - echo snapshot-$(stat -c '%D:%i' "$base")-$(stat -c '%D:%i' "$cow") + name="snapshot-$(stat -c '%D:%i' "$base")-$(stat -c '%D:%i' "$cow")" + if [ -n "$cow2" ]; then + name="$name-$(stat -c '%D:%i' "$cow2")" + fi + echo "$name" } create_dm_snapshot() { - local base_dev cow_dev base_sz + local base_dev cow_dev base_sz base cow dm_devname dm_devname=$1 base=$2 @@ -77,7 +84,7 @@ create_dm_snapshot() { } create_dm_snapshot_origin() { - local base_dev base_sz + local base_dev base_sz dm_devname base dm_devname=$1 base=$2 @@ -103,8 +110,14 @@ case "$command" in fi echo $p > "$HOTPLUG_STORE-params" echo $t > "$HOTPLUG_STORE-type" - base=${p/:*/} - cow=${p/*:/} + base=${p%%:*} + cow=${p#*:} + cow2=${p##*:} + if [ "$cow" != "$cow2" ]; then + cow=${cow%:*} + else + cow2="" + fi if [ -L "$base" ]; then base=$(readlink -f "$base") || fatal "$base link does not exist." @@ -114,6 +127,10 @@ case "$command" in cow=$(readlink -f "$cow") || fatal "$cow link does not exist." fi + if [ -L "$cow2" ]; then + cow2=$(readlink -f "$cow2") || fatal "$cow2 link does not exist." + fi + # first ensure that snapshot device exists (to write somewhere changes from snapshot-origin) dm_devname=$(get_dm_snapshot_name "$base" "$cow") @@ -122,6 +139,12 @@ case "$command" in # prepare snapshot device create_dm_snapshot $dm_devname "$base" "$cow" + if [ -n "$cow2" ]; then + dm_devname_full=$(get_dm_snapshot_name "$base" "$cow" "$cow2") + create_dm_snapshot "$dm_devname_full" "/dev/mapper/$dm_devname" "$cow2" + dm_devname="$dm_devname_full" + fi + if [ "$t" == "snapshot" ]; then #that's all for snapshot, store name of prepared device xenstore_write "$XENBUS_PATH/node" "/dev/mapper/$dm_devname" @@ -152,8 +175,14 @@ case "$command" in case $t in snapshot|origin) p=$3 - base=${p/:*/} - cow=${p/*:/} + base=${p%%:*} + cow=${p#*:} + cow2=${p##*:} + if [ "$cow" != "$cow2" ]; then + cow=${cow%:*} + else + cow2="" + fi if [ -L "$base" ]; then base=$(readlink -f "$base") || fatal "$base link does not exist." @@ -163,6 +192,10 @@ case "$command" in cow=$(readlink -f "$cow") || fatal "$cow link does not exist." fi + if [ -L "$cow2" ]; then + cow2=$(readlink -f "$cow2") || fatal "$cow2 link does not exist." + fi + # first ensure that snapshot device exists (to write somewhere changes from snapshot-origin) dm_devname=$(get_dm_snapshot_name "$base" "$cow") @@ -171,6 +204,12 @@ case "$command" in # prepare snapshot device create_dm_snapshot $dm_devname "$base" "$cow" + if [ -n "$cow2" ]; then + dm_devname_full=$(get_dm_snapshot_name "$base" "$cow" "$cow2") + create_dm_snapshot "$dm_devname_full" "/dev/mapper/$dm_devname" "$cow2" + dm_devname="$dm_devname_full" + fi + if [ "$t" == "snapshot" ]; then #that's all for snapshot, store name of prepared device echo "/dev/mapper/$dm_devname" @@ -232,7 +271,7 @@ case "$command" in fi # get list of used (loop) devices - deps="$(dmsetup deps $node | cut -d: -f2 | sed -e 's#(7, \([0-9]\+\))#/dev/loop\1#g')" + deps="$(dmsetup deps $node -o blkdevname | cut -d: -f2 | sed -e 's#(\([a-z0-9-]\+\))#/dev/\1#g')" # if this is origin if [ "${node/origin/}" != "$node" ]; then @@ -241,7 +280,8 @@ case "$command" in use_count=$(dmsetup info $snap|grep Open|awk '{print $3}') if [ "$use_count" -eq 0 ]; then # unused snapshot - remove it - deps="$deps $(dmsetup deps $snap | cut -d: -f2 | sed -e 's#(7, \([0-9]\+\))#/dev/loop\1#g')" + deps="$deps $(dmsetup deps $snap -o blkdevname | cut -d: -f2 |\ + sed -e 's#(\([a-z0-9-]\+\))#/dev/\1#g')" log debug "Removing $snap" dmsetup remove $snap fi @@ -265,11 +305,18 @@ case "$command" in dmsetup remove $node fi - # try to free loop devices + # try to free unused devices for dev in $deps; do if [ -b "$dev" ]; then log debug "Removing $dev" - losetup -d $dev 2> /dev/null || true + case $dev in + /dev/loop*) + losetup -d $dev 2> /dev/null || true + ;; + /dev/dm-*) + dmsetup remove $dev 2> /dev/null || true + ;; + esac fi done From 01aedb7f18d05bb27a3aa58cc503f6b52db0c489 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Thu, 1 Dec 2016 00:10:37 +0100 Subject: [PATCH 03/12] storage: fix handling snap_on_start=True file volumes Use the right cow image and apply the second layer to provide read-write access. The correct setup is: - base image + base cow -> read-only snapshot (base changes "cached" until committed) - read-only snapshot + VM cow -> read-write snapshot (changes discarded after VM shutdown) This way, even VM without Qubes-specific startup scripts will can benefit from Template VMs, while VMs with Qubes-specific startup scripts may still see original root.img content (for possible signature verification, when storage domain got implemented). QubesOS/qubes-issues#2256 --- qubes/storage/file.py | 37 +++++++++++++++++++++++++++---------- qubes/tests/storage_file.py | 2 +- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/qubes/storage/file.py b/qubes/storage/file.py index dd217643..1f939b0f 100644 --- a/qubes/storage/file.py +++ b/qubes/storage/file.py @@ -235,21 +235,34 @@ class FilePool(qubes.storage.Pool): create_dir_if_not_exists(vm_templates_path) def start(self, volume): - if volume._is_snapshot or volume._is_origin: - _check_path(volume.path) - try: - _check_path(volume.path_cow) - except qubes.storage.StoragePoolException: - create_sparse_file(volume.path_cow, volume.size) - _check_path(volume.path_cow) - elif volume._is_volatile: + if volume._is_volatile: self.reset(volume) + else: + _check_path(volume.path) + if volume.snap_on_start: + if not volume.save_on_stop: + # make sure previous snapshot is removed - even if VM + # shutdown routing wasn't called (power interrupt or so) + _remove_if_exists(volume.path_cow) + try: + _check_path(volume.path_cow) + except qubes.storage.StoragePoolException: + create_sparse_file(volume.path_cow, volume.size) + _check_path(volume.path_cow) + if hasattr(volume, 'path_source_cow'): + try: + _check_path(volume.path_source_cow) + except qubes.storage.StoragePoolException: + create_sparse_file(volume.path_source_cow, volume.size) + _check_path(volume.path_source_cow) return volume def stop(self, volume): if volume.save_on_stop: self.commit(volume) - elif volume._is_volatile: + elif volume.snap_on_start: + _remove_if_exists(volume.path_cow) + else: _remove_if_exists(volume.path) return volume @@ -316,6 +329,8 @@ class FileVolume(qubes.storage.Volume): if self._is_snapshot: self.path = os.path.join(self.dir_path, self.source + '.img') img_name = self.source + '-cow.img' + self.path_source_cow = os.path.join(self.dir_path, img_name) + img_name = self.vid + '-cow.img' self.path_cow = os.path.join(self.dir_path, img_name) elif self._is_volume or self._is_volatile: self.path = os.path.join(self.dir_path, self.vid + '.img') @@ -347,6 +362,8 @@ class FileVolume(qubes.storage.Volume): the libvirt XML template as . ''' path = self.path + if self._is_snapshot: + path += ":" + self.path_source_cow if self._is_origin or self._is_snapshot: path += ":" + self.path_cow return qubes.devices.BlockDevice(path, self.name, self.script, self.rw, @@ -380,7 +397,7 @@ class FileVolume(qubes.storage.Volume): def _is_origin(self): ''' Internal helper. Useful for differentiating volume handling ''' # pylint: disable=line-too-long - return not self.snap_on_start and self.save_on_stop and self.revisions_to_keep > 0 # NOQA + return self.save_on_stop and self.revisions_to_keep > 0 # NOQA @property def _is_snapshot(self): diff --git a/qubes/tests/storage_file.py b/qubes/tests/storage_file.py index e0562be2..1f0547ad 100644 --- a/qubes/tests/storage_file.py +++ b/qubes/tests/storage_file.py @@ -216,7 +216,7 @@ class TC_01_FileVolumes(qubes.tests.QubesTestCase): label='red') expected = vm.template.dir_path + '/root.img:' + vm.template.dir_path \ - + '/root-cow.img' + + '/root-cow.img:' + vm.dir_path + '/root-cow.img' self.assertVolumePath(vm, 'root', expected, rw=False) expected = vm.dir_path + '/private.img:' + \ vm.dir_path + '/private-cow.img' From bcab92ee64c9024b265d863909f484f566b6fe8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Thu, 1 Dec 2016 03:53:34 +0100 Subject: [PATCH 04/12] qubes/vm: make sure to close qmemman socket after failed startup If qmemman socket isn't closed, it will block other VM startups. --- qubes/vm/qubesvm.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/qubes/vm/qubesvm.py b/qubes/vm/qubesvm.py index 233ac634..f19a2a98 100644 --- a/qubes/vm/qubesvm.py +++ b/qubes/vm/qubesvm.py @@ -821,8 +821,12 @@ class QubesVM(qubes.vm.mix.net.NetVMMixin, qubes.vm.BaseVM): self._update_libvirt_domain() qmemman_client = self.request_memory(mem_required) - - self.libvirt_domain.createWithFlags(libvirt.VIR_DOMAIN_START_PAUSED) + try: + self.libvirt_domain.createWithFlags(libvirt.VIR_DOMAIN_START_PAUSED) + except: + if qmemman_client: + qmemman_client.close() + raise try: self.fire_event('domain-spawn', From 0b7145f67d839070402a23705ea03c76b2ecbe4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Sun, 4 Dec 2016 21:28:13 +0100 Subject: [PATCH 05/12] travis: drop debootstrap workaround MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move to qubes-builder Signed-off-by: Marek Marczykowski-Górecki --- .travis.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index db4d04f1..5e00115a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,8 +7,6 @@ python: install: - pip install --quiet -r ci/requirements.txt - git clone https://github.com/"${TRAVIS_REPO_SLUG%%/*}"/qubes-builder ~/qubes-builder -# debootstrap in trusty is old... -before_script: sudo ln -s sid /usr/share/debootstrap/scripts/stretch script: - PYTHONPATH=test-packages pylint --rcfile=ci/pylintrc qubes - ./run-tests --no-syslog From c3fc4062d8293053932271998ca10e0b4845e6e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Sat, 14 Jan 2017 04:56:19 +0100 Subject: [PATCH 06/12] tests: add basic test for qvm-features --- qubes/tests/__init__.py | 1 + qubes/tests/integ/tools/qvm_features.py | 70 +++++++++++++++++++++++++ rpm_spec/core-dom0.spec | 1 + 3 files changed, 72 insertions(+) create mode 100644 qubes/tests/integ/tools/qvm_features.py diff --git a/qubes/tests/__init__.py b/qubes/tests/__init__.py index e0073fa7..b05a2ce5 100644 --- a/qubes/tests/__init__.py +++ b/qubes/tests/__init__.py @@ -908,6 +908,7 @@ def load_tests(loader, tests, pattern): # pylint: disable=unused-argument # tool tests 'qubes.tests.integ.tools.qubes_create', 'qubes.tests.integ.tools.qvm_check', + 'qubes.tests.integ.tools.qvm_features', 'qubes.tests.integ.tools.qvm_firewall', 'qubes.tests.integ.tools.qvm_prefs', 'qubes.tests.integ.tools.qvm_run', diff --git a/qubes/tests/integ/tools/qvm_features.py b/qubes/tests/integ/tools/qvm_features.py new file mode 100644 index 00000000..5a72b209 --- /dev/null +++ b/qubes/tests/integ/tools/qvm_features.py @@ -0,0 +1,70 @@ +#!/usr/bin/python2 +# -*- encoding: utf8 -*- +# +# The Qubes OS Project, http://www.qubes-os.org +# +# Copyright (C) 2017 Marek Marczykowski-Górecki +# +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +import qubes +import qubes.tools.qvm_features + +import qubes.tests +import qubes.tests.tools +import qubes.vm.appvm + + +class TC_00_qvm_features(qubes.tests.SystemTestsMixin, + qubes.tests.QubesTestCase): + def setUp(self): + super(TC_00_qvm_features, self).setUp() + self.init_default_template() + + self.sharedopts = ['--qubesxml', qubes.tests.XMLPATH] + + self.vm1 = self.app.add_new_vm(qubes.vm.appvm.AppVM, + name=self.make_vm_name('vm1'), + template=self.app.default_template, + label='red') + self.app.save() + + def test_000_list(self): + self.assertEqual(0, qubes.tools.qvm_features.main( + self.sharedopts + [self.vm1.name])) + + with self.assertRaises(SystemExit): + qubes.tools.qvm_features.main( + self.sharedopts + ['test-no-such-vm']) + + def test_001_get_missing(self): + self.assertEqual(1, qubes.tools.qvm_features.main( + self.sharedopts + [self.vm1.name, 'no-such-feature'])) + + def test_002_set_and_get(self): + self.assertEqual(0, qubes.tools.qvm_features.main( + self.sharedopts + [self.vm1.name, 'test-feature', 'true'])) + with qubes.tests.tools.StdoutBuffer() as buf: + self.assertEqual(0, qubes.tools.qvm_features.main( + self.sharedopts + [self.vm1.name, 'test-feature'])) + self.assertEqual('true\n', buf.getvalue()) + + def test_003_set_and_list(self): + self.assertEqual(0, qubes.tools.qvm_features.main( + self.sharedopts + [self.vm1.name, 'test-feature', 'true'])) + with qubes.tests.tools.StdoutBuffer() as buf: + self.assertEqual(0, qubes.tools.qvm_features.main( + self.sharedopts + [self.vm1.name])) + self.assertEqual('test-feature true\n', buf.getvalue()) diff --git a/rpm_spec/core-dom0.spec b/rpm_spec/core-dom0.spec index 9b0354ab..610bf4c2 100644 --- a/rpm_spec/core-dom0.spec +++ b/rpm_spec/core-dom0.spec @@ -358,6 +358,7 @@ fi %{python3_sitelib}/qubes/tests/integ/tools/__pycache__/* %{python3_sitelib}/qubes/tests/integ/tools/__init__.py %{python3_sitelib}/qubes/tests/integ/tools/qubes_create.py +%{python3_sitelib}/qubes/tests/integ/tools/qvm_features.py* %{python3_sitelib}/qubes/tests/integ/tools/qvm_firewall.py %{python3_sitelib}/qubes/tests/integ/tools/qvm_check.py %{python3_sitelib}/qubes/tests/integ/tools/qvm_prefs.py From 98edc9779c39995763ebb76ab66d0a6e5c89e5b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Sat, 14 Jan 2017 04:56:49 +0100 Subject: [PATCH 07/12] tools/qvm-features: fix domain argument handling It's args.domains[0], not args.vm. --- qubes/tools/qvm_features.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/qubes/tools/qvm_features.py b/qubes/tools/qvm_features.py index 846e36d7..3aedb639 100644 --- a/qubes/tools/qvm_features.py +++ b/qubes/tools/qvm_features.py @@ -58,21 +58,22 @@ def main(args=None): ''' args = parser.parse_args(args) + vm = args.domains[0] if args.request: # Request mode: instead of setting the features directly, # let the extensions handle them first. - args.vm.fire_event('feature-request', untrusted_features=args.features) + vm.fire_event('feature-request', untrusted_features=args.features) return 0 if args.feature is None: if args.delete: parser.error('--unset requires a feature') - width = max(len(feature) for feature in args.vm.features) - for feature in sorted(args.vm.features): + width = max(len(feature) for feature in vm.features) + for feature in sorted(vm.features): print('{name:{width}s} {value}'.format( - name=feature, value=args.vm.features[feature], width=width)) + name=feature, value=vm.features[feature], width=width)) return 0 @@ -80,7 +81,7 @@ def main(args=None): if args.value is not None: parser.error('cannot both set and unset a value') try: - del args.vm.features[args.feature] + del vm.features[args.feature] args.app.save() except KeyError: pass @@ -88,12 +89,12 @@ def main(args=None): if args.value is None: try: - print(args.vm.features[args.feature]) + print(vm.features[args.feature]) return 0 except KeyError: return 1 - args.vm.features[args.feature] = args.value + vm.features[args.feature] = args.value args.app.save() return 0 From 0c433291882308c6238f8f8ab5072267c96652b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Sat, 14 Jan 2017 05:02:56 +0100 Subject: [PATCH 08/12] tools/qvm-features: fix handling empty list of features --- qubes/tools/qvm_features.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/qubes/tools/qvm_features.py b/qubes/tools/qvm_features.py index 3aedb639..32e4af61 100644 --- a/qubes/tools/qvm_features.py +++ b/qubes/tools/qvm_features.py @@ -70,6 +70,10 @@ def main(args=None): if args.delete: parser.error('--unset requires a feature') + if not vm.features: + # max doesn't like empty list + return 0 + width = max(len(feature) for feature in vm.features) for feature in sorted(vm.features): print('{name:{width}s} {value}'.format( From bd9300b38e216486c0b96012cef7045b6ad60f32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Sat, 14 Jan 2017 05:04:04 +0100 Subject: [PATCH 09/12] tests: copy pool configuration into qubes-test.xml If template choosen for the tests is installed in non-default storage pool, this pool also needs to be imported into qubes-test.xml. --- qubes/tests/__init__.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/qubes/tests/__init__.py b/qubes/tests/__init__.py index b05a2ce5..b7aff1ce 100644 --- a/qubes/tests/__init__.py +++ b/qubes/tests/__init__.py @@ -507,10 +507,20 @@ class SystemTestsMixin(object): elif isinstance(template, str): template = self.host_app.domains[template] + used_pools = [vol.pool for vol in template.volumes.values()] + + for pool in used_pools: + if pool in self.app.pools: + continue + self.app.add_pool(**self.host_app.pools[pool].config) + template_vm = self.app.add_new_vm(qubes.vm.templatevm.TemplateVM, name=template.name, uuid=template.uuid, label='black') + for name, volume in template_vm.volumes.items(): + if volume.pool != template.volumes[name].pool: + template_vm.storage.init_volume(name, volume.config) self.app.default_template = template_vm def init_networking(self): From 1c20aae98aff7e86fc40cb39f98cf3e8e9bf5ceb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Sat, 14 Jan 2017 05:05:11 +0100 Subject: [PATCH 10/12] Fix libvirt xml template for HVM domains QubesOS/qubes-issues#2185 --- templates/libvirt/xen.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/libvirt/xen.xml b/templates/libvirt/xen.xml index 3d6322a4..b5d2ae00 100644 --- a/templates/libvirt/xen.xml +++ b/templates/libvirt/xen.xml @@ -115,9 +115,9 @@ type="stubdom" {% if vm.netvm %} cmdline="-net lwip,client_ip={{ vm.ip -}} - ,server_ip={{ vm.secondary_dns -}} + ,server_ip={{ vm.dns[1] -}} ,dns={{ vm.netvm.gateway -}} - ,gw={{ self.netvm.gateway -}} + ,gw={{ vm.netvm.gateway -}} ,netmask={{ vm.netmask }}" {% endif %} /> From a317e81d7e5b26b2dd9c26829b1e0b0f13cd75f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Wed, 18 Jan 2017 03:26:32 +0100 Subject: [PATCH 11/12] qubes/ext/gui: adjust shm.id path It's moved to /var/run/qubes and now is built based on $DISPLAY. --- qubes/ext/gui.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/qubes/ext/gui.py b/qubes/ext/gui.py index 77e5d91f..34b5989f 100644 --- a/qubes/ext/gui.py +++ b/qubes/ext/gui.py @@ -135,8 +135,8 @@ class GUI(qubes.ext.Extension): GUI daemon securely displays windows from domain. ''' # pylint: disable=no-self-use,unused-argument - if not start_guid or preparing_dvm \ - or not os.path.exists('/var/run/shm.id'): + + if not start_guid or preparing_dvm: return if self.is_guid_running(vm): @@ -150,6 +150,18 @@ class GUI(qubes.ext.Extension): vm.log.error('Not starting gui daemon, no DISPLAY set') return + display = os.getenv('DISPLAY') + if not display.startswith(':'): + vm.log.error('Expected local $DISPLAY, got \'{}\''.format(display)) + return + + display_num = display[1:].partition('.')[0] + shmid_path = '/var/run/qubes/shm.id.{}'.format(display_num) + if not os.path.exists(shmid_path): + vm.log.error( + 'Not starting gui daemon, no {} file'.format(shmid_path)) + return + vm.log.info('Starting gui daemon') guid_cmd = [qubes.config.system_path['qubes_guid_path'], From e50b17a6b35f9ceccffec6166537f9853443a069 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Mon, 13 Feb 2017 00:10:18 +0100 Subject: [PATCH 12/12] tools/qvm-features: make pylint happy reduce number of return statements. --- qubes/tools/qvm_features.py | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/qubes/tools/qvm_features.py b/qubes/tools/qvm_features.py index 32e4af61..1ff879d1 100644 --- a/qubes/tools/qvm_features.py +++ b/qubes/tools/qvm_features.py @@ -64,24 +64,19 @@ def main(args=None): # Request mode: instead of setting the features directly, # let the extensions handle them first. vm.fire_event('feature-request', untrusted_features=args.features) - return 0 - if args.feature is None: + elif args.feature is None: if args.delete: parser.error('--unset requires a feature') - if not vm.features: - # max doesn't like empty list - return 0 + # max doesn't like empty list + if vm.features: + width = max(len(feature) for feature in vm.features) + for feature in sorted(vm.features): + print('{name:{width}s} {value}'.format( + name=feature, value=vm.features[feature], width=width)) - width = max(len(feature) for feature in vm.features) - for feature in sorted(vm.features): - print('{name:{width}s} {value}'.format( - name=feature, value=vm.features[feature], width=width)) - - return 0 - - if args.delete: + elif args.delete: if args.value is not None: parser.error('cannot both set and unset a value') try: @@ -89,17 +84,17 @@ def main(args=None): args.app.save() except KeyError: pass - return 0 - if args.value is None: + elif args.value is None: try: print(vm.features[args.feature]) return 0 except KeyError: return 1 + else: + vm.features[args.feature] = args.value + args.app.save() - vm.features[args.feature] = args.value - args.app.save() return 0