cmd: add 'aprop' command to manipulate Android persistent properties

'aprop' is a debug command to manipulate Android persistent properties.

Usage:
=> aprop
aprop - Load/get/set/test/dump/store Android persistent properties

Usage:
aprop load  <dev> <part>     - load properties from mmc <dev>:<part>
aprop get   <key>            - retrieves the <val> for <key>
aprop set   <key> <val>      - sets <key> to <val>
aprop test  <key> <op> <val> - test <key> agains <val>
aprop dump                   - dump all properties in <key>=<val> format
aprop store                  - store all properties back to mmc
Legend:
<dev>   - MMC device index containing the aprop partition
<part>  - MMC partition index or name containing the property file
<key>   - string/text representing a property key
<val>   - string/text representing a property value
<op>    - the binary operator used in 'aprop test':
          '=' returns true if <val> matches <key>'s value
          '~' returns true if <val> matches a subset of <key>'s value

Signed-off-by: Alexandre Bailon <abailon@baylibre.com>
Signed-off-by: Mattijs Korpershoek <mkorpershoek@baylibre.com>
diff --git a/cmd/Kconfig b/cmd/Kconfig
index 4e61565..0ac4e05 100644
--- a/cmd/Kconfig
+++ b/cmd/Kconfig
@@ -779,6 +779,25 @@
 	    https://android.googlesource.com/platform/bootable/recovery
 	  - Inspect/dump the contents of the BCB fields
 
+
+config CMD_APROP
+	bool "aprop"
+	depends on MMC
+	depends on PARTITIONS
+	help
+      Read/modify/write the Android persistent PROPerties file, usually
+      stored on the "userdata" partition.
+      This file is saved in /userdata/property/persistent_properties.
+      The structure of this file is in protobuf format wich is described in:
+      https://android.googlesource.com/platform/system/core/+/master/
+      init/persistent_properties.proto
+
+      This command can be used for:
+      - Determine the "boot reason" for cases where BCB is not written
+        (typically adb reboot recovery)
+      - Debugging/inspecting Android persistent properties without
+        booting Android
+
 config CMD_BIND
 	bool "bind/unbind - Bind or unbind a device to/from a driver"
 	depends on DM
diff --git a/cmd/Makefile b/cmd/Makefile
index ac843b4..f54e549 100644
--- a/cmd/Makefile
+++ b/cmd/Makefile
@@ -18,6 +18,7 @@
 obj-y += blk_common.o
 obj-$(CONFIG_CMD_SOURCE) += source.o
 obj-$(CONFIG_CMD_BCB) += bcb.o
+obj-$(CONFIG_CMD_APROP) += aprop.o
 obj-$(CONFIG_CMD_BDI) += bdinfo.o
 obj-$(CONFIG_CMD_BEDBUG) += bedbug.o
 obj-$(CONFIG_CMD_BIND) += bind.o
diff --git a/cmd/aprop.c b/cmd/aprop.c
new file mode 100644
index 0000000..0d92dc1
--- /dev/null
+++ b/cmd/aprop.c
@@ -0,0 +1,520 @@
+#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 propery_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);
+		free((void *)buf_location);
+		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");
diff --git a/doc/android/aprop.txt b/doc/android/aprop.txt
new file mode 100644
index 0000000..0b16141
--- /dev/null
+++ b/doc/android/aprop.txt
@@ -0,0 +1,77 @@
+Android persistent Properties command overview
+
+1. OVERVIEW
+---------------------------------
+Android system properties are widely used among Android native services
+to control runtime behaviour or pass information between processes.
+Android's Init deamon can also trigger actions upon property changes [1]
+Some of these properties will persist accross reboots.
+They are stored in mmc, usually on the userdata partition [2]
+
+Persistent properties contain useful information which we might want to
+inspect for debugging.
+
+One of particular interest is "persist.sys.boot.reason", which allows to
+determine the boot reason without relying on BCB.
+
+DISCLAIMER: this is *debug* command. Should not be used in production.
+Also probably won't work in production as userdata is usually encrypted.
+
+
+2. 'APROP'. SHELL COMMAND OVERVIEW
+-----------------------------------
+
+The 'aprop' command provides a CLI to facilitate the development of the
+requirements enumerated above. Below is the command's help message:
+
+=> aprop
+aprop - Load/get/set/test/dump/store Android persistent properties
+
+Usage:
+aprop load  <dev> <part>     - load properties from mmc <dev>:<part>
+aprop get   <key>            - retrieves the <val> for <key>
+aprop set   <key> <val>      - sets <key> to <val>
+aprop test  <key> <op> <val> - test <key> agains <val>
+aprop dump                   - dump all properties in <key>=<val> format
+aprop store                  - store all properties back to mmc
+Legend:
+<dev>   - MMC device index containing the aprop partition
+<part>  - MMC partition index or name containing the property file
+<key>   - string/text representing a property key
+<val>   - string/text representing a property value
+<op>    - the binary operator used in 'aprop test':
+          '=' returns true if <val> matches <key>'s value
+          '~' returns true if <val> matches a subset of <key>'s value
+
+
+3. 'APROP'. EXAMPLE OF GETTING BOOT REASON
+-----------------------------------
+if aprop load 0 userdata; then
+    # valid persist property file found
+    if aprop test persist.sys.bootreason = recovery; then
+        aprop set persist.sys.bootreason "none"; aprop store;
+        # do the equivalent of AOSP ${recoverycmd}
+        # i.e. do anything required for booting into recovery
+    else
+        # boot Android OS normally
+    fi
+else
+    # encrypted/corrupted/non-existent property file
+    # continue boot Android OS or report error (platform-specific)
+fi
+
+
+4. ENABLE ON YOUR BOARD
+-----------------------------------
+The following Kconfig options must be enabled:
+CONFIG_PARTITIONS=y
+CONFIG_MMC=y
+CONFIG_APROP=y
+
+Additionally, filesystem read/write support for the partition
+must be enabled. Usually, the partition is in EXT4:
+CONFIG_FS_EXT4=y
+CONFIG_EXT4_WRITE=y
+
+[1] https://android.googlesource.com/platform/system/core/+/master/init/README.md
+[2] https://android.googlesource.com/platform/system/core/+/refs/heads/master/init/persistent_properties.cpp#42