Skip to content

Commit f13fd33

Browse files
committed
lvm2 pool: Export immutable snapshot of volume
Volumes returned by `export()` must be immutable, since otherwise the backup will be inconsistent. Ensure this by exporting a snapshot of the volume, no the volume itself. Fixes QubesOS/qubes-issues#7198.
1 parent 4bfd808 commit f13fd33

1 file changed

Lines changed: 28 additions & 6 deletions

File tree

qubes/storage/lvm.py

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,8 @@ def list_volumes(self):
156156
continue
157157
if vol_info['pool_lv'] != self.thin_pool:
158158
continue
159-
if vid.endswith('-snap') or vid.endswith('-import'):
159+
if vid.endswith('-snap') or vid.endswith('-import') or \
160+
vid.endswith('+export'):
160161
# implementation detail volume
161162
continue
162163
if vid.endswith('-back'):
@@ -292,6 +293,9 @@ def __init__(self, volume_group, **kwargs):
292293
self._vid_snap = self.vid + '-snap'
293294
if self.save_on_stop:
294295
self._vid_import = self.vid + '-import'
296+
if os.path.exists(self._path_export):
297+
subprocess.check_call(_get_lvm_cmdline(['remove', self._vid_export]))
298+
self._is_exporting = False
295299

296300
@property
297301
def path(self):
@@ -470,13 +474,29 @@ async def remove(self):
470474
# pylint: disable=protected-access
471475
self.pool._volume_objects_cache.pop(self.vid, None)
472476

473-
async def export(self):
477+
@property
478+
def _vid_export(self) -> str:
479+
return self.vid + '+export'
480+
481+
@property
482+
def _path_export(self) -> str:
483+
return '/dev/' + self._vid_export
484+
485+
async def export(self) -> str:
474486
''' Returns an object that can be `open()`. '''
475487
# make sure the device node is available
476-
cmd = ['activate', self.path]
477-
await qubes_lvm_coro(cmd, self.log)
478-
devpath = self.path
479-
return devpath
488+
vid_export = self._vid_export
489+
export_path = self._path_export
490+
if not os.path.exists(export_path):
491+
cmd = ['clone', self._vid_current, vid_export]
492+
await qubes_lvm_coro(cmd, self.log)
493+
self._is_exporting = True
494+
return export_path
495+
496+
async def export_end(self, path: str) -> None:
497+
assert path == self._path_export, f'Refusing to remove incorrect path {path!r} (expected {self.path_export!r})'
498+
await self._remove_if_exists(self._vid_export)
499+
self._is_exporting = False
480500

481501
@qubes.storage.Volume.locked
482502
async def import_volume(self, src_volume):
@@ -681,6 +701,8 @@ async def stop(self):
681701
changed = await self._remove_if_exists(self._vid_snap)
682702
else:
683703
changed = await self._remove_if_exists(self.vid)
704+
if not self._is_exporting:
705+
await self._remove_if_exists(self._vid_export)
684706
finally:
685707
if changed:
686708
await reset_cache_coro()

0 commit comments

Comments
 (0)