From 7db7a6db8f0719fe0fda8219c7111c7caf0b4546 Mon Sep 17 00:00:00 2001 From: Hung-Te Lin Date: Mon, 1 Oct 2018 17:03:42 +0800 Subject: futility: updater: Add '--archive' to read from an archive or directory A firmware update is usually released as a package with multiple images, instructions, signed vblocks and other files. To work with that, a new argument '--archive' is added. The --archive accepts a directory or file, and will determine the correct driver automatically. For resources (for example --image) in relative path, updater should find files from archive. Note in current implementation, only ZIP is supported for file type drivers (and need the system to have libzip already installed). BUG=chromium:875551 TEST=TEST=make futil; tests/futility/run_test_scripts.sh $(pwd)/build/futility BRANCH=None Change-Id: I6a91cbe73fb4ee203c5fa4607f6651a39ba854d5 Signed-off-by: Hung-Te Lin Reviewed-on: https://chromium-review.googlesource.com/1253229 Reviewed-by: Randall Spangler --- Makefile | 16 ++- futility/cmd_update.c | 12 +- futility/updater.c | 48 +++++-- futility/updater.h | 43 +++++- futility/updater_archive.c | 311 ++++++++++++++++++++++++++++++++++++++++++ futility/updater_quirks.c | 2 +- tests/futility/test_update.sh | 7 + 7 files changed, 419 insertions(+), 20 deletions(-) create mode 100644 futility/updater_archive.c diff --git a/Makefile b/Makefile index b717c3bd..a3720135 100644 --- a/Makefile +++ b/Makefile @@ -233,6 +233,14 @@ LDFLAGS += -static PKG_CONFIG += --static endif +# Optional Libraries +LIBZIP_VERSION := $(shell ${PKG_CONFIG} --modversion libzip 2>/dev/null) +HAVE_LIBZIP := $(if ${LIBZIP_VERSION},1) +ifneq (${HAVE_LIBZIP},) + CFLAGS += -DHAVE_LIBZIP $(shell ${PKG_CONFIG} --cflags libzip) + LIBZIP_LIBS := $(shell ${PKG_CONFIG} --libs libzip) +endif + # Determine QEMU architecture needed, if any ifeq (${ARCH},${HOST_ARCH}) # Same architecture; no need for QEMU @@ -688,6 +696,7 @@ FUTIL_SRCS = \ futility/misc.c \ futility/ryu_root_header.c \ futility/updater.c \ + futility/updater_archive.c \ futility/updater_quirks.c \ futility/vb1_helper.c \ futility/vb2_helper.c @@ -1120,7 +1129,10 @@ signing_install: ${SIGNING_SCRIPTS} ${SIGNING_SCRIPTS_DEV} ${SIGNING_COMMON} .PHONY: futil futil: ${FUTIL_BIN} -${FUTIL_BIN}: LDLIBS += ${CRYPTO_LIBS} +# FUTIL_LIBS is shared by FUTIL_BIN and TEST_FUTIL_BINS. +FUTIL_LIBS = ${CRYPTO_LIBS} ${LIBZIP_LIBS} + +${FUTIL_BIN}: LDLIBS += ${FUTIL_LIBS} ${FUTIL_BIN}: ${FUTIL_OBJS} ${UTILLIB} ${FWLIB20} ${UTILBDB} @${PRINTF} " LD $(subst ${BUILD}/,,$@)\n" ${Q}${LD} -o $@ ${CFLAGS} ${LDFLAGS} $^ ${LDLIBS} @@ -1163,7 +1175,7 @@ ${TEST_BINS}: LIBS = ${TESTLIB} ${UTILLIB} ${TEST_FUTIL_BINS}: ${FUTIL_OBJS} ${UTILLIB} ${UTILBDB} ${TEST_FUTIL_BINS}: INCLUDES += -Ifutility ${TEST_FUTIL_BINS}: OBJS += ${FUTIL_OBJS} ${UTILLIB} ${UTILBDB} -${TEST_FUTIL_BINS}: LDLIBS += ${CRYPTO_LIBS} +${TEST_FUTIL_BINS}: LDLIBS += ${FUTIL_LIBS} ${TEST2X_BINS}: ${FWLIB2X} ${TEST2X_BINS}: LIBS += ${FWLIB2X} diff --git a/futility/cmd_update.c b/futility/cmd_update.c index d220988e..830f9ace 100644 --- a/futility/cmd_update.c +++ b/futility/cmd_update.c @@ -22,6 +22,7 @@ static struct option const long_opts[] = { {"ec_image", 1, NULL, 'e'}, {"pd_image", 1, NULL, 'P'}, {"try", 0, NULL, 't'}, + {"archive", 1, NULL, 'a'}, {"quirks", 1, NULL, 'f'}, {"list-quirks", 0, NULL, 'L'}, {"mode", 1, NULL, 'm'}, @@ -37,7 +38,7 @@ static struct option const long_opts[] = { {NULL, 0, NULL, 0}, }; -static const char * const short_opts = "hi:e:tm:p:dv"; +static const char * const short_opts = "hi:e:ta:m:p:dv"; static void print_help(int argc, char *argv[]) { @@ -48,6 +49,7 @@ static void print_help(int argc, char *argv[]) "-e, --ec_image=FILE \tEC firmware image (i.e, ec.bin)\n" " --pd_image=FILE \tPD firmware image (i.e, pd.bin)\n" "-t, --try \tTry A/B update on reboot if possible\n" + "-a, --archive=PATH \tRead resources from archive\n" "-p, --programmer=PRG\tChange AP (host) flashrom programmer\n" " --quirks=LIST \tSpecify the quirks to apply\n" " --list-quirks \tPrint all available quirks\n" @@ -72,6 +74,7 @@ static int do_update(int argc, char *argv[]) const char *opt_image = NULL, *opt_ec_image = NULL, *opt_pd_image = NULL, + *opt_archive = NULL, *opt_quirks = NULL, *opt_mode = NULL, *opt_programmer = NULL, @@ -102,6 +105,9 @@ static int do_update(int argc, char *argv[]) case 't': opt_try_update = 1; break; + case 'a': + opt_archive = optarg; + break; case 'f': opt_quirks = optarg; break; @@ -162,8 +168,8 @@ static int do_update(int argc, char *argv[]) if (!errorcnt) errorcnt += updater_setup_config( cfg, opt_image, opt_ec_image, opt_pd_image, - opt_quirks, opt_mode, opt_programmer, - opt_emulation, opt_sys_props, + opt_archive, opt_quirks, opt_mode, + opt_programmer, opt_emulation, opt_sys_props, opt_write_protection, opt_is_factory, opt_try_update, opt_force_update, opt_verbosity); diff --git a/futility/updater.c b/futility/updater.c index afa4c017..b9e939b1 100644 --- a/futility/updater.c +++ b/futility/updater.c @@ -577,14 +577,21 @@ static int load_firmware_version(struct firmware_image *image, /* * Loads a firmware image from file. + * If archive is provided and file_name is a relative path, read the file from + * archive. * Returns 0 on success, otherwise failure. */ -int load_firmware_image(struct firmware_image *image, const char *file_name) +int load_firmware_image(struct firmware_image *image, const char *file_name, + struct archive *archive) { DEBUG("Load image file from %s...", file_name); - if (vb2_read_file(file_name, &image->data, &image->size) != VB2_SUCCESS) - { + if (!archive_has_entry(archive, file_name)) { + ERROR("Does not exist: %s", file_name); + return -1; + } + if (archive_read_file(archive, file_name, &image->data, &image->size) != + VB2_SUCCESS) { ERROR("Failed to load %s", file_name); return -1; } @@ -632,7 +639,7 @@ int load_system_firmware(struct updater_config *cfg, struct firmware_image *imag RETURN_ON_FAILURE(host_flashrom( FLASHROM_READ, tmp_file, image->programmer, cfg->verbosity, NULL)); - return load_firmware_image(image, tmp_file); + return load_firmware_image(image, tmp_file, NULL); } /* @@ -734,7 +741,7 @@ static int emulate_write_firmware(const char *filename, from.data = image->data; from.size = image->size; - if (load_firmware_image(&to_image, filename)) { + if (load_firmware_image(&to_image, filename, NULL)) { ERROR("Cannot load image from %s.", filename); return -1; } @@ -1254,12 +1261,17 @@ static int legacy_needs_update(struct updater_config *cfg) int has_from, has_to; const char * const tag = "cros_allow_auto_update"; const char *section = FMAP_RW_LEGACY; + const char *tmp_path = updater_create_temp_file(cfg); DEBUG("Checking %s contents...", FMAP_RW_LEGACY); + if (!tmp_path || + vb2_write_file(tmp_path, cfg->image.data, cfg->image.size)) { + ERROR("Failed to create temporary file for image contents."); + return 0; + } - /* TODO(hungte): Save image_current as temp file and use it. */ - has_to = cbfs_file_exists(cfg->image.file_name, section, tag); - has_from = cbfs_file_exists(cfg->image_current.file_name, section, tag); + has_to = cbfs_file_exists(tmp_path, section, tag); + has_from = cbfs_file_exists(tmp_path, section, tag); if (!has_from || !has_to) { DEBUG("Current legacy firmware has%s updater tag (%s) " @@ -1626,6 +1638,7 @@ int updater_setup_config(struct updater_config *cfg, const char *image, const char *ec_image, const char *pd_image, + const char *archive, const char *quirks, const char *mode, const char *programmer, @@ -1640,9 +1653,17 @@ int updater_setup_config(struct updater_config *cfg, int errorcnt = 0; int check_single_image = 0, check_wp_disabled = 0; const char *default_quirks = NULL; + struct archive *ar; cfg->verbosity = verbosity; + cfg->archive = archive_open(archive ? archive : "."); + if (!cfg->archive) { + ERROR("Failed to open archive: %s", archive); + return ++errorcnt; + } + ar = cfg->archive; + if (try_update) cfg->try_update = 1; if (force_update) @@ -1658,12 +1679,12 @@ int updater_setup_config(struct updater_config *cfg, errorcnt += !!save_from_stdin(image); } if (image) { - errorcnt += !!load_firmware_image(&cfg->image, image); + errorcnt += !!load_firmware_image(&cfg->image, image, ar); } if (ec_image) - errorcnt += !!load_firmware_image(&cfg->ec_image, ec_image); + errorcnt += !!load_firmware_image(&cfg->ec_image, ec_image, ar); if (pd_image) - errorcnt += !!load_firmware_image(&cfg->pd_image, pd_image); + errorcnt += !!load_firmware_image(&cfg->pd_image, pd_image, ar); /* * Quirks must be loaded after images are loaded because we use image @@ -1711,7 +1732,8 @@ int updater_setup_config(struct updater_config *cfg, check_single_image = 1; cfg->emulation = emulation; DEBUG("Using file %s for emulation.", emulation); - errorcnt += load_firmware_image(&cfg->image_current, emulation); + errorcnt += load_firmware_image( + &cfg->image_current, emulation, NULL); } /* Additional checks. */ @@ -1737,5 +1759,7 @@ void updater_delete_config(struct updater_config *cfg) free_firmware_image(&cfg->ec_image); free_firmware_image(&cfg->pd_image); updater_remove_all_temp_files(cfg); + if (cfg->archive) + archive_close(cfg->archive); free(cfg); } diff --git a/futility/updater.h b/futility/updater.h index 2556c167..c04e9c3c 100644 --- a/futility/updater.h +++ b/futility/updater.h @@ -94,11 +94,13 @@ struct tempfile { struct tempfile *next; }; +struct archive; struct updater_config { struct firmware_image image, image_current; struct firmware_image ec_image, pd_image; struct system_property system_properties[SYS_PROP_MAX]; struct quirk_entry quirks[QUIRK_MAX]; + struct archive *archive; struct tempfile *tempfiles; int try_update; int force_update; @@ -152,6 +154,7 @@ int updater_setup_config(struct updater_config *cfg, const char *image, const char *ec_image, const char *pd_image, + const char *archive, const char *quirks, const char *mode, const char *programmer, @@ -198,8 +201,14 @@ int preserve_firmware_section(const struct firmware_image *image_from, struct firmware_image *image_to, const char *section_name); -/* Loads a firmware image from file. Returns 0 on success, otherwise failure. */ -int load_firmware_image(struct firmware_image *image, const char *file_name); +/* + * Loads a firmware image from file. + * If archive is provided and file_name is a relative path, read the file from + * archive. + * Returns 0 on success, otherwise failure. + */ +int load_firmware_image(struct firmware_image *image, const char *file_name, + struct archive *archive); /* * Loads the active system firmware image (usually from SPI flash chip). @@ -230,4 +239,34 @@ const char * const updater_get_default_quirks(struct updater_config *cfg); */ char *host_shell(const char *command); +/* Functions from updater_archive.c */ + +/* + * 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); + +/* + * Closes an archive reference. + * Returns 0 on success, otherwise non-zero as failure. + */ +int archive_close(struct archive *ar); + +/* + * Checks if an entry (either file or directory) exists in archive. + * Returns 1 if exists, otherwise 0 + */ +int archive_has_entry(struct archive *ar, const char *name); + +/* + * Reads a file from archive. + * 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); + #endif /* VBOOT_REFERENCE_FUTILITY_UPDATER_H_ */ diff --git a/futility/updater_archive.c b/futility/updater_archive.c new file mode 100644 index 00000000..dd36f101 --- /dev/null +++ b/futility/updater_archive.c @@ -0,0 +1,311 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_LIBZIP +#include +#endif + +#include "host_misc.h" +#include "updater.h" +#include "vb2_common.h" + +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); +}; + +/* 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; + while (*path == '/') + path++; + if (!*path) + continue; + if (callback(path, arg)) + break; + } + return 0; +} + +/* Callback for archive_has_entry on a general file system. */ +static int archive_fallback_has_entry(void *handle, const char *fname) +{ + int r; + const char *path = fname; + char *temp_path = NULL; + + if (handle && *fname != '/') { + ASPRINTF(&temp_path, "%s/%s", (char *)handle, fname); + path = temp_path; + } + + DEBUG("Checking %s", 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) +{ + int r; + const char *path = fname; + char *temp_path = NULL; + + *data = NULL; + *size = 0; + if (handle && *fname != '/') { + ASPRINTF(&temp_path, "%s/%s", (char *)handle, fname); + path = temp_path; + } + DEBUG("Reading %s", path); + r = vb2_read_file(path, data, size) != VB2_SUCCESS; + 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++) { + if (callback(zip_get_name(zip, i, 0), 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) +{ + 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", fname); + return 1; + } + fp = zip_fopen(zip, fname, 0); + if (!fp) { + ERROR("Failed to open entry in ZIP: %s", fname); + return 1; + } + *data = (uint8_t *)malloc(stat.size); + if (*data) { + if (zip_fread(fp, *data, stat.size) == stat.size) { + *size = stat.size; + } else { + ERROR("Failed to read entry in zip: %s", fname); + free(*data); + *data = NULL; + } + } + zip_fclose(fp); + return *data == NULL; +} +#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", path); + return NULL; + } + + ar = (struct archive *)malloc(sizeof(*ar)); + if (!ar) { + ERROR("Internal error: allocation failure."); + return NULL; + } + + if (S_ISDIR(path_stat.st_mode)) { + DEBUG("Found directory, use fallback (fs) driver: %s", 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; + } else { +#ifdef HAVE_LIBZIP + DEBUG("Found file, use ZIP driver: %s", 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; +#else + ERROR("Found file, but no drivers were enabled: %s", path); + free(ar); + return NULL; +#endif + } + ar->handle = ar->open(path); + if (!ar->handle) { + ERROR("Failed to open archive: %s", 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 entries within archive. + * 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. + * Be aware that some archive may not store "directory" type entries. + * Returns 0 on success otherwise non-zero as failure. + */ +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) +{ + if (!ar || *fname == '/') + return archive_fallback_read_file(NULL, fname, data, size); + return ar->read_file(ar->handle, fname, data, size); +} diff --git a/futility/updater_quirks.c b/futility/updater_quirks.c index 65d28202..8c924a37 100644 --- a/futility/updater_quirks.c +++ b/futility/updater_quirks.c @@ -58,7 +58,7 @@ static int reload_firmware_image(const char *file_path, struct firmware_image *i const char *programmer = image->programmer; free_firmware_image(image); image->programmer = programmer; - return load_firmware_image(image, file_path); + return load_firmware_image(image, file_path, NULL); } /* diff --git a/tests/futility/test_update.sh b/tests/futility/test_update.sh index b7f0e738..cd1bcb61 100755 --- a/tests/futility/test_update.sh +++ b/tests/futility/test_update.sh @@ -282,6 +282,13 @@ test_update "Full update (--quirks min_platform_version)" \ --quirks min_platform_version=3 \ -i "${TO_IMAGE}" --wp=0 --sys_props 0,0x10001,1,3 +mkdir -p "${TMP}.archive" +cp -f "${TO_IMAGE}" "${TMP}.archive/image_in_archive" +test_update "Full update (--archive)" \ + "${FROM_IMAGE}" "${TMP}.expected.full" \ + -a "${TMP}.archive" \ + -i "image_in_archive" --wp=0 --sys_props 0,0x10001,1,3 + # Test special programmer if type flashrom >/dev/null 2>&1; then echo "TEST: Full update (dummy programmer)" -- cgit v1.2.1