GPT: provide commands to selectively rename partitions
authorAlison Chaiken <alison@peloton-tech.com>
Tue, 4 Jul 2017 18:19:18 +0000 (11:19 -0700)
committerTom Rini <trini@konsulko.com>
Sat, 5 Aug 2017 00:35:27 +0000 (20:35 -0400)
This patch provides support in u-boot for renaming GPT
partitions.  The renaming is accomplished via new 'gpt swap'
and 'gpt rename' commands.

The 'swap' mode returns an error if no matching partition names
are found, or if the number of partitions with one name does not equal
the number with the second name.   The 'rename' variant always
succeeds as long as a partition with the provided number exists.

Rewriting the partition table has the side-effect that all partitions
end up with "msftdata" flag set.  The reason is that partition type
PARTITION_BASIC_DATA_GUID is hard-coded in the gpt_fill_pte()
function.  This does not appear to cause any harm.

Signed-off-by: Alison Chaiken <alison@peloton-tech.com>
cmd/Kconfig
cmd/gpt.c
doc/README.gpt

index f18efc1e88b82bd1fa2732ed3f39f2338b8e9913..cd72ea8e56a98a0f67192e1b714bfd3fd875f90c 100644 (file)
@@ -595,6 +595,14 @@ config CMD_GPT
          Enable the 'gpt' command to ready and write GPT style partition
          tables.
 
+config CMD_GPT_RENAME
+       bool "GPT partition renaming commands"
+       depends on CMD_GPT
+       help
+         Enables the 'gpt' command to interchange names on two GPT
+         partitions via the 'gpt swap' command or to rename single
+         partitions via the 'rename' command.
+
 config CMD_ARMFLASH
        #depends on FLASH_CFI_DRIVER
        bool "armflash"
index 9c920fa320799fd80403f373f10d553122460108..dab47c71f9624540fed7dba2a4725221cb091899 100644 (file)
--- a/cmd/gpt.c
+++ b/cmd/gpt.c
@@ -20,6 +20,8 @@
 #include <div64.h>
 #include <memalign.h>
 #include <linux/compat.h>
+#include <linux/sizes.h>
+#include <stdlib.h>
 
 static LIST_HEAD(disk_partitions);
 
@@ -195,16 +197,32 @@ static struct disk_part *allocate_disk_part(disk_partition_t *info, int partnum)
        return newpart;
 }
 
+static void prettyprint_part_size(char *sizestr, lbaint_t partsize,
+                                 lbaint_t blksize)
+{
+       unsigned long long partbytes, partmegabytes;
+
+       partbytes = partsize * blksize;
+       partmegabytes = lldiv(partbytes, SZ_1M);
+       snprintf(sizestr, 16, "%lluMiB", partmegabytes);
+}
+
 static void print_gpt_info(void)
 {
        struct list_head *pos;
        struct disk_part *curr;
+       char partstartstr[16];
+       char partsizestr[16];
 
        list_for_each(pos, &disk_partitions) {
                curr = list_entry(pos, struct disk_part, list);
+               prettyprint_part_size(partstartstr, curr->gpt_part_info.start,
+                                     curr->gpt_part_info.blksz);
+               prettyprint_part_size(partsizestr, curr->gpt_part_info.size,
+                                     curr->gpt_part_info.blksz);
+
                printf("Partition %d:\n", curr->partnum);
-               printf("1st block %x, size %x\n", (unsigned)curr->gpt_part_info.start,
-                      (unsigned)curr->gpt_part_info.size);
+               printf("Start %s, size %s\n", partstartstr, partsizestr);
                printf("Block size %lu, name %s\n", curr->gpt_part_info.blksz,
                       curr->gpt_part_info.name);
                printf("Type %s, bootable %d\n", curr->gpt_part_info.type,
@@ -216,6 +234,73 @@ static void print_gpt_info(void)
        }
 }
 
+static int calc_parts_list_len(int numparts)
+{
+       int partlistlen = UUID_STR_LEN + 1 + strlen("uuid_disk=");
+       /* for the comma */
+       partlistlen++;
+
+       /* per-partition additions; numparts starts at 1, so this should be correct */
+       partlistlen += numparts * (strlen("name=,") + PART_NAME_LEN + 1);
+       /* 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("uuid=;") + UUID_STR_LEN + 1);
+       /* for the terminating null */
+       partlistlen++;
+       debug("Length of partitions_list is %d for %d partitions\n", partlistlen,
+             numparts);
+       return partlistlen;
+}
+
+/*
+ * create the string that upstream 'gpt write' command will accept as an
+ * argument
+ *
+ * From doc/README.gpt, Format of partitions layout:
+ *    "uuid_disk=...;name=u-boot,size=60MiB,uuid=...;
+ *     name=kernel,size=60MiB,uuid=...;"
+ * The fields 'name' and 'size' are mandatory for every partition.
+ * The field 'start' is optional. The fields 'uuid' and 'uuid_disk'
+ * are optional if CONFIG_RANDOM_UUID is enabled.
+ */
+static int create_gpt_partitions_list(int numparts, const char *guid,
+                                     char *partitions_list)
+{
+       struct list_head *pos;
+       struct disk_part *curr;
+       char partstr[PART_NAME_LEN + 1];
+
+       if (!partitions_list)
+               return -EINVAL;
+
+       strcpy(partitions_list, "uuid_disk=");
+       strncat(partitions_list, guid, UUID_STR_LEN + 1);
+       strcat(partitions_list, ";");
+
+       list_for_each(pos, &disk_partitions) {
+               curr = list_entry(pos, struct disk_part, list);
+               strcat(partitions_list, "name=");
+               strncat(partitions_list, (const char *)curr->gpt_part_info.name,
+                       PART_NAME_LEN + 1);
+               strcat(partitions_list, ",start=");
+               prettyprint_part_size(partstr, (unsigned long)curr->gpt_part_info.start,
+                                     (unsigned long) curr->gpt_part_info.blksz);
+               /* one extra byte for NULL */
+               strncat(partitions_list, partstr, PART_NAME_LEN + 1);
+               strcat(partitions_list, ",size=");
+               prettyprint_part_size(partstr, curr->gpt_part_info.size,
+                                     curr->gpt_part_info.blksz);
+               strncat(partitions_list, partstr, PART_NAME_LEN + 1);
+
+               strcat(partitions_list, ",uuid=");
+               strncat(partitions_list, curr->gpt_part_info.uuid,
+                       UUID_STR_LEN + 1);
+               strcat(partitions_list, ";");
+       }
+       return 0;
+}
+
 /*
  * read partition info into disk_partitions list where
  * it can be printed or modified
@@ -227,8 +312,11 @@ static int get_gpt_info(struct blk_desc *dev_desc)
        disk_partition_t info;
        struct disk_part *new_disk_part;
 
-       if (disk_partitions.next == NULL)
-               INIT_LIST_HEAD(&disk_partitions);
+       /*
+        * Always re-read partition info from device, in case
+        * it has changed
+        */
+       INIT_LIST_HEAD(&disk_partitions);
 
        for (p = 1; p <= MAX_SEARCH_PARTITIONS; p++) {
                ret = part_get_info(dev_desc, p, &info);
@@ -305,6 +393,8 @@ static int set_gpt_info(struct blk_desc *dev_desc,
                return -1;
 
        str = strdup(str_part);
+       if (str == NULL)
+               return -ENOMEM;
 
        /* extract disk guid */
        s = str;
@@ -534,6 +624,127 @@ static int do_disk_guid(struct blk_desc *dev_desc, char * const namestr)
        return ret;
 }
 
+#ifdef CONFIG_CMD_GPT_RENAME
+static int do_rename_gpt_parts(struct blk_desc *dev_desc, char *subcomm,
+                              char *name1, char *name2)
+{
+       struct list_head *pos;
+       struct disk_part *curr;
+       disk_partition_t *new_partitions = NULL;
+       char disk_guid[UUID_STR_LEN + 1];
+       char *partitions_list, *str_disk_guid;
+       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"))))
+               return -EINVAL;
+
+       ret = get_disk_guid(dev_desc, disk_guid);
+       if (ret < 0)
+               return ret;
+       numparts = get_gpt_info(dev_desc);
+       if (numparts <=  0)
+               return numparts ? numparts : -ENODEV;
+
+       partlistlen = calc_parts_list_len(numparts);
+       partitions_list = malloc(partlistlen);
+       if (partitions_list == NULL)
+               return -ENOMEM;
+       memset(partitions_list, '\0', partlistlen);
+
+       ret = create_gpt_partitions_list(numparts, disk_guid, partitions_list);
+       if (ret < 0)
+               return ret;
+       /*
+        * Uncomment the following line to print a string that 'gpt write'
+        * or 'gpt verify' will accept as input.
+        */
+       debug("OLD partitions_list is %s with %u chars\n", partitions_list,
+             (unsigned)strlen(partitions_list));
+
+       ret = set_gpt_info(dev_desc, partitions_list, &str_disk_guid,
+                          &new_partitions, &part_count);
+       if (ret < 0)
+               return ret;
+
+       if (!strcmp(subcomm, "swap")) {
+               if ((strlen(name1) > PART_NAME_LEN) || (strlen(name2) > PART_NAME_LEN)) {
+                       printf("Names longer than %d characters are truncated.\n", PART_NAME_LEN);
+                       return -EINVAL;
+               }
+               list_for_each(pos, &disk_partitions) {
+                       curr = list_entry(pos, struct disk_part, list);
+                       if (!strcmp((char *)curr->gpt_part_info.name, name1)) {
+                               strcpy((char *)curr->gpt_part_info.name, name2);
+                               ctr1++;
+                       } else if (!strcmp((char *)curr->gpt_part_info.name, name2)) {
+                               strcpy((char *)curr->gpt_part_info.name, name1);
+                               ctr2++;
+                       }
+               }
+               if ((ctr1 + ctr2 < 2) || (ctr1 != ctr2)) {
+                       printf("Cannot swap partition names except in pairs.\n");
+                       return -EINVAL;
+               }
+       } else { /* rename */
+               if (strlen(name2) > PART_NAME_LEN) {
+                       printf("Names longer than %d characters are truncated.\n", PART_NAME_LEN);
+                       return -EINVAL;
+               }
+               partnum = (int)simple_strtol(name1, NULL, 10);
+               if ((partnum < 0) || (partnum > numparts)) {
+                       printf("Illegal partition number %s\n", name1);
+                       return -EINVAL;
+               }
+               ret = part_get_info(dev_desc, partnum, new_partitions);
+               if (ret < 0)
+                       return ret;
+
+               /* U-Boot partition numbering starts at 1 */
+               list_for_each(pos, &disk_partitions) {
+                       curr = list_entry(pos, struct disk_part, list);
+                       if (i == partnum) {
+                               strcpy((char *)curr->gpt_part_info.name, name2);
+                               break;
+                       }
+                       i++;
+               }
+       }
+
+       ret = create_gpt_partitions_list(numparts, disk_guid, partitions_list);
+       if (ret < 0)
+               return ret;
+       debug("NEW partitions_list is %s with %u chars\n", partitions_list,
+             (unsigned)strlen(partitions_list));
+
+       ret = set_gpt_info(dev_desc, partitions_list, &str_disk_guid,
+                          &new_partitions, &part_count);
+       if (ret < 0)
+               return ret;
+
+       debug("Writing new partition table\n");
+       ret = gpt_restore(dev_desc, disk_guid, new_partitions, numparts);
+       if (ret < 0) {
+               printf("Writing new partition table failed\n");
+               return ret;
+       }
+
+       debug("Reading back new partition table\n");
+       numparts = get_gpt_info(dev_desc);
+       if (numparts <=  0)
+               return numparts ? numparts : -ENODEV;
+       printf("new partition table with %d partitions is:\n", numparts);
+       print_gpt_info();
+
+       del_gpt_info();
+       free(partitions_list);
+       free(str_disk_guid);
+       free(new_partitions);
+       return ret;
+}
+#endif
+
 /**
  * do_gpt(): Perform GPT operations
  *
@@ -551,7 +762,11 @@ static int do_gpt(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
        char *ep;
        struct blk_desc *blk_dev_desc = NULL;
 
+#ifndef CONFIG_CMD_GPT_RENAME
        if (argc < 4 || argc > 5)
+#else
+       if (argc < 4 || argc > 6)
+#endif
                return CMD_RET_USAGE;
 
        dev = (int)simple_strtoul(argv[3], &ep, 10);
@@ -577,6 +792,9 @@ static int do_gpt(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
 #ifdef CONFIG_CMD_GPT_RENAME
        } else if (strcmp(argv[1], "read") == 0) {
                ret = do_get_gpt_info(blk_dev_desc);
+       } 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]);
 #endif
        } else {
                return CMD_RET_USAGE;
@@ -609,4 +827,15 @@ U_BOOT_CMD(gpt, CONFIG_SYS_MAXARGS, 1, do_gpt,
        " Example usage:\n"
        " gpt guid mmc 0\n"
        " gpt guid mmc 0 varname\n"
+#ifdef CONFIG_CMD_GPT_RENAME
+       "gpt partition renaming commands:\n"
+       "gpt swap <interface> <dev> <name1> <name2>\n"
+       "    - change all partitions named name1 to name2\n"
+       "      and vice-versa\n"
+       "gpt rename <interface> <dev> <part> <name>\n"
+       "    - rename the specified partition\n"
+       " Example usage:\n"
+       " gpt swap mmc 0 foo bar\n"
+       " gpt rename mmc 0 3 foo\n"
+#endif
 );
index c415145236f2552dde5f7fbd1791aa9738afe59d..517df551e7b10cbbbff7712b852d63253569d259 100644 (file)
@@ -210,6 +210,24 @@ Following line can be used to assess if GPT verification has succeed:
 U-BOOT> gpt verify mmc 0 $partitions
 U-BOOT> if test $? = 0; then echo "GPT OK"; else echo "GPT ERR"; fi
 
+Renaming GPT partitions from U-Boot:
+====================================
+
+GPT partition names are a mechanism via which userspace and U-Boot can
+communicate about software updates and boot failure.  The 'gpt guid',
+'gpt read', 'gpt rename' and 'gpt swap' commands facilitate
+programmatic renaming of partitions from bootscripts by generating and
+modifying the partitions layout string.  Here is an illustration of
+employing 'swap' to exchange 'primary' and 'backup' partition names:
+
+U-BOOT> gpt swap mmc 0 primary backup
+
+Afterwards, all partitions previously named 'primary' will be named
+'backup', and vice-versa.  Alternatively, single partitions may be
+renamed.  In this example, mmc0's first partition will be renamed
+'primary':
+
+U-BOOT> gpt rename mmc 0 1 primary
 
 The GPT functionality may be tested with the 'sandbox' board by
 creating a disk image as described under 'Block Device Emulation' in
@@ -228,7 +246,7 @@ board/sandbox/README.sandbox:
 =>host bind 0 ./disk.raw
 => gpt read host 0
 [ . . . ]
-=> gpt flip host 0
+=> gpt swap host 0 name othername
 [ . . . ]
 
 Partition type GUID: