diff --git a/qubes/storage/__init__.py b/qubes/storage/__init__.py index 3712ae966..393bcf93f 100644 --- a/qubes/storage/__init__.py +++ b/qubes/storage/__init__.py @@ -355,14 +355,17 @@ def is_outdated(self): ''' raise self._not_implemented("is_outdated") - async def resize(self, size): - ''' Expands volume, throws + async def resize(self, size, allow_shrink=False): + ''' Resizes volume, throws :py:class:`qubes.storage.StoragePoolException` if - given size is less than current_size + given size is less than current_size and allow_shrink + is not True This can be implemented as a coroutine. :param int size: new size in bytes + :param bool allow_shrink: if shrinking volume is allowed. \ + False by default. ''' # pylint: disable=unused-argument raise self._not_implemented("resize") diff --git a/qubes/storage/callback.py b/qubes/storage/callback.py index 520cf8559..eb72825ba 100644 --- a/qubes/storage/callback.py +++ b/qubes/storage/callback.py @@ -454,10 +454,10 @@ async def remove(self): await self._callback('post_volume_remove') return ret - async def resize(self, size): + async def resize(self, size, allow_shrink=False): await self._assert_initialized() await self._callback('pre_volume_resize', cb_args=[size]) - return await coro_maybe(self._cb_impl.resize(size)) + return await coro_maybe(self._cb_impl.resize(size, allow_shrink)) async def start(self): await self._assert_initialized() diff --git a/qubes/storage/file.py b/qubes/storage/file.py index c9eaae75c..154cdfc6f 100644 --- a/qubes/storage/file.py +++ b/qubes/storage/file.py @@ -324,10 +324,11 @@ def _resize_snapshot_dev(self, size): # and make it active subprocess.check_call(['dmsetup', 'resume', path]) - def resize(self, size): # pylint: disable=invalid-overridden-method - ''' Expands volume, throws + def resize(self, size, allow_shrink=False): \ + # pylint: disable=invalid-overridden-method + ''' Resizes volume, throws :py:class:`qubst.storage.qubes.storage.StoragePoolException` if - given size is less than current_size + given size is less than current size and allow_shrink is not True ''' # pylint: disable=no-self-use if not self.rw: msg = 'Can not resize reađonly volume {!s}'.format(self) @@ -340,7 +341,7 @@ def resize(self, size): # pylint: disable=invalid-overridden-method 'the template instead' raise qubes.storage.StoragePoolException(msg) - if size < self.size: + if size < self.size and not allow_shrink: raise qubes.storage.StoragePoolException( 'For your own safety, shrinking of %s is' ' disabled. If you really know what you' diff --git a/qubes/storage/lvm.py b/qubes/storage/lvm.py index 9a4e64e48..2fbe287c5 100644 --- a/qubes/storage/lvm.py +++ b/qubes/storage/lvm.py @@ -605,34 +605,39 @@ async def revert(self, revision=None): return self @qubes.storage.Volume.locked - async def resize(self, size): - ''' Expands volume, throws + async def resize(self, size, allow_shrink=False): + ''' Resizes volume, throws :py:class:`qubst.storage.qubes.storage.StoragePoolException` if - given size is less than current_size + given size is less than current size and allow_shrink is not True ''' + lvm_command = 'extend' + if not self.rw: msg = 'Can not resize reađonly volume {!s}'.format(self) raise qubes.storage.StoragePoolException(msg) if size < self.size: - raise qubes.storage.StoragePoolException( - 'For your own safety, shrinking of %s is' - ' disabled (%d < %d). If you really know what you' - ' are doing, use `lvresize` on %s manually.' % - (self.name, size, self.size, self.vid)) + if allow_shrink: + lvm_command = 'resize' + else: + raise qubes.storage.StoragePoolException( + 'For your own safety, shrinking of %s is' + ' disabled (%d < %d). If you really know what you' + ' are doing, use `lvresize` on %s manually.' % + (self.name, size, self.size, self.vid)) if size == self.size: return if self.is_dirty() or self.snap_on_start: - cmd = ['extend', self._vid_snap, str(size)] + cmd = [lvm_command, self._vid_snap, str(size)] await qubes_lvm_coro(cmd, self.log) elif hasattr(self, '_vid_import') and \ os.path.exists('/dev/' + self._vid_import): - cmd = ['extend', self._vid_import, str(size)] + cmd = [lvm_command, self._vid_import, str(size)] await qubes_lvm_coro(cmd, self.log) elif self.save_on_stop and not self.snap_on_start: - cmd = ['extend', self._vid_current, str(size)] + cmd = [lvm_command, self._vid_current, str(size)] await qubes_lvm_coro(cmd, self.log) self._size = size @@ -774,6 +779,10 @@ def _get_lvm_cmdline(cmd): elif action == 'extend': assert len(cmd) == 3, 'wrong number of arguments for extend' lvm_cmd = ["lvextend", "--size=" + cmd[2] + 'B', '--', cmd[1]] + elif action == 'resize': + assert len(cmd) == 3, 'wrong number of arguments for resize' + lvm_cmd = ["lvresize", "--force", "--size=" + cmd[2] + 'B', + '--', cmd[1]] elif action == 'activate': assert len(cmd) == 2, 'wrong number of arguments for activate' lvm_cmd = ['lvchange', '--activate=y', '--', cmd[1]] diff --git a/qubes/storage/reflink.py b/qubes/storage/reflink.py index aeae3e388..f81525403 100644 --- a/qubes/storage/reflink.py +++ b/qubes/storage/reflink.py @@ -296,10 +296,20 @@ def revert(self, revision=None): # pylint: disable=invalid-overridden-method @qubes.storage.Volume.locked @_coroutinized - def resize(self, size): # pylint: disable=invalid-overridden-method + def resize(self, size, allow_shrink=False): \ + # pylint: disable=invalid-overridden-method ''' Resize a read-write volume; notify any corresponding loop - devices of the size change. + devices of the size change; throw + :py:class:`qubst.storage.qubes.storage.StoragePoolException` if + given size is less than current size and allow_shrink is not True ''' + if size < self._size and not allow_shrink: + raise qubes.storage.StoragePoolException( + 'For your own safety, shrinking of %s is' + ' disabled. If you really know what you' + ' are doing, use `truncate` on %s manually.' % + (self.name, self.vid)) + if not self.rw: raise qubes.storage.StoragePoolException( 'Cannot resize: {} is read-only'.format(self.vid)) diff --git a/qubes/tests/storage_file.py b/qubes/tests/storage_file.py index e3fb2a1c0..83597fdb3 100644 --- a/qubes/tests/storage_file.py +++ b/qubes/tests/storage_file.py @@ -462,6 +462,14 @@ def test_023_resize(self): qubes.utils.coro_maybe(volume.resize(new_size))) self.assertEqual(os.path.getsize(volume.path), new_size) self.assertEqual(volume.size, new_size) + self.loop.run_until_complete( + qubes.utils.coro_maybe(volume.resize(new_size))) + self.assertEqual(os.path.getsize(volume.path), new_size/4) + self.assertEqual(volume.size, new_size) + self.loop.run_until_complete( + qubes.utils.coro_maybe(volume.resize(new_size/4, True))) + self.assertEqual(os.path.getsize(volume.path), new_size/4) + self.assertEqual(volume.size, new_size) def test_024_import_data_with_new_size(self): config = { diff --git a/qubes/tests/storage_lvm.py b/qubes/tests/storage_lvm.py index de4e03003..d20ceafda 100644 --- a/qubes/tests/storage_lvm.py +++ b/qubes/tests/storage_lvm.py @@ -255,6 +255,12 @@ def test_006_resize(self): self.loop.run_until_complete(volume.resize(new_size)) self.assertEqual(self._get_size(path), new_size) self.assertEqual(volume.size, new_size) + self.loop.run_until_complete(volume.resize(new_size/4)) + self.assertEqual(self._get_size(path), new_size) + self.assertEqual(volume.size, new_size) + self.loop.run_until_complete(volume.resize(new_size/4, True)) + self.assertEqual(self._get_size(path), new_size/4) + self.assertEqual(volume.size, new_size/4) def test_007_resize_running(self): old_size = 32 * 1024**2