futility: cmd_update: Implement updater logic "FULL UPDATE".

The logic is same as --mode=factory or --mode=recovery,--wp=0 in legacy
firmware updater.

BUG=chromium:875551
TEST=make futil; futility update -i IMAGE
     tests/futility/run_test_scripts.sh $(pwd)/build/futility
BRANCH=None

Change-Id: Ifbfc4fb76f954483e779c8b508377d07561b67da
Signed-off-by: Hung-Te Lin <hungte@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/1183651
Reviewed-by: Randall Spangler <rspangler@chromium.org>
diff --git a/futility/cmd_update.c b/futility/cmd_update.c
index a3ca288..a6727f0 100644
--- a/futility/cmd_update.c
+++ b/futility/cmd_update.c
@@ -270,10 +270,141 @@
 	memset(image, 0, sizeof(*image));
 }
 
+/*
+ * Emulates writing to firmware.
+ * Returns 0 if success, non-zero if error.
+ */
+static int emulate_write_firmware(const char *filename,
+				  const struct firmware_image *image,
+				  const char *section_name)
+{
+	struct firmware_image to_image = {0};
+	struct firmware_section from, to;
+	int errorcnt = 0;
+
+	from.data = image->data;
+	from.size = image->size;
+
+	if (load_image(filename, &to_image)) {
+		Error("%s: Cannot load image from %s.\n", __FUNCTION__,
+		      filename);
+		return -1;
+	}
+
+	if (section_name) {
+		find_firmware_section(&from, image, section_name);
+		if (!from.data) {
+			Error("%s: No section %s in source image %s.\n",
+			      __FUNCTION__, section_name, image->file_name);
+			errorcnt++;
+		}
+		find_firmware_section(&to, &to_image, section_name);
+		if (!to.data) {
+			Error("%s: No section %s in destination image %s.\n",
+			      __FUNCTION__, section_name, filename);
+			errorcnt++;
+		}
+	} else if (image->size != to_image.size) {
+		Error("%s: Image size is different (%s:%d != %s:%d)\n",
+		      __FUNCTION__, image->file_name, image->size,
+		      to_image.file_name, to_image.size);
+		errorcnt++;
+	} else {
+		to.data = to_image.data;
+		to.size = to_image.size;
+	}
+
+	if (!errorcnt) {
+		size_t to_write = Min(to.size, from.size);
+
+		assert(from.data && to.data);
+		Debug("%s: Writing %d bytes\n", __FUNCTION__, to_write);
+		memcpy(to.data, from.data, to_write);
+	}
+
+	if (!errorcnt && vb2_write_file(
+			filename, to_image.data, to_image.size)) {
+		Error("%s: Failed writing to file: %s\n", __FUNCTION__,
+		      filename);
+		errorcnt++;
+	}
+
+	free_image(&to_image);
+	return errorcnt;
+}
+
+/*
+ * Writes a section from given firmware image to system firmware.
+ * If section_name is NULL, write whole image.
+ * Returns 0 if success, non-zero if error.
+ */
+static int write_firmware(struct updater_config *cfg,
+			  const struct firmware_image *image,
+			  const char *section_name)
+{
+	/* TODO(hungte) replace by mkstemp */
+	const char *tmp_file = "/tmp/.fwupdate.write";
+	const char *programmer = cfg->emulate ? image->emulation :
+			image->programmer;
+
+	if (cfg->emulate) {
+		printf("%s: (emulation) %s %s from %s to %s.\n",
+		       __FUNCTION__,
+		       image->emulation ? "Writing" : "Skipped writing",
+		       section_name ? section_name : "whole image",
+		       image->file_name, programmer);
+
+		if (!image->emulation)
+			return 0;
+
+		/*
+		 * TODO(hungte): Extract the real target from image->emulation,
+		 * and allow to emulate writing with flashrom.
+		 */
+		return emulate_write_firmware(
+				cfg->image_current.file_name, image,
+				section_name);
+
+	}
+	if (vb2_write_file(tmp_file, image->data, image->size) != VB2_SUCCESS) {
+		Error("%s: Cannot write temporary file for output: %s\n",
+		      __FUNCTION__, tmp_file);
+		return -1;
+	}
+	return host_flashrom(FLASHROM_WRITE, tmp_file, programmer, 1,
+			     section_name);
+}
+
+/*
+ * Write a section from given firmware image to system firmware if possible.
+ * If section_name is NULL, write whole image.  If the image has no data or if
+ * the section does not exist, ignore and return success.
+ * Returns 0 if success, non-zero if error.
+ */
+static int write_optional_firmware(struct updater_config *cfg,
+				   const struct firmware_image *image,
+				   const char *section_name)
+{
+	if (!image->data) {
+		Debug("%s: No data in <%s> image.\n", __FUNCTION__,
+		      image->programmer);
+		return 0;
+	}
+	if (section_name && !firmware_section_exists(image, section_name)) {
+		Debug("%s: Image %s<%s> does not have section %s.\n",
+		      __FUNCTION__, image->file_name, image->programmer,
+		      section_name);
+		return 0;
+	}
+
+	return write_firmware(cfg, image, section_name);
+}
+
 enum updater_error_codes {
 	UPDATE_ERR_DONE,
 	UPDATE_ERR_NO_IMAGE,
 	UPDATE_ERR_SYSTEM_IMAGE,
+	UPDATE_ERR_WRITE_FIRMWARE,
 	UPDATE_ERR_UNKNOWN,
 };
 
@@ -281,10 +412,32 @@
 	[UPDATE_ERR_DONE] = "Done (no error)",
 	[UPDATE_ERR_NO_IMAGE] = "No image to update; try specify with -i.",
 	[UPDATE_ERR_SYSTEM_IMAGE] = "Cannot load system active firmware.",
+	[UPDATE_ERR_WRITE_FIRMWARE] = "Failed writing firmware.",
 	[UPDATE_ERR_UNKNOWN] = "Unknown error.",
 };
 
 /*
+ * The main updater for "Full update".
+ * This was also known as "--mode=factory" or "--mode=recovery, --wp=0" in
+ * legacy updater.
+ * Returns UPDATE_ERR_DONE if success, otherwise error.
+ */
+static enum updater_error_codes update_whole_firmware(
+		struct updater_config *cfg,
+		struct firmware_image *image_to)
+{
+	printf(">> FULL UPDATE: Updating whole firmware image(s), RO+RW.\n");
+
+	/* FMAP may be different so we should just update all. */
+	if (write_firmware(cfg, image_to, NULL) ||
+	    write_optional_firmware(cfg, &cfg->ec_image, NULL) ||
+	    write_optional_firmware(cfg, &cfg->pd_image, NULL))
+		return UPDATE_ERR_WRITE_FIRMWARE;
+
+	return UPDATE_ERR_DONE;
+}
+
+/*
  * The main updater to update system firmware using the configuration parameter.
  * Returns UPDATE_ERR_DONE if success, otherwise failure.
  */
@@ -312,7 +465,7 @@
 	       image_from->file_name, image_from->ro_version,
 	       image_from->rw_version_a, image_from->rw_version_b);
 
-	return UPDATE_ERR_DONE;
+	return update_whole_firmware(cfg, image_to);
 }
 
 /*
diff --git a/tests/futility/test_update.sh b/tests/futility/test_update.sh
index c130a56..7e2a142 100755
--- a/tests/futility/test_update.sh
+++ b/tests/futility/test_update.sh
@@ -6,25 +6,37 @@
 me=${0##*/}
 TMP="$me.tmp"
 
-# Include /usr/sbin for flahsrom(8)
-PATH=/usr/sbin:"${PATH}"
-
 # Test data files
 LINK_BIOS="${SCRIPTDIR}/data/bios_link_mp.bin"
 PEPPY_BIOS="${SCRIPTDIR}/data/bios_peppy_mp.bin"
-LINK_VERSION="Google_Link.2695.1.133"
-PEPPY_VERSION="Google_Peppy.4389.89.0"
 
 # Work in scratch directory
 cd "$OUTDIR"
 set -o pipefail
 
-# Prepare temporary files.
-cp -f "${LINK_BIOS}" "${TMP}.emu"
+# In all the test scenario, we want to test "updating from PEPPY to LINK".
+TO_IMAGE=${TMP}.src.link
+FROM_IMAGE=${TMP}.src.peppy
+cp -f ${LINK_BIOS} ${TO_IMAGE}
+cp -f ${PEPPY_BIOS} ${FROM_IMAGE}
 
-# Test command execution.
-versions="$("${FUTILITY}" update -i "${PEPPY_BIOS}" --emulate "${TMP}.emu" |
-	    sed -n 's/.*(//; s/).*//p')"
-test "${versions}" = \
-"RO:${PEPPY_VERSION}, RW/A:${PEPPY_VERSION}, RW/B:${PEPPY_VERSION}
-RO:${LINK_VERSION}, RW/A:${LINK_VERSION}, RW/B:${LINK_VERSION}"
+cp -f "${TO_IMAGE}" "${TMP}.expected.full"
+
+test_update() {
+	local test_name="$1"
+	local emu_src="$2"
+	local expected="$3"
+	local error_msg="${expected#!}"
+	local msg
+
+	shift 3
+	cp -f "${emu_src}" "${TMP}.emu"
+	echo "*** Test Item: ${test_name}"
+	"${FUTILITY}" update --emulate "${TMP}.emu" "$@"
+	cmp "${TMP}.emu" "${expected}"
+}
+
+# Test Full update.
+test_update "Full update" \
+	"${FROM_IMAGE}" "${TMP}.expected.full" \
+	-i "${TO_IMAGE}"