qemu and build: review fixes across the rig driver, boot script, and fetch helpers

Bounded waits and validated arguments in run.sh and ui-drive.sh, a seeded
settings directory and root-only staged rootfs permissions with their own
tests, qmp.py and imgtools.py hardening, the fetch scripts checking what they
download, and ASCII typography throughout. Each fix carries its test under
qemu/tests or tests/.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01N3G6m9Aw5RyVY4ZowtKzEj
This commit is contained in:
Noah
2026-09-09 19:17:54 -06:00
co-authored by Claude Fable 5.1
parent bda6c6c633
commit 2b6e8a2098
24 changed files with 1823 additions and 137 deletions
+28 -12
View File
@@ -114,10 +114,34 @@ reap() {
done
kill -KILL "$p" 2>/dev/null || true
}
# Poll for PATH to appear, checking every 0.1s for up to 5 seconds -- the
# rs.sock/rs.pty/rs.ctl handshake budget below, now set in one place instead
# of three copies that could drift out of step with each other. When PID is
# given, also stop the moment PID has died: a process that's already gone
# will never create the path, so there is no reason to spend the rest of the
# budget waiting on it. The exit status carries no verdict -- each call site
# still makes its own existence (and, for rs.ctl, liveness) check right after
# this returns, exactly as it did before the loop was pulled out.
wait_for_path() {
local path="$1" pid="${2:-}" _i
for _i in $(seq 1 50); do
[ -e "$path" ] && return 0
if [ -n "$pid" ]; then
kill -0 "$pid" 2>/dev/null || return 0
fi
sleep 0.1
done
return 0
}
cleanup() {
for p in $SIM_PIDS; do reap "$p"; done
if [ -n "$QEMU_PID" ]; then
python3 "$HERE/qmp.py" "$WORK/qmp.sock" quit 2>/dev/null || true
# `timeout` here is a second, independent bound on top of qmp.py's own
# QMP_TIMEOUT_S socket timeout: whichever one it is that stalls, this
# call must not itself keep cleanup() from reaching reap "$QEMU_PID"
# below -- the one thing meant to guarantee a wedged qemu-system-arm
# cannot outlive this script.
timeout -k 5 25 python3 "$HERE/qmp.py" "$WORK/qmp.sock" quit 2>/dev/null || true
sleep 1
reap "$QEMU_PID"
fi
@@ -173,7 +197,7 @@ if [ -n "$RS485_DEVICES" ]; then
command -v socat >/dev/null || { echo "FATAL: --rs485-devices needs socat" >&2; exit 1; }
[ -n "${FLARE_EDGE:-}" ] && [ -f "$FLARE_EDGE/tools/modbus-sim/mbsim.py" ] || {
echo "FATAL: --rs485-devices needs FLARE_EDGE to point at a flare-edge checkout (mbsim.py)" >&2; exit 1; }
for _i in $(seq 1 50); do [ -S "$WORK/rs.sock" ] && break; sleep 0.1; done
wait_for_path "$WORK/rs.sock"
[ -S "$WORK/rs.sock" ] || {
echo "FATAL: rs485 bus socket never appeared at $WORK/rs.sock (qemu's --rs485 chardev never came up)" >&2
tail -25 "$WORK/console.log" >&2
@@ -186,11 +210,7 @@ if [ -n "$RS485_DEVICES" ]; then
# poll budget: a socat that never links the pty is usually already gone
# (bad UNIX-CONNECT target, no pty node available), and kill -0 catches
# that in one tick instead of five seconds.
for _i in $(seq 1 50); do
[ -e "$WORK/rs.pty" ] && break
kill -0 "$socat_pid" 2>/dev/null || break
sleep 0.1
done
wait_for_path "$WORK/rs.pty" "$socat_pid"
[ -e "$WORK/rs.pty" ] || {
echo "FATAL: rs485 socat never created rs.pty (see $WORK/socat.log)" >&2
cat "$WORK/socat.log" >&2
@@ -204,11 +224,7 @@ if [ -n "$RS485_DEVICES" ]; then
python3 "$FLARE_EDGE/tools/modbus-sim/mbsim.py" --port "$WORK/rs.pty" --control "$WORK/rs.ctl" "${dev_args[@]}" > "$WORK/mbsim.log" 2>&1 &
mbsim_pid=$!
SIM_PIDS="$SIM_PIDS $mbsim_pid"
for _i in $(seq 1 50); do
[ -S "$WORK/rs.ctl" ] && break
kill -0 "$mbsim_pid" 2>/dev/null || break
sleep 0.1
done
wait_for_path "$WORK/rs.ctl" "$mbsim_pid"
{ [ -S "$WORK/rs.ctl" ] && kill -0 "$mbsim_pid" 2>/dev/null; } || {
echo "FATAL: rs485 simulator (mbsim.py) never came up (see $WORK/mbsim.log)" >&2
cat "$WORK/mbsim.log" >&2