From a3eb350649757efc5eac3bfe41d1fbf0e51b0690 Mon Sep 17 00:00:00 2001 From: Joshua Watt Date: Thu, 31 Aug 2023 10:51:36 -0600 Subject: [PATCH 01/10] tests: gpt: Remove test order dependency Re-create a clean disk image for each test to prevent modifications from one test affecting another Signed-off-by: Joshua Watt --- test/py/tests/test_gpt.py | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/test/py/tests/test_gpt.py b/test/py/tests/test_gpt.py index 73bfbf77a27..339468bc127 100644 --- a/test/py/tests/test_gpt.py +++ b/test/py/tests/test_gpt.py @@ -61,18 +61,14 @@ class GptTestDiskImage(object): cmd = ('cp', persistent, self.path) u_boot_utils.run_and_log(u_boot_console, cmd) -gtdi = None @pytest.fixture(scope='function') def state_disk_image(u_boot_console): """pytest fixture to provide a GptTestDiskImage object to tests. This is function-scoped because it uses u_boot_console, which is also - function-scoped. However, we don't need to actually do any function-scope - work, so this simply returns the same object over and over each time.""" + function-scoped. A new disk is returned each time to prevent tests from + interfering with each other.""" - global gtdi - if not gtdi: - gtdi = GptTestDiskImage(u_boot_console) - return gtdi + return GptTestDiskImage(u_boot_console) @pytest.mark.boardspec('sandbox') @pytest.mark.buildconfigspec('cmd_gpt') @@ -186,12 +182,12 @@ def test_gpt_swap_partitions(state_disk_image, u_boot_console): u_boot_console.run_command('host bind 0 ' + state_disk_image.path) output = u_boot_console.run_command('part list host 0') - assert '0x00000800 0x00000fff "first"' in output - assert '0x00001000 0x00001bff "second"' in output - u_boot_console.run_command('gpt swap host 0 first second') + assert '0x00000800 0x00000fff "part1"' in output + assert '0x00001000 0x00001bff "part2"' in output + u_boot_console.run_command('gpt swap host 0 part1 part2') output = u_boot_console.run_command('part list host 0') - assert '0x00000800 0x00000fff "second"' in output - assert '0x00001000 0x00001bff "first"' in output + assert '0x00000800 0x00000fff "part2"' in output + assert '0x00001000 0x00001bff "part1"' in output @pytest.mark.boardspec('sandbox') @pytest.mark.buildconfigspec('cmd_gpt') From b1433affd9a9de10150c31929564f68ca338911a Mon Sep 17 00:00:00 2001 From: Joshua Watt Date: Thu, 31 Aug 2023 10:51:37 -0600 Subject: [PATCH 02/10] cmd: gpt: Add gpt_partition_bootable variable Adds an additional variable called gpt_partition_bootable that indicates if the given partition is bootable or not. Signed-off-by: Joshua Watt --- cmd/gpt.c | 9 +++++++-- doc/usage/cmd/gpt.rst | 5 +++++ test/py/tests/test_gpt.py | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 45 insertions(+), 2 deletions(-) diff --git a/cmd/gpt.c b/cmd/gpt.c index 3cc6436b28b..ce85632377e 100644 --- a/cmd/gpt.c +++ b/cmd/gpt.c @@ -723,7 +723,7 @@ static int gpt_enumerate(struct blk_desc *desc) * gpt_setenv_part_variables() - setup partition environmental variables * * Setup the gpt_partition_name, gpt_partition_entry, gpt_partition_addr - * and gpt_partition_size environment variables. + * and gpt_partition_size, gpt_partition_bootable environment variables. * * @pinfo: pointer to disk partition * @i: partition entry @@ -750,6 +750,10 @@ static int gpt_setenv_part_variables(struct disk_partition *pinfo, int i) if (ret) goto fail; + ret = env_set_ulong("gpt_partition_bootable", !!(pinfo->bootable & PART_BOOTABLE)); + if (ret) + goto fail; + return 0; fail: @@ -1055,7 +1059,8 @@ U_BOOT_CMD(gpt, CONFIG_SYS_MAXARGS, 1, do_gpt, " gpt setenv mmc 0 $name\n" " - setup environment variables for partition $name:\n" " gpt_partition_addr, gpt_partition_size,\n" - " gpt_partition_name, gpt_partition_entry\n" + " gpt_partition_name, gpt_partition_entry,\n" + " gpt_partition_bootable\n" " gpt enumerate mmc 0\n" " - store list of partitions to gpt_partition_list environment variable\n" " gpt guid \n" diff --git a/doc/usage/cmd/gpt.rst b/doc/usage/cmd/gpt.rst index 6387c8116fe..b505b159d0a 100644 --- a/doc/usage/cmd/gpt.rst +++ b/doc/usage/cmd/gpt.rst @@ -108,6 +108,9 @@ gpt_partition_name gpt_partition_entry the partition number in the table, e.g. 1, 2, 3, etc. +gpt_partition_bootable + 1 if the partition is marked as bootable, 0 if not + gpt swap ~~~~~~~~ @@ -167,6 +170,8 @@ Get the information about the partition named 'rootfs':: rootfs => echo ${gpt_partition_entry} 2 + => echo ${gpt_partition_bootable} + 0 Get the list of partition names on the disk:: diff --git a/test/py/tests/test_gpt.py b/test/py/tests/test_gpt.py index 339468bc127..946858800d6 100644 --- a/test/py/tests/test_gpt.py +++ b/test/py/tests/test_gpt.py @@ -49,6 +49,7 @@ class GptTestDiskImage(object): u_boot_utils.run_and_log(u_boot_console, cmd) # part1 offset 1MB size 1MB cmd = ('sgdisk', '--new=1:2048:4095', '--change-name=1:part1', + '-A 1:set:2', persistent) # part2 offset 2MB size 1.5MB u_boot_utils.run_and_log(u_boot_console, cmd) @@ -117,6 +118,38 @@ def test_gpt_guid(state_disk_image, u_boot_console): output = u_boot_console.run_command('gpt guid host 0') assert '375a56f7-d6c9-4e81-b5f0-09d41ca89efe' in output +@pytest.mark.boardspec('sandbox') +@pytest.mark.buildconfigspec('cmd_gpt') +@pytest.mark.requiredtool('sgdisk') +def test_gpt_setenv(state_disk_image, u_boot_console): + """Test the gpt setenv command.""" + u_boot_console.run_command('host bind 0 ' + state_disk_image.path) + output = u_boot_console.run_command('gpt setenv host 0 part1') + assert 'success!' in output + output = u_boot_console.run_command('echo ${gpt_partition_addr}') + assert output.rstrip() == '800' + output = u_boot_console.run_command('echo ${gpt_partition_size}') + assert output.rstrip() == '800' + output = u_boot_console.run_command('echo ${gpt_partition_name}') + assert output.rstrip() == 'part1' + output = u_boot_console.run_command('echo ${gpt_partition_entry}') + assert output.rstrip() == '1' + output = u_boot_console.run_command('echo ${gpt_partition_bootable}') + assert output.rstrip() == '1' + + output = u_boot_console.run_command('gpt setenv host 0 part2') + assert 'success!' in output + output = u_boot_console.run_command('echo ${gpt_partition_addr}') + assert output.rstrip() == '1000' + output = u_boot_console.run_command('echo ${gpt_partition_size}') + assert output.rstrip() == 'c00' + output = u_boot_console.run_command('echo ${gpt_partition_name}') + assert output.rstrip() == 'part2' + output = u_boot_console.run_command('echo ${gpt_partition_entry}') + assert output.rstrip() == '2' + output = u_boot_console.run_command('echo ${gpt_partition_bootable}') + assert output.rstrip() == '0' + @pytest.mark.boardspec('sandbox') @pytest.mark.buildconfigspec('cmd_gpt') @pytest.mark.requiredtool('sgdisk') From a1e793add5dd21c2a1b08bc57ac99e43183913b6 Mon Sep 17 00:00:00 2001 From: Joshua Watt Date: Thu, 31 Aug 2023 10:51:38 -0600 Subject: [PATCH 03/10] cmd: gpt: Add command to set bootable flags Adds a command that can be used to modify the GPT partition table to indicate which partitions should have the bootable flag set Signed-off-by: Joshua Watt --- cmd/gpt.c | 80 +++++++++++++++++++++++++++++++++++++++ doc/usage/cmd/gpt.rst | 12 ++++++ test/py/tests/test_gpt.py | 22 +++++++++++ 3 files changed, 114 insertions(+) diff --git a/cmd/gpt.c b/cmd/gpt.c index ce85632377e..42e289c8724 100644 --- a/cmd/gpt.c +++ b/cmd/gpt.c @@ -970,6 +970,81 @@ static int do_rename_gpt_parts(struct blk_desc *dev_desc, char *subcomm, free(partitions_list); return ret; } + +/** + * gpt_set_bootable() - Set bootable flags for partitions + * + * Sets the bootable flag for any partition names in the comma separated list of + * partition names. Any partitions not in the list have their bootable flag + * cleared + * + * @desc: block device descriptor + * @name: Comma separated list of partition names + * + * @Return: '0' on success and -ve error on failure + */ +static int gpt_set_bootable(struct blk_desc *blk_dev_desc, char *const part_list) +{ + char *name; + char disk_guid[UUID_STR_LEN + 1]; + struct list_head *pos; + struct disk_part *curr; + struct disk_partition *partitions = NULL; + int part_count = 0; + int ret = get_disk_guid(blk_dev_desc, disk_guid); + + if (ret < 0) + return ret; + + ret = get_gpt_info(blk_dev_desc); + if (ret <= 0) + goto out; + + part_count = ret; + partitions = malloc(sizeof(*partitions) * part_count); + if (!partitions) { + ret = -ENOMEM; + goto out; + } + + /* Copy partitions and clear bootable flag */ + part_count = 0; + list_for_each(pos, &disk_partitions) { + curr = list_entry(pos, struct disk_part, list); + partitions[part_count] = curr->gpt_part_info; + partitions[part_count].bootable &= ~PART_BOOTABLE; + part_count++; + } + + name = strtok(part_list, ","); + while (name) { + bool found = false; + + for (int i = 0; i < part_count; i++) { + if (strcmp((char *)partitions[i].name, name) == 0) { + partitions[i].bootable |= PART_BOOTABLE; + found = true; + } + } + + if (!found) { + printf("Warning: No partition matching '%s' found\n", + name); + } + + name = strtok(NULL, ","); + } + + ret = gpt_restore(blk_dev_desc, disk_guid, partitions, part_count); + +out: + del_gpt_info(); + + if (partitions) + free(partitions); + + return ret; +} #endif /** @@ -1029,6 +1104,8 @@ static int do_gpt(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) } else if ((strcmp(argv[1], "swap") == 0) || (strcmp(argv[1], "rename") == 0)) { ret = do_rename_gpt_parts(blk_dev_desc, argv[1], argv[4], argv[5]); + } else if ((strcmp(argv[1], "set-bootable") == 0)) { + ret = gpt_set_bootable(blk_dev_desc, argv[4]); #endif } else { return CMD_RET_USAGE; @@ -1080,8 +1157,11 @@ U_BOOT_CMD(gpt, CONFIG_SYS_MAXARGS, 1, do_gpt, " and vice-versa\n" " gpt rename \n" " - rename the specified partition\n" + " gpt set-bootable \n" + " - make partition names in list bootable\n" " Example usage:\n" " gpt swap mmc 0 foo bar\n" " gpt rename mmc 0 3 foo\n" + " gpt set-bootable mmc 0 boot_a,boot_b\n" #endif ); diff --git a/doc/usage/cmd/gpt.rst b/doc/usage/cmd/gpt.rst index b505b159d0a..288dd365c00 100644 --- a/doc/usage/cmd/gpt.rst +++ b/doc/usage/cmd/gpt.rst @@ -13,6 +13,7 @@ Synopsis gpt read [] gpt rename gpt repair + gpt set-bootable gpt setenv gpt swap gpt verify [] @@ -90,6 +91,13 @@ gpt repair Repairs the GPT partition tables if it they become corrupted. +gpt set-bootable +~~~~~~~~~~~~~~~~ + +Sets the bootable flag for all partitions in the table. If the partition name +is in 'partition list' (separated by ','), the bootable flag is set, otherwise +it is cleared. CONFIG_CMD_GPT_RENAME=y is required. + gpt setenv ~~~~~~~~~~ @@ -187,3 +195,7 @@ Get the GUID for a disk:: => gpt guid mmc gpt_disk_uuid => echo ${gpt_disk_uuid} bec9fc2a-86c1-483d-8a0e-0109732277d7 + +Set the bootable flag for the 'boot' partition and clear it for all others:: + + => gpt set-bootable mmc 0 boot diff --git a/test/py/tests/test_gpt.py b/test/py/tests/test_gpt.py index 946858800d6..5d23f9b2925 100644 --- a/test/py/tests/test_gpt.py +++ b/test/py/tests/test_gpt.py @@ -222,6 +222,28 @@ def test_gpt_swap_partitions(state_disk_image, u_boot_console): assert '0x00000800 0x00000fff "part2"' in output assert '0x00001000 0x00001bff "part1"' in output +@pytest.mark.buildconfigspec('cmd_gpt') +@pytest.mark.buildconfigspec('cmd_gpt_rename') +@pytest.mark.buildconfigspec('cmd_part') +@pytest.mark.requiredtool('sgdisk') +def test_gpt_set_bootable(state_disk_image, u_boot_console): + """Test the gpt set-bootable command.""" + + u_boot_console.run_command('host bind 0 ' + state_disk_image.path) + parts = ('part2', 'part1') + for bootable in parts: + output = u_boot_console.run_command(f'gpt set-bootable host 0 {bootable}') + assert 'success!' in output + + for p in parts: + output = u_boot_console.run_command(f'gpt setenv host 0 {p}') + assert 'success!' in output + output = u_boot_console.run_command('echo ${gpt_partition_bootable}') + if p == bootable: + assert output.rstrip() == '1' + else: + assert output.rstrip() == '0' + @pytest.mark.boardspec('sandbox') @pytest.mark.buildconfigspec('cmd_gpt') @pytest.mark.buildconfigspec('cmd_part') From 368beaf7bb609b1daede46e3b31f5788d52e44e5 Mon Sep 17 00:00:00 2001 From: Joshua Watt Date: Thu, 31 Aug 2023 10:51:39 -0600 Subject: [PATCH 04/10] cmd: gpt: Preserve type GUID if enabled If CONFIG_PARTITION_TYPE_GUID is enabled, the type GUID will be preserved when writing out the partition string. It was already respected when writing out partitions; this ensures that if you capture the current partition layout and write it back (such as when renaming), the type GUIDs are preserved. Signed-off-by: Joshua Watt --- cmd/gpt.c | 16 ++++++++++ test/py/tests/test_gpt.py | 65 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+) diff --git a/cmd/gpt.c b/cmd/gpt.c index 42e289c8724..9cc72934388 100644 --- a/cmd/gpt.c +++ b/cmd/gpt.c @@ -173,6 +173,9 @@ static int calc_parts_list_len(int numparts) /* see part.h for definition of struct disk_partition */ partlistlen += numparts * (strlen("start=MiB,") + sizeof(lbaint_t) + 1); partlistlen += numparts * (strlen("size=MiB,") + sizeof(lbaint_t) + 1); +#ifdef CONFIG_PARTITION_TYPE_GUID + partlistlen += numparts * (strlen("type=,") + UUID_STR_LEN + 1); +#endif partlistlen += numparts * (strlen("uuid=;") + UUID_STR_LEN + 1); /* for the terminating null */ partlistlen++; @@ -211,6 +214,11 @@ static struct disk_part *allocate_disk_part(struct disk_partition *info, PART_TYPE_LEN); newpart->gpt_part_info.type[PART_TYPE_LEN - 1] = '\0'; newpart->gpt_part_info.bootable = info->bootable; +#ifdef CONFIG_PARTITION_TYPE_GUID + strncpy(newpart->gpt_part_info.type_guid, (const char *)info->type_guid, + UUID_STR_LEN); + newpart->gpt_part_info.type_guid[UUID_STR_LEN] = '\0'; +#endif if (IS_ENABLED(CONFIG_PARTITION_UUIDS)) { strlcpy(newpart->gpt_part_info.uuid, disk_partition_uuid(info), UUID_STR_LEN + 1); @@ -250,6 +258,9 @@ static void print_gpt_info(void) curr->gpt_part_info.name); printf("Type %s, bootable %d\n", curr->gpt_part_info.type, curr->gpt_part_info.bootable & PART_BOOTABLE); +#ifdef CONFIG_PARTITION_TYPE_GUID + printf("Type GUID %s\n", curr->gpt_part_info.type_guid); +#endif #ifdef CONFIG_PARTITION_UUIDS printf("UUID %s\n", curr->gpt_part_info.uuid); #endif @@ -297,6 +308,11 @@ static int create_gpt_partitions_list(int numparts, const char *guid, curr->gpt_part_info.blksz); strncat(partitions_list, partstr, PART_NAME_LEN + 1); +#ifdef CONFIG_PARTITION_TYPE_GUID + strcat(partitions_list, ",type="); + strncat(partitions_list, curr->gpt_part_info.type_guid, + UUID_STR_LEN + 1); +#endif strcat(partitions_list, ",uuid="); strncat(partitions_list, curr->gpt_part_info.uuid, UUID_STR_LEN + 1); diff --git a/test/py/tests/test_gpt.py b/test/py/tests/test_gpt.py index 5d23f9b2925..93007dee9a1 100644 --- a/test/py/tests/test_gpt.py +++ b/test/py/tests/test_gpt.py @@ -16,6 +16,35 @@ the test. # Mark all tests here as slow pytestmark = pytest.mark.slow +def parse_gpt_parts(disk_str): + """Parser a partition string into a list of partitions. + + Args: + disk_str: The disk description string, as returned by `gpt read` + + Returns: + A list of parsed partitions. Each partition is a dictionary with the + string value from each specified key in the partition description, or a + key with with the value True for a boolean flag + """ + parts = [] + for part_str in disk_str.split(';'): + part = {} + for option in part_str.split(","): + if not option: + continue + + if "=" in option: + key, value = option.split("=") + part[key] = value + else: + part[option] = True + + if part: + parts.append(part) + + return parts + class GptTestDiskImage(object): """Disk Image used by the GPT tests.""" @@ -49,11 +78,13 @@ class GptTestDiskImage(object): u_boot_utils.run_and_log(u_boot_console, cmd) # part1 offset 1MB size 1MB cmd = ('sgdisk', '--new=1:2048:4095', '--change-name=1:part1', + '--partition-guid=1:33194895-67f6-4561-8457-6fdeed4f50a3', '-A 1:set:2', persistent) # part2 offset 2MB size 1.5MB u_boot_utils.run_and_log(u_boot_console, cmd) cmd = ('sgdisk', '--new=2:4096:7167', '--change-name=2:part2', + '--partition-guid=2:cc9c6e4a-6551-4cb5-87be-3210f96c86fb', persistent) u_boot_utils.run_and_log(u_boot_console, cmd) cmd = ('sgdisk', '--load-backup=' + persistent) @@ -88,6 +119,40 @@ def test_gpt_read(state_disk_image, u_boot_console): assert '0x00000800 0x00000fff "part1"' in output assert '0x00001000 0x00001bff "part2"' in output +@pytest.mark.boardspec('sandbox') +@pytest.mark.buildconfigspec('cmd_gpt') +@pytest.mark.buildconfigspec('partition_type_guid') +@pytest.mark.requiredtool('sgdisk') +def test_gpt_read_var(state_disk_image, u_boot_console): + """Test the gpt read command.""" + + u_boot_console.run_command('host bind 0 ' + state_disk_image.path) + output = u_boot_console.run_command('gpt read host 0 gpt_parts') + assert 'success!' in output + + output = u_boot_console.run_command('echo ${gpt_parts}') + parts = parse_gpt_parts(output.rstrip()) + + assert parts == [ + { + "uuid_disk": "375a56f7-d6c9-4e81-b5f0-09d41ca89efe", + }, + { + "name": "part1", + "start": "0x100000", + "size": "0x100000", + "type": "0fc63daf-8483-4772-8e79-3d69d8477de4", + "uuid": "33194895-67f6-4561-8457-6fdeed4f50a3", + }, + { + "name": "part2", + "start": "0x200000", + "size": "0x180000", + "type": "0fc63daf-8483-4772-8e79-3d69d8477de4", + "uuid": "cc9c6e4a-6551-4cb5-87be-3210f96c86fb", + }, + ] + @pytest.mark.boardspec('sandbox') @pytest.mark.buildconfigspec('cmd_gpt') @pytest.mark.requiredtool('sgdisk') From 648140f77aff55d3bab072166a88ef179c474524 Mon Sep 17 00:00:00 2001 From: Joshua Watt Date: Thu, 31 Aug 2023 10:51:40 -0600 Subject: [PATCH 05/10] cmd: gpt: Preserve bootable flag Sets the bootable flag when constructing the partition string from the current partition configuration. This ensures that when the partitions are written back (for example, when renaming a partition), the flag is preserved. Signed-off-by: Joshua Watt --- cmd/gpt.c | 3 +++ test/py/tests/test_gpt.py | 1 + 2 files changed, 4 insertions(+) diff --git a/cmd/gpt.c b/cmd/gpt.c index 9cc72934388..d5256035c3a 100644 --- a/cmd/gpt.c +++ b/cmd/gpt.c @@ -176,6 +176,7 @@ static int calc_parts_list_len(int numparts) #ifdef CONFIG_PARTITION_TYPE_GUID partlistlen += numparts * (strlen("type=,") + UUID_STR_LEN + 1); #endif + partlistlen += numparts * strlen("bootable,"); partlistlen += numparts * (strlen("uuid=;") + UUID_STR_LEN + 1); /* for the terminating null */ partlistlen++; @@ -316,6 +317,8 @@ static int create_gpt_partitions_list(int numparts, const char *guid, strcat(partitions_list, ",uuid="); strncat(partitions_list, curr->gpt_part_info.uuid, UUID_STR_LEN + 1); + if (curr->gpt_part_info.bootable & PART_BOOTABLE) + strcat(partitions_list, ",bootable"); strcat(partitions_list, ";"); } return 0; diff --git a/test/py/tests/test_gpt.py b/test/py/tests/test_gpt.py index 93007dee9a1..b4c03bc3a2d 100644 --- a/test/py/tests/test_gpt.py +++ b/test/py/tests/test_gpt.py @@ -143,6 +143,7 @@ def test_gpt_read_var(state_disk_image, u_boot_console): "size": "0x100000", "type": "0fc63daf-8483-4772-8e79-3d69d8477de4", "uuid": "33194895-67f6-4561-8457-6fdeed4f50a3", + "bootable": True, }, { "name": "part2", From 7cc1d87d7e1e64d7bb280ead94c55a51c4f3ee63 Mon Sep 17 00:00:00 2001 From: Joshua Watt Date: Thu, 31 Aug 2023 10:51:41 -0600 Subject: [PATCH 06/10] cmd: gpt: Add command to swap partition order Adds a command called "gpt transpose" which will swap the order two partition table entries in the GPT partition table (but leaves them pointing to the same locations on disk). This can be useful for swapping bootloaders in systems that use an A/B partitioning scheme where the bootrom is hard coded to look for the bootloader in a specific index in the GPT partition table. Signed-off-by: Joshua Watt --- cmd/gpt.c | 46 ++++++++++++++++++++++++++++++++++++--- doc/usage/cmd/gpt.rst | 25 +++++++++++++++++++++ test/py/tests/test_gpt.py | 19 ++++++++++++++++ 3 files changed, 87 insertions(+), 3 deletions(-) diff --git a/cmd/gpt.c b/cmd/gpt.c index d5256035c3a..d4068ee63d1 100644 --- a/cmd/gpt.c +++ b/cmd/gpt.c @@ -856,8 +856,9 @@ static int do_rename_gpt_parts(struct blk_desc *dev_desc, char *subcomm, u8 part_count = 0; int partlistlen, ret, numparts = 0, partnum, i = 1, ctr1 = 0, ctr2 = 0; - if ((subcomm == NULL) || (name1 == NULL) || (name2 == NULL) || - (strcmp(subcomm, "swap") && (strcmp(subcomm, "rename")))) + if (!subcomm || !name1 || !name2 || + (strcmp(subcomm, "swap") && strcmp(subcomm, "rename") && + strcmp(subcomm, "transpose"))) return -EINVAL; ret = get_disk_guid(dev_desc, disk_guid); @@ -918,6 +919,41 @@ static int do_rename_gpt_parts(struct blk_desc *dev_desc, char *subcomm, ret = -EINVAL; goto out; } + } else if (!strcmp(subcomm, "transpose")) { + int idx1, idx2; + struct disk_partition* first = NULL; + struct disk_partition* second= NULL; + struct disk_partition tmp_part; + + idx1 = simple_strtoul(name1, NULL, 10); + idx2 = simple_strtoul(name2, NULL, 10); + if (idx1 == idx2) { + printf("Cannot swap partition with itself\n"); + ret = -EINVAL; + goto out; + } + + list_for_each(pos, &disk_partitions) { + curr = list_entry(pos, struct disk_part, list); + if (curr->partnum == idx1) + first = &curr->gpt_part_info; + else if (curr->partnum == idx2) + second = &curr->gpt_part_info; + } + if (!first) { + printf("Illegal partition number %s\n", name1); + ret = -EINVAL; + goto out; + } + if (!second) { + printf("Illegal partition number %s\n", name2); + ret = -EINVAL; + goto out; + } + + tmp_part = *first; + *first = *second; + *second = tmp_part; } else { /* rename */ if (strlen(name2) > PART_NAME_LEN) { printf("Names longer than %d characters are truncated.\n", PART_NAME_LEN); @@ -1121,7 +1157,8 @@ static int do_gpt(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) } else if (strcmp(argv[1], "read") == 0) { ret = do_get_gpt_info(blk_dev_desc, (argc == 5) ? argv[4] : NULL); } else if ((strcmp(argv[1], "swap") == 0) || - (strcmp(argv[1], "rename") == 0)) { + (strcmp(argv[1], "rename") == 0) || + (strcmp(argv[1], "transpose") == 0)) { ret = do_rename_gpt_parts(blk_dev_desc, argv[1], argv[4], argv[5]); } else if ((strcmp(argv[1], "set-bootable") == 0)) { ret = gpt_set_bootable(blk_dev_desc, argv[4]); @@ -1174,6 +1211,8 @@ U_BOOT_CMD(gpt, CONFIG_SYS_MAXARGS, 1, do_gpt, " gpt swap \n" " - change all partitions named name1 to name2\n" " and vice-versa\n" + " gpt transpose \n" + " - Swap the order of the entries for part1 and part2 in the partition table\n" " gpt rename \n" " - rename the specified partition\n" " gpt set-bootable \n" @@ -1182,5 +1221,6 @@ U_BOOT_CMD(gpt, CONFIG_SYS_MAXARGS, 1, do_gpt, " gpt swap mmc 0 foo bar\n" " gpt rename mmc 0 3 foo\n" " gpt set-bootable mmc 0 boot_a,boot_b\n" + " gpt transpose mmc 0 1 2\n" #endif ); diff --git a/doc/usage/cmd/gpt.rst b/doc/usage/cmd/gpt.rst index 288dd365c00..f6115ecb0ee 100644 --- a/doc/usage/cmd/gpt.rst +++ b/doc/usage/cmd/gpt.rst @@ -16,6 +16,7 @@ Synopsis gpt set-bootable gpt setenv gpt swap + gpt transpose gpt verify [] gpt write @@ -126,6 +127,13 @@ Changes the names of all partitions that are named 'name1' to be 'name2', and all partitions named 'name2' to be 'name1'. CONFIG_CMD_GPT_RENAME=y is required. +gpt transpose +~~~~~~~~~~~~~ + +Swaps the order of two partition table entries with indexes 'part1' and 'part2' +in the partition table, but otherwise leaves the actual partition data +untouched. + gpt verify ~~~~~~~~~~ @@ -199,3 +207,20 @@ Get the GUID for a disk:: Set the bootable flag for the 'boot' partition and clear it for all others:: => gpt set-bootable mmc 0 boot + +Swap the order of the 'boot' and 'rootfs' partition table entries:: + => gpt setenv mmc 0 rootfs + => echo ${gpt_partition_entry} + 2 + => gpt setenv mmc 0 boot + => echo ${gpt_partition_entry} + 1 + + => gpt transpose mmc 0 1 2 + + => gpt setenv mmc 0 rootfs + => echo ${gpt_partition_entry} + 1 + => gpt setenv mmc 0 boot + => echo ${gpt_partition_entry} + 2 diff --git a/test/py/tests/test_gpt.py b/test/py/tests/test_gpt.py index b4c03bc3a2d..6e135b663e8 100644 --- a/test/py/tests/test_gpt.py +++ b/test/py/tests/test_gpt.py @@ -329,3 +329,22 @@ def test_gpt_write(state_disk_image, u_boot_console): assert '0x00001000 0x00001bff "second"' in output output = u_boot_console.run_command('gpt guid host 0') assert '375a56f7-d6c9-4e81-b5f0-09d41ca89efe' in output + +@pytest.mark.buildconfigspec('cmd_gpt') +@pytest.mark.buildconfigspec('cmd_gpt_rename') +@pytest.mark.buildconfigspec('cmd_part') +@pytest.mark.requiredtool('sgdisk') +def test_gpt_transpose(state_disk_image, u_boot_console): + """Test the gpt transpose command.""" + + u_boot_console.run_command('host bind 0 ' + state_disk_image.path) + output = u_boot_console.run_command('part list host 0') + assert '1\t0x00000800\t0x00000fff\t"part1"' in output + assert '2\t0x00001000\t0x00001bff\t"part2"' in output + + output = u_boot_console.run_command('gpt transpose host 0 1 2') + assert 'success!' in output + + output = u_boot_console.run_command('part list host 0') + assert '2\t0x00000800\t0x00000fff\t"part1"' in output + assert '1\t0x00001000\t0x00001bff\t"part2"' in output From 782c7f1bdb0a25e1851a47ff1aba9f71162c2f9d Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Sat, 2 Sep 2023 09:35:21 +0200 Subject: [PATCH 07/10] part: rename disk_partition_type_uuid() Rename disk_partition_type_uuid to disk_partition_type_guid. Provide function descriptions for the getter and setter. Fixes: bcd645428c34 ("part: Add accessors for struct disk_partition type_uuid") Signed-off-by: Heinrich Schuchardt Reviewed-by: Simon Glass --- disk/part_efi.c | 2 +- include/part.h | 26 +++++++++++++++++++++++--- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/disk/part_efi.c b/disk/part_efi.c index 39382c5faee..b7aef3731b5 100644 --- a/disk/part_efi.c +++ b/disk/part_efi.c @@ -299,7 +299,7 @@ int part_get_info_efi(struct blk_desc *desc, int part, } if (IS_ENABLED(CONFIG_PARTITION_TYPE_GUID)) { uuid_bin_to_str(gpt_pte[part - 1].partition_type_guid.b, - (char *)disk_partition_type_uuid(info), + (char *)disk_partition_type_guid(info), UUID_STR_FORMAT_GUID); } diff --git a/include/part.h b/include/part.h index f321479a5e9..db34bc6bb7d 100644 --- a/include/part.h +++ b/include/part.h @@ -108,18 +108,38 @@ static inline void disk_partition_clr_uuid(struct disk_partition *info) } /* Accessors for struct disk_partition field ->type_guid */ -extern char *__invalid_use_of_disk_partition_type_uuid; +extern char *__invalid_use_of_disk_partition_type_guid; +/** + * disk_partition_type_guid() - get partition type GUID + * + * By using this function to get the partition type GUID we can use + * 'if (IS_ENABLED(CONFIG_PARTITION_TYPE_GUID))' instead of + * '#ifdef CONFIG_PARTITION_TYPE_GUID'. + * + * @info: partition information + * Return: partition type GUID + */ static inline const -char *disk_partition_type_uuid(const struct disk_partition *info) +char *disk_partition_type_guid(const struct disk_partition *info) { #ifdef CONFIG_PARTITION_TYPE_GUID return info->type_guid; #else - return __invalid_use_of_disk_partition_type_uuid; + return __invalid_use_of_disk_partition_type_guid; #endif } +/** + * disk_partition_set_type_guid() - set partition type GUID + * + * By using this function to set the partition type GUID we can use + * 'if (IS_ENABLED(CONFIG_PARTITION_TYPE_GUID))' instead of + * '#ifdef CONFIG_PARTITION_TYPE_GUID'. + * + * @info: partition information + * @val: partition type GUID as string + */ static inline void disk_partition_set_type_guid(struct disk_partition *info, const char *val) { From 396f315520bcbcc2b710ab696326b239577c1ad2 Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Sat, 2 Sep 2023 09:35:22 +0200 Subject: [PATCH 08/10] cmd: gpt: use UUID accessor more consistently disk_partition_uuid() and disk_partition_set_uuid() were introduced to let us avoid the usage of #ifdef when dealing with the field uuid of struct disk_partition. In allocate_disk_part() commit c5f1d005f517 ("part: Add accessors for struct disk_partition uuid") missed to use the setter. print_gpt_info() and create_gpt_partitions_list() are further functions where we should use the getter. Fixes: c5f1d005f517 ("part: Add accessors for struct disk_partition uuid") Signed-off-by: Heinrich Schuchardt Reviewed-by: Simon Glass --- cmd/gpt.c | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/cmd/gpt.c b/cmd/gpt.c index d4068ee63d1..96cd8efaafb 100644 --- a/cmd/gpt.c +++ b/cmd/gpt.c @@ -220,10 +220,9 @@ static struct disk_part *allocate_disk_part(struct disk_partition *info, UUID_STR_LEN); newpart->gpt_part_info.type_guid[UUID_STR_LEN] = '\0'; #endif - if (IS_ENABLED(CONFIG_PARTITION_UUIDS)) { - strlcpy(newpart->gpt_part_info.uuid, disk_partition_uuid(info), - UUID_STR_LEN + 1); - } + if (IS_ENABLED(CONFIG_PARTITION_UUIDS)) + disk_partition_set_uuid(&newpart->gpt_part_info, + disk_partition_uuid(info)); newpart->partnum = partnum; return newpart; @@ -262,9 +261,9 @@ static void print_gpt_info(void) #ifdef CONFIG_PARTITION_TYPE_GUID printf("Type GUID %s\n", curr->gpt_part_info.type_guid); #endif -#ifdef CONFIG_PARTITION_UUIDS - printf("UUID %s\n", curr->gpt_part_info.uuid); -#endif + if (CONFIG_IS_ENABLED(PARTITION_UUIDS)) + printf("UUID %s\n", + disk_partition_uuid(&curr->gpt_part_info)); printf("\n"); } } @@ -314,9 +313,12 @@ static int create_gpt_partitions_list(int numparts, const char *guid, strncat(partitions_list, curr->gpt_part_info.type_guid, UUID_STR_LEN + 1); #endif - strcat(partitions_list, ",uuid="); - strncat(partitions_list, curr->gpt_part_info.uuid, - UUID_STR_LEN + 1); + if (CONFIG_IS_ENABLED(PARTITION_UUIDS)) { + strcat(partitions_list, ",uuid="); + strncat(partitions_list, + disk_partition_uuid(&curr->gpt_part_info), + UUID_STR_LEN + 1); + } if (curr->gpt_part_info.bootable & PART_BOOTABLE) strcat(partitions_list, ",bootable"); strcat(partitions_list, ";"); From 69f4d37302583c3a2c8445ff69826c52c6fce7e3 Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Sat, 2 Sep 2023 09:35:23 +0200 Subject: [PATCH 09/10] cmd: gpt: fix calc_parts_list_len() * Avoid incrementing by moving comma into strlen("uuid_disk=,") and considering NUL byte. * Appending a UUID only adds UUID_STR_LEN bytes. Don't count the terminating NUL. * The length of the hexadecimal representation of lba_int is 2 * sizeof(lba_int). * We don't use a 'MiB' postfix but a '0x' prefix. * The uuid field is only needed if configured. Fixes: 2fcaa413b3f6 ("gpt: harden set_gpt_info() against non NULL-terminated strings") Signed-off-by: Heinrich Schuchardt --- cmd/gpt.c | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/cmd/gpt.c b/cmd/gpt.c index 96cd8efaafb..c404785c1d1 100644 --- a/cmd/gpt.c +++ b/cmd/gpt.c @@ -162,26 +162,32 @@ static bool found_key(const char *str, const char *key) return result; } +/** + * calc_parts_list_len() - get size of partition table description + * + * @numparts: number of partitions + * Return: string size including terminating NUL + */ static int calc_parts_list_len(int numparts) { - int partlistlen = UUID_STR_LEN + 1 + strlen("uuid_disk="); - /* for the comma */ - partlistlen++; + /* number of hexadecimal digits of the lbaint_t representation */ + const int lbaint_size = 2 * sizeof(lbaint_t); + int partlistlen; - /* per-partition additions; numparts starts at 1, so this should be correct */ - partlistlen += numparts * (strlen("name=,") + PART_NAME_LEN + 1); + /* media description including terminating NUL */ + partlistlen = strlen("uuid_disk=;") + UUID_STR_LEN + 1; + /* per-partition descriptions; numparts */ + partlistlen += numparts * (strlen("name=,") + PART_NAME_LEN); /* see part.h for definition of struct disk_partition */ - partlistlen += numparts * (strlen("start=MiB,") + sizeof(lbaint_t) + 1); - partlistlen += numparts * (strlen("size=MiB,") + sizeof(lbaint_t) + 1); + partlistlen += numparts * (strlen("start=0x,") + lbaint_size); + partlistlen += numparts * (strlen("size=0x,") + lbaint_size); #ifdef CONFIG_PARTITION_TYPE_GUID partlistlen += numparts * (strlen("type=,") + UUID_STR_LEN + 1); #endif - partlistlen += numparts * strlen("bootable,"); - partlistlen += numparts * (strlen("uuid=;") + UUID_STR_LEN + 1); - /* for the terminating null */ - partlistlen++; - debug("Length of partitions_list is %d for %d partitions\n", partlistlen, - numparts); + if (IS_ENABLED(CONFIG_PARTITION_UUIDS)) + partlistlen += numparts * (strlen("uuid=;") + UUID_STR_LEN); + debug("Length of partitions_list is %d for %d partitions\n", + partlistlen, numparts); return partlistlen; } From f5e4b056c47215cdbe8d8e30a12b036b7a6afa8d Mon Sep 17 00:00:00 2001 From: Heinrich Schuchardt Date: Sat, 2 Sep 2023 09:35:24 +0200 Subject: [PATCH 10/10] cmd: gpt: fix gpt read To partition a block device the partition type GUIDs are needed but 'gpt read' does not provide these. Add the missing parts. Signed-off-by: Heinrich Schuchardt Reviewed-by: Simon Glass --- cmd/gpt.c | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/cmd/gpt.c b/cmd/gpt.c index c404785c1d1..d7e96529a69 100644 --- a/cmd/gpt.c +++ b/cmd/gpt.c @@ -181,11 +181,10 @@ static int calc_parts_list_len(int numparts) /* see part.h for definition of struct disk_partition */ partlistlen += numparts * (strlen("start=0x,") + lbaint_size); partlistlen += numparts * (strlen("size=0x,") + lbaint_size); -#ifdef CONFIG_PARTITION_TYPE_GUID - partlistlen += numparts * (strlen("type=,") + UUID_STR_LEN + 1); -#endif if (IS_ENABLED(CONFIG_PARTITION_UUIDS)) - partlistlen += numparts * (strlen("uuid=;") + UUID_STR_LEN); + partlistlen += numparts * (strlen("uuid=,") + UUID_STR_LEN); + if (IS_ENABLED(CONFIG_PARTITION_TYPE_GUID)) + partlistlen += numparts * (strlen("type=;") + UUID_STR_LEN); debug("Length of partitions_list is %d for %d partitions\n", partlistlen, numparts); return partlistlen; @@ -221,14 +220,12 @@ static struct disk_part *allocate_disk_part(struct disk_partition *info, PART_TYPE_LEN); newpart->gpt_part_info.type[PART_TYPE_LEN - 1] = '\0'; newpart->gpt_part_info.bootable = info->bootable; -#ifdef CONFIG_PARTITION_TYPE_GUID - strncpy(newpart->gpt_part_info.type_guid, (const char *)info->type_guid, - UUID_STR_LEN); - newpart->gpt_part_info.type_guid[UUID_STR_LEN] = '\0'; -#endif if (IS_ENABLED(CONFIG_PARTITION_UUIDS)) disk_partition_set_uuid(&newpart->gpt_part_info, disk_partition_uuid(info)); + if (IS_ENABLED(CONFIG_PARTITION_TYPE_GUID)) + disk_partition_set_type_guid(&newpart->gpt_part_info, + disk_partition_type_guid(info)); newpart->partnum = partnum; return newpart; @@ -264,12 +261,12 @@ static void print_gpt_info(void) curr->gpt_part_info.name); printf("Type %s, bootable %d\n", curr->gpt_part_info.type, curr->gpt_part_info.bootable & PART_BOOTABLE); -#ifdef CONFIG_PARTITION_TYPE_GUID - printf("Type GUID %s\n", curr->gpt_part_info.type_guid); -#endif if (CONFIG_IS_ENABLED(PARTITION_UUIDS)) printf("UUID %s\n", disk_partition_uuid(&curr->gpt_part_info)); + if (IS_ENABLED(CONFIG_PARTITION_TYPE_GUID)) + printf("Type GUID %s\n", + disk_partition_type_guid(&curr->gpt_part_info)); printf("\n"); } } @@ -314,11 +311,12 @@ static int create_gpt_partitions_list(int numparts, const char *guid, curr->gpt_part_info.blksz); strncat(partitions_list, partstr, PART_NAME_LEN + 1); -#ifdef CONFIG_PARTITION_TYPE_GUID - strcat(partitions_list, ",type="); - strncat(partitions_list, curr->gpt_part_info.type_guid, - UUID_STR_LEN + 1); -#endif + if (IS_ENABLED(CONFIG_PARTITION_TYPE_GUID)) { + strcat(partitions_list, ",type="); + strncat(partitions_list, + disk_partition_type_guid(&curr->gpt_part_info), + UUID_STR_LEN + 1); + } if (CONFIG_IS_ENABLED(PARTITION_UUIDS)) { strcat(partitions_list, ",uuid="); strncat(partitions_list,