blob: 361399f462f48ec1151bcedf2aea57f52b418303 [file] [log] [blame]
#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");