| #include <command.h> |
| #include <common.h> |
| #include <fs.h> |
| #include <stdlib.h> |
| |
| #define PERSIST_FILE "/property/persistent_properties" |
| |
| /* protobuf: A wire type of 2 (length-delimited) means that the value is a |
| * varint encoded length followed by the specified number of bytes of data. */ |
| #define WIRE_TYPE_LENGTH_DELIMITED 0x12 |
| #define WIRE_TYPE_MULTIPLE 0xa |
| |
| struct property_header { |
| unsigned char magic; |
| unsigned char size; |
| char data[]; |
| } __attribute__((packed)); |
| |
| struct property_field { |
| struct property_header hdr; |
| char string[]; |
| } __attribute__((packed)); |
| |
| #define PROPERTY_MAX_SIZE (255 + 1) |
| struct android_property { |
| char key[PROPERTY_MAX_SIZE]; |
| char val[PROPERTY_MAX_SIZE]; |
| }; |
| |
| struct aprop_data { |
| int mmc_dev; /* mmc device we load PERSIST_FILE from */ |
| int mmc_part; /* mmc partition we load PERSIST_FILE from */ |
| int count; /* amount of parsed Android persistent properties */ |
| struct android_property |
| *cache; /* cache of parsed Android persistent properties */ |
| }; |
| |
| static struct aprop_data aprop = { |
| .mmc_dev = -1, |
| .mmc_part = -1, |
| .count = -1, |
| .cache = NULL, |
| }; |
| |
| enum aprop_cmd { |
| APROP_CMD_LOAD, |
| APROP_CMD_GET, |
| APROP_CMD_SET, |
| APROP_CMD_TEST, |
| APROP_CMD_DUMP, |
| APROP_CMD_STORE, |
| }; |
| |
| static int aprop_cmd_get(char *cmd) |
| { |
| if (!strcmp(cmd, "load")) |
| return APROP_CMD_LOAD; |
| if (!strcmp(cmd, "get")) |
| return APROP_CMD_GET; |
| if (!strcmp(cmd, "set")) |
| return APROP_CMD_SET; |
| if (!strcmp(cmd, "test")) |
| return APROP_CMD_TEST; |
| if (!strcmp(cmd, "dump")) |
| return APROP_CMD_DUMP; |
| if (!strcmp(cmd, "store")) |
| return APROP_CMD_STORE; |
| else |
| return -1; |
| } |
| |
| static int aprop_is_misused(int argc, char *const argv[]) |
| { |
| int cmd = aprop_cmd_get(argv[0]); |
| |
| switch (cmd) { |
| case APROP_CMD_LOAD: |
| if (argc != 3) |
| goto err; |
| break; |
| case APROP_CMD_GET: |
| if (argc != 2) |
| goto err; |
| break; |
| case APROP_CMD_SET: |
| if (argc != 3) |
| goto err; |
| break; |
| case APROP_CMD_TEST: |
| if (argc != 4) |
| goto err; |
| break; |
| case APROP_CMD_DUMP: |
| case APROP_CMD_STORE: |
| if (argc != 1) |
| goto err; |
| break; |
| default: |
| printf("Error: 'aprop %s' not supported\n", argv[0]); |
| return -1; |
| } |
| |
| if (cmd != APROP_CMD_LOAD && |
| (aprop.mmc_dev < 0 || aprop.mmc_part < 0)) { |
| printf("Error: Please, 'load' first!\n"); |
| return -1; |
| } |
| |
| return 0; |
| err: |
| printf("Error: Bad usage of 'aprop %s'\n", argv[0]); |
| |
| return -1; |
| } |
| |
| /** |
| * Computes the amount of elements (struct android_property) present in |
| * a buffer corresponding to Android persistent property file |
| * |
| * @buffer: a valid buffer read from PERSIST_FILE |
| * @size: buffer size (in bytes) |
| */ |
| static int aprop_count_from_buf(unsigned long buffer, loff_t size) |
| { |
| struct property_header *hdr; |
| unsigned long end = buffer + size; |
| int count = 0; |
| |
| while (buffer < end) { |
| hdr = (struct property_header *)buffer; |
| buffer += hdr->size + sizeof(*hdr); |
| count += 1; |
| } |
| |
| return count; |
| } |
| |
| /** |
| * Deserializes a buffer into an Android property cache |
| * This is a well known format described in protobuf here: |
| * https://android.googlesource.com/platform/system/core/+/master/init/persistent_properties.proto |
| * |
| * @buf_location: a valid buffer read from PERSIST_FILE |
| * @count: the amount of items in the cache |
| * @cache: a valid android property cache, allocatd by caller |
| */ |
| static void aprop_deserialize_buf(ulong buf_location, int count, |
| struct android_property *cache) |
| { |
| struct property_header *hdr; |
| struct property_field *field; |
| |
| for (int i = 0; i < count; ++i) { |
| hdr = (struct property_header *)buf_location; |
| |
| field = (struct property_field *)hdr->data; |
| strncpy(cache[i].key, field->string, field->hdr.size); |
| cache[i].key[field->hdr.size] = '\0'; |
| |
| field = (struct property_field *)(field->string + |
| field->hdr.size); |
| strncpy(cache[i].val, field->string, field->hdr.size); |
| cache[i].val[field->hdr.size] = '\0'; |
| |
| buf_location += hdr->size + sizeof(*hdr); |
| } |
| } |
| |
| /** |
| * Computes the amount of bytes we need on disk to store the current |
| * Android property cache. |
| * |
| * @cache: a valid android property cache (usually aprop.cache) |
| * @count: the amount of items in the cache |
| */ |
| static size_t aprop_bufsize_for_cache(struct android_property *cache, int count) |
| { |
| size_t bytes = 0; |
| |
| for (int i = 0; i < count; i++) { |
| size_t bytes_key = strlen(cache[i].key); |
| size_t bytes_val = strlen(cache[i].val); |
| |
| bytes += (bytes_key + sizeof(struct property_field)) + |
| (bytes_val + sizeof(struct property_field)) + |
| sizeof(struct property_header); |
| } |
| |
| return bytes; |
| } |
| |
| /** |
| * Serializes an Android property cache into a buffer recognized by Android |
| * This is a well known format described in protobuf here: |
| * https://android.googlesource.com/platform/system/core/+/master/init/persistent_properties.proto |
| * |
| * @cache: a valid android property cache |
| * @count: the amount of items in the cache |
| * @buf_location: the buffer's address, allocated by the caller |
| * |
| * @return: the amount of bytes the buffer uses in memory |
| */ |
| loff_t aprop_serialize_to_buf(struct android_property *cache, int count, |
| unsigned long buf_location) |
| { |
| struct property_header *hdr; |
| struct property_field *field; |
| loff_t bytes = 0; |
| |
| for (int i = 0; i < count; i++) { |
| hdr = (struct property_header *)buf_location; |
| hdr->magic = WIRE_TYPE_MULTIPLE; |
| hdr->size = 0; |
| |
| field = (struct property_field *)hdr->data; |
| field->hdr.magic = WIRE_TYPE_MULTIPLE; |
| field->hdr.size = strlen(aprop.cache[i].key); |
| strncpy(field->string, aprop.cache[i].key, field->hdr.size); |
| hdr->size += field->hdr.size + sizeof(struct property_field); |
| |
| field = (struct property_field *)(field->string + |
| field->hdr.size); |
| field->hdr.magic = WIRE_TYPE_LENGTH_DELIMITED; |
| field->hdr.size = strlen(aprop.cache[i].val); |
| strncpy(field->string, aprop.cache[i].val, field->hdr.size); |
| hdr->size += field->hdr.size + sizeof(struct property_field); |
| |
| bytes += hdr->size + sizeof(*hdr); |
| buf_location += hdr->size + sizeof(*hdr); |
| } |
| |
| return bytes; |
| } |
| |
| static int aprop_find(const char *const key) |
| { |
| int i; |
| |
| for (i = 0; i < aprop.count; i++) |
| if (!strncmp(key, aprop.cache[i].key, PROPERTY_MAX_SIZE)) |
| return i; |
| |
| return -1; |
| } |
| |
| static int do_aprop_load(cmd_tbl_t *cmdtp, int flag, int argc, |
| char *const argv[]) |
| { |
| struct blk_desc *desc; |
| disk_partition_t info; |
| loff_t filesize; |
| loff_t actread; |
| char *endp; |
| int part, ret; |
| |
| ret = blk_get_device_by_str("mmc", argv[1], &desc); |
| if (ret < 0) { |
| printf("Error: mmc %s:%s read failed (%d)\n", argv[1], argv[2], |
| ret); |
| goto err; |
| } |
| |
| aprop.mmc_dev = desc->devnum; |
| |
| part = simple_strtoul(argv[2], &endp, 0); |
| if (*endp == '\0') { |
| ret = part_get_info(desc, part, &info); |
| if (ret) { |
| printf("Error: mmc %s:%s read failed (%d)\n", argv[1], |
| argv[2], ret); |
| goto err; |
| } |
| } else { |
| part = part_get_info_by_name(desc, argv[2], &info); |
| if (part < 0) { |
| printf("Error: mmc %s:%s read failed (%d)\n", argv[1], |
| argv[2], ret); |
| ret = part; |
| goto err; |
| } |
| } |
| aprop.mmc_part = part; |
| |
| fs_set_blk_dev_with_part(desc, part); |
| ret = fs_size(PERSIST_FILE, &filesize); |
| if (ret < 0) { |
| printf("Error: getsize %s failed(%d)\n", PERSIST_FILE, ret); |
| goto err; |
| } |
| |
| unsigned long buf_location = (unsigned long)malloc(filesize); |
| if (!buf_location) { |
| printf("Error: malloc to load %s failed\n", PERSIST_FILE); |
| ret = -ENOMEM; |
| goto err; |
| } |
| |
| fs_set_blk_dev_with_part(desc, part); |
| ret = fs_read(PERSIST_FILE, buf_location, 0, filesize, &actread); |
| if (ret < 0) { |
| printf("Error: file %s read failed (%d)\n", PERSIST_FILE, ret); |
| goto err_free_filebuf; |
| } |
| |
| filesize = actread; |
| |
| aprop.count = aprop_count_from_buf(buf_location, filesize); |
| aprop.cache = malloc(sizeof(struct android_property) * aprop.count); |
| if (!aprop.cache) { |
| printf("Error: allocate memory to store aprop cache failed\n"); |
| ret = -ENOMEM; |
| goto err_free_filebuf; |
| } |
| |
| aprop_deserialize_buf(buf_location, aprop.count, aprop.cache); |
| free((void *)buf_location); |
| |
| debug("%s: Loaded from mmc %d:%d\n", __func__, aprop.mmc_dev, |
| aprop.mmc_part); |
| |
| return CMD_RET_SUCCESS; |
| |
| err_free_filebuf: |
| free((void *)buf_location); |
| err: |
| aprop.mmc_dev = -1; |
| aprop.mmc_part = -1; |
| aprop.count = -1; |
| aprop.cache = NULL; |
| |
| return CMD_RET_FAILURE; |
| } |
| |
| static int do_aprop_get(cmd_tbl_t *cmdtp, int flag, int argc, |
| char *const argv[]) |
| { |
| int index; |
| const char *const requested_key = argv[1]; |
| |
| index = aprop_find(requested_key); |
| if (index < 0) { |
| printf("Error: could not find property %s\n", requested_key); |
| return CMD_RET_FAILURE; |
| } |
| |
| printf("%s\n", aprop.cache[index].val); |
| return CMD_RET_SUCCESS; |
| } |
| |
| static int do_aprop_set(cmd_tbl_t *cmdtp, int flag, int argc, |
| char *const argv[]) |
| { |
| size_t requested_val_len; |
| int index; |
| |
| index = aprop_find(argv[1]); |
| if (index < 0) { |
| printf("Error: could not find property %s\n", argv[1]); |
| return CMD_RET_FAILURE; |
| } |
| |
| requested_val_len = strnlen(argv[2], PROPERTY_MAX_SIZE); |
| if (requested_val_len == PROPERTY_MAX_SIZE) { |
| printf("Warning: %s is too long, will truncate to %u chars\n", |
| argv[2], PROPERTY_MAX_SIZE); |
| } |
| |
| strncpy(aprop.cache[index].val, argv[2], requested_val_len); |
| aprop.cache[index].val[requested_val_len] = '\0'; |
| |
| return CMD_RET_SUCCESS; |
| } |
| |
| static int do_aprop_test(cmd_tbl_t *cmdtp, int flag, int argc, |
| char *const argv[]) |
| { |
| const char *const op = argv[2]; |
| size_t size; |
| int index; |
| |
| index = aprop_find(argv[1]); |
| if (index == -1) { |
| printf("Error: could not find property %s\n", argv[1]); |
| return CMD_RET_FAILURE; |
| } |
| |
| size = strnlen(aprop.cache[index].val, PROPERTY_MAX_SIZE); |
| |
| if (*op == '=' && *(op + 1) == '\0') { |
| if (!strncmp(aprop.cache[index].val, argv[3], size)) |
| return CMD_RET_SUCCESS; |
| else |
| return CMD_RET_FAILURE; |
| } else if (*op == '~' && *(op + 1) == '\0') { |
| if (!strstr(aprop.cache[index].val, argv[3])) |
| return CMD_RET_FAILURE; |
| else |
| return CMD_RET_SUCCESS; |
| } else { |
| printf("Error: Unknown operator '%s'\n", op); |
| } |
| |
| return CMD_RET_FAILURE; |
| } |
| |
| static int do_aprop_dump(cmd_tbl_t *cmdtp, int flag, int argc, |
| char *const argv[]) |
| { |
| for (int i = 0; i < aprop.count; i++) { |
| printf("%s=%s\n", aprop.cache[i].key, aprop.cache[i].val); |
| } |
| |
| return CMD_RET_SUCCESS; |
| } |
| |
| static int do_aprop_store(cmd_tbl_t *cmdtp, int flag, int argc, |
| char *const argv[]) |
| { |
| struct blk_desc *desc; |
| disk_partition_t info; |
| loff_t actwrite; |
| loff_t bufsize; |
| int ret; |
| int cmd_ret = CMD_RET_FAILURE; |
| |
| desc = blk_get_devnum_by_type(IF_TYPE_MMC, aprop.mmc_dev); |
| if (!desc) { |
| printf("Error: mmc %d:%d write failed\n", aprop.mmc_dev, |
| aprop.mmc_part); |
| ret = -ENODEV; |
| goto end; |
| } |
| |
| ret = part_get_info(desc, aprop.mmc_part, &info); |
| if (ret < 0) { |
| printf("Error: mmc %d:%d write failed (%d)\n", aprop.mmc_dev, |
| aprop.mmc_part, ret); |
| goto end; |
| } |
| |
| bufsize = aprop_bufsize_for_cache(aprop.cache, aprop.count); |
| unsigned long buf_location = (unsigned long)malloc(bufsize); |
| if (!buf_location) { |
| printf("Error: malloc to store %s failed (bytes=%llu)\n", |
| PERSIST_FILE, bufsize); |
| ret = -ENOMEM; |
| goto end; |
| } |
| |
| aprop_serialize_to_buf(aprop.cache, aprop.count, buf_location); |
| |
| fs_set_blk_dev_with_part(desc, aprop.mmc_part); |
| ret = fs_write(PERSIST_FILE, buf_location, 0, bufsize, &actwrite); |
| if (ret < 0) { |
| printf("Error: file %s write failed (%d) bytes=%llu\n", |
| PERSIST_FILE, ret, actwrite); |
| goto end_free_filebuf; |
| } |
| |
| cmd_ret = CMD_RET_SUCCESS; |
| |
| end_free_filebuf: |
| free((void *)buf_location); |
| end: |
| return cmd_ret; |
| } |
| |
| static cmd_tbl_t cmd_aprop_sub[] = { |
| U_BOOT_CMD_MKENT(load, CONFIG_SYS_MAXARGS, 1, do_aprop_load, "", ""), |
| U_BOOT_CMD_MKENT(get, CONFIG_SYS_MAXARGS, 1, do_aprop_get, "", ""), |
| U_BOOT_CMD_MKENT(set, CONFIG_SYS_MAXARGS, 1, do_aprop_set, "", ""), |
| U_BOOT_CMD_MKENT(test, CONFIG_SYS_MAXARGS, 1, do_aprop_test, "", ""), |
| U_BOOT_CMD_MKENT(dump, CONFIG_SYS_MAXARGS, 1, do_aprop_dump, "", ""), |
| U_BOOT_CMD_MKENT(store, CONFIG_SYS_MAXARGS, 1, do_aprop_store, "", ""), |
| }; |
| |
| static int do_aprop(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[]) |
| { |
| cmd_tbl_t *c; |
| |
| if (argc < 2) |
| return CMD_RET_USAGE; |
| |
| argc--; |
| argv++; |
| |
| c = find_cmd_tbl(argv[0], cmd_aprop_sub, ARRAY_SIZE(cmd_aprop_sub)); |
| if (!c) |
| return CMD_RET_USAGE; |
| |
| if (aprop_is_misused(argc, argv)) { |
| /* |
| * We try to improve the user experience by reporting the |
| * root-cause of misusage, so don't return CMD_RET_USAGE, |
| * since the latter prints out the full-blown help text |
| */ |
| return CMD_RET_FAILURE; |
| } |
| |
| return c->cmd(cmdtp, flag, argc, argv); |
| } |
| |
| U_BOOT_CMD( |
| aprop, CONFIG_SYS_MAXARGS, 1, do_aprop, |
| "Load/get/set/test/dump/store Android persistent properties", |
| "load <dev> <part> - load properties from mmc <dev>:<part>\n" |
| "aprop get <key> - retrieves the <val> for <key>\n" |
| "aprop set <key> <val> - sets <key> to <val>\n" |
| "aprop test <key> <op> <val> - test <key> agains <val>\n" |
| "aprop dump - dump all properties in <key>=<val> format\n" |
| "aprop store - store all properties back to mmc\n" |
| "Legend:\n" |
| "<dev> - MMC device index containing the aprop partition\n" |
| "<part> - MMC partition index or name containing the property file\n" |
| "<key> - string/text representing a property key\n" |
| "<val> - string/text representing a property value\n" |
| "<op> - the binary operator used in 'aprop test':\n" |
| " '=' returns true if <val> matches <key>'s value\n" |
| " '~' returns true if <val> matches a subset of <key>'s value\n"); |