From 2540d56602c8fc054e474e1d77e28422d2eed96e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcus=20Sch=C3=A4fer?= Date: Sat, 24 Feb 2024 10:57:32 +0100 Subject: [PATCH] Add support for discoverable partitions Set PARTUUID according to systemd-id128 if applicable This Fixes #1385 --- kiwi/partitioner/base.py | 11 ++++ kiwi/partitioner/dasd.py | 11 ++++ kiwi/partitioner/gpt.py | 15 ++++++ kiwi/partitioner/msdos.py | 11 ++++ kiwi/storage/disk.py | 80 +++++++++++++++++++++++++++++- test/data/systemd-id128.out | 46 +++++++++++++++++ test/unit/partitioner/base_test.py | 4 ++ test/unit/partitioner/gpt_test.py | 7 +++ test/unit/storage/disk_test.py | 45 ++++++++++++++--- 9 files changed, 223 insertions(+), 7 deletions(-) create mode 100644 test/data/systemd-id128.out diff --git a/kiwi/partitioner/base.py b/kiwi/partitioner/base.py index bac91869..e8d8875d 100644 --- a/kiwi/partitioner/base.py +++ b/kiwi/partitioner/base.py @@ -98,6 +98,17 @@ class PartitionerBase: """ raise NotImplementedError + def set_uuid(self, partition_id: int, uuid: str): + """ + Set partition UUID + + Implementation in specialized partitioner class + + :param int partition_id: unused + :param string uuid: unused + """ + raise NotImplementedError + def set_hybrid_mbr(self): """ Turn partition table into hybrid table if supported diff --git a/kiwi/partitioner/dasd.py b/kiwi/partitioner/dasd.py index 3be1e3a0..32653b67 100644 --- a/kiwi/partitioner/dasd.py +++ b/kiwi/partitioner/dasd.py @@ -83,6 +83,17 @@ class PartitionerDasd(PartitionerBase): # that point. log.debug('potential fdasd errors were ignored') + def set_uuid(self, partition_id: int, uuid: str) -> None: + """ + Set partition UUID + + Nothing to be done here for DASD devices + + :param int partition_id: unused + :param string uuid: unused + """ + pass # pragma: nocover + def resize_table(self, entries: int = None) -> None: """ Resize partition table diff --git a/kiwi/partitioner/gpt.py b/kiwi/partitioner/gpt.py index fc52a79c..52c127cc 100644 --- a/kiwi/partitioner/gpt.py +++ b/kiwi/partitioner/gpt.py @@ -114,6 +114,21 @@ class PartitionerGpt(PartitionerBase): else: log.warning('Flag %s ignored on GPT', flag_name) + def set_uuid(self, partition_id: int, uuid: str) -> None: + """ + Set partition UUID (GUID) + + :param int partition_id: partition number + :param string uuid: UUID + """ + Command.run( + [ + 'sgdisk', + '--partition-guid', f'{partition_id}:{uuid}', + self.disk_device + ] + ) + def set_hybrid_mbr(self) -> None: """ Turn partition table into hybrid GPT/MBR table diff --git a/kiwi/partitioner/msdos.py b/kiwi/partitioner/msdos.py index 55ab2027..c1c121ff 100644 --- a/kiwi/partitioner/msdos.py +++ b/kiwi/partitioner/msdos.py @@ -121,6 +121,17 @@ class PartitionerMsDos(PartitionerBase): """ pass + def set_uuid(self, partition_id: int, uuid: str) -> None: + """ + Set partition UUID + + Nothing to be done here for MSDOS devices + + :param int partition_id: unused + :param string uuid: unused + """ + pass # pragma: nocover + def set_start_sector(self, start_sector: int): """ Set start sector of first partition as configured. diff --git a/kiwi/storage/disk.py b/kiwi/storage/disk.py index 9cf8d524..c05c01f2 100644 --- a/kiwi/storage/disk.py +++ b/kiwi/storage/disk.py @@ -29,7 +29,11 @@ from kiwi.storage.device_provider import DeviceProvider from kiwi.storage.mapped_device import MappedDevice from kiwi.partitioner import Partitioner from kiwi.runtime_config import RuntimeConfig -from kiwi.exceptions import KiwiCustomPartitionConflictError +from kiwi.exceptions import ( + KiwiCustomPartitionConflictError, + KiwiCommandError, + KiwiCommandNotFound +) ptable_entry_type = NamedTuple( 'ptable_entry_type', [ @@ -86,6 +90,9 @@ class Disk(DeviceProvider): 'efi' ] + #: Unified partition UUIDs according to systemd + self.gUID = self.get_discoverable_partition_ids() + self.partition_map: Dict[str, str] = {} self.public_partition_id_map: Dict[str, str] = {} self.partition_id_map: Dict[str, str] = {} @@ -161,6 +168,11 @@ class Disk(DeviceProvider): ) self._add_to_map(map_name) self._add_to_public_id_map(id_name) + part_uuid = self.gUID.get(entry.partition_name) + if part_uuid: + self.partitioner.set_uuid( + self.partition_id_map[map_name], part_uuid + ) def create_root_partition(self, mbsize: str, clone: int = 0): """ @@ -182,6 +194,11 @@ class Disk(DeviceProvider): self._add_to_public_id_map('kiwi_RWPart') if 'kiwi_BootPart' not in self.public_partition_id_map: self._add_to_public_id_map('kiwi_BootPart') + root_uuid = self.gUID.get('root') + if root_uuid: + self.partitioner.set_uuid( + self.partition_id_map['root'], root_uuid + ) def create_root_lvm_partition(self, mbsize: str, clone: int = 0): """ @@ -198,6 +215,11 @@ class Disk(DeviceProvider): self.partitioner.create('p.lxlvm', mbsize, 't.lvm') self._add_to_map('root') self._add_to_public_id_map('kiwi_RootPart') + root_uuid = self.gUID.get('root') + if root_uuid: + self.partitioner.set_uuid( + self.partition_id_map['root'], root_uuid + ) def create_root_raid_partition(self, mbsize: str, clone: int = 0): """ @@ -217,6 +239,11 @@ class Disk(DeviceProvider): self._add_to_map('root') self._add_to_public_id_map('kiwi_RootPart') self._add_to_public_id_map('kiwi_RaidPart') + root_uuid = self.gUID.get('root') + if root_uuid: + self.partitioner.set_uuid( + self.partition_id_map['root'], root_uuid + ) def create_root_readonly_partition(self, mbsize: str, clone: int = 0): """ @@ -236,6 +263,11 @@ class Disk(DeviceProvider): self.partitioner.create('p.lxreadonly', mbsize, 't.linux') self._add_to_map('readonly') self._add_to_public_id_map('kiwi_ROPart') + root_uuid = self.gUID.get('root') + if root_uuid: + self.partitioner.set_uuid( + self.partition_id_map['readonly'], root_uuid + ) def create_boot_partition(self, mbsize: str, clone: int = 0): """ @@ -252,6 +284,11 @@ class Disk(DeviceProvider): self.partitioner.create('p.lxboot', mbsize, 't.linux') self._add_to_map('boot') self._add_to_public_id_map('kiwi_BootPart') + boot_uuid = self.gUID.get('xbootldr') + if boot_uuid: + self.partitioner.set_uuid( + self.partition_id_map['boot'], boot_uuid + ) def create_prep_partition(self, mbsize: str): """ @@ -291,6 +328,11 @@ class Disk(DeviceProvider): self.partitioner.create('p.swap', mbsize, 't.swap') self._add_to_map('swap') self._add_to_public_id_map('kiwi_SwapPart') + swap_uuid = self.gUID.get('swap') + if swap_uuid: + self.partitioner.set_uuid( + self.partition_id_map['swap'], swap_uuid + ) def create_efi_csm_partition(self, mbsize: str): """ @@ -317,6 +359,11 @@ class Disk(DeviceProvider): self.partitioner.create('p.UEFI', mbsize, 't.efi') self._add_to_map('efi') self._add_to_public_id_map('kiwi_EfiPart') + esp_uuid = self.gUID.get('esp') + if esp_uuid: + self.partitioner.set_uuid( + self.partition_id_map['efi'], esp_uuid + ) def activate_boot_partition(self): """ @@ -424,6 +471,37 @@ class Disk(DeviceProvider): sorted(self.public_partition_id_map.items()) ) + def get_discoverable_partition_ids(self) -> Dict[str, str]: + """ + Ask systemd for a list of standardized GUIDs for the + current architecture and return them in a dictionary. + If there is no such information available an empty + dictionary is returned + + :return: key:value dict from systemd-id128 + + :rtype: dict + """ + discoverable_ids = {} + try: + raw_lines = Command.run( + ['systemd-id128', 'show'] + ).output.split(os.linesep)[1:] + for line in raw_lines: + if line: + line = ' '.join(line.split()) + partition_name, uuid = line.split(' ') + discoverable_ids[partition_name] = uuid + except KiwiCommandNotFound as issue: + log.warning( + f'Failed to call systemd-id128: {issue}' + ) + except KiwiCommandError as issue: + log.warning( + f'Failed to obtain discoverable partition IDs: {issue}' + ) + return discoverable_ids + def _create_clones( self, name: str, clone: int, type_flag: str, mbsize: str ) -> None: diff --git a/test/data/systemd-id128.out b/test/data/systemd-id128.out new file mode 100644 index 00000000..1dc20706 --- /dev/null +++ b/test/data/systemd-id128.out @@ -0,0 +1,46 @@ +NAME ID +root-x86 44479540f29741b29af7d131d5f0458a +root-x86-verity d13c5d3bb5d1422ab29f9454fdc89d76 +root-x86-64 4f68bce3e8cd4db196e7fbcaf984b709 +root-x86-64-verity 2c7357edebd246d9aec123d437ec2bf5 +root-arm 69dad7102ce44e3cb16c21a1d49abed3 +root-arm-verity 7386cdf2203c47a9a498f2ecce45a2d6 +root-arm64 b921b0451df041c3af444c6f280d3fae +root-arm64-verity df3300ced69f4c92978c9bfb0f38d820 +root-ia64 993d8d3df80e4225855a9daf8ed7ea97 +root-ia64-verity 86ed10d5b60745bb8957d350f23d0571 +root-riscv32 60d5a7fe8e7d435cb7143dd8162144e1 +root-riscv32-verity ae0253be11674007ac6843926c14c5de +root-riscv64 72ec70a6cf7440e6bd494bda08e8f224 +root-riscv64-verity b6ed5582440b4209b8da5ff7c419ea3d +root 4f68bce3e8cd4db196e7fbcaf984b709 +root-verity 2c7357edebd246d9aec123d437ec2bf5 +root-secondary 44479540f29741b29af7d131d5f0458a +root-secondary-verity d13c5d3bb5d1422ab29f9454fdc89d76 +usr-x86 75250d768cc6458ebd66bd47cc81a812 +usr-x86-verity 8f461b0d14ee4e819aa9049b6fb97abd +usr-x86-64 8484680c952148c69c11b0720656f69e +usr-x86-64-verity 77ff5f63e7b64633acf41565b864c0e6 +usr-arm 7d0359a302b34f0a865c654403e70625 +usr-arm-verity c215d7517bcd4649be906627490a4c05 +usr-arm64 b0e01050ee5f4390949a9101b17104e9 +usr-arm64-verity 6e11a4e7fbca4dedb9e9e1a512bb664e +usr-ia64 4301d2a64e3b4b2abb949e0b2c4225ea +usr-ia64-verity 6a491e033be745458e3883320e0ea880 +usr-riscv32 b933fb225c3f4f91af90e2bb0fa50702 +usr-riscv32-verity cb1ee4e38cd04136a0a4aa61a32e8730 +usr-riscv64 beaec34b8442439ba40b984381ed097d +usr-riscv64-verity 8f1056be9b0547c481d6be53128e5b54 +usr 8484680c952148c69c11b0720656f69e +usr-verity 77ff5f63e7b64633acf41565b864c0e6 +usr-secondary 75250d768cc6458ebd66bd47cc81a812 +usr-secondary-verity 8f461b0d14ee4e819aa9049b6fb97abd +esp c12a7328f81f11d2ba4b00a0c93ec93b +xbootldr bc13c2ff59e64262a352b275fd6f7172 +swap 0657fd6da4ab43c484e50933c84b4f4f +home 933ac7e12eb44f13b8440e14e2aef915 +srv 3b8f842520e04f3b907f1a25a76f98e8 +var 4d21b016b53445c2a9fb5c16e091fd2d +tmp 7ec6f5573bc54acab29316ef5df639d1 +user-home 773f91ef66d449b5bd83d683bf40ad16 +linux-generic 0fc63daf848347728e793d69d8477de4 diff --git a/test/unit/partitioner/base_test.py b/test/unit/partitioner/base_test.py index f07b836b..ac2a2b49 100644 --- a/test/unit/partitioner/base_test.py +++ b/test/unit/partitioner/base_test.py @@ -18,6 +18,10 @@ class TestPartitionerBase: def test_get_id(self): assert self.partitioner.get_id() == 0 + def test_set_uuid(self): + with raises(NotImplementedError): + self.partitioner.set_uuid(100, 'ID') + def test_create(self): with raises(NotImplementedError): self.partitioner.create('name', 100, 'type', ['flag']) diff --git a/test/unit/partitioner/gpt_test.py b/test/unit/partitioner/gpt_test.py index af6acabe..7631f625 100644 --- a/test/unit/partitioner/gpt_test.py +++ b/test/unit/partitioner/gpt_test.py @@ -116,3 +116,10 @@ class TestPartitionerGpt: mock_command.assert_called_once_with( ['sgdisk', '--resize-table', '42', '/dev/loop0'] ) + + @patch('kiwi.partitioner.gpt.Command.run') + def test_set_uuid(self, mock_Command_run): + self.partitioner.set_uuid(42, 'ID') + mock_Command_run.assert_called_once_with( + ['sgdisk', '--partition-guid', '42:ID', '/dev/loop0'] + ) diff --git a/test/unit/storage/disk_test.py b/test/unit/storage/disk_test.py index 2ee4704d..be1f9a63 100644 --- a/test/unit/storage/disk_test.py +++ b/test/unit/storage/disk_test.py @@ -10,7 +10,11 @@ import unittest.mock as mock from kiwi.storage.disk import ptable_entry_type from kiwi.storage.disk import Disk -from kiwi.exceptions import KiwiCustomPartitionConflictError +from kiwi.exceptions import ( + KiwiCustomPartitionConflictError, + KiwiCommandError, + KiwiCommandNotFound +) class TestDisk: @@ -18,9 +22,13 @@ class TestDisk: def inject_fixtures(self, caplog): self._caplog = caplog + @patch.object(Disk, 'get_discoverable_partition_ids') @patch('kiwi.storage.disk.Partitioner.new') @patch('kiwi.storage.disk.RuntimeConfig') - def setup(self, mock_RuntimeConfig, mock_partitioner): + def setup( + self, mock_RuntimeConfig, mock_partitioner, + mock_get_discoverable_partition_ids + ): runtime_config = Mock() runtime_config.get_mapper_tool.return_value = 'partx' mock_RuntimeConfig.return_value = runtime_config @@ -304,8 +312,11 @@ class TestDisk: ['partprobe', '/dev/loop0'] ) + @patch.object(Disk, 'get_discoverable_partition_ids') @patch('kiwi.storage.disk.Command.run') - def test_destructor_partx_loop_cleanup_failed(self, mock_command): + def test_context_manager_exit_partx_loop_cleanup_failed( + self, mock_command, mock_get_discoverable_partition_ids + ): mock_command.side_effect = Exception with Disk('gpt', self.storage_provider) as disk: disk.is_mapped = True @@ -315,8 +326,11 @@ class TestDisk: ['partx', '--delete', '/dev/loop0'] ) + @patch.object(Disk, 'get_discoverable_partition_ids') @patch('kiwi.storage.disk.Command.run') - def test_destructor_dm_loop_cleanup_failed(self, mock_command): + def test_context_manager_exit_dm_loop_cleanup_failed( + self, mock_command, mock_get_discoverable_partition_ids + ): mock_command.side_effect = Exception with Disk('gpt', self.storage_provider) as disk: disk.partition_mapper = 'kpartx' @@ -327,8 +341,11 @@ class TestDisk: ['dmsetup', 'remove', '/dev/mapper/loop0p1'] ) + @patch.object(Disk, 'get_discoverable_partition_ids') @patch('kiwi.storage.disk.Command.run') - def test_destructor_partx(self, mock_command): + def test_context_manager_exit_partx( + self, mock_command, mock_get_discoverable_partition_ids + ): with Disk('gpt', self.storage_provider) as disk: disk.is_mapped = True disk.partition_map = {'root': '/dev/loop0p1'} @@ -336,8 +353,11 @@ class TestDisk: call(['partx', '--delete', '/dev/loop0']) ] + @patch.object(Disk, 'get_discoverable_partition_ids') @patch('kiwi.storage.disk.Command.run') - def test_destructor_kpartx(self, mock_command): + def test_context_manager_exit_kpartx( + self, mock_command, mock_get_discoverable_partition_ids + ): with Disk('gpt', self.storage_provider) as disk: disk.partition_mapper = 'kpartx' disk.is_mapped = True @@ -371,3 +391,16 @@ class TestDisk: (size, clone_size) = self.disk._parse_size('clone:100:all_free') assert size == '100' assert clone_size == 'all_free' + + @patch('kiwi.storage.disk.Command.run') + def test_get_discoverable_partition_ids(self, mock_Command_run): + command = Mock() + with open('../data/systemd-id128.out') as ids: + command.output = ids.read() + mock_Command_run.return_value = command + assert self.disk.get_discoverable_partition_ids()['root'] == \ + '4f68bce3e8cd4db196e7fbcaf984b709' + mock_Command_run.side_effect = KiwiCommandError('issue') + assert self.disk.get_discoverable_partition_ids().get('root') is None + mock_Command_run.side_effect = KiwiCommandNotFound('issue') + assert self.disk.get_discoverable_partition_ids().get('root') is None