Merge remote-tracking branch 'origin/pr/266'

* origin/pr/266:
  storage/reflink: volume.resize(): succeed without image
  storage/reflink: volume.resize(): remove safety check
  storage/reflink: simplify volume.usage()
  storage/reflink: update volume size in __init__()
  storage/reflink: update snap_on_start volume size in start()
This commit is contained in:
Marek Marczykowski-Górecki 2019-06-21 20:55:09 +02:00
commit ad8c2d4b09
No known key found for this signature in database
GPG Key ID: 063938BA42CFA724
2 changed files with 23 additions and 27 deletions

View File

@ -36,7 +36,6 @@ from contextlib import contextmanager, suppress
import qubes.storage import qubes.storage
BLKSIZE = 512
FICLONE = 1074041865 # defined in <linux/fs.h> FICLONE = 1074041865 # defined in <linux/fs.h>
LOOP_SET_CAPACITY = 0x4C07 # defined in <linux/loop.h> LOOP_SET_CAPACITY = 0x4C07 # defined in <linux/loop.h>
LOGGER = logging.getLogger('qubes.storage.reflink') LOGGER = logging.getLogger('qubes.storage.reflink')
@ -141,6 +140,13 @@ class ReflinkVolume(qubes.storage.Volume):
self._path_import = self._path_vid + '-import.img' self._path_import = self._path_vid + '-import.img'
self.path = self._path_dirty self.path = self._path_dirty
# In case the volume was previously resized, but then a crash
# prevented qubesd from serializing the new size to qubes.xml:
for path in (self._path_dirty, self._path_clean):
with suppress(FileNotFoundError):
self.size = os.path.getsize(path)
break
@_unblock @_unblock
def create(self): def create(self):
if self.save_on_stop and not self.snap_on_start: if self.save_on_stop and not self.snap_on_start:
@ -198,6 +204,7 @@ class ReflinkVolume(qubes.storage.Volume):
if self.snap_on_start: if self.snap_on_start:
# pylint: disable=protected-access # pylint: disable=protected-access
_copy_file(self.source._path_clean, self._path_clean) _copy_file(self.source._path_clean, self._path_clean)
self.size = os.path.getsize(self._path_clean)
if self.snap_on_start or self.save_on_stop: if self.snap_on_start or self.save_on_stop:
_copy_file(self._path_clean, self._path_dirty) _copy_file(self._path_clean, self._path_dirty)
else: else:
@ -249,28 +256,20 @@ class ReflinkVolume(qubes.storage.Volume):
@_unblock @_unblock
def resize(self, size): def resize(self, size):
''' Expand a read-write volume image; notify any corresponding ''' Resize a read-write volume image; notify any corresponding
loop devices of the size change. loop devices of the size change.
''' '''
if not self.rw: if not self.rw:
raise qubes.storage.StoragePoolException( raise qubes.storage.StoragePoolException(
'Cannot resize: {} is read-only'.format(self.vid)) 'Cannot resize: {} is read-only'.format(self.vid))
if size < self.size: for path in (self._path_dirty, self._path_clean):
raise qubes.storage.StoragePoolException( with suppress(FileNotFoundError):
'For your own safety, shrinking of {} is disabled' _resize_file(path, size)
' ({} < {}). If you really know what you are doing,' break
' use "truncate" manually.'.format(self.vid, size, self.size))
try: # assume volume is not (cleanly) stopped ...
_resize_file(self._path_dirty, size)
update = True
except FileNotFoundError: # ... but it actually is.
_resize_file(self._path_clean, size)
update = False
self.size = size self.size = size
if update: if path == self._path_dirty:
_update_loopdev_sizes(self._path_dirty) _update_loopdev_sizes(self._path_dirty)
return self return self
@ -334,10 +333,9 @@ class ReflinkVolume(qubes.storage.Volume):
''' Return volume disk usage from the VM's perspective. It is ''' Return volume disk usage from the VM's perspective. It is
usually much lower from the host's perspective due to CoW. usually much lower from the host's perspective due to CoW.
''' '''
with suppress(FileNotFoundError): for path in (self._path_dirty, self._path_clean):
return _get_file_disk_usage(self._path_dirty) with suppress(FileNotFoundError):
with suppress(FileNotFoundError): return os.stat(path).st_blocks * 512
return _get_file_disk_usage(self._path_clean)
return 0 return 0
@ -361,10 +359,6 @@ def _replace_file(dst):
_remove_file(tmp.name) _remove_file(tmp.name)
raise raise
def _get_file_disk_usage(path):
''' Return real disk usage (not logical file size) of a file. '''
return os.stat(path).st_blocks * BLKSIZE
def _fsync_dir(path): def _fsync_dir(path):
dir_fd = os.open(path, os.O_RDONLY | os.O_DIRECTORY) dir_fd = os.open(path, os.O_RDONLY | os.O_DIRECTORY)
try: try:

View File

@ -62,15 +62,17 @@ class ReflinkMixin:
os.symlink(img_real, img_sym) os.symlink(img_real, img_sym)
reflink._create_sparse_file(img_real, size_initial) reflink._create_sparse_file(img_real, size_initial)
self.assertEqual(reflink._get_file_disk_usage(img_real), 0) stat = os.stat(img_real)
self.assertEqual(os.stat(img_real).st_size, size_initial) self.assertEqual(stat.st_blocks, 0)
self.assertEqual(stat.st_size, size_initial)
dev_from_real = setup_loopdev(img_real, cleanup_via=self.addCleanup) dev_from_real = setup_loopdev(img_real, cleanup_via=self.addCleanup)
dev_from_sym = setup_loopdev(img_sym, cleanup_via=self.addCleanup) dev_from_sym = setup_loopdev(img_sym, cleanup_via=self.addCleanup)
reflink._resize_file(img_real, size_resized) reflink._resize_file(img_real, size_resized)
self.assertEqual(reflink._get_file_disk_usage(img_real), 0) stat = os.stat(img_real)
self.assertEqual(os.stat(img_real).st_size, size_resized) self.assertEqual(stat.st_blocks, 0)
self.assertEqual(stat.st_size, size_resized)
reflink_update_loopdev_sizes(os.path.join(self.test_dir, 'unrelated')) reflink_update_loopdev_sizes(os.path.join(self.test_dir, 'unrelated'))