qemu: full OTA apply scenario — write rootfs_b, flip AvbABData, boot it
Closes the loop every prior harness stopped short of, entirely off-hardware: the real flared (WARDEN_HARD_RESET-gated build) pulls a real signed tier-1 .wfw whose payload is a BOOTABLE rootfs stamped 0.0.2, verifies it, writes /dev/block/by-name/rootfs_b inside disk.img, and flips the AvbABData (slot B: priority 15, 3 tries, unsuccessful — the exact pre-first-boot arming state, round-tripped through a portal check-in). The harness then boots slot _b and asserts the applied version + marker are what runs. OTA-APPLY-PASS verified end to end. - mkimage: the misc partition now carries REAL provisioned AvbABData (bytes mirror flare-edge's provisioning defaults) — slotctl fail-closes on bad AB magic before writing, which a zeroed misc tripped. - run.sh --allow-apply / cmdline warden.fwapply: per-boot opt-in that makes stage-2 init export WARDEN_FW_ALLOW_APPLY=1; never the default. - stage-2 init also exports WARDEN_HARD_RESET=0 (the CRU poke is fatal on virt, same class as the HPMCU probe); the harness performs the reboot. - ADR-0006 boundary documented in the scenario and README: BCB slot CHOICE and the physical reset remain emulated. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018HUayid7W5w7jBdb9Rrj1K
This commit is contained in:
co-authored by
Claude Fable 5
parent
ff62991e8d
commit
d63c2117c1
@@ -79,6 +79,15 @@ stage-2 init when present.
|
|||||||
(a 200 ms hold — an instantaneous press+release lands inside one LVGL poll
|
(a 200 ms hold — an instantaneous press+release lands inside one LVGL poll
|
||||||
and never clicks), and asserts the frame changed. `qmp.py` is the tiny QMP
|
and never clicks), and asserts the frame changed. `qmp.py` is the tiny QMP
|
||||||
client.
|
client.
|
||||||
|
- `ota-apply.sh <zImage>` (needs `FLARE_EDGE`) — the FULL apply loop the
|
||||||
|
portal scenario stops short of: a real signed tier-1 `.wfw` whose payload
|
||||||
|
is a bootable rootfs is pulled, verified, WRITTEN to rootfs_b
|
||||||
|
(`run.sh --allow-apply` gates it per boot), the AvbABData in `misc` is
|
||||||
|
flipped (mkimage provisions real A/B metadata), and the harness reboots
|
||||||
|
slot `_b` and asserts the applied version is running. The BCB slot CHOICE
|
||||||
|
and the physical reset stay emulated by the harness (ADR-0006 boundary);
|
||||||
|
the VM exports `WARDEN_HARD_RESET=0` so flared's post-apply reset surfaces
|
||||||
|
as a clean reported error instead of a /dev/mem fault.
|
||||||
- Watchdog: `run.sh --watchdog`, arm `/dev/watchdog` in the guest, don't pet —
|
- Watchdog: `run.sh --watchdog`, arm `/dev/watchdog` in the guest, don't pet —
|
||||||
the VM resets ~30 s later (verified). Do NOT combine with a flared payload
|
the VM resets ~30 s later (verified). Do NOT combine with a flared payload
|
||||||
expecting survival: flared pets only while the UI heartbeat is fresh.
|
expecting survival: flared pets only while the UI heartbeat is fresh.
|
||||||
|
|||||||
+25
-1
@@ -96,10 +96,15 @@ place_partition() {
|
|||||||
rootfs_a|rootfs_b) stage="$ROOT" ;;
|
rootfs_a|rootfs_b) stage="$ROOT" ;;
|
||||||
userdata) stage="$UDATA" ;;
|
userdata) stage="$UDATA" ;;
|
||||||
oem_a|oem_b) stage="$SCRATCH/empty" ;;
|
oem_a|oem_b) stage="$SCRATCH/empty" ;;
|
||||||
|
# misc carries REAL AvbABData (byte 2048): flared's slotctl fail-closes
|
||||||
|
# on a bad magic before any OTA write, so a zeroed misc blocks apply
|
||||||
|
# scenarios. Bytes mirror flare-edge tools/mk-misc.py provisioning
|
||||||
|
# defaults (A: prio 15 successful, B: prio 14 successful, CRC32-BE).
|
||||||
|
misc) stage="__misc__" ;;
|
||||||
# Boot-chain partitions the VM never reads: present at the right offsets,
|
# Boot-chain partitions the VM never reads: present at the right offsets,
|
||||||
# left zeroed. Enumerated (not a wildcard) so a typo'd name in
|
# left zeroed. Enumerated (not a wildcard) so a typo'd name in
|
||||||
# blkdevparts.conf fails HERE, not as a confusing mount error at boot.
|
# blkdevparts.conf fails HERE, not as a confusing mount error at boot.
|
||||||
env|idblock|uboot|misc|boot_a|boot_b|recovery) stage="" ;;
|
env|idblock|uboot|boot_a|boot_b|recovery) stage="" ;;
|
||||||
*) echo "FATAL: unknown partition name '$name' in blkdevparts.conf" >&2; exit 1 ;;
|
*) echo "FATAL: unknown partition name '$name' in blkdevparts.conf" >&2; exit 1 ;;
|
||||||
esac
|
esac
|
||||||
# dd in 4K blocks — every offset in the canonical layout is 4K-aligned;
|
# dd in 4K blocks — every offset in the canonical layout is 4K-aligned;
|
||||||
@@ -112,6 +117,25 @@ place_partition() {
|
|||||||
[ $((off + size)) -gt "$DISK_END" ] && DISK_END=$((off + size))
|
[ $((off + size)) -gt "$DISK_END" ] && DISK_END=$((off + size))
|
||||||
[ -z "$stage" ] && return 0
|
[ -z "$stage" ] && return 0
|
||||||
local img="$SCRATCH/$name.img"
|
local img="$SCRATCH/$name.img"
|
||||||
|
if [ "$stage" = "__misc__" ]; then
|
||||||
|
python3 - "$img" "$size" <<'PYMISC'
|
||||||
|
import struct, sys, zlib
|
||||||
|
img, size = sys.argv[1], int(sys.argv[2])
|
||||||
|
s = bytearray(28)
|
||||||
|
s[0:4] = b"\0AB0" # AB_MAGIC
|
||||||
|
s[4] = 1 # major
|
||||||
|
s[8:12] = bytes([15, 0, 1, 0]) # slot A: priority, tries, successful
|
||||||
|
s[12:16] = bytes([14, 0, 1, 0]) # slot B
|
||||||
|
meta = bytes(s) + struct.pack(">I", zlib.crc32(bytes(s)) & 0xFFFFFFFF)
|
||||||
|
buf = bytearray(size)
|
||||||
|
buf[2048:2048 + len(meta)] = meta
|
||||||
|
open(img, "wb").write(buf)
|
||||||
|
PYMISC
|
||||||
|
dd if="$img" of="$DISK" bs=4096 seek=$((off / 4096)) \
|
||||||
|
conv=notrunc,sparse status=none
|
||||||
|
qemu_log " $name: AvbABData provisioned @ +2048"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
mkfs_part "$stage" "$size" "$img"
|
mkfs_part "$stage" "$size" "$img"
|
||||||
dd if="$img" of="$DISK" bs=4096 seek=$((off / 4096)) \
|
dd if="$img" of="$DISK" bs=4096 seek=$((off / 4096)) \
|
||||||
conv=notrunc,sparse status=none
|
conv=notrunc,sparse status=none
|
||||||
|
|||||||
@@ -70,6 +70,16 @@ export WARDEN_FLARE_INSECURE=1
|
|||||||
# here, and flared's /dev/mem poke dies with an external abort (SIGBUS). The
|
# here, and flared's /dev/mem poke dies with an external abort (SIGBUS). The
|
||||||
# SCR1 supervisor state machine is modeled in sim/src/hpmcu.rs instead.
|
# SCR1 supervisor state machine is modeled in sim/src/hpmcu.rs instead.
|
||||||
export WARDEN_HPMCU=0
|
export WARDEN_HPMCU=0
|
||||||
|
# Same class: the CRU reset ladder's /dev/mem poke is fatal on virt. A
|
||||||
|
# post-apply "reboot" surfaces as a clean flared error; the scenario harness
|
||||||
|
# performs the actual reboot into the applied slot.
|
||||||
|
export WARDEN_HARD_RESET=0
|
||||||
|
# OTA apply is opt-in per boot (run.sh --allow-apply): writing rootfs_b is
|
||||||
|
# safe inside disk.img but must never be the default posture.
|
||||||
|
if grep -qw warden.fwapply /proc/cmdline; then
|
||||||
|
export WARDEN_FW_ALLOW_APPLY=1
|
||||||
|
echo "init: OTA APPLY ENABLED (warden.fwapply)"
|
||||||
|
fi
|
||||||
for d in /usr/bin/warden-flared /usr/bin/warden-modbus; do
|
for d in /usr/bin/warden-flared /usr/bin/warden-modbus; do
|
||||||
if [ -x "$d" ]; then
|
if [ -x "$d" ]; then
|
||||||
name="$(basename "$d")"
|
name="$(basename "$d")"
|
||||||
|
|||||||
+5
-1
@@ -22,6 +22,8 @@
|
|||||||
# --http-port N hostfwd 127.0.0.1:N -> guest :80 (default 8080; 0 disables)
|
# --http-port N hostfwd 127.0.0.1:N -> guest :80 (default 8080; 0 disables)
|
||||||
# --api-port N hostfwd 127.0.0.1:N -> guest :28443 (default 28443; 0 disables)
|
# --api-port N hostfwd 127.0.0.1:N -> guest :28443 (default 28443; 0 disables)
|
||||||
# --shell interactive shell in the guest instead of daemon hold
|
# --shell interactive shell in the guest instead of daemon hold
|
||||||
|
# --allow-apply let flared ACTUALLY apply OTA firmware (writes rootfs_b
|
||||||
|
# inside disk.img — safe in the VM, never the default)
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
QEMU_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
QEMU_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
@@ -30,7 +32,7 @@ QEMU_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|||||||
OUT="${OUT:-$QEMU_DIR/out}"
|
OUT="${OUT:-$QEMU_DIR/out}"
|
||||||
|
|
||||||
KERNEL="" INITRD="$OUT/initramfs.cpio.gz" DISK="" NO_DISK=0 SLOT="_a"
|
KERNEL="" INITRD="$OUT/initramfs.cpio.gz" DISK="" NO_DISK=0 SLOT="_a"
|
||||||
RTC="" RS485="" WATCHDOG=0 QMP="" DISPLAY_MODE="off" SHELL_FLAG=0
|
RTC="" RS485="" WATCHDOG=0 QMP="" DISPLAY_MODE="off" SHELL_FLAG=0 ALLOW_APPLY=0
|
||||||
SSH_PORT=2222 HTTP_PORT=8080 API_PORT=28443
|
SSH_PORT=2222 HTTP_PORT=8080 API_PORT=28443
|
||||||
EXTRA=()
|
EXTRA=()
|
||||||
|
|
||||||
@@ -50,6 +52,7 @@ while [ $# -gt 0 ]; do
|
|||||||
--http-port) HTTP_PORT="${2:?}"; shift 2 ;;
|
--http-port) HTTP_PORT="${2:?}"; shift 2 ;;
|
||||||
--api-port) API_PORT="${2:?}"; shift 2 ;;
|
--api-port) API_PORT="${2:?}"; shift 2 ;;
|
||||||
--shell) SHELL_FLAG=1; shift ;;
|
--shell) SHELL_FLAG=1; shift ;;
|
||||||
|
--allow-apply) ALLOW_APPLY=1; shift ;;
|
||||||
--) shift; EXTRA=("$@"); break ;;
|
--) shift; EXTRA=("$@"); break ;;
|
||||||
*) echo "FATAL: unknown argument '$1' (see header of $0)" >&2; exit 1 ;;
|
*) echo "FATAL: unknown argument '$1' (see header of $0)" >&2; exit 1 ;;
|
||||||
esac
|
esac
|
||||||
@@ -99,6 +102,7 @@ if [ -n "$DISK" ] && [ "$NO_DISK" -eq 0 ]; then
|
|||||||
-device "virtio-blk-device,drive=vd0" )
|
-device "virtio-blk-device,drive=vd0" )
|
||||||
fi
|
fi
|
||||||
[ "$SHELL_FLAG" -eq 1 ] && APPEND="$APPEND warden.shell"
|
[ "$SHELL_FLAG" -eq 1 ] && APPEND="$APPEND warden.shell"
|
||||||
|
[ "$ALLOW_APPLY" -eq 1 ] && APPEND="$APPEND warden.fwapply"
|
||||||
[ -n "$RTC" ] && ARGS+=( -rtc "base=$RTC" )
|
[ -n "$RTC" ] && ARGS+=( -rtc "base=$RTC" )
|
||||||
[ "$WATCHDOG" -eq 1 ] && ARGS+=( -device i6300esb -action watchdog=reset )
|
[ "$WATCHDOG" -eq 1 ] && ARGS+=( -device i6300esb -action watchdog=reset )
|
||||||
[ -n "$RS485" ] && ARGS+=( -chardev "socket,id=rs485,path=$RS485,server=on,wait=off"
|
[ -n "$RS485" ] && ARGS+=( -chardev "socket,id=rs485,path=$RS485,server=on,wait=off"
|
||||||
|
|||||||
Executable
+165
@@ -0,0 +1,165 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# FULL OTA apply scenario — the loop the desk e2e stops short of: the real
|
||||||
|
# flared inside the VM downloads a real signed tier-1 .wfw whose payload is a
|
||||||
|
# bootable rootfs, verifies it, and ACTUALLY WRITES rootfs_b (safe: it is a
|
||||||
|
# region inside disk.img); the harness then reboots into slot _b and asserts
|
||||||
|
# the applied firmware version is running.
|
||||||
|
#
|
||||||
|
# Documented emulation gaps (ADR-0006): the BCB slot CHOICE and the physical
|
||||||
|
# reset are performed by the harness (cmdline slot + a fresh qemu boot), not
|
||||||
|
# by U-Boot/CRU — those stay bench territory.
|
||||||
|
#
|
||||||
|
# FAILS CLOSED on missing prerequisites.
|
||||||
|
#
|
||||||
|
# Usage: ota-apply.sh <zImage-virt>
|
||||||
|
# Env: FLARE_EDGE path to a flare-edge checkout (mock portal, mk-wfw, dev key)
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # qemu/tests/
|
||||||
|
QDIR="$(cd "$HERE/.." && pwd)" # qemu/
|
||||||
|
# mkfs.ext4 lives in sbin (not on user PATH on Debian).
|
||||||
|
PATH="$PATH:/usr/sbin:/sbin"
|
||||||
|
|
||||||
|
ZIMAGE="${1:-}"
|
||||||
|
if [ -z "$ZIMAGE" ] || [ ! -f "$ZIMAGE" ]; then
|
||||||
|
echo "FATAL: usage: $0 <zImage> — the virt.fragment kernel variant" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ -z "${FLARE_EDGE:-}" ] || [ ! -f "$FLARE_EDGE/tools/mock-flare-portal.py" ]; then
|
||||||
|
echo "FATAL: FLARE_EDGE must point at a flare-edge checkout" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
[ -x "$QDIR/payload/warden-flared" ] || {
|
||||||
|
echo "FATAL: no qemu/payload/warden-flared (needs the WARDEN_HARD_RESET-gated build)" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
command -v qemu-system-arm >/dev/null || {
|
||||||
|
echo "FATAL: qemu-system-arm not on PATH — see qemu/README.md" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
WORK="$(mktemp -d /tmp/wqo.XXXXXX)"
|
||||||
|
QEMU_PID="" MOCK_PID=""
|
||||||
|
cleanup() {
|
||||||
|
if [ -n "$QEMU_PID" ]; then kill "$QEMU_PID" 2>/dev/null || true; fi
|
||||||
|
if [ -n "$MOCK_PID" ]; then kill "$MOCK_PID" 2>/dev/null || true; fi
|
||||||
|
rm -rf "$WORK"
|
||||||
|
}
|
||||||
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
DEVICE_ID="$(python3 -c 'import uuid; print(uuid.uuid4())')"
|
||||||
|
API_KEY="$(python3 -c 'import secrets; print(secrets.token_hex(24))')"
|
||||||
|
PORT=$((20000 + RANDOM % 20000))
|
||||||
|
|
||||||
|
# 0. The offer's payload is a REAL bootable rootfs: the same skeleton the
|
||||||
|
# disk uses, stamped with the NEW version — booting it is the proof.
|
||||||
|
QEMU_DIR="$QDIR"
|
||||||
|
OUT="$QDIR/out"
|
||||||
|
# shellcheck source=qemu/lib.sh disable=SC1091
|
||||||
|
. "$QDIR/lib.sh"
|
||||||
|
qemu_get_busybox
|
||||||
|
qemu_stage_rootfs "$WORK/newroot"
|
||||||
|
printf '0.0.2\n' > "$WORK/newroot/etc/warden-firmware-version"
|
||||||
|
printf 'applied-via-ota\n' > "$WORK/newroot/etc/ota-marker"
|
||||||
|
truncate -s 64M "$WORK/rootfs-payload.img"
|
||||||
|
mkfs.ext4 -F -q -d "$WORK/newroot" "$WORK/rootfs-payload.img"
|
||||||
|
|
||||||
|
FW_SIGNING_KEY_FILE="$FLARE_EDGE/tools/testdata/fw-dev-key.seed" \
|
||||||
|
WARDEN_KERNEL_VERSION=6.18.46 WARDEN_BUILDROOT_VERSION=2025.02 \
|
||||||
|
WARDEN_UBOOT_VERSION=2017.09 \
|
||||||
|
bash "$FLARE_EDGE/tools/mk-wfw.sh" "$WORK/rootfs-payload.img" 1 0.0.2 "$WORK/offer.wfw"
|
||||||
|
|
||||||
|
# 1. mock portal offering it.
|
||||||
|
python3 "$FLARE_EDGE/tools/mock-flare-portal.py" \
|
||||||
|
--port "$PORT" --device "$DEVICE_ID:$API_KEY" \
|
||||||
|
--wfw "$WORK/offer.wfw" > "$WORK/mock.log" 2>&1 &
|
||||||
|
MOCK_PID=$!
|
||||||
|
mock_ready=0
|
||||||
|
for _ in $(seq 1 50); do
|
||||||
|
curl -so /dev/null "http://127.0.0.1:$PORT/" && { mock_ready=1; break; }
|
||||||
|
kill -0 "$MOCK_PID" 2>/dev/null || { echo "FATAL: mock portal died:" >&2; cat "$WORK/mock.log" >&2; exit 1; }
|
||||||
|
sleep 0.2
|
||||||
|
done
|
||||||
|
[ "$mock_ready" = 1 ] || { echo "FATAL: mock never answered on :$PORT" >&2; exit 1; }
|
||||||
|
echo "== mock portal on :$PORT offering 0.0.2 (payload: bootable rootfs, 64M)"
|
||||||
|
|
||||||
|
# 2. image at version 0.0.1, enrolment seeded.
|
||||||
|
bash "$QDIR/mkinitramfs.sh"
|
||||||
|
bash "$QDIR/mkimage.sh" \
|
||||||
|
--portal-url "http://10.0.2.2:$PORT" \
|
||||||
|
--state "flare.device_id=$DEVICE_ID" \
|
||||||
|
--state "flare.api_key=$API_KEY" \
|
||||||
|
--state "flare.site=qemu-devsim" \
|
||||||
|
--fw-version 0.0.1
|
||||||
|
|
||||||
|
# 3. boot slot _a WITH APPLY ENABLED; flared should pull, verify, write
|
||||||
|
# rootfs_b, and surface the (gated) reboot attempt.
|
||||||
|
QEMU_PID=""
|
||||||
|
for _attempt in 1 2 3; do
|
||||||
|
VMBASE=$((20000 + RANDOM % 20000))
|
||||||
|
: > "$WORK/console.log"
|
||||||
|
bash "$QDIR/run.sh" --kernel "$ZIMAGE" --allow-apply \
|
||||||
|
--ssh-port "$VMBASE" --http-port $((VMBASE + 1)) --api-port $((VMBASE + 2)) \
|
||||||
|
> "$WORK/console.log" 2>&1 &
|
||||||
|
QEMU_PID=$!
|
||||||
|
sleep 3
|
||||||
|
kill -0 "$QEMU_PID" 2>/dev/null && break
|
||||||
|
if grep -aq 'Could not set up host forwarding' "$WORK/console.log"; then
|
||||||
|
echo "== hostfwd port collision on base $VMBASE — retrying"
|
||||||
|
QEMU_PID=""
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
echo "FATAL: VM died at launch:" >&2; tail -20 "$WORK/console.log" >&2; exit 1
|
||||||
|
done
|
||||||
|
if [ -z "$QEMU_PID" ] || ! kill -0 "$QEMU_PID" 2>/dev/null; then
|
||||||
|
echo "FATAL: could not launch the VM after 3 port attempts" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 4. wait for the apply to conclude. flared logs to its in-guest file, not
|
||||||
|
# the console — but the next check-in REPORTS the outcome to the portal:
|
||||||
|
# detail "hard reset failed after slot flip" is the exact post-apply state
|
||||||
|
# under the gated reset (write done, AvbABData flipped, reboot refused).
|
||||||
|
deadline=$((SECONDS + 420))
|
||||||
|
staged=0
|
||||||
|
while [ $SECONDS -lt $deadline ]; do
|
||||||
|
grep -aq 'hard reset failed after slot flip' "$WORK/mock.log" && { staged=1; break; }
|
||||||
|
grep -aq 'WARDEN-QEMU-MOUNT-FAILED' "$WORK/console.log" && {
|
||||||
|
echo "FATAL: guest mount failed" >&2; exit 1; }
|
||||||
|
kill -0 "$QEMU_PID" 2>/dev/null || {
|
||||||
|
echo "FATAL: VM exited early" >&2; tail -30 "$WORK/console.log" >&2; exit 1; }
|
||||||
|
sleep 3
|
||||||
|
done
|
||||||
|
[ "$staged" = 1 ] || {
|
||||||
|
echo "FATAL: apply never reached the post-flip state within 420s; tails:" >&2
|
||||||
|
tail -20 "$WORK/console.log" >&2
|
||||||
|
tail -10 "$WORK/mock.log" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
grep -aq "GET /api/v1/devices/$DEVICE_ID/firmware/assets/.* -> 200" "$WORK/mock.log" || {
|
||||||
|
echo "FATAL: staged without a portal asset download?!" >&2
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
echo "== apply staged (asset downloaded, rootfs_b written, reset gated) — rebooting into _b"
|
||||||
|
kill "$QEMU_PID" 2>/dev/null || true
|
||||||
|
wait "$QEMU_PID" 2>/dev/null || true
|
||||||
|
QEMU_PID=""
|
||||||
|
|
||||||
|
# 5. the harness performs the "reboot": boot slot _b, assert the OTA'd rootfs
|
||||||
|
# is what runs.
|
||||||
|
{ sleep 40; printf 'cat /etc/warden-firmware-version /etc/ota-marker\n'; sleep 3; printf 'poweroff -f\n'; sleep 8; } | \
|
||||||
|
timeout 180 bash "$QDIR/run.sh" --kernel "$ZIMAGE" --slot _b --shell \
|
||||||
|
--ssh-port 0 --http-port 0 --api-port 0 \
|
||||||
|
> "$WORK/boot-b.log" 2>&1 || true
|
||||||
|
grep -aq 'WARDEN-QEMU-ROOTFS-OK slot=_b' "$WORK/boot-b.log" || {
|
||||||
|
echo "FATAL: slot _b did not boot; tail:" >&2; tail -25 "$WORK/boot-b.log" >&2; exit 1
|
||||||
|
}
|
||||||
|
grep -aq '^0.0.2' "$WORK/boot-b.log" || {
|
||||||
|
echo "FATAL: _b is not running the applied 0.0.2 firmware" >&2
|
||||||
|
grep -a 'warden-firmware-version' -A2 "$WORK/boot-b.log" >&2 || true
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
grep -aq 'applied-via-ota' "$WORK/boot-b.log" || {
|
||||||
|
echo "FATAL: OTA marker missing on _b" >&2; exit 1
|
||||||
|
}
|
||||||
|
echo "OTA-APPLY-PASS: 0.0.1 -> 0.0.2 applied over the air and booted from slot _b"
|
||||||
Reference in New Issue
Block a user