// SPDX-License-Identifier: GPL-2.0+ /* * cmd_gpt.c -- GPT (GUID Partition Table) handling command * * Copyright (C) 2015 * Lukasz Majewski * * Copyright (C) 2012 Samsung Electronics * author: Lukasz Majewski * author: Piotr Wilczek */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static LIST_HEAD(disk_partitions); /** * extract_env(): Expand env name from string format '&{env_name}' * and return pointer to the env (if the env is set) * * @param str - pointer to string * @param env - pointer to pointer to extracted env * * @return - zero on successful expand and env is set */ static int extract_env(const char *str, char **env) { int ret = -1; char *e, *s; #ifdef CONFIG_RANDOM_UUID char uuid_str[UUID_STR_LEN + 1]; #endif if (!str || strlen(str) < 4) return -1; if (!((strncmp(str, "${", 2) == 0) && (str[strlen(str) - 1] == '}'))) return -1; s = strdup(str); if (s == NULL) return -1; memset(s + strlen(s) - 1, '\0', 1); memmove(s, s + 2, strlen(s) - 1); e = env_get(s); if (e == NULL) { #ifdef CONFIG_RANDOM_UUID debug("%s unset. ", str); gen_rand_uuid_str(uuid_str, UUID_STR_FORMAT_GUID); env_set(s, uuid_str); e = env_get(s); if (e) { debug("Set to random.\n"); ret = 0; } else { debug("Can't get random UUID.\n"); } #else debug("%s unset.\n", str); #endif } else { debug("%s get from environment.\n", str); ret = 0; } *env = e; free(s); return ret; } /** * extract_val(): Extract value from a key=value pair list (comma separated). * Only value for the given key is returend. * Function allocates memory for the value, remember to free! * * @param str - pointer to string with key=values pairs * @param key - pointer to the key to search for * * @return - pointer to allocated string with the value */ static char *extract_val(const char *str, const char *key) { char *v, *k; char *s, *strcopy; char *new = NULL; strcopy = strdup(str); if (strcopy == NULL) return NULL; s = strcopy; while (s) { v = strsep(&s, ","); if (!v) break; k = strsep(&v, "="); if (!k) break; if (strcmp(k, key) == 0) { new = strdup(v); break; } } free(strcopy); return new; } /** * found_key(): Found key without value in parameter list (comma separated). * * @param str - pointer to string with key * @param key - pointer to the key to search for * * @return - true on found key */ static bool found_key(const char *str, const char *key) { char *k; char *s, *strcopy; bool result = false; strcopy = strdup(str); if (!strcopy) return NULL; s = strcopy; while (s) { k = strsep(&s, ","); if (!k) break; if (strcmp(k, key) == 0) { result = true; break; } } free(strcopy); return result; } 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; } #ifdef CONFIG_CMD_GPT_RENAME static void del_gpt_info(void) { struct list_head *pos = &disk_partitions; struct disk_part *curr; while (!list_empty(pos)) { curr = list_entry(pos->next, struct disk_part, list); list_del(pos->next); free(curr); } } static struct disk_part *allocate_disk_part(struct disk_partition *info, int partnum) { struct disk_part *newpart; newpart = calloc(1, sizeof(struct disk_part)); if (!newpart) return ERR_PTR(-ENOMEM); newpart->gpt_part_info.start = info->start; newpart->gpt_part_info.size = info->size; newpart->gpt_part_info.blksz = info->blksz; strncpy((char *)newpart->gpt_part_info.name, (const char *)info->name, PART_NAME_LEN); newpart->gpt_part_info.name[PART_NAME_LEN - 1] = '\0'; strncpy((char *)newpart->gpt_part_info.type, (const char *)info->type, PART_TYPE_LEN); newpart->gpt_part_info.type[PART_TYPE_LEN - 1] = '\0'; newpart->gpt_part_info.bootable = info->bootable; #ifdef CONFIG_PARTITION_UUIDS strncpy(newpart->gpt_part_info.uuid, (const char *)info->uuid, UUID_STR_LEN); /* UUID_STR_LEN is correct, as uuid[]'s length is UUID_STR_LEN+1 chars */ newpart->gpt_part_info.uuid[UUID_STR_LEN] = '\0'; #endif newpart->partnum = 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("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, curr->gpt_part_info.bootable & PART_BOOTABLE); #ifdef CONFIG_PARTITION_UUIDS printf("UUID %s\n", curr->gpt_part_info.uuid); #endif printf("\n"); } } /* * 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); sprintf(partstr, ",start=0x%llx", (unsigned long long)curr->gpt_part_info.start * curr->gpt_part_info.blksz); /* one extra byte for NULL */ strncat(partitions_list, partstr, PART_NAME_LEN + 1); sprintf(partstr, ",size=0x%llx", (unsigned long long)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 */ static int get_gpt_info(struct blk_desc *dev_desc) { /* start partition numbering at 1, as U-Boot does */ int valid_parts = 0, p, ret; struct disk_partition info; struct disk_part *new_disk_part; /* * 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); if (ret) continue; /* Add 1 here because counter is zero-based but p1 is the first partition */ new_disk_part = allocate_disk_part(&info, valid_parts+1); if (IS_ERR(new_disk_part)) goto out; list_add_tail(&new_disk_part->list, &disk_partitions); valid_parts++; } if (valid_parts == 0) { printf("** No valid partitions found **\n"); goto out; } return valid_parts; out: if (valid_parts >= 1) del_gpt_info(); return -ENODEV; } /* a wrapper to test get_gpt_info */ static int do_get_gpt_info(struct blk_desc *dev_desc) { int ret; ret = get_gpt_info(dev_desc); if (ret > 0) { print_gpt_info(); del_gpt_info(); return 0; } return ret; } #endif /** * set_gpt_info(): Fill partition information from string * function allocates memory, remember to free! * * @param dev_desc - pointer block device descriptor * @param str_part - pointer to string with partition information * @param str_disk_guid - pointer to pointer to allocated string with disk guid * @param partitions - pointer to pointer to allocated partitions array * @param parts_count - number of partitions * * @return - zero on success, otherwise error * */ static int set_gpt_info(struct blk_desc *dev_desc, const char *str_part, char **str_disk_guid, struct disk_partition **partitions, u8 *parts_count) { char *tok, *str, *s; int i; char *val, *p; int p_count; struct disk_partition *parts; int errno = 0; uint64_t size_ll, start_ll; lbaint_t offset = 0; int max_str_part = calc_parts_list_len(MAX_SEARCH_PARTITIONS); debug("%s: lba num: 0x%x %d\n", __func__, (unsigned int)dev_desc->lba, (unsigned int)dev_desc->lba); if (str_part == NULL) return -1; str = strdup(str_part); if (str == NULL) return -ENOMEM; /* extract disk guid */ s = str; val = extract_val(str, "uuid_disk"); if (!val) { #ifdef CONFIG_RANDOM_UUID *str_disk_guid = malloc(UUID_STR_LEN + 1); if (*str_disk_guid == NULL) return -ENOMEM; gen_rand_uuid_str(*str_disk_guid, UUID_STR_FORMAT_STD); #else free(str); return -2; #endif } else { val = strsep(&val, ";"); if (extract_env(val, &p)) p = val; *str_disk_guid = strdup(p); free(val); /* Move s to first partition */ strsep(&s, ";"); } if (s == NULL) { printf("Error: is the partitions string NULL-terminated?\n"); return -EINVAL; } if (strnlen(s, max_str_part) == 0) return -3; i = strnlen(s, max_str_part) - 1; if (s[i] == ';') s[i] = '\0'; /* calculate expected number of partitions */ p_count = 1; p = s; while (*p) { if (*p++ == ';') p_count++; } /* allocate memory for partitions */ parts = calloc(sizeof(struct disk_partition), p_count); if (parts == NULL) return -ENOMEM; /* retrieve partitions data from string */ for (i = 0; i < p_count; i++) { tok = strsep(&s, ";"); if (tok == NULL) break; /* uuid */ val = extract_val(tok, "uuid"); if (!val) { /* 'uuid' is optional if random uuid's are enabled */ #ifdef CONFIG_RANDOM_UUID gen_rand_uuid_str(parts[i].uuid, UUID_STR_FORMAT_STD); #else errno = -4; goto err; #endif } else { if (extract_env(val, &p)) p = val; if (strnlen(p, max_str_part) >= sizeof(parts[i].uuid)) { printf("Wrong uuid format for partition %d\n", i); errno = -4; goto err; } strncpy((char *)parts[i].uuid, p, max_str_part); free(val); } #ifdef CONFIG_PARTITION_TYPE_GUID /* guid */ val = extract_val(tok, "type"); if (val) { /* 'type' is optional */ if (extract_env(val, &p)) p = val; if (strnlen(p, max_str_part) >= sizeof(parts[i].type_guid)) { printf("Wrong type guid format for partition %d\n", i); errno = -4; goto err; } strncpy((char *)parts[i].type_guid, p, max_str_part); free(val); } #endif /* name */ val = extract_val(tok, "name"); if (!val) { /* name is mandatory */ errno = -4; goto err; } if (extract_env(val, &p)) p = val; if (strnlen(p, max_str_part) >= sizeof(parts[i].name)) { errno = -4; goto err; } strncpy((char *)parts[i].name, p, max_str_part); free(val); /* size */ val = extract_val(tok, "size"); if (!val) { /* 'size' is mandatory */ errno = -4; goto err; } if (extract_env(val, &p)) p = val; if ((strcmp(p, "-") == 0)) { /* Let part efi module to auto extend the size */ parts[i].size = 0; } else { size_ll = ustrtoull(p, &p, 0); parts[i].size = lldiv(size_ll, dev_desc->blksz); } free(val); /* start address */ val = extract_val(tok, "start"); if (val) { /* start address is optional */ if (extract_env(val, &p)) p = val; start_ll = ustrtoull(p, &p, 0); parts[i].start = lldiv(start_ll, dev_desc->blksz); free(val); } offset += parts[i].size + parts[i].start; /* bootable */ if (found_key(tok, "bootable")) parts[i].bootable = PART_BOOTABLE; } *parts_count = p_count; *partitions = parts; free(str); return 0; err: free(str); free(*str_disk_guid); free(parts); return errno; } static int gpt_default(struct blk_desc *blk_dev_desc, const char *str_part) { int ret; char *str_disk_guid; u8 part_count = 0; struct disk_partition *partitions = NULL; /* fill partitions */ ret = set_gpt_info(blk_dev_desc, str_part, &str_disk_guid, &partitions, &part_count); if (ret) { if (ret == -1) printf("No partition list provided\n"); if (ret == -2) printf("Missing disk guid\n"); if ((ret == -3) || (ret == -4)) printf("Partition list incomplete\n"); return -1; } /* save partitions layout to disk */ ret = gpt_restore(blk_dev_desc, str_disk_guid, partitions, part_count); free(str_disk_guid); free(partitions); return ret; } static int gpt_verify(struct blk_desc *blk_dev_desc, const char *str_part) { ALLOC_CACHE_ALIGN_BUFFER_PAD(gpt_header, gpt_head, 1, blk_dev_desc->blksz); struct disk_partition *partitions = NULL; gpt_entry *gpt_pte = NULL; char *str_disk_guid; u8 part_count = 0; int ret = 0; /* fill partitions */ ret = set_gpt_info(blk_dev_desc, str_part, &str_disk_guid, &partitions, &part_count); if (ret) { if (ret == -1) { printf("No partition list provided - only basic check\n"); ret = gpt_verify_headers(blk_dev_desc, gpt_head, &gpt_pte); goto out; } if (ret == -2) printf("Missing disk guid\n"); if ((ret == -3) || (ret == -4)) printf("Partition list incomplete\n"); return -1; } /* Check partition layout with provided pattern */ ret = gpt_verify_partitions(blk_dev_desc, partitions, part_count, gpt_head, &gpt_pte); free(str_disk_guid); free(partitions); out: free(gpt_pte); return ret; } static int do_disk_guid(struct blk_desc *dev_desc, char * const namestr) { int ret; char disk_guid[UUID_STR_LEN + 1]; ret = get_disk_guid(dev_desc, disk_guid); if (ret < 0) return CMD_RET_FAILURE; if (namestr) env_set(namestr, disk_guid); else printf("%s\n", disk_guid); 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; struct disk_partition *new_partitions = NULL; char disk_guid[UUID_STR_LEN + 1]; char *partitions_list, *str_disk_guid = NULL; 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; /* * Allocates disk_partitions, requiring matching call to del_gpt_info() * if successful. */ 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) { del_gpt_info(); return -ENOMEM; } memset(partitions_list, '\0', partlistlen); ret = create_gpt_partitions_list(numparts, disk_guid, partitions_list); if (ret < 0) { free(partitions_list); 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)); /* set_gpt_info allocates new_partitions and str_disk_guid */ ret = set_gpt_info(dev_desc, partitions_list, &str_disk_guid, &new_partitions, &part_count); if (ret < 0) goto out; 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); ret = -EINVAL; goto out; } 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"); ret = -EINVAL; goto out; } } else { /* rename */ if (strlen(name2) > PART_NAME_LEN) { printf("Names longer than %d characters are truncated.\n", PART_NAME_LEN); ret = -EINVAL; goto out; } partnum = (int)simple_strtol(name1, NULL, 10); if ((partnum < 0) || (partnum > numparts)) { printf("Illegal partition number %s\n", name1); ret = -EINVAL; goto out; } ret = part_get_info(dev_desc, partnum, new_partitions); if (ret < 0) goto out; /* 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) goto out; 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); /* * Even though valid pointers are here passed into set_gpt_info(), * it mallocs again, and there's no way to tell which failed. */ if (ret < 0) goto out; 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"); goto out; } debug("Reading back new partition table\n"); /* * Empty the existing disk_partitions list, as otherwise the memory in * the original list is unreachable. */ del_gpt_info(); numparts = get_gpt_info(dev_desc); if (numparts <= 0) { ret = numparts ? numparts : -ENODEV; goto out; } printf("new partition table with %d partitions is:\n", numparts); print_gpt_info(); out: del_gpt_info(); #ifdef CONFIG_RANDOM_UUID free(str_disk_guid); #endif free(new_partitions); free(partitions_list); return ret; } #endif /** * do_gpt(): Perform GPT operations * * @param cmdtp - command name * @param flag * @param argc * @param argv * * @return zero on success; otherwise error */ static int do_gpt(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) { int ret = CMD_RET_SUCCESS; int dev = 0; 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); if (!ep || ep[0] != '\0') { printf("'%s' is not a number\n", argv[3]); return CMD_RET_USAGE; } blk_dev_desc = blk_get_dev(argv[2], dev); if (!blk_dev_desc) { printf("%s: %s dev %d NOT available\n", __func__, argv[2], dev); return CMD_RET_FAILURE; } if ((strcmp(argv[1], "write") == 0) && (argc == 5)) { printf("Writing GPT: "); ret = gpt_default(blk_dev_desc, argv[4]); } else if ((strcmp(argv[1], "verify") == 0)) { ret = gpt_verify(blk_dev_desc, argv[4]); printf("Verify GPT: "); } else if (strcmp(argv[1], "guid") == 0) { ret = do_disk_guid(blk_dev_desc, argv[4]); #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; } if (ret) { printf("error!\n"); return CMD_RET_FAILURE; } printf("success!\n"); return CMD_RET_SUCCESS; } U_BOOT_CMD(gpt, CONFIG_SYS_MAXARGS, 1, do_gpt, "GUID Partition Table", " \n" " - GUID partition table restoration and validity check\n" " Restore or verify GPT information on a device connected\n" " to interface\n" " Example usage:\n" " gpt write mmc 0 $partitions\n" " gpt verify mmc 0 $partitions\n" " gpt guid \n" " - print disk GUID\n" " gpt guid \n" " - set environment variable to disk GUID\n" " 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 read \n" " - read GPT into a data structure for manipulation\n" " gpt swap \n" " - change all partitions named name1 to name2\n" " and vice-versa\n" " gpt rename \n" " - rename the specified partition\n" " Example usage:\n" " gpt swap mmc 0 foo bar\n" " gpt rename mmc 0 3 foo\n" #endif );