summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHung-Te Lin <hungte@chromium.org>2018-10-01 17:03:42 +0800
committerchrome-bot <chrome-bot@chromium.org>2018-10-13 11:42:13 -0700
commit7db7a6db8f0719fe0fda8219c7111c7caf0b4546 (patch)
treeb0492521037e1082220794315d12f9089c201cfa
parent66381ae51d7da4a385b5898473d3ccaa8ee20c11 (diff)
downloadvboot-7db7a6db8f0719fe0fda8219c7111c7caf0b4546.tar.gz
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 <hungte@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/1253229 Reviewed-by: Randall Spangler <rspangler@chromium.org>
-rw-r--r--Makefile16
-rw-r--r--futility/cmd_update.c12
-rw-r--r--futility/updater.c48
-rw-r--r--futility/updater.h43
-rw-r--r--futility/updater_archive.c311
-rw-r--r--futility/updater_quirks.c2
-rwxr-xr-xtests/futility/test_update.sh7
7 files changed, 419 insertions, 20 deletions
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 <assert.h>
+#include <fts.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#ifdef HAVE_LIBZIP
+#include <zip.h>
+#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)"