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
This commit is contained in:
Marek Marczykowski-Górecki 2016-12-01 00:10:37 +01:00
parent b89689e278
commit 01aedb7f18
No known key found for this signature in database
GPG Key ID: 063938BA42CFA724
2 changed files with 28 additions and 11 deletions

View File

@ -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 <disk>.
'''
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):

View File

@ -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'