storage/reflink: preferably get volume size from image size

There were (at least) five ways for the volume's nominal size and the
volume image file's actual size to desynchronize:

- loading a stale qubes.xml if a crash happened right after resizing the
  image but before saving the updated qubes.xml (-> previously fixed)
- restarting a snap_on_start volume after resizing the volume or its
  source volume (-> previously fixed)
- reverting to a differently sized revision
- importing a volume
- user tinkering with image files

Rather than trying to fix these one by one and hoping that there aren't
any others, override the volume size getter itself to always update from
the image file size. (If the getter is called though the storage API, it
takes the volume lock to avoid clobbering the nominal size when resize()
is running concurrently.)
This commit is contained in:
Rusty Bird 2019-06-23 12:48:00 +00:00
parent de159a1e1e
commit 2b4b45ead8
No known key found for this signature in database
GPG Key ID: 469D78F47AAF2ADF

View File

@ -154,7 +154,7 @@ class ReflinkVolume(qubes.storage.Volume):
@_locked
def create(self):
if self.save_on_stop and not self.snap_on_start:
_create_sparse_file(self._path_clean, self.size)
_create_sparse_file(self._path_clean, self._get_size())
return self
@_coroutinized
@ -213,7 +213,7 @@ class ReflinkVolume(qubes.storage.Volume):
if self.snap_on_start or self.save_on_stop:
_copy_file(self._path_clean, self._path_dirty)
else:
_create_sparse_file(self._path_dirty, self.size)
_create_sparse_file(self._path_dirty, self._get_size())
return self
@_coroutinized
@ -276,7 +276,7 @@ class ReflinkVolume(qubes.storage.Volume):
_resize_file(path, size)
break
self.size = size
self._size = size
if path == self._path_dirty:
_update_loopdev_sizes(self._path_dirty)
return self
@ -293,7 +293,7 @@ class ReflinkVolume(qubes.storage.Volume):
if not self.save_on_stop:
raise NotImplementedError(
'Cannot import_data: {} is not save_on_stop'.format(self.vid))
_create_sparse_file(self._path_import, self.size)
_create_sparse_file(self._path_import, self._get_size())
return self._path_import
def _import_data_end(self, success):
@ -338,6 +338,15 @@ class ReflinkVolume(qubes.storage.Volume):
return collections.OrderedDict(sorted(items,
key=lambda item: int(item[0])))
def _get_size(self):
for path in (self._path_dirty, self._path_clean):
with suppress(FileNotFoundError):
self._size = os.path.getsize(path)
break
return self._size
size = property(_locked(_get_size))
@property
def usage(self):
''' Return volume disk usage from the VM's perspective. It is