futility: updater: support /firmware as a raw archive

For early bring up and the lab deployment, the developers need a simple
way to update the firmware directly from the build artifacts (either
from CPFE firmware archives or /build/$BOARD/firmware) before the
firmware is pinned and available via chromeos-firmwareupdate.

To simplify the process, we want the updater to understand the layout of
files in /firmware. This change supports that as "raw archive" so
developers can update by:

  # Verify what's available.
  futility update -a /build/$BOARD/firmware --manifest

  # Update the firmware for a specific model.
  futility update -a /build/$BOARD/firmware --model $MODEL

  # On DUT, detect the model and update the firmware.
  mkdir /tmp/firmware
  tar -xvf firmware.tbz -C /tmp/firmware
  futility update -a /tmp/firmware

BUG=b:230679721
TEST=make; run tests
BRANCH=None

Change-Id: I8a262f1d2ec916da62e49a53fd45bdec9fa0ad9b
Signed-off-by: Hung-Te Lin <hungte@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/vboot_reference/+/3611322
Reviewed-by: Sergey Frolov <sfrolov@google.com>
Reviewed-by: Mike Frysinger <vapier@chromium.org>
Reviewed-by: Julius Werner <jwerner@chromium.org>
diff --git a/futility/updater_archive.c b/futility/updater_archive.c
index 1d916db..65b448d 100644
--- a/futility/updater_archive.c
+++ b/futility/updater_archive.c
@@ -821,6 +821,48 @@
 	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 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);
+}
+
 /**
  * get_manifest_key() - Wrapper to get the firmware manifest key from crosid
  *
@@ -1009,6 +1051,9 @@
 	manifest.archive = archive;
 	manifest.default_model = -1;
 	archive_walk(archive, &manifest, manifest_scan_entries);
+	if (manifest.num == 0)
+		archive_walk(archive, &manifest, manifest_scan_raw_entries);
+
 	if (manifest.num == 0) {
 		const char *image_name = NULL;
 		struct firmware_image image = {0};