host: add host library for accessing chromeos-config

Host side library for accessing chromeos-config. Initially, this will
be used by futility to access the /firmware:image-name property during
a firmware update.

More background: go/mosys-firmware-name

(note: despite the name "mosys" in the design doc, this is an effort
to *not* rely on mosys during the firmware update)

BUG=chromium:1061192
BRANCH=none
TEST=provided unit tests

Signed-off-by: Jack Rosenthal <jrosenth@chromium.org>
Change-Id: Ib8e5f8f836a93695e3b30731ae227501f37c4633
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/vboot_reference/+/2099449
Reviewed-by: Julius Werner <jwerner@chromium.org>
diff --git a/Makefile b/Makefile
index 58f930a..6a610c8 100644
--- a/Makefile
+++ b/Makefile
@@ -442,6 +442,7 @@
 	cgpt/cgpt_show.c \
 	futility/dump_kernel_config_lib.c \
 	host/arch/${ARCH}/lib/crossystem_arch.c \
+	host/lib/chromeos_config.c \
 	host/lib/crossystem.c \
 	host/lib/file_keys.c \
 	host/lib/fmap.c \
@@ -498,6 +499,7 @@
 	firmware/stub/vboot_api_stub_init.c \
 	futility/dump_kernel_config_lib.c \
 	host/arch/${ARCH}/lib/crossystem_arch.c \
+	host/lib/chromeos_config.c \
 	host/lib/crossystem.c \
 	host/lib/extract_vmlinuz.c \
 	host/lib/fmap.c \
@@ -661,6 +663,7 @@
 
 # And some compiled tests.
 TEST_NAMES = \
+	tests/chromeos_config_tests \
 	tests/cgptlib_test \
 	tests/sha_benchmark \
 	tests/subprocess_tests \
diff --git a/host/lib/chromeos_config.c b/host/lib/chromeos_config.c
new file mode 100644
index 0000000..6307318
--- /dev/null
+++ b/host/lib/chromeos_config.c
@@ -0,0 +1,108 @@
+/* Copyright 2020 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include <errno.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "2common.h"
+#include "2return_codes.h"
+#include "chromeos_config.h"
+#include "host_misc.h"
+
+#define CHROMEOS_CONFIG_BASE "/run/chromeos-config/v1"
+
+vb2_error_t chromeos_config_get_string(const char *path, const char *property,
+				       char **val_out)
+{
+	vb2_error_t rv;
+	uint32_t size;
+	char filepath[PATH_MAX];
+
+	*val_out = NULL;
+
+	if (!path || path[0] != '/') {
+		VB2_DEBUG("Path parameter must begin with /");
+		return VB2_ERROR_INVALID_PARAMETER;
+	}
+
+	if (strstr(path, "//")) {
+		VB2_DEBUG("Path cannot contain //");
+		return VB2_ERROR_INVALID_PARAMETER;
+	}
+
+	if (strchr(property, '/')) {
+		VB2_DEBUG("Property cannot contain /");
+		return VB2_ERROR_INVALID_PARAMETER;
+	}
+
+	snprintf(filepath, sizeof(filepath), CHROMEOS_CONFIG_BASE "%s/%s", path,
+		 property);
+	rv = vb2_read_file(filepath, (uint8_t **)val_out, &size);
+
+	if (rv == VB2_SUCCESS) {
+		*val_out = realloc(*val_out, size + 1);
+		(*val_out)[size] = '\0';
+	}
+
+	return rv;
+}
+
+vb2_error_t chromeos_config_get_boolean(const char *path, const char *property,
+					bool *val_out)
+{
+	char *val_string;
+	vb2_error_t rv;
+
+	*val_out = false;
+	if ((rv = chromeos_config_get_string(path, property, &val_string)) !=
+	    VB2_SUCCESS)
+		return rv;
+
+	if (!strcmp(val_string, "false"))
+		goto exit;
+
+	if (!strcmp(val_string, "true")) {
+		*val_out = true;
+		goto exit;
+	}
+
+	VB2_DEBUG("Config entry is not a boolean: %s:%s", path, property);
+	rv = VB2_ERROR_INVALID_PARAMETER;
+
+ exit:
+	free(val_string);
+	return rv;
+}
+
+vb2_error_t chromeos_config_get_integer(const char *path, const char *property,
+					int *val_out)
+{
+	char *endptr;
+	char *val_string;
+	vb2_error_t rv;
+
+	*val_out = -1;
+	if ((rv = chromeos_config_get_string(path, property, &val_string)) !=
+	    VB2_SUCCESS)
+		goto exit;
+
+	errno = 0;
+	*val_out = strtol(val_string, &endptr, 10);
+	if (errno || endptr) {
+		VB2_DEBUG("Config entry is not an integer: %s:%s", path,
+			  property);
+		rv = VB2_ERROR_INVALID_PARAMETER;
+		goto exit;
+	}
+
+ exit:
+	free(val_string);
+	return rv;
+}
diff --git a/host/lib/include/chromeos_config.h b/host/lib/include/chromeos_config.h
new file mode 100644
index 0000000..e83570e
--- /dev/null
+++ b/host/lib/include/chromeos_config.h
@@ -0,0 +1,56 @@
+/* Copyright 2020 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#ifndef VBOOT_REFERENCE_CHROMEOS_CONFIG_H_
+#define VBOOT_REFERENCE_CHROMEOS_CONFIG_H_
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "2common.h"
+#include "2return_codes.h"
+
+/**
+ * Get a value from the model configuration on the device as a string.
+ *
+ * Note: this function allocates memory by its use of vb2_read_file, and relies
+ * on the caller to free the allocated memory. The output parameter will be set
+ * to NULL upon failure, so free can be safely called on this parameter.
+ *
+ * @param path          The path in the config schema to the object containing
+ *                      the requested property.
+ * @param property      The name of the requested property.
+ * @param val_out       Output parameter which gets assigned to a
+ *                      null-terminated string.
+ * @return VB2_SUCCESS on success, or a relevant error upon error.
+ */
+vb2_error_t chromeos_config_get_string(const char *path, const char *property,
+				       char **val_out);
+
+/**
+ * Get a value from the model configuration on the device as a boolean.
+ *
+ * @param path          The path in the config schema to the object containing
+ *                      the requested property.
+ * @param property      The name of the requested property.
+ * @param val_out       Output parameter which gets assigned to a boolean.
+ * @return VB2_SUCCESS on success, or a relevant error upon error.
+ */
+vb2_error_t chromeos_config_get_boolean(const char *path, const char *property,
+					bool *val_out);
+
+/**
+ * Get a value from the model configuration on the device as an integer.
+ *
+ * @param path          The path in the config schema to the object containing
+ *                      the requested property.
+ * @param property      The name of the requested property.
+ * @param val_out       Output parameter which gets assigned to an integer.
+ * @return VB2_SUCCESS on success, or a relevant error upon error.
+ */
+vb2_error_t chromeos_config_get_integer(const char *path, const char *property,
+					int *val_out);
+
+#endif /* VBOOT_REFERENCE_CHROMEOS_CONFIG_H_ */
diff --git a/tests/chromeos_config_tests.c b/tests/chromeos_config_tests.c
new file mode 100644
index 0000000..2408d8c
--- /dev/null
+++ b/tests/chromeos_config_tests.c
@@ -0,0 +1,158 @@
+/* Copyright 2020 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "2common.h"
+#include "2return_codes.h"
+#include "chromeos_config.h"
+#include "host_misc.h"
+#include "test_common.h"
+
+static struct {
+	const char *path;
+	const char *data;
+} fakefs[] = {
+	{"/run/chromeos-config/v1/name", "bleh_model"},
+	{"/run/chromeos-config/v1/brand-code", "ZZCR"},
+	{"/run/chromeos-config/v1/identity/sku-id", "7"},
+	{"/run/chromeos-config/v1/firmware/image-name", "bloop"},
+	{"/run/chromeos-config/v1/auto-night-light", "true"},
+	{"/run/chromeos-config/v1/hardware-properties/is-lid-convertible",
+	 "false"},
+};
+
+vb2_error_t vb2_read_file(const char *filepath, uint8_t **data_ptr,
+			  uint32_t *size_ptr)
+{
+	*data_ptr = NULL;
+	*size_ptr = 0;
+
+	for (size_t i = 0; i < ARRAY_SIZE(fakefs); i++) {
+		if (!strcmp(fakefs[i].path, filepath)) {
+			*size_ptr = strlen(fakefs[i].data);
+			*data_ptr = malloc(*size_ptr);
+
+			if (!*data_ptr)
+				return VB2_ERROR_READ_FILE_ALLOC;
+
+			memcpy(*data_ptr, fakefs[i].data, *size_ptr);
+			return VB2_SUCCESS;
+		}
+	}
+
+	return VB2_ERROR_READ_FILE_OPEN;
+}
+
+static void test_get_string(void)
+{
+	char *val_out;
+
+	TEST_EQ(chromeos_config_get_string("/firmware", "image-name", &val_out),
+		VB2_SUCCESS, "Reading a string is successful");
+	TEST_STR_EQ(val_out, "bloop", "The string is the correct value");
+	free(val_out);
+}
+
+static void test_get_boolean_true(void)
+{
+	bool val_out;
+
+	TEST_EQ(chromeos_config_get_boolean("/", "auto-night-light", &val_out),
+		VB2_SUCCESS, "Reading a true boolean is successful");
+	TEST_EQ(val_out, true, "The resulting boolean is true");
+}
+
+static void test_get_boolean_false(void)
+{
+	bool val_out;
+
+	TEST_EQ(chromeos_config_get_boolean("/hardware-properties",
+					    "is-lid-convertible", &val_out),
+		VB2_SUCCESS, "Reading a false boolean is successful");
+	TEST_EQ(val_out, false, "The resulting boolean is false");
+}
+
+static void test_get_integer(void)
+{
+	int val_out;
+
+	TEST_EQ(chromeos_config_get_integer("/identity", "sku-id", &val_out),
+		VB2_SUCCESS, "Reading an integer is successful");
+	TEST_EQ(val_out, 7, "The resulting integer is correct");
+}
+
+static void test_get_no_exist(void)
+{
+	char *val_out;
+
+	TEST_NEQ(
+		chromeos_config_get_string("/this/does", "not-exist", &val_out),
+		VB2_SUCCESS, "Reading non-existent property fails");
+	free(val_out);
+}
+
+static void test_get_bad_path(void)
+{
+	char *val_out;
+
+	TEST_NEQ(chromeos_config_get_string("name", "name", &val_out),
+		 VB2_SUCCESS, "Reading bad path fails");
+	free(val_out);
+}
+
+static void test_get_bad_path2(void)
+{
+	char *val_out;
+
+	TEST_NEQ(chromeos_config_get_string("//name", "name", &val_out),
+		 VB2_SUCCESS, "Reading bad path fails");
+	free(val_out);
+}
+
+static void test_get_bad_property(void)
+{
+	char *val_out;
+
+	TEST_NEQ(chromeos_config_get_string("/firmware", "/image-name",
+					    &val_out),
+		 VB2_SUCCESS, "Reading bad property fails");
+	free(val_out);
+}
+
+static void test_get_not_boolean(void)
+{
+	bool val_out;
+
+	TEST_NEQ(chromeos_config_get_boolean("/identity", "sku-id", &val_out),
+		 VB2_SUCCESS, "Reading integer as boolean fails");
+}
+
+static void test_get_not_integer(void)
+{
+	int val_out;
+
+	TEST_NEQ(chromeos_config_get_integer("/", "brand-code", &val_out),
+		 VB2_SUCCESS, "Reading string as integer fails");
+}
+
+int main(int argc, char *argv[])
+{
+	test_get_string();
+	test_get_boolean_true();
+	test_get_boolean_false();
+	test_get_integer();
+	test_get_no_exist();
+	test_get_bad_path();
+	test_get_bad_path2();
+	test_get_bad_property();
+	test_get_not_boolean();
+	test_get_not_integer();
+
+	return gTestSuccess ? 0 : 255;
+}