futility: updater: split manifest implementation from updater_archive

The updater_archive has two functions: the implementation of archives,
and the creation of manifest. Since we are having more support of
archives in different format and the logic of manifests is getting more
complicated, so it is better to move the manifest to its own file.

No functional changes, only internal refactoring.

BUG=None
TEST=make; run test
BRANCH=None

Change-Id: I01ff9561583357eac4c6add8a09384f1c4c9d671
Signed-off-by: Hung-Te Lin <hungte@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/vboot_reference/+/3647407
Reviewed-by: Yu-Ping Wu <yupingso@chromium.org>
diff --git a/Makefile b/Makefile
index beaffaf..9d82d0a2 100644
--- a/Makefile
+++ b/Makefile
@@ -679,6 +679,7 @@
 FUTIL_SRCS += host/lib/flashrom_drv.c \
 	futility/flashrom_wp_drv.c \
 	futility/updater_archive.c \
+	futility/updater_manifest.c \
 	futility/updater_quirks.c \
 	futility/updater_utils.c \
 	futility/updater.c
diff --git a/futility/updater.h b/futility/updater.h
index 3dee719..cf03465 100644
--- a/futility/updater.h
+++ b/futility/updater.h
@@ -240,6 +240,16 @@
 		       uint8_t *data, uint32_t size, int64_t mtime);
 
 /*
+ * Traverses all files within archive (directories are ignored).
+ * For every entry, the path (relative the archive root) will be passed to
+ * callback function, until the callback returns non-zero.
+ * The arg argument will also be passed to callback.
+ * Returns 0 on success otherwise non-zero as failure.
+ */
+int archive_walk(struct u_archive *ar, void *arg,
+		 int (*callback)(const char *path, void *arg));
+
+/*
  * Copies all entries from one archive to another.
  * Returns 0 on success, otherwise non-zero as failure.
  */
diff --git a/futility/updater_archive.c b/futility/updater_archive.c
index dafce3c..c6e8f28 100644
--- a/futility/updater_archive.c
+++ b/futility/updater_archive.c
@@ -6,15 +6,11 @@
  */
 
 #include <assert.h>
-#include <ctype.h>
 #include <errno.h>
 #if defined(__OpenBSD__)
 #include <sys/types.h>
 #endif
 #include <fts.h>
-#include <string.h>
-#include <stdio.h>
-#include <stdlib.h>
 #include <sys/stat.h>
 #include <sys/time.h>
 #include <unistd.h>
@@ -35,64 +31,15 @@
 #include <zip.h>
 #endif
 
-#ifdef HAVE_CROSID
-#include <crosid.h>
-#endif
-
 #include "host_misc.h"
 #include "updater.h"
-#include "util_misc.h"
 
 /*
  * A firmware update package (archive) is a file packed by either shar(1) or
  * zip(1). See https://chromium.googlesource.com/chromiumos/platform/firmware/
  * for more information.
- *
- * A package for single board (i.e., not Unified Build) will have all the image
- * files in top folder:
- *  - host: 'image.bin' (or 'bios.bin' as legacy name before CL:1318712)
- *  - ec: 'ec.bin'
- *  - pd: 'pd.bin'
- * If custom label is supported, a 'keyset/' folder will be available, with key
- * files in it:
- *  - rootkey.$CLTAG
- *  - vblock_A.$CLTAG
- *  - vblock_B.$CLTAG
- * The $CLTAG should come from VPD value 'custom_label_tag'. For legacy devices,
- * the VPD name may be 'whitelabel_tag', or 'customization_id'.
- * The 'customization_id' has a different format: LOEM[-VARIANT] and we can only
- * take LOEM as $CLTAG, for example A-B => $CLTAG=A.
- *
- * A package for Unified Build is more complicated. There will be a models/
- * folder, and each model (by $(mosys platform model) ) should appear as a sub
- * folder, with a 'setvars.sh' file inside. The 'setvars.sh' is a shell script
- * describing what files should be used and the signature ID ($SIGID) to use.
- *
- * Similar to custom label in non-Unified-Build, the keys and vblock files will
- * be in 'keyset/' folder:
- *  - rootkey.$SIGID
- *  - vblock_A.$SIGID
- *  - vblock_B.$SIGID
- * If $SIGID starts with 'sig-id-in-*' then we have to replace it by VPD value
- * 'custom_label_tag' as '$MODEL-$CLTAG'.
  */
 
-static const char * const SETVARS_IMAGE_MAIN = "IMAGE_MAIN",
-		  * const SETVARS_IMAGE_EC = "IMAGE_EC",
-		  * const SETVARS_IMAGE_PD = "IMAGE_PD",
-		  * const SETVARS_SIGNATURE_ID = "SIGNATURE_ID",
-		  * const SIG_ID_IN_VPD_PREFIX = "sig-id-in",
-		  * const DIR_KEYSET = "keyset",
-		  * const DIR_MODELS = "models",
-		  * const DEFAULT_MODEL_NAME = "default",
-		  * const VPD_CUSTOM_LABEL_TAG = "custom_label_tag",
-		  * const VPD_CUSTOM_LABEL_TAG_LEGACY = "whitelabel_tag",
-		  * const VPD_CUSTOMIZATION_ID = "customization_id",
-		  * const ENV_VAR_MODEL_DIR = "${MODEL_DIR}",
-		  * const PATH_STARTSWITH_KEYSET = "keyset/",
-		  * const PATH_SIGNER_CONFIG = "signer_config.csv",
-		  * const PATH_ENDSWITH_SETVARS = "/setvars.sh";
-
 struct u_archive {
 	void *handle;
 
@@ -109,7 +56,7 @@
 };
 
 /*
- * -- Begin of archive implementations --
+ * -- The fallback driver (using general file system). --
  */
 
 /* Callback for archive_open on a general file system. */
@@ -243,6 +190,10 @@
 	return r;
 }
 
+/*
+ * -- The cache driver (used by other drivers). --
+ */
+
 #ifdef HAVE_LIBARCHIVE
 
 /*
@@ -318,6 +269,10 @@
 	return NULL;
 }
 
+/*
+ * -- The libarchive driver (multiple formats but very slow). --
+ */
+
 enum {
 	FILTER_IGNORE,
 	FILTER_ABORT,
@@ -463,6 +418,10 @@
 }
 #endif
 
+/*
+ * -- The libzip driver (for ZIP, the official format for CrOS fw updater). --
+ */
+
 #ifdef HAVE_LIBZIP
 
 /* Callback for archive_open on a ZIP file. */
@@ -578,6 +537,10 @@
 #endif
 
 /*
+ * -- The public functions for using u_archive. --
+ */
+
+/*
  * Opens an archive from given path.
  * The type of archive will be determined automatically.
  * Returns a pointer to reference to archive (must be released by archive_close
@@ -690,8 +653,8 @@
  * The arg argument will also be passed to callback.
  * Returns 0 on success otherwise non-zero as failure.
  */
-static int archive_walk(struct u_archive *ar, void *arg,
-			int (*callback)(const char *path, void *arg))
+int archive_walk(struct u_archive *ar, void *arg,
+		 int (*callback)(const char *path, void *arg))
 {
 	if (!ar)
 		return archive_fallback_walk(NULL, arg, callback);
@@ -762,831 +725,3 @@
 	struct _copy_arg arg = { .from = from, .to = to };
 	return archive_walk(from, &arg, archive_copy_callback);
 }
-
-/*
- * -- End of archive implementations --
- */
-
-/* Utility function to convert a string. */
-static void str_convert(char *s, int (*convert)(int c))
-{
-	int c;
-
-	for (; *s; s++) {
-		c = *s;
-		if (!isascii(c))
-			continue;
-		*s = convert(c);
-	}
-}
-
-/* Returns 1 if name ends by given pattern, otherwise 0. */
-static int str_endswith(const char *name, const char *pattern)
-{
-	size_t name_len = strlen(name), pattern_len = strlen(pattern);
-	if (name_len < pattern_len)
-		return 0;
-	return strcmp(name + name_len - pattern_len, pattern) == 0;
-}
-
-/* Returns 1 if name starts by given pattern, otherwise 0. */
-static int str_startswith(const char *name, const char *pattern)
-{
-	return strncmp(name, pattern, strlen(pattern)) == 0;
-}
-
-/* Returns the VPD value by given key name, or NULL on error (or no value). */
-static char *vpd_get_value(const char *fpath, const char *key)
-{
-	char *command, *result;
-
-	assert(fpath);
-	ASPRINTF(&command, "vpd -g %s -f %s 2>/dev/null", key, fpath);
-	result = host_shell(command);
-	free(command);
-
-	if (result && !*result) {
-		free(result);
-		result = NULL;
-	}
-	return result;
-}
-
-/*
- * Reads and parses a setvars type file from archive, then stores into config.
- * Returns 0 on success (at least one entry found), otherwise failure.
- */
-static int model_config_parse_setvars_file(
-		struct model_config *cfg, struct u_archive *archive,
-		const char *fpath)
-{
-	uint8_t *data;
-	uint32_t len;
-
-	char *ptr_line = NULL, *ptr_token = NULL;
-	char *line, *k, *v;
-	int valid = 0;
-
-	if (archive_read_file(archive, fpath, &data, &len, NULL) != 0) {
-		ERROR("Failed reading: %s\n", fpath);
-		return -1;
-	}
-
-	/* Valid content should end with \n, or \"; ensure ASCIIZ for parsing */
-	if (len)
-		data[len - 1] = '\0';
-
-	for (line = strtok_r((char *)data, "\n\r", &ptr_line); line;
-	     line = strtok_r(NULL, "\n\r", &ptr_line)) {
-		char *expand_path = NULL;
-		int found_valid = 1;
-
-		/* Format: KEY="value" */
-		k = strtok_r(line, "=", &ptr_token);
-		if (!k)
-			continue;
-		v = strtok_r(NULL, "\"", &ptr_token);
-		if (!v)
-			continue;
-
-		/* Some legacy updaters may be still using ${MODEL_DIR}. */
-		if (str_startswith(v, ENV_VAR_MODEL_DIR)) {
-			ASPRINTF(&expand_path, "%s/%s%s", DIR_MODELS, cfg->name,
-				 v + strlen(ENV_VAR_MODEL_DIR));
-		}
-
-		if (strcmp(k, SETVARS_IMAGE_MAIN) == 0)
-			cfg->image = strdup(v);
-		else if (strcmp(k, SETVARS_IMAGE_EC) == 0)
-			cfg->ec_image = strdup(v);
-		else if (strcmp(k, SETVARS_IMAGE_PD) == 0)
-			cfg->pd_image = strdup(v);
-		else if (strcmp(k, SETVARS_SIGNATURE_ID) == 0) {
-			cfg->signature_id = strdup(v);
-			if (str_startswith(v, SIG_ID_IN_VPD_PREFIX))
-				cfg->is_custom_label = 1;
-		} else
-			found_valid = 0;
-		free(expand_path);
-		valid += found_valid;
-	}
-	free(data);
-	return valid == 0;
-}
-
-/*
- * Changes the rootkey in firmware GBB to given new key.
- * Returns 0 on success, otherwise failure.
- */
-static int change_gbb_rootkey(struct firmware_image *image,
-			      const char *section_name,
-			      const uint8_t *rootkey, uint32_t rootkey_len)
-{
-	const struct vb2_gbb_header *gbb = find_gbb(image);
-	uint8_t *gbb_rootkey;
-	if (!gbb) {
-		ERROR("Cannot find GBB in image %s.\n", image->file_name);
-		return -1;
-	}
-	if (gbb->rootkey_size < rootkey_len) {
-		ERROR("New root key (%u bytes) larger than GBB (%u bytes).\n",
-		      rootkey_len, gbb->rootkey_size);
-		return -1;
-	}
-
-	gbb_rootkey = (uint8_t *)gbb + gbb->rootkey_offset;
-	/* See cmd_gbb_utility: root key must be first cleared with zero. */
-	memset(gbb_rootkey, 0, gbb->rootkey_size);
-	memcpy(gbb_rootkey, rootkey, rootkey_len);
-	return 0;
-}
-
-/*
- * Changes the VBlock in firmware section to new data.
- * Returns 0 on success, otherwise failure.
- */
-static int change_vblock(struct firmware_image *image, const char *section_name,
-			 const uint8_t *vblock, uint32_t vblock_len)
-{
-	struct firmware_section section;
-
-	find_firmware_section(&section, image, section_name);
-	if (!section.data) {
-		ERROR("Need section %s in image %s.\n", section_name,
-		      image->file_name);
-		return -1;
-	}
-	if (section.size < vblock_len) {
-		ERROR("Section %s too small (%zu bytes) for vblock (%u bytes).\n",
-		      section_name, section.size, vblock_len);
-		return -1;
-	}
-	memcpy(section.data, vblock, vblock_len);
-	return 0;
-}
-
-/*
- * Applies a key file to firmware image.
- * Returns 0 on success, otherwise failure.
- */
-static int apply_key_file(
-		struct firmware_image *image, const char *path,
-		struct u_archive *archive, const char *section_name,
-		int (*apply)(struct firmware_image *image, const char *section,
-			     const uint8_t *data, uint32_t len))
-{
-	int r = 0;
-	uint8_t *data = NULL;
-	uint32_t len;
-
-	r = archive_read_file(archive, path, &data, &len, NULL);
-	if (r == 0) {
-		VB2_DEBUG("Loaded file: %s\n", path);
-		r = apply(image, section_name, data, len);
-		if (r)
-			ERROR("Failed applying %s to %s\n", path, section_name);
-	} else {
-		ERROR("Failed reading: %s\n", path);
-	}
-	free(data);
-	return r;
-}
-
-/*
- * Modifies a firmware image from patch information specified in model config.
- * Returns 0 on success, otherwise number of failures.
- */
-int patch_image_by_model(
-		struct firmware_image *image, const struct model_config *model,
-		struct u_archive *archive)
-{
-	int err = 0;
-	if (model->patches.rootkey)
-		err += !!apply_key_file(
-				image, model->patches.rootkey, archive,
-				FMAP_RO_GBB, change_gbb_rootkey);
-	if (model->patches.vblock_a)
-		err += !!apply_key_file(
-				image, model->patches.vblock_a, archive,
-				FMAP_RW_VBLOCK_A, change_vblock);
-	if (model->patches.vblock_b)
-		err += !!apply_key_file(
-				image, model->patches.vblock_b, archive,
-				FMAP_RW_VBLOCK_B, change_vblock);
-	return err;
-}
-
-/*
- * Finds available patch files by given model.
- * Updates `model` argument with path of patch files.
- */
-static void find_patches_for_model(struct model_config *model,
-				   struct u_archive *archive,
-				   const char *signature_id)
-{
-	char *path;
-	int i;
-
-	const char *names[] = {
-		"rootkey",
-		"vblock_A",
-		"vblock_B",
-	};
-
-	char **targets[] = {
-		&model->patches.rootkey,
-		&model->patches.vblock_a,
-		&model->patches.vblock_b,
-	};
-
-	assert(ARRAY_SIZE(names) == ARRAY_SIZE(targets));
-	for (i = 0; i < ARRAY_SIZE(names); i++) {
-		ASPRINTF(&path, "%s/%s.%s", DIR_KEYSET, names[i], signature_id);
-		if (archive_has_entry(archive, path))
-			*targets[i] = path;
-		else
-			free(path);
-	}
-}
-
-/*
- * Adds and copies one new model config to the existing list of given manifest.
- * Returns a pointer to the newly allocated config, or NULL on failure.
- */
-static struct model_config *manifest_add_model(
-		struct manifest *manifest,
-		const struct model_config *cfg)
-{
-	struct model_config *model;
-	manifest->num++;
-	manifest->models = (struct model_config *)realloc(
-			manifest->models, manifest->num * sizeof(*model));
-	if (!manifest->models) {
-		ERROR("Internal error: failed to allocate buffer.\n");
-		return NULL;
-	}
-	model = &manifest->models[manifest->num - 1];
-	memcpy(model, cfg, sizeof(*model));
-	return model;
-}
-
-/*
- * A callback function for manifest to scan files in archive.
- * Returns 0 to keep scanning, or non-zero to stop.
- */
-static int manifest_scan_entries(const char *name, void *arg)
-{
-	struct manifest *manifest = (struct manifest *)arg;
-	struct u_archive *archive = manifest->archive;
-	struct model_config model = {0};
-	char *slash;
-
-	if (str_startswith(name, PATH_STARTSWITH_KEYSET))
-		manifest->has_keyset = 1;
-	if (!str_endswith(name, PATH_ENDSWITH_SETVARS))
-		return 0;
-
-	/* name: models/$MODEL/setvars.sh */
-	model.name = strdup(strchr(name, '/') + 1);
-	slash = strchr(model.name, '/');
-	if (slash)
-		*slash = '\0';
-
-	VB2_DEBUG("Found model <%s> setvars: %s\n", model.name, name);
-	if (model_config_parse_setvars_file(&model, archive, name)) {
-		ERROR("Invalid setvars file: %s\n", name);
-		return 0;
-	}
-
-	/* In legacy setvars.sh, the ec_image and pd_image may not exist. */
-	if (model.ec_image && !archive_has_entry(archive, model.ec_image)) {
-		VB2_DEBUG("Ignore non-exist EC image: %s\n", model.ec_image);
-		free(model.ec_image);
-		model.ec_image = NULL;
-	}
-	if (model.pd_image && !archive_has_entry(archive, model.pd_image)) {
-		VB2_DEBUG("Ignore non-exist PD image: %s\n", model.pd_image);
-		free(model.pd_image);
-		model.pd_image = NULL;
-	}
-
-	/* Find patch files. */
-	if (model.signature_id)
-		find_patches_for_model(&model, archive, model.signature_id);
-
-	return !manifest_add_model(manifest, &model);
-}
-
-/*
- * A callback function for manifest to scan files in raw /firmware archive.
- * Returns 0 to keep scanning, or non-zero to stop.
- */
-static int manifest_scan_raw_entries(const char *name, void *arg)
-{
-	struct manifest *manifest = (struct manifest *)arg;
-	struct u_archive *archive = manifest->archive;
-	struct model_config model = {0};
-	char *ec_name = NULL, *zephyr_name = NULL;
-	int chars_read = 0;
-
-	/*
-	 * /build/$BOARD/firmware (or CPFE firmware archives) layout:
-	 * - image-${MODEL}{,.serial,.dev...}.bin
-	 * - ${MODEL}/ec.bin or ${MODEL}/zephyr.bin
-	 */
-
-	if (sscanf(name, "image-%m[^.].bin%n", &model.name, &chars_read) != 1)
-		return 0;
-
-	/* Ignore the names with extra modifiers like image-$MODEL.serial.bin */
-	if (!chars_read || name[chars_read]) {
-		free(model.name);
-		return 0;
-	}
-
-	VB2_DEBUG("Found model <%s>: %s\n", model.name, name);
-	model.image = strdup(name);
-
-	ASPRINTF(&ec_name, "%s/ec.bin", model.name);
-	ASPRINTF(&zephyr_name, "%s/zephyr.bin", model.name);
-	if (archive_has_entry(archive, ec_name))
-		model.ec_image = strdup(ec_name);
-	else if (archive_has_entry(archive, zephyr_name))
-		model.ec_image = strdup(zephyr_name);
-	free(ec_name);
-	free(zephyr_name);
-
-	return !manifest_add_model(manifest, &model);
-}
-
-/* Returns the matched model config from the manifest, or NULL if not found. */
-static struct model_config *manifest_get_model_config(
-		const struct manifest *manifest, const char *name)
-{
-	int i = 0;
-
-	for (i = 0; i < manifest->num; i++) {
-		if (!strcmp(name, manifest->models[i].name))
-			return &manifest->models[i];
-	}
-	return NULL;
-}
-
-/*
- * Creates the manifest from the 'signer_config.csv' file.
- * Returns 0 on success (loaded), otherwise failure.
- */
-static int manifest_from_signer_config(struct manifest *manifest)
-{
-	struct u_archive *archive = manifest->archive;
-	uint32_t size;
-	uint8_t *data;
-	char *s, *tok_ptr = NULL;
-
-	if (!archive_has_entry(archive, PATH_SIGNER_CONFIG))
-		return -1;
-
-	/*
-	 * CSV format: model_name,firmware_image,key_id,ec_image
-	 *
-	 * Note the key_id is not signature_id and won't be used, and ec_image
-	 * may be optional (for example sarien).
-	 */
-
-	if (archive_read_file(archive, PATH_SIGNER_CONFIG, &data, &size,NULL)) {
-		ERROR("Failed reading: %s\n", PATH_SIGNER_CONFIG);
-		return -1;
-	}
-
-	/* Skip headers. */
-	s = strtok_r((char *)data, "\n", &tok_ptr);
-	if (!s || !strchr(s, ',')) {
-		ERROR("Invalid %s: missing header.\n", PATH_SIGNER_CONFIG);
-		free(data);
-		return -1;
-	}
-
-	for (s = strtok_r(NULL, "\n", &tok_ptr); s != NULL;
-	     s = strtok_r(NULL, "\n", &tok_ptr)) {
-
-		struct model_config model = {0};
-		int discard_model = 0;
-
-		/*
-		 * Both keyid (%3) and ec_image (%4) are optional so we want to
-		 * read at least 2 fields.
-		 */
-		if (sscanf(s, "%m[^,],%m[^,],%*[^,],%m[^,]",
-		    &model.name, &model.image, &model.ec_image) < 2) {
-			ERROR("Invalid entry(%s): %s\n", PATH_SIGNER_CONFIG, s);
-			discard_model = 1;
-		} else if (strchr(model.name, '-')) {
-			/* format: BaseModel-CustomLabel */
-			char *tok_dash;
-			char *base_model;
-			struct model_config *base_model_config;
-
-			VB2_DEBUG("Found custom-label: %s\n", model.name);
-			discard_model = 1;
-			base_model = strtok_r(model.name, "-", &tok_dash);
-			assert(base_model);
-
-			/*
-			 * Currently we assume the base model (e.g., base_model)
-			 * is always listed before CL models in the CSV file -
-			 * this is based on how the signerbot and the
-			 * chromeos-config works today (validated on octopus).
-			 */
-			base_model_config = manifest_get_model_config(
-					manifest, base_model);
-
-			if (!base_model_config) {
-				ERROR("Invalid CL-model: %s\n", base_model);
-			} else if (!base_model_config->is_custom_label) {
-				base_model_config->is_custom_label = 1;
-				/*
-				 * Rewriting signature_id is not necessary,
-				 * but in order to generate the same manifest
-				 * from setvars, we want to temporarily use
-				 * the special value.
-				 */
-				free(base_model_config->signature_id);
-				base_model_config->signature_id = strdup(
-						"sig-id-in-customization-id");
-			}
-		}
-
-		if (discard_model) {
-			free(model.name);
-			free(model.image);
-			free(model.ec_image);
-			continue;
-		}
-
-		model.signature_id = strdup(model.name);
-		if (!manifest_add_model(manifest, &model))
-			break;
-	}
-	free(data);
-	return 0;
-}
-
-/*
- * Creates the manifest from a simple (legacy) folder with only 1 set of
- * firmware images.
- * Returns 0 on success (loaded), otherwise failure.
- */
-static int manifest_from_simple_folder(struct manifest *manifest)
-{
-	const char * const host_image_name = "image.bin",
-		   * const old_host_image_name = "bios.bin",
-		   * const ec_name = "ec.bin",
-		   * const pd_name = "pd.bin";
-	struct u_archive *archive = manifest->archive;
-	const char *image_name = NULL;
-	struct firmware_image image = {0};
-	struct model_config model = {0};
-
-	/* Try to load from current folder. */
-	if (archive_has_entry(archive, old_host_image_name))
-		image_name = old_host_image_name;
-	else if (archive_has_entry(archive, host_image_name))
-		image_name = host_image_name;
-	else
-		return 1;
-
-	model.image = strdup(image_name);
-	if (archive_has_entry(archive, ec_name))
-		model.ec_image = strdup(ec_name);
-	if (archive_has_entry(archive, pd_name))
-		model.pd_image = strdup(pd_name);
-	/* Extract model name from FWID: $Vendor_$Platform.$Version */
-	if (!load_firmware_image(&image, image_name, archive)) {
-		char *token = NULL;
-		if (strtok(image.ro_version, "_"))
-			token = strtok(NULL, ".");
-		if (token && *token) {
-			str_convert(token, tolower);
-			model.name = strdup(token);
-		}
-		free_firmware_image(&image);
-	}
-	if (!model.name)
-		model.name = strdup(DEFAULT_MODEL_NAME);
-	if (manifest->has_keyset)
-		model.is_custom_label = 1;
-	manifest_add_model(manifest, &model);
-	manifest->default_model = manifest->num - 1;
-
-	return 0;
-}
-
-/**
- * get_manifest_key() - Wrapper to get the firmware manifest key from crosid
- *
- * @manifest_key_out - Output parameter of the firmware manifest key.
- *
- * Returns:
- * - <0 if libcrosid is unavailable or there was an error reading
- *   device data
- * - >=0 (the matched device index) success
- */
-static int get_manifest_key(char **manifest_key_out)
-{
-#ifdef HAVE_CROSID
-	return crosid_get_firmware_manifest_key(manifest_key_out);
-#else
-	ERROR("This version of futility was compiled without libcrosid "
-	      "(perhaps compiled outside of the Chrome OS build system?) and "
-	      "the update command is not fully supported.  Either compile "
-	      "from the Chrome OS build, or pass --model to manually specify "
-	      "the machine model.\n");
-	return -1;
-#endif
-}
-
-/*
- * Finds the existing model_config from manifest that best matches current
- * system (as defined by model_name).
- * Returns a model_config from manifest, or NULL if not found.
- */
-const struct model_config *manifest_find_model(const struct manifest *manifest,
-					       const char *model_name)
-{
-	char *manifest_key = NULL;
-	const struct model_config *model = NULL;
-	int i;
-	int matched_index;
-
-	/*
-	 * For manifest with single model defined, we should just return because
-	 * there are other mechanisms like platform name check to double confirm
-	 * if the firmware is valid.
-	 */
-	if (manifest->num == 1)
-		return &manifest->models[0];
-
-	if (!model_name) {
-		matched_index = get_manifest_key(&manifest_key);
-		if (matched_index < 0) {
-			ERROR("Failed to get device identity.  "
-			      "Run \"crosid -v\" for explanation.\n");
-			return NULL;
-		}
-
-		INFO("Identified the device using libcrosid, "
-		     "matched chromeos-config index: %d, "
-		     "manifest key (model): %s\n",
-		     matched_index, manifest_key);
-		model_name = manifest_key;
-	}
-
-	model = manifest_get_model_config(manifest, model_name);
-
-	if (!model) {
-		ERROR("Unsupported model: '%s'.\n", model_name);
-
-		fprintf(stderr,
-			"The firmware manifest key '%s' is not present in this "
-			"updater archive. The known keys to this updater "
-			"archive are:\n", model_name);
-
-		for (i = 0; i < manifest->num; i++)
-			fprintf(stderr, " %s", manifest->models[i].name);
-		fprintf(stderr, "\n\n");
-		fprintf(stderr,
-			"Perhaps you are trying to use an updater archive for "
-			"the wrong board, or designed for an older OS version "
-			"before this model was supported.\n");
-		fprintf(stderr,
-			"Hint: Read the FIRMWARE_MANIFEST_KEY from the output "
-			"of the crosid command.\n");
-	}
-
-
-	free(manifest_key);
-	return model;
-}
-
-/*
- * Determines the signature ID to use for custom label.
- * Returns the signature ID for looking up rootkey and vblock files.
- * Caller must free the returned string.
- */
-static char *resolve_signature_id(struct model_config *model, const char *image)
-{
-	int is_unibuild = model->signature_id ? 1 : 0;
-	char *tag = vpd_get_value(image, VPD_CUSTOM_LABEL_TAG);
-	char *sig_id = NULL;
-
-	if (tag == NULL)
-		tag = vpd_get_value(image, VPD_CUSTOM_LABEL_TAG_LEGACY);
-
-	/* Unified build: $model.$tag, or $model (b/126800200). */
-	if (is_unibuild) {
-		if (!tag) {
-			WARN("No VPD '%s' set for custom label. "
-			     "Use model name '%s' as default.\n",
-			     VPD_CUSTOM_LABEL_TAG, model->name);
-			return strdup(model->name);
-		}
-
-		ASPRINTF(&sig_id, "%s-%s", model->name, tag);
-		free(tag);
-		return sig_id;
-	}
-
-	/* Non-Unibuild: Upper($tag), or Upper(${cid%%-*}). */
-	if (!tag) {
-		char *cid = vpd_get_value(image, VPD_CUSTOMIZATION_ID);
-		if (cid) {
-			/* customization_id in format LOEM[-VARIANT]. */
-			char *dash = strchr(cid, '-');
-			if (dash)
-				*dash = '\0';
-			tag = cid;
-		}
-	}
-	if (tag)
-		str_convert(tag, toupper);
-	return tag;
-}
-
-/*
- * Applies custom label information to an existing model configuration.
- * Collects signature ID information from either parameter signature_id or
- * image file (via VPD) and updates model.patches for key files.
- * Returns 0 on success, otherwise failure.
- */
-int model_apply_custom_label(
-		struct model_config *model,
-		struct u_archive *archive,
-		const char *signature_id,
-		const char *image)
-{
-	char *sig_id = NULL;
-	int r = 0;
-
-	if (!signature_id) {
-		sig_id = resolve_signature_id(model, image);
-		signature_id = sig_id;
-	}
-
-	if (signature_id) {
-		VB2_DEBUG("Find custom label patches by signature ID: '%s'.\n",
-		      signature_id);
-		find_patches_for_model(model, archive, signature_id);
-	} else {
-		signature_id = "";
-		WARN("No VPD '%s' set for custom label - use default keys.\n",
-		     VPD_CUSTOM_LABEL_TAG);
-	}
-	if (!model->patches.rootkey) {
-		ERROR("No keys found for signature_id: '%s'\n", signature_id);
-		r = 1;
-	} else {
-		INFO("Applied for custom label: %s\n", signature_id);
-	}
-	free(sig_id);
-	return r;
-}
-
-/*
- * Creates a new manifest object by scanning files in archive.
- * Returns the manifest on success, otherwise NULL for failure.
- */
-struct manifest *new_manifest_from_archive(struct u_archive *archive)
-{
-	struct manifest manifest = {0}, *new_manifest;
-
-	manifest.archive = archive;
-	manifest.default_model = -1;
-
-	VB2_DEBUG("Try to build a manifest from *%s\n", PATH_ENDSWITH_SETVARS);
-	archive_walk(archive, &manifest, manifest_scan_entries);
-
-	if (manifest.num == 0) {
-		VB2_DEBUG("Try to build a manifest from %s\n",
-			  PATH_SIGNER_CONFIG);
-		manifest_from_signer_config(&manifest);
-	}
-	if (manifest.num == 0) {
-		VB2_DEBUG("Try to build a manifest from a */firmware folder\n");
-		archive_walk(archive, &manifest, manifest_scan_raw_entries);
-	}
-	if (manifest.num == 0) {
-		VB2_DEBUG("Try to build a manifest from a simple folder\n");
-		manifest_from_simple_folder(&manifest);
-	}
-
-	VB2_DEBUG("%d model(s) loaded.\n", manifest.num);
-	if (!manifest.num) {
-		ERROR("No valid configurations found from archive.\n");
-		return NULL;
-	}
-
-	new_manifest = (struct manifest *)malloc(sizeof(manifest));
-	if (!new_manifest) {
-		ERROR("Internal error: memory allocation error.\n");
-		return NULL;
-	}
-	memcpy(new_manifest, &manifest, sizeof(manifest));
-	return new_manifest;
-}
-
-/* Releases all resources allocated by given manifest object. */
-void delete_manifest(struct manifest *manifest)
-{
-	int i;
-	assert(manifest);
-	for (i = 0; i < manifest->num; i++) {
-		struct model_config *model = &manifest->models[i];
-		free(model->name);
-		free(model->signature_id);
-		free(model->image);
-		free(model->ec_image);
-		free(model->pd_image);
-		free(model->patches.rootkey);
-		free(model->patches.vblock_a);
-		free(model->patches.vblock_b);
-	}
-	free(manifest->models);
-	free(manifest);
-}
-
-static const char *get_gbb_key_hash(const struct vb2_gbb_header *gbb,
-				    int32_t offset, int32_t size)
-{
-	struct vb2_packed_key *key;
-
-	if (!gbb)
-		return "<No GBB>";
-	key = (struct vb2_packed_key *)((uint8_t *)gbb + offset);
-	if (vb2_packed_key_looks_ok(key, size))
-		return "<Invalid key>";
-	return packed_key_sha1_string(key);
-}
-
-/* Prints the information of given image file in JSON format. */
-static void print_json_image(
-		const char *name, const char *fpath, struct model_config *m,
-		struct u_archive *archive, int indent, int is_host)
-{
-	struct firmware_image image = {0};
-	const struct vb2_gbb_header *gbb = NULL;
-	if (!fpath)
-		return;
-	if (load_firmware_image(&image, fpath, archive))
-		return;
-	if (!is_host)
-		printf(",\n");
-	printf("%*s\"%s\": { \"versions\": { \"ro\": \"%s\", \"rw\": \"%s\" },",
-	       indent, "", name, image.ro_version, image.rw_version_a);
-	indent += 2;
-	if (!is_host) {
-		/* No extra information to be printed */
-        } else if (patch_image_by_model(&image, m, archive) != 0) {
-		ERROR("Failed to patch images by model: %s\n", m->name);
-	} else if (NULL != (gbb = find_gbb(&image))) {
-		printf("\n%*s\"keys\": { \"root\": \"%s\", ",
-		       indent, "",
-		       get_gbb_key_hash(gbb, gbb->rootkey_offset,
-					gbb->rootkey_size));
-		printf("\"recovery\": \"%s\" },",
-		       get_gbb_key_hash(gbb, gbb->recovery_key_offset,
-					gbb->recovery_key_size));
-	}
-	printf("\n%*s\"image\": \"%s\" }", indent, "", fpath);
-	free_firmware_image(&image);
-}
-
-/* Prints the information of objects in manifest (models and images) in JSON. */
-void print_json_manifest(const struct manifest *manifest)
-{
-	int i, indent;
-	struct u_archive *ar = manifest->archive;
-
-	printf("{\n");
-	for (i = 0, indent = 2; i < manifest->num; i++) {
-		struct model_config *m = &manifest->models[i];
-		printf("%s%*s\"%s\": {\n", i ? ",\n" : "", indent, "", m->name);
-		indent += 2;
-		print_json_image("host", m->image, m, ar, indent, 1);
-		print_json_image("ec", m->ec_image, m, ar, indent, 0);
-		print_json_image("pd", m->pd_image, m, ar, indent, 0);
-		if (m->patches.rootkey) {
-			struct patch_config *p = &m->patches;
-			printf(",\n%*s\"patches\": { \"rootkey\": \"%s\", "
-			       "\"vblock_a\": \"%s\", \"vblock_b\": \"%s\" }",
-			       indent, "", p->rootkey, p->vblock_a,
-			       p->vblock_b);
-		}
-		if (m->signature_id)
-			printf(",\n%*s\"signature_id\": \"%s\"", indent, "",
-			       m->signature_id);
-		printf("\n  }");
-		indent -= 2;
-		assert(indent == 2);
-	}
-	printf("\n}\n");
-}
diff --git a/futility/updater_manifest.c b/futility/updater_manifest.c
new file mode 100644
index 0000000..cb18434
--- /dev/null
+++ b/futility/updater_manifest.c
@@ -0,0 +1,894 @@
+/* Copyright 2022 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.
+ *
+ * Build up the list of updater resources from an archive.
+ */
+
+#include <assert.h>
+#if defined(__OpenBSD__)
+#include <sys/types.h>
+#endif
+
+#ifdef HAVE_CROSID
+#include <crosid.h>
+#endif
+
+#include "updater.h"
+#include "util_misc.h"
+
+/*
+ * The updater reads image files from a package. The package is usually an
+ * archive (see updater_archive.c) with image files and configuration files, and
+ * the meta data is maintained by a "manifest" that described below.
+ *
+ * A package for single board (i.e., not Unified Build) will have all the image
+ * files in top folder:
+ *  - host: 'image.bin' (or 'bios.bin' as legacy name before CL:1318712)
+ *  - ec: 'ec.bin'
+ *  - pd: 'pd.bin'
+ * If custom label is supported, a 'keyset/' folder will be available, with key
+ * files in it:
+ *  - rootkey.$CLTAG
+ *  - vblock_A.$CLTAG
+ *  - vblock_B.$CLTAG
+ * The $CLTAG should come from VPD value 'custom_label_tag'. For legacy devices,
+ * the VPD name may be 'whitelabel_tag', or 'customization_id'.
+ * The 'customization_id' has a different format: LOEM[-VARIANT] and we can only
+ * take LOEM as $CLTAG, for example A-B => $CLTAG=A.
+ *
+ * A package for Unified Build is more complicated. There will be a models/
+ * folder, and each model (by $(mosys platform model) ) should appear as a sub
+ * folder, with a 'setvars.sh' file inside. The 'setvars.sh' is a shell script
+ * describing what files should be used and the signature ID ($SIGID) to use.
+ *
+ * Similar to custom label in non-Unified-Build, the keys and vblock files will
+ * be in 'keyset/' folder:
+ *  - rootkey.$SIGID
+ *  - vblock_A.$SIGID
+ *  - vblock_B.$SIGID
+ * If $SIGID starts with 'sig-id-in-*' then we have to replace it by VPD value
+ * 'custom_label_tag' as '$MODEL-$CLTAG'.
+ */
+
+static const char * const SETVARS_IMAGE_MAIN = "IMAGE_MAIN",
+		  * const SETVARS_IMAGE_EC = "IMAGE_EC",
+		  * const SETVARS_IMAGE_PD = "IMAGE_PD",
+		  * const SETVARS_SIGNATURE_ID = "SIGNATURE_ID",
+		  * const SIG_ID_IN_VPD_PREFIX = "sig-id-in",
+		  * const DIR_KEYSET = "keyset",
+		  * const DIR_MODELS = "models",
+		  * const DEFAULT_MODEL_NAME = "default",
+		  * const VPD_CUSTOM_LABEL_TAG = "custom_label_tag",
+		  * const VPD_CUSTOM_LABEL_TAG_LEGACY = "whitelabel_tag",
+		  * const VPD_CUSTOMIZATION_ID = "customization_id",
+		  * const ENV_VAR_MODEL_DIR = "${MODEL_DIR}",
+		  * const PATH_STARTSWITH_KEYSET = "keyset/",
+		  * const PATH_SIGNER_CONFIG = "signer_config.csv",
+		  * const PATH_ENDSWITH_SETVARS = "/setvars.sh";
+
+/* Utility function to convert a string. */
+static void str_convert(char *s, int (*convert)(int c))
+{
+	int c;
+
+	for (; *s; s++) {
+		c = *s;
+		if (!isascii(c))
+			continue;
+		*s = convert(c);
+	}
+}
+
+/* Returns 1 if name ends by given pattern, otherwise 0. */
+static int str_endswith(const char *name, const char *pattern)
+{
+	size_t name_len = strlen(name), pattern_len = strlen(pattern);
+	if (name_len < pattern_len)
+		return 0;
+	return strcmp(name + name_len - pattern_len, pattern) == 0;
+}
+
+/* Returns 1 if name starts by given pattern, otherwise 0. */
+static int str_startswith(const char *name, const char *pattern)
+{
+	return strncmp(name, pattern, strlen(pattern)) == 0;
+}
+
+/* Returns the VPD value by given key name, or NULL on error (or no value). */
+static char *vpd_get_value(const char *fpath, const char *key)
+{
+	char *command, *result;
+
+	assert(fpath);
+	ASPRINTF(&command, "vpd -g %s -f %s 2>/dev/null", key, fpath);
+	result = host_shell(command);
+	free(command);
+
+	if (result && !*result) {
+		free(result);
+		result = NULL;
+	}
+	return result;
+}
+
+/*
+ * Reads and parses a setvars type file from archive, then stores into config.
+ * Returns 0 on success (at least one entry found), otherwise failure.
+ */
+static int model_config_parse_setvars_file(
+		struct model_config *cfg, struct u_archive *archive,
+		const char *fpath)
+{
+	uint8_t *data;
+	uint32_t len;
+
+	char *ptr_line = NULL, *ptr_token = NULL;
+	char *line, *k, *v;
+	int valid = 0;
+
+	if (archive_read_file(archive, fpath, &data, &len, NULL) != 0) {
+		ERROR("Failed reading: %s\n", fpath);
+		return -1;
+	}
+
+	/* Valid content should end with \n, or \"; ensure ASCIIZ for parsing */
+	if (len)
+		data[len - 1] = '\0';
+
+	for (line = strtok_r((char *)data, "\n\r", &ptr_line); line;
+	     line = strtok_r(NULL, "\n\r", &ptr_line)) {
+		char *expand_path = NULL;
+		int found_valid = 1;
+
+		/* Format: KEY="value" */
+		k = strtok_r(line, "=", &ptr_token);
+		if (!k)
+			continue;
+		v = strtok_r(NULL, "\"", &ptr_token);
+		if (!v)
+			continue;
+
+		/* Some legacy updaters may be still using ${MODEL_DIR}. */
+		if (str_startswith(v, ENV_VAR_MODEL_DIR)) {
+			ASPRINTF(&expand_path, "%s/%s%s", DIR_MODELS, cfg->name,
+				 v + strlen(ENV_VAR_MODEL_DIR));
+		}
+
+		if (strcmp(k, SETVARS_IMAGE_MAIN) == 0)
+			cfg->image = strdup(v);
+		else if (strcmp(k, SETVARS_IMAGE_EC) == 0)
+			cfg->ec_image = strdup(v);
+		else if (strcmp(k, SETVARS_IMAGE_PD) == 0)
+			cfg->pd_image = strdup(v);
+		else if (strcmp(k, SETVARS_SIGNATURE_ID) == 0) {
+			cfg->signature_id = strdup(v);
+			if (str_startswith(v, SIG_ID_IN_VPD_PREFIX))
+				cfg->is_custom_label = 1;
+		} else
+			found_valid = 0;
+		free(expand_path);
+		valid += found_valid;
+	}
+	free(data);
+	return valid == 0;
+}
+
+/*
+ * Changes the rootkey in firmware GBB to given new key.
+ * Returns 0 on success, otherwise failure.
+ */
+static int change_gbb_rootkey(struct firmware_image *image,
+			      const char *section_name,
+			      const uint8_t *rootkey, uint32_t rootkey_len)
+{
+	const struct vb2_gbb_header *gbb = find_gbb(image);
+	uint8_t *gbb_rootkey;
+	if (!gbb) {
+		ERROR("Cannot find GBB in image %s.\n", image->file_name);
+		return -1;
+	}
+	if (gbb->rootkey_size < rootkey_len) {
+		ERROR("New root key (%u bytes) larger than GBB (%u bytes).\n",
+		      rootkey_len, gbb->rootkey_size);
+		return -1;
+	}
+
+	gbb_rootkey = (uint8_t *)gbb + gbb->rootkey_offset;
+	/* See cmd_gbb_utility: root key must be first cleared with zero. */
+	memset(gbb_rootkey, 0, gbb->rootkey_size);
+	memcpy(gbb_rootkey, rootkey, rootkey_len);
+	return 0;
+}
+
+/*
+ * Changes the VBlock in firmware section to new data.
+ * Returns 0 on success, otherwise failure.
+ */
+static int change_vblock(struct firmware_image *image, const char *section_name,
+			 const uint8_t *vblock, uint32_t vblock_len)
+{
+	struct firmware_section section;
+
+	find_firmware_section(&section, image, section_name);
+	if (!section.data) {
+		ERROR("Need section %s in image %s.\n", section_name,
+		      image->file_name);
+		return -1;
+	}
+	if (section.size < vblock_len) {
+		ERROR("'%s' is too small (%zu bytes) for vblock (%u bytes).\n",
+		      section_name, section.size, vblock_len);
+		return -1;
+	}
+	memcpy(section.data, vblock, vblock_len);
+	return 0;
+}
+
+/*
+ * Applies a key file to firmware image.
+ * Returns 0 on success, otherwise failure.
+ */
+static int apply_key_file(
+		struct firmware_image *image, const char *path,
+		struct u_archive *archive, const char *section_name,
+		int (*apply)(struct firmware_image *image, const char *section,
+			     const uint8_t *data, uint32_t len))
+{
+	int r = 0;
+	uint8_t *data = NULL;
+	uint32_t len;
+
+	r = archive_read_file(archive, path, &data, &len, NULL);
+	if (r == 0) {
+		VB2_DEBUG("Loaded file: %s\n", path);
+		r = apply(image, section_name, data, len);
+		if (r)
+			ERROR("Failed applying %s to %s\n", path, section_name);
+	} else {
+		ERROR("Failed reading: %s\n", path);
+	}
+	free(data);
+	return r;
+}
+
+/*
+ * Modifies a firmware image from patch information specified in model config.
+ * Returns 0 on success, otherwise number of failures.
+ */
+int patch_image_by_model(
+		struct firmware_image *image, const struct model_config *model,
+		struct u_archive *archive)
+{
+	int err = 0;
+	if (model->patches.rootkey)
+		err += !!apply_key_file(
+				image, model->patches.rootkey, archive,
+				FMAP_RO_GBB, change_gbb_rootkey);
+	if (model->patches.vblock_a)
+		err += !!apply_key_file(
+				image, model->patches.vblock_a, archive,
+				FMAP_RW_VBLOCK_A, change_vblock);
+	if (model->patches.vblock_b)
+		err += !!apply_key_file(
+				image, model->patches.vblock_b, archive,
+				FMAP_RW_VBLOCK_B, change_vblock);
+	return err;
+}
+
+/*
+ * Finds available patch files by given model.
+ * Updates `model` argument with path of patch files.
+ */
+static void find_patches_for_model(struct model_config *model,
+				   struct u_archive *archive,
+				   const char *signature_id)
+{
+	char *path;
+	int i;
+
+	const char * const names[] = {
+		"rootkey",
+		"vblock_A",
+		"vblock_B",
+	};
+
+	char **targets[] = {
+		&model->patches.rootkey,
+		&model->patches.vblock_a,
+		&model->patches.vblock_b,
+	};
+
+	assert(ARRAY_SIZE(names) == ARRAY_SIZE(targets));
+	for (i = 0; i < ARRAY_SIZE(names); i++) {
+		ASPRINTF(&path, "%s/%s.%s", DIR_KEYSET, names[i], signature_id);
+		if (archive_has_entry(archive, path))
+			*targets[i] = path;
+		else
+			free(path);
+	}
+}
+
+/*
+ * Adds and copies one new model config to the existing list of given manifest.
+ * Returns a pointer to the newly allocated config, or NULL on failure.
+ */
+static struct model_config *manifest_add_model(
+		struct manifest *manifest,
+		const struct model_config *cfg)
+{
+	struct model_config *model;
+	manifest->num++;
+	manifest->models = (struct model_config *)realloc(
+			manifest->models, manifest->num * sizeof(*model));
+	if (!manifest->models) {
+		ERROR("Internal error: failed to allocate buffer.\n");
+		return NULL;
+	}
+	model = &manifest->models[manifest->num - 1];
+	memcpy(model, cfg, sizeof(*model));
+	return model;
+}
+
+/*
+ * A callback function for manifest to scan files in archive.
+ * Returns 0 to keep scanning, or non-zero to stop.
+ */
+static int manifest_scan_entries(const char *name, void *arg)
+{
+	struct manifest *manifest = (struct manifest *)arg;
+	struct u_archive *archive = manifest->archive;
+	struct model_config model = {0};
+	char *slash;
+
+	if (str_startswith(name, PATH_STARTSWITH_KEYSET))
+		manifest->has_keyset = 1;
+	if (!str_endswith(name, PATH_ENDSWITH_SETVARS))
+		return 0;
+
+	/* name: models/$MODEL/setvars.sh */
+	model.name = strdup(strchr(name, '/') + 1);
+	slash = strchr(model.name, '/');
+	if (slash)
+		*slash = '\0';
+
+	VB2_DEBUG("Found model <%s> setvars: %s\n", model.name, name);
+	if (model_config_parse_setvars_file(&model, archive, name)) {
+		ERROR("Invalid setvars file: %s\n", name);
+		return 0;
+	}
+
+	/* In legacy setvars.sh, the ec_image and pd_image may not exist. */
+	if (model.ec_image && !archive_has_entry(archive, model.ec_image)) {
+		VB2_DEBUG("Ignore non-exist EC image: %s\n", model.ec_image);
+		free(model.ec_image);
+		model.ec_image = NULL;
+	}
+	if (model.pd_image && !archive_has_entry(archive, model.pd_image)) {
+		VB2_DEBUG("Ignore non-exist PD image: %s\n", model.pd_image);
+		free(model.pd_image);
+		model.pd_image = NULL;
+	}
+
+	/* Find patch files. */
+	if (model.signature_id)
+		find_patches_for_model(&model, archive, model.signature_id);
+
+	return !manifest_add_model(manifest, &model);
+}
+
+/*
+ * A callback function for manifest to scan files in raw /firmware archive.
+ * Returns 0 to keep scanning, or non-zero to stop.
+ */
+static int manifest_scan_raw_entries(const char *name, void *arg)
+{
+	struct manifest *manifest = (struct manifest *)arg;
+	struct u_archive *archive = manifest->archive;
+	struct model_config model = {0};
+	char *ec_name = NULL, *zephyr_name = NULL;
+	int chars_read = 0;
+
+	/*
+	 * /build/$BOARD/firmware (or CPFE firmware archives) layout:
+	 * - image-${MODEL}{,.serial,.dev...}.bin
+	 * - ${MODEL}/ec.bin or ${MODEL}/zephyr.bin
+	 */
+
+	if (sscanf(name, "image-%m[^.].bin%n", &model.name, &chars_read) != 1)
+		return 0;
+
+	/* Ignore the names with extra modifiers like image-$MODEL.serial.bin */
+	if (!chars_read || name[chars_read]) {
+		free(model.name);
+		return 0;
+	}
+
+	VB2_DEBUG("Found model <%s>: %s\n", model.name, name);
+	model.image = strdup(name);
+
+	ASPRINTF(&ec_name, "%s/ec.bin", model.name);
+	ASPRINTF(&zephyr_name, "%s/zephyr.bin", model.name);
+	if (archive_has_entry(archive, ec_name))
+		model.ec_image = strdup(ec_name);
+	else if (archive_has_entry(archive, zephyr_name))
+		model.ec_image = strdup(zephyr_name);
+	free(ec_name);
+	free(zephyr_name);
+
+	return !manifest_add_model(manifest, &model);
+}
+
+/* Returns the matched model config from the manifest, or NULL if not found. */
+static struct model_config *manifest_get_model_config(
+		const struct manifest *manifest, const char *name)
+{
+	int i = 0;
+
+	for (i = 0; i < manifest->num; i++) {
+		if (!strcmp(name, manifest->models[i].name))
+			return &manifest->models[i];
+	}
+	return NULL;
+}
+
+/*
+ * Creates the manifest from the 'signer_config.csv' file.
+ * Returns 0 on success (loaded), otherwise failure.
+ */
+static int manifest_from_signer_config(struct manifest *manifest)
+{
+	struct u_archive *archive = manifest->archive;
+	uint32_t size;
+	uint8_t *data;
+	char *s, *tok_ptr = NULL;
+
+	if (!archive_has_entry(archive, PATH_SIGNER_CONFIG))
+		return -1;
+
+	/*
+	 * CSV format: model_name,firmware_image,key_id,ec_image
+	 *
+	 * Note the key_id is not signature_id and won't be used, and ec_image
+	 * may be optional (for example sarien).
+	 */
+
+	if (archive_read_file(archive, PATH_SIGNER_CONFIG, &data, &size,NULL)) {
+		ERROR("Failed reading: %s\n", PATH_SIGNER_CONFIG);
+		return -1;
+	}
+
+	/* Skip headers. */
+	s = strtok_r((char *)data, "\n", &tok_ptr);
+	if (!s || !strchr(s, ',')) {
+		ERROR("Invalid %s: missing header.\n", PATH_SIGNER_CONFIG);
+		free(data);
+		return -1;
+	}
+
+	for (s = strtok_r(NULL, "\n", &tok_ptr); s != NULL;
+	     s = strtok_r(NULL, "\n", &tok_ptr)) {
+
+		struct model_config model = {0};
+		int discard_model = 0;
+
+		/*
+		 * Both keyid (%3) and ec_image (%4) are optional so we want to
+		 * read at least 2 fields.
+		 */
+		if (sscanf(s, "%m[^,],%m[^,],%*[^,],%m[^,]",
+		    &model.name, &model.image, &model.ec_image) < 2) {
+			ERROR("Invalid entry(%s): %s\n", PATH_SIGNER_CONFIG, s);
+			discard_model = 1;
+		} else if (strchr(model.name, '-')) {
+			/* format: BaseModel-CustomLabel */
+			char *tok_dash;
+			char *base_model;
+			struct model_config *base_model_config;
+
+			VB2_DEBUG("Found custom-label: %s\n", model.name);
+			discard_model = 1;
+			base_model = strtok_r(model.name, "-", &tok_dash);
+			assert(base_model);
+
+			/*
+			 * Currently we assume the base model (e.g., base_model)
+			 * is always listed before CL models in the CSV file -
+			 * this is based on how the signerbot and the
+			 * chromeos-config works today (validated on octopus).
+			 */
+			base_model_config = manifest_get_model_config(
+					manifest, base_model);
+
+			if (!base_model_config) {
+				ERROR("Invalid CL-model: %s\n", base_model);
+			} else if (!base_model_config->is_custom_label) {
+				base_model_config->is_custom_label = 1;
+				/*
+				 * Rewriting signature_id is not necessary,
+				 * but in order to generate the same manifest
+				 * from setvars, we want to temporarily use
+				 * the special value.
+				 */
+				free(base_model_config->signature_id);
+				base_model_config->signature_id = strdup(
+						"sig-id-in-customization-id");
+			}
+		}
+
+		if (discard_model) {
+			free(model.name);
+			free(model.image);
+			free(model.ec_image);
+			continue;
+		}
+
+		model.signature_id = strdup(model.name);
+		if (!manifest_add_model(manifest, &model))
+			break;
+	}
+	free(data);
+	return 0;
+}
+
+/*
+ * Creates the manifest from a simple (legacy) folder with only 1 set of
+ * firmware images.
+ * Returns 0 on success (loaded), otherwise failure.
+ */
+static int manifest_from_simple_folder(struct manifest *manifest)
+{
+	const char * const host_image_name = "image.bin",
+		   * const old_host_image_name = "bios.bin",
+		   * const ec_name = "ec.bin",
+		   * const pd_name = "pd.bin";
+	struct u_archive *archive = manifest->archive;
+	const char *image_name = NULL;
+	struct firmware_image image = {0};
+	struct model_config model = {0};
+
+	/* Try to load from current folder. */
+	if (archive_has_entry(archive, old_host_image_name))
+		image_name = old_host_image_name;
+	else if (archive_has_entry(archive, host_image_name))
+		image_name = host_image_name;
+	else
+		return 1;
+
+	model.image = strdup(image_name);
+	if (archive_has_entry(archive, ec_name))
+		model.ec_image = strdup(ec_name);
+	if (archive_has_entry(archive, pd_name))
+		model.pd_image = strdup(pd_name);
+	/* Extract model name from FWID: $Vendor_$Platform.$Version */
+	if (!load_firmware_image(&image, image_name, archive)) {
+		char *token = NULL;
+		if (strtok(image.ro_version, "_"))
+			token = strtok(NULL, ".");
+		if (token && *token) {
+			str_convert(token, tolower);
+			model.name = strdup(token);
+		}
+		free_firmware_image(&image);
+	}
+	if (!model.name)
+		model.name = strdup(DEFAULT_MODEL_NAME);
+	if (manifest->has_keyset)
+		model.is_custom_label = 1;
+	manifest_add_model(manifest, &model);
+	manifest->default_model = manifest->num - 1;
+
+	return 0;
+}
+
+/**
+ * get_manifest_key() - Wrapper to get the firmware manifest key from crosid
+ *
+ * @manifest_key_out - Output parameter of the firmware manifest key.
+ *
+ * Returns:
+ * - <0 if libcrosid is unavailable or there was an error reading
+ *   device data
+ * - >=0 (the matched device index) success
+ */
+static int get_manifest_key(char **manifest_key_out)
+{
+#ifdef HAVE_CROSID
+	return crosid_get_firmware_manifest_key(manifest_key_out);
+#else
+	ERROR("This version of futility was compiled without libcrosid "
+	      "(perhaps compiled outside of the Chrome OS build system?) and "
+	      "the update command is not fully supported.  Either compile "
+	      "from the Chrome OS build, or pass --model to manually specify "
+	      "the machine model.\n");
+	return -1;
+#endif
+}
+
+/*
+ * Finds the existing model_config from manifest that best matches current
+ * system (as defined by model_name).
+ * Returns a model_config from manifest, or NULL if not found.
+ */
+const struct model_config *manifest_find_model(const struct manifest *manifest,
+					       const char *model_name)
+{
+	char *manifest_key = NULL;
+	const struct model_config *model = NULL;
+	int i;
+	int matched_index;
+
+	/*
+	 * For manifest with single model defined, we should just return because
+	 * there are other mechanisms like platform name check to double confirm
+	 * if the firmware is valid.
+	 */
+	if (manifest->num == 1)
+		return &manifest->models[0];
+
+	if (!model_name) {
+		matched_index = get_manifest_key(&manifest_key);
+		if (matched_index < 0) {
+			ERROR("Failed to get device identity.  "
+			      "Run \"crosid -v\" for explanation.\n");
+			return NULL;
+		}
+
+		INFO("Identified the device using libcrosid, "
+		     "matched chromeos-config index: %d, "
+		     "manifest key (model): %s\n",
+		     matched_index, manifest_key);
+		model_name = manifest_key;
+	}
+
+	model = manifest_get_model_config(manifest, model_name);
+
+	if (!model) {
+		ERROR("Unsupported model: '%s'.\n", model_name);
+
+		fprintf(stderr,
+			"The firmware manifest key '%s' is not present in this "
+			"updater archive. The known keys to this updater "
+			"archive are:\n", model_name);
+
+		for (i = 0; i < manifest->num; i++)
+			fprintf(stderr, " %s", manifest->models[i].name);
+		fprintf(stderr, "\n\n");
+		fprintf(stderr,
+			"Perhaps you are trying to use an updater archive for "
+			"the wrong board, or designed for an older OS version "
+			"before this model was supported.\n");
+		fprintf(stderr,
+			"Hint: Read the FIRMWARE_MANIFEST_KEY from the output "
+			"of the crosid command.\n");
+	}
+
+
+	free(manifest_key);
+	return model;
+}
+
+/*
+ * Determines the signature ID to use for custom label.
+ * Returns the signature ID for looking up rootkey and vblock files.
+ * Caller must free the returned string.
+ */
+static char *resolve_signature_id(struct model_config *model, const char *image)
+{
+	int is_unibuild = model->signature_id ? 1 : 0;
+	char *tag = vpd_get_value(image, VPD_CUSTOM_LABEL_TAG);
+	char *sig_id = NULL;
+
+	if (tag == NULL)
+		tag = vpd_get_value(image, VPD_CUSTOM_LABEL_TAG_LEGACY);
+
+	/* Unified build: $model.$tag, or $model (b/126800200). */
+	if (is_unibuild) {
+		if (!tag) {
+			WARN("No VPD '%s' set for custom label. "
+			     "Use model name '%s' as default.\n",
+			     VPD_CUSTOM_LABEL_TAG, model->name);
+			return strdup(model->name);
+		}
+
+		ASPRINTF(&sig_id, "%s-%s", model->name, tag);
+		free(tag);
+		return sig_id;
+	}
+
+	/* Non-Unibuild: Upper($tag), or Upper(${cid%%-*}). */
+	if (!tag) {
+		char *cid = vpd_get_value(image, VPD_CUSTOMIZATION_ID);
+		if (cid) {
+			/* customization_id in format LOEM[-VARIANT]. */
+			char *dash = strchr(cid, '-');
+			if (dash)
+				*dash = '\0';
+			tag = cid;
+		}
+	}
+	if (tag)
+		str_convert(tag, toupper);
+	return tag;
+}
+
+/*
+ * Applies custom label information to an existing model configuration.
+ * Collects signature ID information from either parameter signature_id or
+ * image file (via VPD) and updates model.patches for key files.
+ * Returns 0 on success, otherwise failure.
+ */
+int model_apply_custom_label(
+		struct model_config *model,
+		struct u_archive *archive,
+		const char *signature_id,
+		const char *image)
+{
+	char *sig_id = NULL;
+	int r = 0;
+
+	if (!signature_id) {
+		sig_id = resolve_signature_id(model, image);
+		signature_id = sig_id;
+	}
+
+	if (signature_id) {
+		VB2_DEBUG("Find custom label patches by signature ID: '%s'.\n",
+		      signature_id);
+		find_patches_for_model(model, archive, signature_id);
+	} else {
+		signature_id = "";
+		WARN("No VPD '%s' set for custom label - use default keys.\n",
+		     VPD_CUSTOM_LABEL_TAG);
+	}
+	if (!model->patches.rootkey) {
+		ERROR("No keys found for signature_id: '%s'\n", signature_id);
+		r = 1;
+	} else {
+		INFO("Applied for custom label: %s\n", signature_id);
+	}
+	free(sig_id);
+	return r;
+}
+
+/*
+ * Creates a new manifest object by scanning files in archive.
+ * Returns the manifest on success, otherwise NULL for failure.
+ */
+struct manifest *new_manifest_from_archive(struct u_archive *archive)
+{
+	struct manifest manifest = {0}, *new_manifest;
+
+	manifest.archive = archive;
+	manifest.default_model = -1;
+
+	VB2_DEBUG("Try to build a manifest from *%s\n", PATH_ENDSWITH_SETVARS);
+	archive_walk(archive, &manifest, manifest_scan_entries);
+
+	if (manifest.num == 0) {
+		VB2_DEBUG("Try to build a manifest from %s\n",
+			  PATH_SIGNER_CONFIG);
+		manifest_from_signer_config(&manifest);
+	}
+	if (manifest.num == 0) {
+		VB2_DEBUG("Try to build a manifest from a */firmware folder\n");
+		archive_walk(archive, &manifest, manifest_scan_raw_entries);
+	}
+	if (manifest.num == 0) {
+		VB2_DEBUG("Try to build a manifest from a simple folder\n");
+		manifest_from_simple_folder(&manifest);
+	}
+
+	VB2_DEBUG("%d model(s) loaded.\n", manifest.num);
+	if (!manifest.num) {
+		ERROR("No valid configurations found from archive.\n");
+		return NULL;
+	}
+
+	new_manifest = (struct manifest *)malloc(sizeof(manifest));
+	if (!new_manifest) {
+		ERROR("Internal error: memory allocation error.\n");
+		return NULL;
+	}
+	memcpy(new_manifest, &manifest, sizeof(manifest));
+	return new_manifest;
+}
+
+/* Releases all resources allocated by given manifest object. */
+void delete_manifest(struct manifest *manifest)
+{
+	int i;
+	assert(manifest);
+	for (i = 0; i < manifest->num; i++) {
+		struct model_config *model = &manifest->models[i];
+		free(model->name);
+		free(model->signature_id);
+		free(model->image);
+		free(model->ec_image);
+		free(model->pd_image);
+		free(model->patches.rootkey);
+		free(model->patches.vblock_a);
+		free(model->patches.vblock_b);
+	}
+	free(manifest->models);
+	free(manifest);
+}
+
+static const char *get_gbb_key_hash(const struct vb2_gbb_header *gbb,
+				    int32_t offset, int32_t size)
+{
+	struct vb2_packed_key *key;
+
+	if (!gbb)
+		return "<No GBB>";
+	key = (struct vb2_packed_key *)((uint8_t *)gbb + offset);
+	if (vb2_packed_key_looks_ok(key, size))
+		return "<Invalid key>";
+	return packed_key_sha1_string(key);
+}
+
+/* Prints the information of given image file in JSON format. */
+static void print_json_image(
+		const char *name, const char *fpath, struct model_config *m,
+		struct u_archive *archive, int indent, int is_host)
+{
+	struct firmware_image image = {0};
+	const struct vb2_gbb_header *gbb = NULL;
+	if (!fpath)
+		return;
+	if (load_firmware_image(&image, fpath, archive))
+		return;
+	if (!is_host)
+		printf(",\n");
+	printf("%*s\"%s\": { \"versions\": { \"ro\": \"%s\", \"rw\": \"%s\" },",
+	       indent, "", name, image.ro_version, image.rw_version_a);
+	indent += 2;
+	if (is_host) {
+		if (patch_image_by_model(&image, m, archive))
+			ERROR("Failed to patch images by model: %s\n", m->name);
+		else
+			gbb = find_gbb(&image);
+	}
+	if (gbb != NULL) {
+		printf("\n%*s\"keys\": { \"root\": \"%s\", ",
+		       indent, "",
+		       get_gbb_key_hash(gbb, gbb->rootkey_offset,
+					gbb->rootkey_size));
+		printf("\"recovery\": \"%s\" },",
+		       get_gbb_key_hash(gbb, gbb->recovery_key_offset,
+					gbb->recovery_key_size));
+	}
+	printf("\n%*s\"image\": \"%s\" }", indent, "", fpath);
+	free_firmware_image(&image);
+}
+
+/* Prints the information of objects in manifest (models and images) in JSON. */
+void print_json_manifest(const struct manifest *manifest)
+{
+	int i, indent;
+	struct u_archive *ar = manifest->archive;
+
+	printf("{\n");
+	for (i = 0, indent = 2; i < manifest->num; i++) {
+		struct model_config *m = &manifest->models[i];
+		printf("%s%*s\"%s\": {\n", i ? ",\n" : "", indent, "", m->name);
+		indent += 2;
+		print_json_image("host", m->image, m, ar, indent, 1);
+		print_json_image("ec", m->ec_image, m, ar, indent, 0);
+		print_json_image("pd", m->pd_image, m, ar, indent, 0);
+		if (m->patches.rootkey) {
+			struct patch_config *p = &m->patches;
+			printf(",\n%*s\"patches\": { \"rootkey\": \"%s\", "
+			       "\"vblock_a\": \"%s\", \"vblock_b\": \"%s\" }",
+			       indent, "", p->rootkey, p->vblock_a,
+			       p->vblock_b);
+		}
+		if (m->signature_id)
+			printf(",\n%*s\"signature_id\": \"%s\"", indent, "",
+			       m->signature_id);
+		printf("\n  }");
+		indent -= 2;
+		assert(indent == 2);
+	}
+	printf("\n}\n");
+}