/* Copyright 2018 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.
 *
 * Accessing updater resources from an archive.
 */

#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <fts.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <unistd.h>

#ifdef HAVE_LIBZIP
#include <zip.h>
#endif

#include "host_misc.h"
#include "updater.h"
#include "util_misc.h"
#include "vb2_common.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 white label is supported, a 'keyset/' folder will be available, with key
 * files in it:
 *  - rootkey.$WLTAG
 *  - vblock_A.$WLTAG
 *  - vblock_B.$WLTAG
 * The $WLTAG should come from VPD value 'whitelabel_tag', or the
 * 'customization_id'. Note 'customization_id' is in format LOEM[-VARIANT] and
 * we can only take LOEM as $WLTAG, for example A-B => $WLTAG=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 write 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
 * 'whitelabel_tag' as '$MODEL-$WLTAG'.
 */

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_WHITELABEL_TAG = "whitelabel_tag",
		  * const VPD_CUSTOMIZATION_ID = "customization_id",
		  * const ENV_VAR_MODEL_DIR = "${MODEL_DIR}",
		  * const PATH_STARTSWITH_KEYSET = "keyset/",
		  * const PATH_ENDSWITH_SERVARS = "/setvars.sh";

struct archive {
	void *handle;

	void * (*open)(const char *name);
	int (*close)(void *handle);

	int (*walk)(void *handle, void *arg,
		    int (*callback)(const char *path, void *arg));
	int (*has_entry)(void *handle, const char *name);
	int (*read_file)(void *handle, const char *fname,
			 uint8_t **data, uint32_t *size, int64_t *mtime);
	int (*write_file)(void *handle, const char *fname,
			  uint8_t *data, uint32_t size, int64_t mtime);
};

/*
 * -- Begin of archive implementations --
 */

/* Callback for archive_open on a general file system. */
static void *archive_fallback_open(const char *name)
{
	assert(name && *name);
	return strdup(name);
}

/* Callback for archive_close on a general file system. */
static int archive_fallback_close(void *handle)
{
	free(handle);
	return 0;
}

/* Callback for archive_walk on a general file system. */
static int archive_fallback_walk(
		void *handle, void *arg,
		int (*callback)(const char *path, void *arg))
{
	FTS *fts_handle;
	FTSENT *ent;
	char *fts_argv[2] = {};
	char default_path[] = ".";
	char *root = default_path;
	size_t root_len;

	if (handle)
		root = (char *)handle;
	root_len = strlen(root);
	fts_argv[0] = root;

	fts_handle = fts_open(fts_argv, FTS_NOCHDIR, NULL);
	if (!fts_handle)
		return -1;

	while ((ent = fts_read(fts_handle)) != NULL) {
		char *path = ent->fts_path + root_len;
		if (ent->fts_info != FTS_F && ent->fts_info != FTS_SL)
			continue;
		while (*path == '/')
			path++;
		if (!*path)
			continue;
		if (callback(path, arg))
			break;
	}
	return 0;
}

/* Callback for fallback drivers to get full path easily. */
static const char *archive_fallback_get_path(void *handle, const char *fname,
					     char **temp_path)
{
	if (handle && *fname != '/') {
		ASPRINTF(temp_path, "%s/%s", (char *)handle, fname);
		return *temp_path;
	}
	return fname;
}

/* Callback for archive_has_entry on a general file system. */
static int archive_fallback_has_entry(void *handle, const char *fname)
{
	int r;
	char *temp_path = NULL;
	const char *path = archive_fallback_get_path(handle, fname, &temp_path);

	VB2_DEBUG("Checking %s\n", path);
	r = access(path, R_OK);
	free(temp_path);
	return r == 0;
}

/* Callback for archive_read_file on a general file system. */
static int archive_fallback_read_file(void *handle, const char *fname,
				      uint8_t **data, uint32_t *size, int64_t *mtime)
{
	int r;
	char *temp_path = NULL;
	const char *path = archive_fallback_get_path(handle, fname, &temp_path);
	struct stat st;

	VB2_DEBUG("Reading %s\n", path);
	*data = NULL;
	*size = 0;
	r = vb2_read_file(path, data, size) != VB2_SUCCESS;
	if (mtime) {
		if (stat(path, &st) == 0)
			*mtime = st.st_mtime;
		else
			WARN("Unable to stat %s: %s\n", path, strerror(errno));
	}
	free(temp_path);
	return r;
}

/* Callback for archive_write_file on a general file system. */
static int archive_fallback_write_file(void *handle, const char *fname,
				       uint8_t *data, uint32_t size, int64_t mtime)
{
	int r;
	char *temp_path = NULL;
	const char *path = archive_fallback_get_path(handle, fname, &temp_path);

	VB2_DEBUG("Writing %s\n", path);
	if (strchr(path, '/')) {
		char *dirname = strdup(path);
		*strrchr(dirname, '/') = '\0';
		/* TODO(hungte): call mkdir(2) instead of shell invocation. */
		if (access(dirname, W_OK) != 0) {
			char *command;
			ASPRINTF(&command, "mkdir -p %s", dirname);
			free(host_shell(command));
			free(command);
		}
		free(dirname);
	}
	r = vb2_write_file(path, data, size) != VB2_SUCCESS;
	if (mtime) {
		struct timeval times[2] = {
			{.tv_sec = mtime, .tv_usec = 0},
			{.tv_sec = mtime, .tv_usec = 0},
		};
		if (utimes(path, times) != 0)
			WARN("Unable to set times on %s: %s\n", path, strerror(errno));
	}
	free(temp_path);
	return r;
}

#ifdef HAVE_LIBZIP

/* Callback for archive_open on a ZIP file. */
static void *archive_zip_open(const char *name)
{
	return zip_open(name, 0, NULL);
}

/* Callback for archive_close on a ZIP file. */
static int archive_zip_close(void *handle)
{
	struct zip *zip = (struct zip *)handle;

	if (zip)
		return zip_close(zip);
	return 0;
}

/* Callback for archive_has_entry on a ZIP file. */
static int archive_zip_has_entry(void *handle, const char *fname)
{
	struct zip *zip = (struct zip *)handle;
	assert(zip);
	return zip_name_locate(zip, fname, 0) != -1;
}

/* Callback for archive_walk on a ZIP file. */
static int archive_zip_walk(
		void *handle, void *arg,
		int (*callback)(const char *name, void *arg))
{
	zip_int64_t num, i;
	struct zip *zip = (struct zip *)handle;
	assert(zip);

	num = zip_get_num_entries(zip, 0);
	if (num < 0)
		return 1;
	for (i = 0; i < num; i++) {
		const char *name = zip_get_name(zip, i, 0);
		if (*name && name[strlen(name) - 1] == '/')
			continue;
		if (callback(name, arg))
			break;
	}
	return 0;
}

/* Callback for archive_zip_read_file on a ZIP file. */
static int archive_zip_read_file(void *handle, const char *fname,
			     uint8_t **data, uint32_t *size, int64_t *mtime)
{
	struct zip *zip = (struct zip *)handle;
	struct zip_file *fp;
	struct zip_stat stat;

	assert(zip);
	*data = NULL;
	*size = 0;
	zip_stat_init(&stat);
	if (zip_stat(zip, fname, 0, &stat)) {
		ERROR("Fail to stat entry in ZIP: %s\n", fname);
		return 1;
	}
	fp = zip_fopen(zip, fname, 0);
	if (!fp) {
		ERROR("Failed to open entry in ZIP: %s\n", fname);
		return 1;
	}
	*data = (uint8_t *)malloc(stat.size);
	if (*data) {
		if (zip_fread(fp, *data, stat.size) == stat.size) {
			if (mtime)
				*mtime = stat.mtime;
			*size = stat.size;
		} else {
			ERROR("Failed to read entry in zip: %s\n", fname);
			free(*data);
			*data = NULL;
		}
	}
	zip_fclose(fp);
	return *data == NULL;
}

/* Callback for archive_zip_write_file on a ZIP file. */
static int archive_zip_write_file(void *handle, const char *fname,
				  uint8_t *data, uint32_t size, int64_t mtime)
{
	struct zip *zip = (struct zip *)handle;
	struct zip_source *src;

	VB2_DEBUG("Writing %s\n", fname);
	assert(zip);
	src = zip_source_buffer(zip, data, size, 0);
	if (!src) {
		ERROR("Internal error: cannot allocate buffer: %s\n", fname);
		return 1;
	}

	if (zip_file_add(zip, fname, src, ZIP_FL_OVERWRITE) < 0) {
		zip_source_free(src);
		ERROR("Internal error: failed to add: %s\n", fname);
		return 1;
	}
	/* zip_source_free is not needed if zip_file_add success. */
#if LIBZIP_VERSION_MAJOR >= 1
	zip_file_set_mtime(zip, zip_name_locate(zip, fname, 0), mtime, 0);
#endif
	return 0;
}
#endif

/*
 * 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
 * when not used), otherwise NULL on error.
 */
struct archive *archive_open(const char *path)
{
	struct stat path_stat;
	struct archive *ar;

	if (stat(path, &path_stat) != 0) {
		ERROR("Cannot identify type of path: %s\n", path);
		return NULL;
	}

	ar = (struct archive *)malloc(sizeof(*ar));
	if (!ar) {
		ERROR("Internal error: allocation failure.\n");
		return NULL;
	}

	if (S_ISDIR(path_stat.st_mode)) {
		VB2_DEBUG("Found directory, use fallback (fs) driver: %s\n",
			  path);
		/* Regular file system. */
		ar->open = archive_fallback_open;
		ar->close = archive_fallback_close;
		ar->walk = archive_fallback_walk;
		ar->has_entry = archive_fallback_has_entry;
		ar->read_file = archive_fallback_read_file;
		ar->write_file = archive_fallback_write_file;
	} else {
#ifdef HAVE_LIBZIP
		VB2_DEBUG("Found file, use ZIP driver: %s\n", path);
		ar->open = archive_zip_open;
		ar->close = archive_zip_close;
		ar->walk = archive_zip_walk;
		ar->has_entry = archive_zip_has_entry;
		ar->read_file = archive_zip_read_file;
		ar->write_file = archive_zip_write_file;
#else
		ERROR("Found file, but no drivers were enabled: %s\n", path);
		free(ar);
		return NULL;
#endif
	}
	ar->handle = ar->open(path);
	if (!ar->handle) {
		ERROR("Failed to open archive: %s\n", path);
		free(ar);
		return NULL;
	}
	return ar;
}

/*
 * Closes an archive reference.
 * Returns 0 on success, otherwise non-zero as failure.
 */
int archive_close(struct archive *ar)
{
	int r = ar->close(ar->handle);
	free(ar);
	return r;
}

/*
 * Checks if an entry (either file or directory) exists in archive.
 * If entry name (fname) is an absolute path (/file), always check
 * with real file system.
 * Returns 1 if exists, otherwise 0
 */
int archive_has_entry(struct archive *ar, const char *name)
{
	if (!ar || *name == '/')
		return archive_fallback_has_entry(NULL, name);
	return ar->has_entry(ar->handle, name);
}

/*
 * 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.
 */
static int archive_walk(struct archive *ar, void *arg,
			int (*callback)(const char *path, void *arg))
{
	if (!ar)
		return archive_fallback_walk(NULL, arg, callback);
	return ar->walk(ar->handle, arg, callback);
}

/*
 * Reads a file from archive.
 * If entry name (fname) is an absolute path (/file), always read
 * from real file system.
 * Returns 0 on success (data and size reflects the file content),
 * otherwise non-zero as failure.
 */
int archive_read_file(struct archive *ar, const char *fname,
		      uint8_t **data, uint32_t *size, int64_t *mtime)
{
	if (!ar || *fname == '/')
		return archive_fallback_read_file(NULL, fname, data, size, mtime);
	return ar->read_file(ar->handle, fname, data, size, mtime);
}

/*
 * Writes a file into archive.
 * If entry name (fname) is an absolute path (/file), always write into real
 * file system.
 * Returns 0 on success, otherwise non-zero as failure.
 */
int archive_write_file(struct archive *ar, const char *fname,
		       uint8_t *data, uint32_t size, int64_t mtime)
{
	if (!ar || *fname == '/')
		return archive_fallback_write_file(NULL, fname, data, size, mtime);
	return ar->write_file(ar->handle, fname, data, size, mtime);
}

struct _copy_arg {
	struct archive *from, *to;
};

/* Callback for archive_copy. */
static int archive_copy_callback(const char *path, void *_arg)
{
	const struct _copy_arg *arg = (const struct _copy_arg*)_arg;
	uint32_t size;
	uint8_t *data;
	int64_t mtime;
	int r;

	INFO("Copying: %s\n", path);
	if (archive_read_file(arg->from, path, &data, &size, &mtime)) {
		ERROR("Failed reading: %s\n", path);
		return 1;
	}
	r = archive_write_file(arg->to, path, data, size, mtime);
	VB2_DEBUG("result=%d\n", r);
	free(data);
	return r;
}

/*
 * Copies all entries from one archive to another.
 * Returns 0 on success, otherwise non-zero as failure.
 */
int archive_copy(struct archive *from, struct archive *to)
{
	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 archive *archive,
		const char *fpath)
{
	uint8_t *data;
	uint32_t len;

	char *ptr_line, *ptr_token;
	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_white_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 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 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 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 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_SERVARS))
		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);
}

/*
 * 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 *sys_model_name = NULL;
	const struct model_config *model = NULL;
	int i;

	/*
	 * 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) {
		sys_model_name = host_shell("mosys platform model");
		VB2_DEBUG("System model name: '%s'\n", sys_model_name);
		model_name = sys_model_name;
	}

	for (i = 0; !model && i < manifest->num; i++) {
		if (strcmp(model_name, manifest->models[i].name) == 0)
			model = &manifest->models[i];
	}
	if (!model) {
		if (!*model_name)
			ERROR("Cannot get model name.\n");
		else
			ERROR("Unsupported model: '%s'.\n", model_name);

		fprintf(stderr,
			"You are probably running an image for wrong board, or "
			"a device in early stage that 'mosys' command is not "
			"ready, or image from old (or factory) branches that "
			"Unified Build config is not updated yet for 'mosys'.\n"
			"Please check command 'mosys platform model', "
			"which should output one of the supported models below:"
			"\n");

		for (i = 0; i < manifest->num; i++)
			fprintf(stderr, " %s", manifest->models[i].name);
		fprintf(stderr, "\n");
	}


	free(sys_model_name);
	return model;
}

/*
 * Determines the signature ID to use for white 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 *wl_tag = vpd_get_value(image, VPD_WHITELABEL_TAG);
	char *sig_id = NULL;

	/* Unified build: $model.$wl_tag, or $model (b/126800200). */
	if (is_unibuild) {
		if (!wl_tag) {
			WARN("No VPD '%s' set for white label - use model name "
			     "'%s' as default.\n", VPD_WHITELABEL_TAG,
			     model->name);
			return strdup(model->name);
		}

		ASPRINTF(&sig_id, "%s-%s", model->name, wl_tag);
		free(wl_tag);
		return sig_id;
	}

	/* Non-Unibuild: Upper($wl_tag), or Upper(${cid%%-*}). */
	if (!wl_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';
			wl_tag = cid;
		}
	}
	if (wl_tag)
		str_convert(wl_tag, toupper);
	return wl_tag;
}

/*
 * Applies white 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_white_label(
		struct model_config *model,
		struct 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 white 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 white label - use default keys.\n",
		     VPD_WHITELABEL_TAG);
	}
	if (!model->patches.rootkey) {
		ERROR("No keys found for signature_id: '%s'\n", signature_id);
		r = 1;
	} else {
		INFO("Applied for white 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 archive *archive)
{
	struct manifest manifest = {0}, *new_manifest;
	struct model_config model = {0};
	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";

	manifest.archive = archive;
	manifest.default_model = -1;
	archive_walk(archive, &manifest, manifest_scan_entries);
	if (manifest.num == 0) {
		const char *image_name = NULL;
		struct firmware_image image = {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 0;

		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_white_label = 1;
		manifest_add_model(&manifest, &model);
		manifest.default_model = manifest.num - 1;
	}
	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 (!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 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)
		gbb = find_gbb(&image);
	else
		printf(",\n");
	printf("%*s\"%s\": { \"versions\": { \"ro\": \"%s\", \"rw\": \"%s\" },",
	       indent, "", name, image.ro_version, image.rw_version_a);
	indent += 2;
	if (is_host && patch_image_by_model(&image, m, archive) != 0) {
		ERROR("Failed to patch images by model: %s\n", m->name);
	} else if (gbb) {
		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 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");
}
