From 56326a75363cf93df69963194d4c6348580a1f2a Mon Sep 17 00:00:00 2001 From: Sunil Mohan Adapa Date: Tue, 4 Aug 2020 23:41:11 -0700 Subject: [PATCH] storage: Fix expanding partitions on GPT partition tables GPT scheme has two mostly identical partition table headers. One at the beginning of the disk and one at the end. When an image is written to larger disk, the second header is not at the end of the disk. Fix that by moving second partition to end of the disk before attempting partition Tests: - Unit tests run as root work. - On A64-OLinuXino board, boot with eMMC and UEFI image. The partition does not expand on initial setup. Trying to manually expand in storage app fails. Apply patch. Manual expansion works. Signed-off-by: Sunil Mohan Adapa Reviewed-by: James Valleroy --- actions/storage | 21 ++++++++++++ debian/control | 2 ++ plinth/modules/storage/tests/test_storage.py | 36 ++++++++++++++++---- 3 files changed, 52 insertions(+), 7 deletions(-) diff --git a/actions/storage b/actions/storage index bf818d3de..9ed372117 100755 --- a/actions/storage +++ b/actions/storage @@ -76,10 +76,31 @@ def subcommand_expand_partition(arguments): file=sys.stderr) sys.exit(4) + if requested_partition['table_type'] == 'gpt': + _move_gpt_second_header(device) + _resize_partition(device, requested_partition, free_space) _resize_file_system(device, requested_partition, free_space) +def _move_gpt_second_header(device): + """Move second header to the end of the disk. + + GPT scheme has two mostly identical partition table headers. One at the + beginning of the disk and one at the end. When an image is written to + larger disk, the second header is not at the end of the disk. Fix that by + moving second partition to end of the disk before attempting partition + resize. + + """ + command = ['sgdisk', '--move-second-header', device] + try: + subprocess.run(command, check=True) + except subprocess.CalledProcessError: + print('Error moving GPT second header to the end') + sys.exit(6) + + def _resize_partition(device, requested_partition, free_space): """Resize the partition table entry.""" command = [ diff --git a/debian/control b/debian/control index 59cbb3da6..eac0f8fd1 100644 --- a/debian/control +++ b/debian/control @@ -72,6 +72,8 @@ Depends: e2fsprogs, fonts-fork-awesome, fonts-lato, +# sgdisk is used in storage app to expand GPT disks + gdisk, gettext, gir1.2-glib-2.0, gir1.2-nm-1.0, diff --git a/plinth/modules/storage/tests/test_storage.py b/plinth/modules/storage/tests/test_storage.py index 3a3f84b49..db11baca0 100644 --- a/plinth/modules/storage/tests/test_storage.py +++ b/plinth/modules/storage/tests/test_storage.py @@ -43,6 +43,14 @@ class Disk(): self.disk_file = disk_file + def expand_disk_file(self, size): + """Expand the disk file.""" + command = f'truncate --size={size}M {self.disk_file.name}' + subprocess.run(command.split(), check=True) + self._unmount_file_systems() + self._cleanup_loopback() + self._setup_loopback() + def _setup_loopback(self): """Setup loop back on the create disk file.""" command = 'losetup --show --find {file}'.format( @@ -112,6 +120,7 @@ class Disk(): self._create_partitions() self._setup_loopback() self._create_file_systems() + return self def __exit__(self, *exc): """Exit the context, destroy the test disk.""" @@ -162,32 +171,45 @@ class TestActions: 'mkpart extended 8 12', 'mkpart extended 12 16', 'mkpart extended 16 160' ] - with Disk(self, 256, disk_info, [(5, 'btrfs')]): + with Disk(self, 192, disk_info, [(5, 'btrfs')]) as disk: + # Second header already at the end + self.assert_free_space(5, space=True) + self.expand_partition(5, success=True) + self.expand_partition(5, success=False) + disk.expand_disk_file(256) + # Second header not at the end self.assert_free_space(5, space=True) self.expand_partition(5, success=True) self.expand_partition(5, success=False) @pytest.mark.usefixtures('needs_root') - def test_unsupported_file_system(self): + @pytest.mark.parametrize('partition_table_type', ['gpt', 'msdos']) + def test_unsupported_file_system(self, partition_table_type): """Test that free space after unknown file system does not count.""" - disk_info = ['mktable msdos', 'mkpart primary 1 8'] + disk_info = [f'mktable {partition_table_type}', 'mkpart primary 1 8'] with Disk(self, 32, disk_info): self.assert_free_space(1, space=False) self.expand_partition(1, success=False) @pytest.mark.usefixtures('needs_root') - def test_btrfs_expansion(self): + @pytest.mark.parametrize('partition_table_type', ['gpt', 'msdos']) + def test_btrfs_expansion(self, partition_table_type): """Test that btrfs file system can be expanded.""" - disk_info = ['mktable msdos', 'mkpart primary btrfs 1 200'] + disk_info = [ + f'mktable {partition_table_type}', 'mkpart primary btrfs 1 200' + ] with Disk(self, 256, disk_info, [(1, 'btrfs')]): self.expand_partition(1, success=True) self.expand_partition(1, success=False) self.assert_btrfs_file_system_healthy(1) @pytest.mark.usefixtures('needs_root') - def test_ext4_expansion(self): + @pytest.mark.parametrize('partition_table_type', ['gpt', 'msdos']) + def test_ext4_expansion(self, partition_table_type): """Test that ext4 file system can be expanded.""" - disk_info = ['mktable msdos', 'mkpart primary ext4 1 64'] + disk_info = [ + f'mktable {partition_table_type}', 'mkpart primary ext4 1 64' + ] with Disk(self, 128, disk_info, [(1, 'ext4')]): self.expand_partition(1, success=True) self.expand_partition(1, success=False)