From 24ade38b0e093b8ede30a17cf0c762804d01d811 Mon Sep 17 00:00:00 2001 From: Tinic Uro Date: Thu, 1 Oct 2026 08:19:08 -0700 Subject: [PATCH 1/5] run-workbench.sh: let a run select and stage anxnet.device AMINETXDUO_SANA2_VENDOR used ${:-1}, so an empty value could never select anxnet.device; it is now ${-1}: unset means vendor, empty means anxnet. The A2065 vendor-driver injection is gated on a non-empty VENDOR so an anxnet run on that board does not stage Commodore's bytes. An anxnet run exports DRIVER_NAME/DIR/DEVICE/CARD before _sana2_names, as tools/classicwb.sh does, so the bytes are staged as anxnet.device and not under the board's vendor file name. The staged file's sha256 must equal the archive's anxnet.device whenever sana2_select chose anxnet; the run fails closed otherwise. The stale anxnet.device fixture no longer overwrites a staged anxnet driver: that file stands in as the existing driver, and only the "live file differs from the stale one" check is skipped for it. The generic CARD_SELECTED match accepts the drawer-install form AmiNetXDuo:Devs/Networks/. Co-Authored-By: Claude Opus 5.5 --- install/test/run-workbench.sh | 84 ++++++++++++++++++++++++++++++----- 1 file changed, 72 insertions(+), 12 deletions(-) diff --git a/install/test/run-workbench.sh b/install/test/run-workbench.sh index 5f6c9f12..3b09b2ba 100755 --- a/install/test/run-workbench.sh +++ b/install/test/run-workbench.sh @@ -410,15 +410,20 @@ fi # file this installer writes names is the driver that came with the card, so # that is what this run boots. The device check below is about the file # landing; everything after the power cycle is about the vendor driver working. -# AMINETXDUO_SANA2_VENDOR is set here for the same reason and can still be -# unset by a caller that wants the other one. -AMINETXDUO_SANA2_VENDOR="${AMINETXDUO_SANA2_VENDOR:-1}" +# AMINETXDUO_SANA2_VENDOR defaults to 1 for the same reason. Unset means 1; +# a caller that wants anxnet.device sets it EMPTY (AMINETXDUO_SANA2_VENDOR=), +# which is kept empty, and sana2_select then picks anxnet.device for every +# board tools/sana2-stage.sh maps onto it. +AMINETXDUO_SANA2_VENDOR="${AMINETXDUO_SANA2_VENDOR-1}" export AMINETXDUO_SANA2_VENDOR # The A2065 keeps its own variable and its own search: it is Commodore's, it # comes out of the OS sources rather than the asset store, and every existing -# caller of this script sets AMINETXDUO_A2065 and nothing else. -if [ "$BOARD" = a2065 ] && [ -z "${AMINETXDUO_SANA2_DRIVER:-}" ]; then +# caller of this script sets AMINETXDUO_A2065 and nothing else. Vendor runs +# only: on an anxnet run this path would win over SANA2_SEL_PATH below and +# stage Commodore's bytes for a run that selected anxnet.device. +if [ "$BOARD" = a2065 ] && [ -n "${AMINETXDUO_SANA2_VENDOR:-}" ] && + [ -z "${AMINETXDUO_SANA2_DRIVER:-}" ]; then A2065="${AMINETXDUO_A2065:-}" if [ -z "$A2065" ]; then for candidate in \ @@ -447,6 +452,19 @@ DRIVER_PATH="${AMINETXDUO_SANA2_DRIVER:-$SANA2_SEL_PATH}" # at a time, after saying it had found the file two lines earlier. AMINETXDUO_SANA2_DRIVER="$DRIVER_PATH" export AMINETXDUO_SANA2_DRIVER + +# anxnet.device needs its NAME handed back too, before _sana2_names, which +# otherwise names the staged file after the board (ariadne2 -> ariadne_ii.device) +# and stages our bytes under the vendor's name. Mirrors tools/classicwb.sh. +# Never on a vendor run: that would stage vendor bytes as anxnet.device. +if [ "$SANA2_SEL_SOURCE" = anxnet ]; then + AMINETXDUO_SANA2_DRIVER_NAME=anxnet.device + AMINETXDUO_SANA2_DIR=Networks + AMINETXDUO_SANA2_DEVICE=DEVS:Networks/anxnet.device + AMINETXDUO_SANA2_CARD="$SANA2_SEL_CARD" + export AMINETXDUO_SANA2_DRIVER_NAME AMINETXDUO_SANA2_DIR \ + AMINETXDUO_SANA2_DEVICE AMINETXDUO_SANA2_CARD +fi _sana2_names "$BOARD" # A MISSING DRIVER IS AN INGREDIENT, NOT A FAILURE, except in the manual-card @@ -1181,21 +1199,38 @@ startup_count() { # anxnet.device there first and prove the installer replaces it and keeps # the old one beside it as anxnet.device.old. # +# An anxnet run (any card, the A2065 included) has already staged the real +# anxnet.device at that name, and the stale text would overwrite the driver +# under test. That file is left alone and is itself the existing driver: +# the backup assertions still hold, and only "the live file is no longer the +# old one" cannot, because its bytes are the archive's by construction. +# # Written before the archive is unpacked, so nothing here can be confused with # something the unpack left lying about. DEVS_NETWORKS_BEFORE=absent STALE_DEVICE="" STALE_SUM="" +STALE_IS_STAGED=0 if [ -d "$HD/Devs/Networks" ]; then DEVS_NETWORKS_BEFORE=present STALE_DEVICE="$HD/Devs/Networks/anxnet.device" - printf 'not a driver: a stale anxnet.device left by an earlier install, %s\n' \ - "$TAG" > "$STALE_DEVICE" - chmod 644 "$STALE_DEVICE" + if [ -n "$STAGED_AT" ] && [ "$STAGED_AT" = "$STALE_DEVICE" ] && + [ -f "$STAGED_AT" ]; then + STALE_IS_STAGED=1 + else + printf 'not a driver: a stale anxnet.device left by an earlier install, %s\n' \ + "$TAG" > "$STALE_DEVICE" + chmod 644 "$STALE_DEVICE" + fi STALE_SUM=$(shasum "$STALE_DEVICE" | cut -d' ' -f1) fi -echo "==> DEVS:Networks before the install: $DEVS_NETWORKS_BEFORE" \ - "${STALE_DEVICE:+(a stale anxnet.device staged in it)}" +if [ "$STALE_IS_STAGED" = 1 ]; then + echo "==> DEVS:Networks before the install: $DEVS_NETWORKS_BEFORE" \ + "(the staged anxnet.device under test, no stale fixture)" +else + echo "==> DEVS:Networks before the install: $DEVS_NETWORKS_BEFORE" \ + "${STALE_DEVICE:+(a stale anxnet.device staged in it)}" +fi FOREIGN_STACK_BEFORE="" if [ "$DRAWER" = "1" ]; then @@ -1228,6 +1263,30 @@ SUPPLIED_DRIVERS="anxnet.device anxgenet.device anxwifipi.device" cp "$INSTALLER" "$UNPACK/AmiNetXDuo/Installer" chmod -R a+rx "$UNPACK" +# THE STAGED BYTES ARE THE ARCHIVE'S anxnet.device. Keyed on what sana2_select +# chose, not on AMINETXDUO_SANA2_DRIVER: that is the file that was copied, so +# comparing against it compares the staged file with itself. Fails closed: an +# anxnet run with nothing to compare against is not an anxnet run. +STAGED_SHA256="" +if [ "$SANA2_SEL_SOURCE" = anxnet ] && [ "$MANUAL_CARD" = 0 ] && + [ "$NO_CARD" = 0 ]; then + _anx_ref="$UNPACK/AmiNetXDuo/Devs/Networks/anxnet.device" + [ -n "$STAGED_AT" ] && [ -f "$STAGED_AT" ] || { + echo "!! anxnet.device selected and nothing staged at ${STAGED_AT:-none}" >&2 + exit 1; } + [ -f "$_anx_ref" ] || { + echo "!! anxnet.device selected and $ARCHIVE carries none to check" >&2 + exit 1; } + STAGED_SHA256=$(shasum -a 256 "$STAGED_AT" | cut -d' ' -f1) + _anx_ref_sum=$(shasum -a 256 "$_anx_ref" | cut -d' ' -f1) + echo "staged_driver_sha256=$STAGED_SHA256 archive_anxnet_sha256=$_anx_ref_sum" + [ "$STAGED_SHA256" = "$_anx_ref_sum" ] || { + echo "!! the staged $SANA2_DRIVER is not the archive's anxnet.device" >&2 + echo " staged $STAGED_AT" >&2 + echo " source ${AMINETXDUO_SANA2_DRIVER:-none}" >&2 + exit 1; } +fi + # A previous AmiNetXDuo install leaves both Pi drivers on every machine. They # are payload, not proof of Pi hardware. Seed precisely that state while the # explicit no-card fixture is active: Novice must still refuse without writing @@ -2441,7 +2500,7 @@ else # The upgrade half: a stale driver was there, and it must be gone from the # live name and kept under .old. Its bytes are unique to this run, so # "gone" is checked rather than assumed. - if [ -n "$ANXNET_INSTALLED" ] && + if [ "$STALE_IS_STAGED" = 0 ] && [ -n "$ANXNET_INSTALLED" ] && [ "$(shasum "$ANXNET_INSTALLED" | cut -d' ' -f1)" = "$STALE_SUM" ]; then echo "!! DEVS:Networks/anxnet.device is still the STALE file this" echo " harness staged before the install. A reinstall that leaves" @@ -2705,7 +2764,8 @@ elif [ -n "$EXPECTED_AUTO_DEVICE" ]; then esac else case "$INSTALLER_DEVICE" in - "$SANA2_DRIVER"|"DEVS:Networks/$SANA2_DRIVER") CARD_SELECTED=yes ;; + "$SANA2_DRIVER"|"DEVS:Networks/$SANA2_DRIVER"|"AmiNetXDuo:Devs/Networks/$SANA2_DRIVER") + CARD_SELECTED=yes ;; esac fi From b69a2353aff54764d6576d833be54aa780b30b25 Mon Sep 17 00:00:00 2001 From: Tinic Uro Date: Thu, 1 Oct 2026 08:28:53 -0700 Subject: [PATCH 2/5] rig: one run per test drive, on every backend, drive before bridge The bridge lock skips SLIRP and run-workbench.sh took it about 660 lines below its HD/WORK wipe, so a second run of one tag in one checkout deleted the live guest's drive and then waited. tools/emu-rig-lock.sh gains rig_claim_drive/rig_drop_drive: an exclusive flock beside the bridge lock, keyed by the cksum of the drive's resolved path (the path is in the holder record), blocking up to AMINETXDUO_DRIVE_WAIT (1800 s) then 6, 2 when it cannot arbitrate. Two checkouts with one tag are two drives and stay parallel. Every launcher that starts the emulator claims its drive before its first wipe, restage, Xvfb, tcpdump or emulator start: amiberry-run.sh, run-workbench.sh, run-nfsmount.sh, run-smbmount.sh, run-console.sh, run-wbgrab.sh, and the standing classicwb.sh, demo-rtg.sh and console-instance.sh (wait 0: refused at once). One lock order everywhere, drive before bridge; the standing check never waits. Helpers that are not the guest drop the drive lock with the others. bridge-lock-selftest.sh adds drive_serial, drive_parallel, drive_refuse, lock_order_live (four contenders over two tags and two backends finish inside the bound) and launcher_order, which finds every script that starts the emulator and fails one with no drive claim, a claim after its first destructive line, or bridge before drive. Co-Authored-By: Claude Opus 5.5 --- install/test/run-nfsmount.sh | 14 ++-- install/test/run-smbmount.sh | 10 ++- install/test/run-workbench.sh | 7 ++ tests/tools/console-instance.sh | 10 ++- tests/tools/run-console.sh | 10 ++- tests/tools/run-wbgrab.sh | 9 ++- tools/amiberry-run.sh | 26 ++++-- tools/bridge-lock-selftest.sh | 135 ++++++++++++++++++++++++++++++-- tools/classicwb.sh | 15 ++-- tools/demo-rtg.sh | 16 ++-- tools/emu-rig-lock.sh | 100 +++++++++++++++++++++++ 11 files changed, 309 insertions(+), 43 deletions(-) diff --git a/install/test/run-nfsmount.sh b/install/test/run-nfsmount.sh index 607f2329..2b0845ed 100755 --- a/install/test/run-nfsmount.sh +++ b/install/test/run-nfsmount.sh @@ -145,16 +145,18 @@ PEERNAME="${PEERHOST#*@}" PEERADDR=$(getent ahostsv4 "$PEERNAME" 2>/dev/null | awk 'NR==1{print $1}') [ -n "$PEERADDR" ] || PEERADDR="$PEERNAME" -# ONE BRIDGED GUEST PER HOST, claimed before this run wipes and restages its -# drive: a second run of the same tag would otherwise delete the drive of the -# guest running under the lock. Held until this script exits; the codes and -# the why are at rig_claim_bridge in tools/emu-rig-lock.sh. +# THIS TAG'S DRIVE, THEN ONE BRIDGED GUEST PER HOST, both claimed before this +# run wipes and restages its drive: a second run of the same tag would +# otherwise delete the drive of the guest that is running. Drive before +# bridge, always. Held until this script exits; the codes and the why are at +# rig_claim_drive and rig_claim_bridge in tools/emu-rig-lock.sh. +OUT="$ROOT/build/nfsmount-$TAG" +HD="$ROOT/build/nfshd-$TAG" # shellcheck source=../../tools/emu-rig-lock.sh . "$ROOT/tools/emu-rig-lock.sh" +rig_claim_drive "$HD" "run-nfsmount $TAG in $ROOT" || exit $? rig_claim_bridge "$BACKEND" "run-nfsmount $TAG in $ROOT" || exit $? -OUT="$ROOT/build/nfsmount-$TAG" -HD="$ROOT/build/nfshd-$TAG" rm -rf "$OUT"; mkdir -p "$OUT" echo "==> $MODEL, OS 3.1, $(basename "$KICKSTART")" diff --git a/install/test/run-smbmount.sh b/install/test/run-smbmount.sh index 8fa158fc..0020e9ab 100755 --- a/install/test/run-smbmount.sh +++ b/install/test/run-smbmount.sh @@ -271,12 +271,14 @@ else echo "==> share $URL (host $SMBHOST), client smb2-handler.$CPUSFX (SMB2/3)" fi -# ONE BRIDGED GUEST PER HOST, claimed before this run wipes and restages its -# drive: a second run of the same tag would otherwise delete the drive of the -# guest running under the lock. Held until this script exits; the codes and -# the why are at rig_claim_bridge in tools/emu-rig-lock.sh. +# THIS TAG'S DRIVE, THEN ONE BRIDGED GUEST PER HOST, both claimed before this +# run wipes and restages its drive: a second run of the same tag would +# otherwise delete the drive of the guest that is running. Drive before +# bridge, always. Held until this script exits; the codes and the why are at +# rig_claim_drive and rig_claim_bridge in tools/emu-rig-lock.sh. # shellcheck source=../../tools/emu-rig-lock.sh . "$ROOT/tools/emu-rig-lock.sh" +rig_claim_drive "$HD" "run-smbmount $TAG in $ROOT" || exit $? rig_claim_bridge "$BACKEND" "run-smbmount $TAG in $ROOT" || exit $? # --------------------------------------------------------------- the SYS: -- diff --git a/install/test/run-workbench.sh b/install/test/run-workbench.sh index 3b09b2ba..4f4daddb 100755 --- a/install/test/run-workbench.sh +++ b/install/test/run-workbench.sh @@ -389,6 +389,13 @@ if [ "$SPACES" = "1" ]; then fi WORK="$ROOT/build/testwork-$TAG" +# THIS TAG'S DRIVE, ON EVERY BACKEND, before anything below can wipe or +# restage it: a second run of the tag would otherwise delete the live guest's +# DH0: and Work: and then wait for the bridge. One claim covers both +# directories (they are named by the same tag), and it is held until the +# script exits. Drive before bridge, always: tools/emu-rig-lock.sh. +rig_claim_drive "$HD" "run-workbench $TAG in $ROOT" || exit $? + # ------------------------------------------------------------ ingredients -- INSTALLER="${AMINETXDUO_INSTALLER:-}" diff --git a/tests/tools/console-instance.sh b/tests/tools/console-instance.sh index 5d988f9f..9e9a0b5d 100755 --- a/tests/tools/console-instance.sh +++ b/tests/tools/console-instance.sh @@ -135,6 +135,14 @@ if [ "$(curl -s -m 4 -o /dev/null -w '%{http_code}' "http://$ADDRESS:$PORT/" || exit 2 fi +# The drive, before anything below assembles, wipes or restages it. Wait 0: +# a standing guest is up for hours (tools/emu-rig-lock.sh has the codes). +rig_claim_drive "$HD" "console-instance in $RUN" 0 || { + rc=$? + say RESULT INFRA + exit "$rc" +} + HTTPD="$BUILD/src/tools/httpd" BSD="$BUILD/src/bsdsocket/bsdsocket.library" for f in "$HTTPD" "$BSD"; do @@ -279,7 +287,7 @@ EOF ( trap '' PIPE; exec "$AMIBERRY" -f "$CFG" ) >>"$EMULOG" 2>&1 & printf '%s\n' "$!" > "$PIDFILE" # The emulator holds the standing lock now; the reader below may not. -rig_drop_standing +rig_drop_standing; rig_drop_drive # The reader, retried: /wait holds the emulator until this connects, and the # emulator has to have opened the listener first. Appends, so a restart of the diff --git a/tests/tools/run-console.sh b/tests/tools/run-console.sh index 58cc23de..87ad6bba 100755 --- a/tests/tools/run-console.sh +++ b/tests/tools/run-console.sh @@ -361,6 +361,14 @@ fi # shellcheck source=tools/emu-rig-lock.sh . "$ROOT/tools/emu-rig-lock.sh" +# The drive first, on every backend, then the bridge (tools/emu-rig-lock.sh): +# stage() below wipes $HD, which is one directory for every run in this tree. +HD="$ROOT/build/console-dh0" +rig_claim_drive "$HD" "console ($BACKEND) in $ROOT" || { + rc=$? + say RESULT INFRA + exit "$rc" +} rig_claim_bridge "$BACKEND" "console ($BACKEND) in $ROOT" || { rc=$? say RESULT INFRA @@ -449,8 +457,6 @@ with open(sys.argv[1], "wb") as fh: EOF } -HD="$ROOT/build/console-dh0" - stage() { local depth="$1" mode_id="${2:-}" diff --git a/tests/tools/run-wbgrab.sh b/tests/tools/run-wbgrab.sh index a6f507fa..1c203389 100755 --- a/tests/tools/run-wbgrab.sh +++ b/tests/tools/run-wbgrab.sh @@ -87,6 +87,13 @@ if [ -z "$AMIBERRY" ]; then fi [ -n "$AMIBERRY" ] || { say error "amiberry not found; set AMIBERRY="; exit 2; } +# The drive, before anything below assembles, wipes or restages it: one +# directory for every run in this tree. No network, so no bridge. +HD="$ROOT/build/wbgrab-dh0" +# shellcheck source=tools/emu-rig-lock.sh +. "$ROOT/tools/emu-rig-lock.sh" +rig_claim_drive "$HD" "wbgrab in $ROOT" || exit $? + # ------------------------------------------------------ Workbench 3.1 SYS: -- # shellcheck source=tests/tools/wb31-sys.sh @@ -99,8 +106,6 @@ WB="$WB31_SYS" # ------------------------------------------------------------ the drive ---- -HD="$ROOT/build/wbgrab-dh0" - stage() { rm -rf "$HD" mkdir -p "$HD" diff --git a/tools/amiberry-run.sh b/tools/amiberry-run.sh index c6e3dadf..394005ee 100755 --- a/tools/amiberry-run.sh +++ b/tools/amiberry-run.sh @@ -55,12 +55,13 @@ ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) # cannot take anything away, and it covers every artefact a guest writes rather # than the one that was noticed. # -# EXCEPT THAT ON A BRIDGED RUN THE BRIDGE LOCK COMES FIRST. A second run of -# the same tag in the same checkout would otherwise delete the drive of the -# guest that is running under the lock and then wait for it. So -B is read -# here, in a quiet first pass, and a host-NIC run claims the bridge before the -# wipe. A run refused there exits 6 or 2 with the drive untouched, which is -# right: it is the holder's drive, and a nonzero exit is never a result. +# EXCEPT THAT THE DRIVE LOCK COMES FIRST, ON EVERY BACKEND, THEN THE BRIDGE. +# A second run of the same tag in the same checkout would otherwise delete the +# drive of the guest that is running and then wait for it. So -B is read +# here, in a quiet first pass, and the drive (then, on a host NIC, the bridge) +# is claimed before the wipe. A run refused there exits 6 or 2 with the drive +# untouched, which is right: it is the holder's drive, and a nonzero exit is +# never a result. # # ONE BRIDGED GUEST ON THIS HOST, taken here so that no harness can boot one # without it. The emulator inherits the descriptor and holds it until it @@ -92,7 +93,13 @@ if [ -n "$STANDING" ] && rig_backend_bridged "$_pre_backend"; then rig_standing_exempt "$STANDING ${AMINETXDUO_RUN_TAG:-amiberry} in $ROOT" \ "$AMINETXDUO_AMIBERRY_MAC" "$_pre_backend" || exit $? export AMINETXDUO_AMIBERRY_MAC -else +fi +# The drive, on every backend, and BEFORE the bridge (tools/emu-rig-lock.sh, +# rig_claim_drive): two SLIRP runs of one tag in this checkout are one drive. +# The standing check above never waits, so it may come first. +rig_claim_drive "$ROOT/build/amiberry-testhd-${AMINETXDUO_RUN_TAG:-amiberry}" \ + "${AMINETXDUO_RUN_TAG:-amiberry} ($_pre_backend) in $ROOT" || exit $? +if [ -z "$STANDING" ] || ! rig_backend_bridged "$_pre_backend"; then rig_claim_bridge "$_pre_backend" \ "${AMINETXDUO_RUN_TAG:-amiberry} ($_pre_backend) in $ROOT" || exit $? fi @@ -661,6 +668,7 @@ cleanup() { rig_release_port rig_drop_bridge rig_drop_standing + rig_drop_drive return 0 } trap cleanup EXIT INT TERM HUP @@ -700,7 +708,8 @@ fi LOGPIPE="$ROOT/build/amiberry-$TAG.logpipe" rm -f "$LOGPIPE" if [ -x "$ROOT/tools/logcap.sh" ] && mkfifo "$LOGPIPE" 2>/dev/null; then - ( rig_drop_bridge; rig_drop_standing; exec "$ROOT/tools/logcap.sh" ) \ + ( rig_drop_bridge; rig_drop_standing; rig_drop_drive + exec "$ROOT/tools/logcap.sh" ) \ < "$LOGPIPE" > "$UAELOG" & LOGCAP_PID=$! else @@ -754,6 +763,7 @@ fi [ -z "${RIG_PORT_FD:-}" ] || eval "exec ${RIG_PORT_FD}>&-" 2>/dev/null || true rig_drop_bridge rig_drop_standing + rig_drop_drive reader="" trap '[ -z "$reader" ] || kill -TERM "$reader" 2>/dev/null; exit 0' TERM INT for _ in $(seq 1 60); do diff --git a/tools/bridge-lock-selftest.sh b/tools/bridge-lock-selftest.sh index 872487a5..95763f9a 100755 --- a/tools/bridge-lock-selftest.sh +++ b/tools/bridge-lock-selftest.sh @@ -207,7 +207,7 @@ run A holder ens18 STUB_SECS=4 & HOLD=$! for _ in $(seq 1 50); do grep -q '^start holder' "$EV" && break; sleep 0.2; done HDA="$S/A/build/amiberry-testhd-ifsurvive" -run A sametree ens18 AMINETXDUO_BRIDGE_WAIT=1 +run A sametree ens18 AMINETXDUO_BRIDGE_WAIT=1 AMINETXDUO_DRIVE_WAIT=1 if [ -e "$HDA/stub-alive" ] && [ "$(cat "$S/sametree.rc")" = 6 ]; then kv same_tree_hd ok else @@ -261,7 +261,7 @@ fi : > "$EV" run C unwritable ens18 AMINETXDUO_BRIDGE_LOCK="$S/no/such/dir/bridge.lock" if [ "$(cat "$S/unwritable.rc")" = 2 ] && [ ! -s "$EV" ] && - grep -q 'cannot create the bridge lock' "$S/unwritable.out"; then + grep -qE 'cannot create the (drive|bridge) lock' "$S/unwritable.out"; then kv unwritable_rc ok else kv unwritable_rc "wrong:rc=$(cat "$S/unwritable.rc")" @@ -483,15 +483,18 @@ for spec in tools/classicwb.sh:1 tools/demo-rtg.sh:1 \ wait 2> /dev/null; rm -f "$S/mac-held" done - # Every long-lived helper closes the standing lock in its own subshell - # (Xvfb, tcpdump, the log capper), and the script drops its copy right - # after the emulator starts, so the watchdog and readers never get it. + # Every long-lived helper closes the standing and drive locks in its own + # subshell (Xvfb, tcpdump, the log capper), and the script drops its + # copies right after the emulator starts, so the watchdog and readers + # never get them. while IFS=: read -r n _; do - sed -n "$((n - 1)),${n}p" "$ROOT/$f" | grep -q 'rig_drop_standing' || + sed -n "$((n - 1)),${n}p" "$ROOT/$f" | + grep -q 'rig_drop_standing; rig_drop_drive' || ov_bad="$ov_bad $f:$n:helper-keeps-lock" - done < <(grep -nE '^[[:space:]]*(\( *rig_drop_standing; *)?(exec +)?(Xvfb |tcpdump -i|"[$]ROOT/tools/logcap\.sh")' "$ROOT/$f") + done < <(grep -nE '^[[:space:]]*(\( *rig_drop_standing; *(rig_drop_drive; *)?)?(exec +)?(Xvfb |tcpdump -i|"[$]ROOT/tools/logcap\.sh")' "$ROOT/$f") while IFS=: read -r n _; do - sed -n "$((n + 1)),$((n + 4))p" "$ROOT/$f" | grep -q '^rig_drop_standing' || + sed -n "$((n + 1)),$((n + 4))p" "$ROOT/$f" | + grep -q '^rig_drop_standing; rig_drop_drive' || ov_bad="$ov_bad $f:$n:script-keeps-lock" done < <(grep -nE '^(setsid "[$]AMIBERRY"|start_emulator "[$]CFG"|\( trap .* exec "[$]AMIBERRY")' "$ROOT/$f") done @@ -553,6 +556,122 @@ else kv standing_helper_free "wrong:helper_alive=$helper_alive:relaunch_rc=$again" fi +# ------------------------------------------------ one run per drive, any backend +# Two SLIRP runs of one tag in one checkout share a drive: the second waits, +# without touching it, and boots once the first has exited. +: > "$EV" +run A d1 slirp STUB_SECS=3 & +D1=$! +for _ in $(seq 1 50); do grep -q '^start d1' "$EV" && break; sleep 0.2; done +run A d2 slirp STUB_SECS=0.5 & +D2=$! +sleep 1.5 +HDA="$S/A/build/amiberry-testhd-ifsurvive" +kept=no; [ -e "$HDA/stub-alive" ] && grep -q '^start d1' "$EV" && + ! grep -q '^start d2' "$EV" && kept=yes +wait "$D1" "$D2" +order=$(awk '{print $1 "-" $2}' "$EV" | tr '\n' ' ') +if [ "$kept" = yes ] && [ "$order" = "start-d1 end-d1 start-d2 end-d2 " ] && + [ "$(cat "$S/d1.rc")" = 0 ] && [ "$(cat "$S/d2.rc")" = 0 ] && + grep -q 'another run is using' "$S/d2.out"; then + kv drive_serial ok +else + kv drive_serial "wrong:kept_while_waiting=$kept:order=[$order]:rc=$(cat "$S/d1.rc"),$(cat "$S/d2.rc")" +fi + +# Two tags in one checkout are two drives: parallel. +: > "$EV" +run A t1 slirp STUB_SECS=2 AMINETXDUO_RUN_TAG=drive-t1 & +run A t2 slirp STUB_SECS=2 AMINETXDUO_RUN_TAG=drive-t2 & +wait +order=$(awk '{print $1}' "$EV" | tr '\n' ' ') +if [ "$order" = "start start end end " ] && + [ "$(cat "$S/t1.rc")" = 0 ] && [ "$(cat "$S/t2.rc")" = 0 ]; then + kv drive_parallel ok +else + kv drive_parallel "wrong:order=[$order]:rc=$(cat "$S/t1.rc"),$(cat "$S/t2.rc")" +fi + +# A run that cannot get the drive in time exits 6, unbooted, and the holder's +# drive is untouched. +: > "$EV" +run A dh slirp STUB_SECS=4 & +DH=$! +for _ in $(seq 1 50); do grep -q '^start dh' "$EV" && break; sleep 0.2; done +run A dr slirp AMINETXDUO_DRIVE_WAIT=1 +if [ "$(cat "$S/dr.rc")" = 6 ] && ! grep -q '^start dr' "$EV" && + [ -e "$HDA/stub-alive" ] && grep -q 'REFUSING to touch' "$S/dr.out"; then + kv drive_refuse ok +else + kv drive_refuse "wrong:rc=$(cat "$S/dr.rc"):drive=$([ -e "$HDA/stub-alive" ] && echo kept || echo gone)" +fi +wait "$DH" + +# LOCK ORDER: drive before bridge. A holder up on tag t1 and the bridge; +# behind it a t1 SLIRP run (wants the drive), a t2 bridged run (wants the +# bridge) and a t1 bridged run (wants both). With one order nothing can hold +# one lock while waiting for the other's, so all four finish well inside the +# bound; mixed orders would sit out both waits. +: > "$EV" +t0=$(date +%s) +run A oh ens18 STUB_SECS=2 AMINETXDUO_RUN_TAG=order-t1 & +for _ in $(seq 1 50); do grep -q '^start oh' "$EV" && break; sleep 0.2; done +run A ox slirp STUB_SECS=1 AMINETXDUO_RUN_TAG=order-t1 \ + AMINETXDUO_DRIVE_WAIT=30 AMINETXDUO_BRIDGE_WAIT=30 & +run A oy ens18 STUB_SECS=1 AMINETXDUO_RUN_TAG=order-t2 \ + AMINETXDUO_DRIVE_WAIT=30 AMINETXDUO_BRIDGE_WAIT=30 & +run A oz ens18 STUB_SECS=1 AMINETXDUO_RUN_TAG=order-t1 \ + AMINETXDUO_DRIVE_WAIT=30 AMINETXDUO_BRIDGE_WAIT=30 & +wait +took=$(( $(date +%s) - t0 )) +rcs=$(cat "$S/oh.rc" "$S/ox.rc" "$S/oy.rc" "$S/oz.rc" | tr -d '\n') +ends=$(grep -c '^end o' "$EV") +if [ "$rcs" = 0000 ] && [ "$ends" = 4 ] && [ "$took" -lt 25 ]; then + kv lock_order_live ok +else + kv lock_order_live "wrong:rc=$rcs:ended=$ends:took=${took}s" +fi +echo "lock_order_took_s=$took" + +# EVERY LAUNCHER, statically: any script that starts the emulator claims its +# drive before its first destructive step (a wipe, an extraction, a mkdir or +# cp restage, the shared Workbench build, Xvfb, tcpdump, a kill or the +# emulator), and a bridged one claims the bridge after the drive and before +# the emulator. Found by what they do, so a new launcher with no claim fails +# here rather than waiting for a reviewer to spot it. +START='(exec|setsid) +(setsid +)?"[$]AMIBERRY"|"[$]AMIBERRY" +(--log +)?-f|start_emulator "' +DESTRUCTIVE_ALL="$DESTRUCTIVE|lha +x|tar x" +lo_bad="" +launchers=$(cd "$ROOT" && grep -rlE "$START" --include='*.sh' tools tests install | + grep -v '^tools/bridge-lock-selftest\.sh$' | sort) +for f in $launchers; do + from=1 + [ "$f" != tests/tools/console-instance.sh ] || + from=$(grep -n '^esac' "$ROOT/$f" | head -1 | cut -d: -f1) + first() { awk -v from="$from" -v re="$1" ' + NR >= from && $0 !~ /^[[:space:]]*#/ && $0 ~ re { print NR; exit }' "$ROOT/$f"; } + d=$(first '^[[:space:]]*rig_claim_drive ') + b=$(first '^[[:space:]]*rig_claim_bridge ') + x=$(first "$DESTRUCTIVE_ALL") + e=$(first "$START") + echo "launcher_$(basename "$f" .sh)=drive:${d:-none} bridge:${b:-none} first_destructive:${x:-none} emulator:${e:-none}" + [ -n "$d" ] || { lo_bad="$lo_bad $f:no-drive-claim"; continue; } + [ -z "$x" ] || [ "$d" -lt "$x" ] || lo_bad="$lo_bad $f:drive($d)>=destructive($x)" + if [ -n "$b" ]; then + [ "$d" -lt "$b" ] || lo_bad="$lo_bad $f:bridge($b)-before-drive($d)" + [ -z "$e" ] || [ "$b" -lt "$e" ] || lo_bad="$lo_bad $f:bridge($b)>=emulator($e)" + fi +done +case "$launchers" in + *tools/amiberry-run.sh*install/test/run-workbench.sh*|*install/test/run-workbench.sh*tools/amiberry-run.sh*) ;; + *) lo_bad="$lo_bad discovery-missed-known-launchers" ;; +esac +if [ -z "$lo_bad" ]; then + kv launcher_order ok +else + kv launcher_order "wrong:${lo_bad# }" +fi + echo "bridge_selftest=$WRONG" [ "$WRONG" = 0 ] || { for f in "$S"/*.out; do echo "---- $f"; tail -15 "$f"; done diff --git a/tools/classicwb.sh b/tools/classicwb.sh index 594dce1d..42b15739 100755 --- a/tools/classicwb.sh +++ b/tools/classicwb.sh @@ -198,6 +198,12 @@ done TAG="${AMINETXDUO_RUN_TAG:-cwb-$(printf '%s' "$MODEL" | tr '[:upper:]' '[:lower:]')-$VARIANT}" +# THE DRIVE, before the archive build or the copy below can wipe it. A +# standing guest is up for hours, so a second launch on this drive is refused +# at once (wait 0) rather than queued; tools/emu-rig-lock.sh has the codes. +HD="$ROOT/build/classicwb-$TAG-dh0" +rig_claim_drive "$HD" "classicwb $MODEL:$VARIANT in $ROOT" 0 || exit $? + say model "$MODEL" say variant "$VARIANT" say classicwb "$DIST" @@ -256,7 +262,6 @@ say archive_bytes "$(wc -c < "$ARCHIVE" | tr -d ' ')" # A copy, every time. The snapshot is the install and is never written to, so # a run that wedges its drive is recovered by deleting the copy. -HD="$ROOT/build/classicwb-$TAG-dh0" rm -rf "$HD" mkdir -p "$HD" cp -a "$SNAP/." "$HD/" @@ -409,7 +414,7 @@ start_emulator() { LOGPIPE="$log.pipe" rm -f "$LOGPIPE" if [ -x "$ROOT/tools/logcap.sh" ] && mkfifo "$LOGPIPE" 2>/dev/null; then - ( rig_drop_standing; exec "$ROOT/tools/logcap.sh" ) \ + ( rig_drop_standing; rig_drop_drive; exec "$ROOT/tools/logcap.sh" ) \ < "$LOGPIPE" > "$log" & LOGCAP_PID=$! else @@ -860,7 +865,7 @@ if [ "$VARIANT" = rtg ]; then [ -e "/tmp/.X11-unix/X$n" ] || { XDISP=":$n"; break; } done [ -n "$XDISP" ] || { say error "no free X display in :90..:99"; exit 2; } - ( rig_drop_standing; exec Xvfb "$XDISP" -screen 0 1280x1024x24 ) \ + ( rig_drop_standing; rig_drop_drive; exec Xvfb "$XDISP" -screen 0 1280x1024x24 ) \ >/dev/null 2>&1 & XVFB_PID=$! sleep 2 @@ -909,7 +914,7 @@ EMULOG="$ROOT/build/amiberry-$TAG.log" WIRE="$ROOT/build/classicwb-wire-$TAG.txt" : > "$WIRE" # Helpers close the standing lock: only the emulator may keep the address. -( rig_drop_standing +( rig_drop_standing; rig_drop_drive exec tcpdump -i "$BACKEND" -n -l -e \ "arp or (udp port 67 or udp port 68) or (udp port 5353)" ) \ >> "$WIRE" 2>/dev/null & @@ -919,7 +924,7 @@ sleep 1 start_emulator "$CFG" "$EMULOG" # The emulator holds the standing lock now; nothing started below may. -rig_drop_standing +rig_drop_standing; rig_drop_drive say emulator_pid "$EMU_PID" # A guest that is left up needs an end, and the emulator is the thing that has diff --git a/tools/demo-rtg.sh b/tools/demo-rtg.sh index 25873c75..528036d9 100755 --- a/tools/demo-rtg.sh +++ b/tools/demo-rtg.sh @@ -95,6 +95,12 @@ rig_standing_exempt "demo-rtg $TAG in $ROOT" "$MAC" "$BACKEND" || { exit "$rc" } +# Keyed by the MAC: one address, one drive. Two instances that hash to one +# address are refused above, so they cannot share a drive either; the drive +# lock (wait 0: a standing guest is up for hours) covers a pinned MAC too. +HD="$ROOT/build/demo-rtg-dh0-$(printf '%s' "$MAC" | tr -d ':')" +rig_claim_drive "$HD" "demo-rtg $TAG in $ROOT" 0 || exit $? + # ------------------------------------------------------ Workbench 3.1 SYS: -- # shellcheck source=tests/tools/wb31-sys.sh @@ -156,10 +162,6 @@ EOF # ------------------------------------------------------------- the drive ---- -# Keyed by the MAC: one address, one drive. Two instances that hash to one -# address are refused above, so they cannot share a drive either. -HD="$ROOT/build/demo-rtg-dh0-$(printf '%s' "$MAC" | tr -d ':')" - rm -rf "$HD" mkdir -p "$HD/Public/Docs" "$HD/Console" cp -R "$WB/." "$HD/" @@ -283,7 +285,7 @@ for n in $(seq 90 99); do [ -e "/tmp/.X11-unix/X$n" ] || { XDISP=":$n"; break; } done [ -n "$XDISP" ] || { say error "no free X display in :90..:99"; exit 2; } -( rig_drop_standing; exec Xvfb "$XDISP" -screen 0 1280x1024x24 ) >/dev/null 2>&1 & +( rig_drop_standing; rig_drop_drive; exec Xvfb "$XDISP" -screen 0 1280x1024x24 ) >/dev/null 2>&1 & XVFB_PID=$! sleep 2 export DISPLAY="$XDISP" @@ -323,7 +325,7 @@ EMULOG="$ROOT/build/amiberry-$TAG.log" WIRE="$ROOT/build/demo-rtg-wire-$TAG.txt" : > "$WIRE" # Helpers close the standing lock: only the emulator may keep the address. -( rig_drop_standing +( rig_drop_standing; rig_drop_drive exec tcpdump -i "$BACKEND" -n -l -e \ "arp or (udp port 67 or udp port 68) or (udp port 5353)" ) \ >> "$WIRE" 2>/dev/null & @@ -334,7 +336,7 @@ sleep 1 setsid "$AMIBERRY" -f "$CFG" >"$EMULOG" 2>&1 & EMU_PID=$! # The emulator holds the standing lock now; nothing started below may. -rig_drop_standing +rig_drop_standing; rig_drop_drive say emulator_pid "$EMU_PID" # The window. A demo that is left up needs an end, and the emulator is the diff --git a/tools/emu-rig-lock.sh b/tools/emu-rig-lock.sh index 637e4da5..72efc0cd 100755 --- a/tools/emu-rig-lock.sh +++ b/tools/emu-rig-lock.sh @@ -367,6 +367,106 @@ rig_drop_bridge() { RIG_BRIDGE_FD="" } +# ONE RUN PER TEST DRIVE, ON EVERY BACKEND. +# +# rig_claim_drive [wait] # 0 held, 6 busy, 2 cannot arbitrate +# rig_drop_drive # close it in this shell or subshell +# +# The bridge lock is about the wire and skips SLIRP, so two runs of one tag in +# one checkout -- one drive directory -- still wiped each other's live drive +# before either reached it. Every launcher takes this before its first wipe, +# restage, Xvfb, tcpdump or emulator start. +# +# KEYED BY THE DRIVE'S REAL PATH, beside the bridge lock (host-wide, and never +# under build/, where a wipe or a clean could delete it): +# aminetxduo-drive-.lock, the path in the record. +# Two checkouts with the same tag are two drives and stay parallel. +# +# Blocking and bounded like the bridge: [wait], else AMINETXDUO_DRIVE_WAIT, +# else 1800 seconds, then 6. 2 is no flock(1) or a lock file that cannot be +# made. Inherited by the emulator, so the drive stays claimed while a guest +# outlives a killed launcher; long-lived helpers call rig_drop_drive first. +# +# LOCK ORDER, EVERYWHERE: DRIVE BEFORE BRIDGE. A path that held the bridge +# and waited for a drive, beside one that held the drive and waited for the +# bridge, would deadlock until both waits expired. rig_standing_exempt and +# the port, name and address claims never wait, so they may come anywhere. +RIG_DRIVE_FD="${RIG_DRIVE_FD:-}" +RIG_DRIVE_WAITER="${RIG_DRIVE_WAITER:-}" + +# The drive's absolute path with its parent resolved; the drive itself may not +# exist yet, so realpath(1) -- whose -m is GNU-only -- is not used. +rig_drive_realpath() { # dir + local p b r + b=$(basename "$1") + p=$(dirname "$1") + if r=$(cd "$p" 2> /dev/null && pwd -P); then + printf '%s/%s\n' "${r%/}" "$b" + else + case "$1" in /*) printf '%s\n' "$1" ;; *) printf '%s/%s\n' "$PWD" "$1" ;; esac + fi +} + +rig_drive_path() { # dir + printf '%s/aminetxduo-drive-%s.lock\n' "$(dirname "$(rig_bridge_path)")" \ + "$(rig_drive_realpath "$1" | cksum | cut -d' ' -f1)" +} + +rig_claim_drive() { # dir who [wait] + [ -n "${1:-}" ] || { echo "rig_claim_drive: no drive named" >&2; return 2; } + [ -z "$RIG_DRIVE_FD" ] || return 0 + rig_have_flock || { rig_no_flock; return 2; } + local who="${2:-$$}" limit="${3:-${AMINETXDUO_DRIVE_WAIT:-1800}}" + local f fd rc start waited real + mkdir -p "$(dirname "$1")" 2> /dev/null || true + real=$(rig_drive_realpath "$1") + f=$(rig_drive_path "$1") + + ( umask 000; : >> "$f" ) 2> /dev/null || { + echo "cannot create the drive lock $f" >&2; return 2; } + exec {fd}>>"$f" || { echo "cannot open the drive lock $f" >&2; return 2; } + + if ! flock -n -x "$fd" 2> /dev/null; then + echo "==> another run is using $real; waiting up to ${limit}s:" >&2 + sed 's/^/ /' "$f" >&2 2> /dev/null || true + start=$(date +%s) + rc=0 + if [ "$limit" -gt 0 ] 2> /dev/null; then + flock -x -w "$limit" "$fd" 2> /dev/null & + RIG_DRIVE_WAITER=$! + wait "$RIG_DRIVE_WAITER" || rc=$? + RIG_DRIVE_WAITER="" + else + rc=1 + fi + waited=$(( $(date +%s) - start )) + if [ "$rc" != 0 ]; then + echo "REFUSING to touch $real (waited ${waited}s); it is held by:" >&2 + sed 's/^/ /' "$f" >&2 2> /dev/null || true + echo " Wiping it would destroy that run's live drive. Wait for it," >&2 + echo " use another AMINETXDUO_RUN_TAG, or raise AMINETXDUO_DRIVE_WAIT." >&2 + exec {fd}>&- + return 6 + fi + echo "==> drive free after ${waited}s" + fi + + printf 'drive path=%s pid=%s who=%s since=%s\n' \ + "$real" "$$" "$who" "$(date +%FT%T)" > "$f" + RIG_DRIVE_FD="$fd" + return 0 +} + +rig_drop_drive() { + if [ -n "$RIG_DRIVE_WAITER" ]; then + kill "$RIG_DRIVE_WAITER" 2> /dev/null || true + RIG_DRIVE_WAITER="" + fi + [ -n "$RIG_DRIVE_FD" ] || return 0 + eval "exec ${RIG_DRIVE_FD}>&-" 2> /dev/null || true + RIG_DRIVE_FD="" +} + # Exit 1 from ping means "no reply"; anything else means the prober itself # could not run, and an unusable prober must never read as an empty LAN. rig_prober_usable() { From 230c3d94e819eb55553cf492024f78e2d0dc12a9 Mon Sep 17 00:00:00 2001 From: Tinic Uro Date: Thu, 1 Oct 2026 08:36:36 -0700 Subject: [PATCH 3/5] rig: drive lock degrades without flock(1) off the wire rig_claim_drive takes the backend as its third argument. With no flock(1) a non-bridged run gets one warning ("no flock: drive not locked; do not run two of this tag at once") and 0, unlocked; a bridged one still exits 2. Every caller passes its backend (run-wbgrab.sh passes none); run-workbench.sh claims the drive once the backend is known, still before its first destructive line. Standing launchers are unchanged: rig_standing_exempt still requires flock(1) ahead of the drive claim. bridge-lock-selftest.sh: noflock_drive, with flock(1) off PATH the function returns 0 with the warning for slirp and 2 for ens18, and a SLIRP amiberry-run.sh gets past the drive with the warning. Co-Authored-By: Claude Opus 5.5 --- install/test/run-nfsmount.sh | 2 +- install/test/run-smbmount.sh | 2 +- install/test/run-workbench.sh | 14 +++++++------- tests/tools/console-instance.sh | 2 +- tests/tools/run-console.sh | 2 +- tests/tools/run-wbgrab.sh | 2 +- tools/amiberry-run.sh | 3 ++- tools/bridge-lock-selftest.sh | 21 +++++++++++++++++++++ tools/classicwb.sh | 3 ++- tools/demo-rtg.sh | 2 +- tools/emu-rig-lock.sh | 20 ++++++++++++++------ 11 files changed, 52 insertions(+), 21 deletions(-) diff --git a/install/test/run-nfsmount.sh b/install/test/run-nfsmount.sh index 2b0845ed..50503a4b 100755 --- a/install/test/run-nfsmount.sh +++ b/install/test/run-nfsmount.sh @@ -154,7 +154,7 @@ OUT="$ROOT/build/nfsmount-$TAG" HD="$ROOT/build/nfshd-$TAG" # shellcheck source=../../tools/emu-rig-lock.sh . "$ROOT/tools/emu-rig-lock.sh" -rig_claim_drive "$HD" "run-nfsmount $TAG in $ROOT" || exit $? +rig_claim_drive "$HD" "run-nfsmount $TAG in $ROOT" "$BACKEND" || exit $? rig_claim_bridge "$BACKEND" "run-nfsmount $TAG in $ROOT" || exit $? rm -rf "$OUT"; mkdir -p "$OUT" diff --git a/install/test/run-smbmount.sh b/install/test/run-smbmount.sh index 0020e9ab..66bd47dc 100755 --- a/install/test/run-smbmount.sh +++ b/install/test/run-smbmount.sh @@ -278,7 +278,7 @@ fi # rig_claim_drive and rig_claim_bridge in tools/emu-rig-lock.sh. # shellcheck source=../../tools/emu-rig-lock.sh . "$ROOT/tools/emu-rig-lock.sh" -rig_claim_drive "$HD" "run-smbmount $TAG in $ROOT" || exit $? +rig_claim_drive "$HD" "run-smbmount $TAG in $ROOT" "$BACKEND" || exit $? rig_claim_bridge "$BACKEND" "run-smbmount $TAG in $ROOT" || exit $? # --------------------------------------------------------------- the SYS: -- diff --git a/install/test/run-workbench.sh b/install/test/run-workbench.sh index 4f4daddb..9d388cc1 100755 --- a/install/test/run-workbench.sh +++ b/install/test/run-workbench.sh @@ -389,13 +389,6 @@ if [ "$SPACES" = "1" ]; then fi WORK="$ROOT/build/testwork-$TAG" -# THIS TAG'S DRIVE, ON EVERY BACKEND, before anything below can wipe or -# restage it: a second run of the tag would otherwise delete the live guest's -# DH0: and Work: and then wait for the bridge. One claim covers both -# directories (they are named by the same tag), and it is held until the -# script exits. Drive before bridge, always: tools/emu-rig-lock.sh. -rig_claim_drive "$HD" "run-workbench $TAG in $ROOT" || exit $? - # ------------------------------------------------------------ ingredients -- INSTALLER="${AMINETXDUO_INSTALLER:-}" @@ -604,6 +597,13 @@ slirp|slirp_inbound|none) ;; esac +# THIS TAG'S DRIVE, as soon as the backend is known and before anything below can wipe or +# restage it: a second run of the tag would otherwise delete the live guest's +# DH0: and Work: and then wait for the bridge. One claim covers both +# directories (they are named by the same tag), and it is held until the +# script exits. Drive before bridge, always: tools/emu-rig-lock.sh. +rig_claim_drive "$HD" "run-workbench $TAG in $ROOT" "$BACKEND" || exit $? + # ONE MAC PER TAG, derived, not pinned. A fixed address here put every run of # this harness on the bridge under 52:54:00:c0:ff:ee, beside the demo instance # and beside other checkouts' guests: two machines at two addresses sharing one diff --git a/tests/tools/console-instance.sh b/tests/tools/console-instance.sh index 9e9a0b5d..58fe08d4 100755 --- a/tests/tools/console-instance.sh +++ b/tests/tools/console-instance.sh @@ -137,7 +137,7 @@ fi # The drive, before anything below assembles, wipes or restages it. Wait 0: # a standing guest is up for hours (tools/emu-rig-lock.sh has the codes). -rig_claim_drive "$HD" "console-instance in $RUN" 0 || { +rig_claim_drive "$HD" "console-instance in $RUN" "$BACKEND" 0 || { rc=$? say RESULT INFRA exit "$rc" diff --git a/tests/tools/run-console.sh b/tests/tools/run-console.sh index 87ad6bba..b2b58269 100755 --- a/tests/tools/run-console.sh +++ b/tests/tools/run-console.sh @@ -364,7 +364,7 @@ fi # The drive first, on every backend, then the bridge (tools/emu-rig-lock.sh): # stage() below wipes $HD, which is one directory for every run in this tree. HD="$ROOT/build/console-dh0" -rig_claim_drive "$HD" "console ($BACKEND) in $ROOT" || { +rig_claim_drive "$HD" "console ($BACKEND) in $ROOT" "$BACKEND" || { rc=$? say RESULT INFRA exit "$rc" diff --git a/tests/tools/run-wbgrab.sh b/tests/tools/run-wbgrab.sh index 1c203389..324fc224 100755 --- a/tests/tools/run-wbgrab.sh +++ b/tests/tools/run-wbgrab.sh @@ -92,7 +92,7 @@ fi HD="$ROOT/build/wbgrab-dh0" # shellcheck source=tools/emu-rig-lock.sh . "$ROOT/tools/emu-rig-lock.sh" -rig_claim_drive "$HD" "wbgrab in $ROOT" || exit $? +rig_claim_drive "$HD" "wbgrab in $ROOT" none || exit $? # ------------------------------------------------------ Workbench 3.1 SYS: -- diff --git a/tools/amiberry-run.sh b/tools/amiberry-run.sh index 394005ee..27a93f0b 100755 --- a/tools/amiberry-run.sh +++ b/tools/amiberry-run.sh @@ -98,7 +98,8 @@ fi # rig_claim_drive): two SLIRP runs of one tag in this checkout are one drive. # The standing check above never waits, so it may come first. rig_claim_drive "$ROOT/build/amiberry-testhd-${AMINETXDUO_RUN_TAG:-amiberry}" \ - "${AMINETXDUO_RUN_TAG:-amiberry} ($_pre_backend) in $ROOT" || exit $? + "${AMINETXDUO_RUN_TAG:-amiberry} ($_pre_backend) in $ROOT" "$_pre_backend" || + exit $? if [ -z "$STANDING" ] || ! rig_backend_bridged "$_pre_backend"; then rig_claim_bridge "$_pre_backend" \ "${AMINETXDUO_RUN_TAG:-amiberry} ($_pre_backend) in $ROOT" || exit $? diff --git a/tools/bridge-lock-selftest.sh b/tools/bridge-lock-selftest.sh index 95763f9a..e348aa58 100755 --- a/tools/bridge-lock-selftest.sh +++ b/tools/bridge-lock-selftest.sh @@ -258,6 +258,27 @@ else kv noflock_rc "wrong:function=$fn_rc:run=$(cat "$S/noflock.rc")" fi +# The drive lock degrades without flock(1) when nothing is on the wire: one +# warning and 0, unlocked. On a bridged backend it stays strict. Through +# amiberry-run.sh a SLIRP run gets past the drive with the warning; its 2 is +# rig_claim_port's, which needs flock(1) on main as well. +dn=$(PATH="$S/noflock" AMINETXDUO_BRIDGE_LOCK="$S/bridge.lock" bash -c ' + . "$1/tools/emu-rig-lock.sh" + rig_claim_drive "$2/nf-drive" nf slirp 2> "$2/nf-slirp.err"; a=$? + rig_claim_drive "$2/nf-drive" nf ens18 2> /dev/null; b=$? + echo "$a,$b"' _ "$ROOT" "$S") +: > "$EV" +PATH="$S/noflock" run C noflockslirp slirp +if [ "$dn" = 0,2 ] && + grep -q '^no flock: drive .*nf-drive not locked; do not run two of this tag at once$' \ + "$S/nf-slirp.err" && + grep -q '^no flock: drive .*amiberry-testhd-ifsurvive not locked' "$S/noflockslirp.out" && + ! grep -q 'drive lock' "$S/noflockslirp.out"; then + kv noflock_drive ok +else + kv noflock_drive "wrong:slirp,bridged=$dn:run=$(cat "$S/noflockslirp.rc")" +fi + : > "$EV" run C unwritable ens18 AMINETXDUO_BRIDGE_LOCK="$S/no/such/dir/bridge.lock" if [ "$(cat "$S/unwritable.rc")" = 2 ] && [ ! -s "$EV" ] && diff --git a/tools/classicwb.sh b/tools/classicwb.sh index 42b15739..44323cdb 100755 --- a/tools/classicwb.sh +++ b/tools/classicwb.sh @@ -202,7 +202,8 @@ TAG="${AMINETXDUO_RUN_TAG:-cwb-$(printf '%s' "$MODEL" | tr '[:upper:]' '[:lower: # standing guest is up for hours, so a second launch on this drive is refused # at once (wait 0) rather than queued; tools/emu-rig-lock.sh has the codes. HD="$ROOT/build/classicwb-$TAG-dh0" -rig_claim_drive "$HD" "classicwb $MODEL:$VARIANT in $ROOT" 0 || exit $? +rig_claim_drive "$HD" "classicwb $MODEL:$VARIANT in $ROOT" "$BACKEND" 0 || + exit $? say model "$MODEL" say variant "$VARIANT" diff --git a/tools/demo-rtg.sh b/tools/demo-rtg.sh index 528036d9..0aa7fd70 100755 --- a/tools/demo-rtg.sh +++ b/tools/demo-rtg.sh @@ -99,7 +99,7 @@ rig_standing_exempt "demo-rtg $TAG in $ROOT" "$MAC" "$BACKEND" || { # address are refused above, so they cannot share a drive either; the drive # lock (wait 0: a standing guest is up for hours) covers a pinned MAC too. HD="$ROOT/build/demo-rtg-dh0-$(printf '%s' "$MAC" | tr -d ':')" -rig_claim_drive "$HD" "demo-rtg $TAG in $ROOT" 0 || exit $? +rig_claim_drive "$HD" "demo-rtg $TAG in $ROOT" "$BACKEND" 0 || exit $? # ------------------------------------------------------ Workbench 3.1 SYS: -- diff --git a/tools/emu-rig-lock.sh b/tools/emu-rig-lock.sh index 72efc0cd..ff031f3e 100755 --- a/tools/emu-rig-lock.sh +++ b/tools/emu-rig-lock.sh @@ -369,7 +369,8 @@ rig_drop_bridge() { # ONE RUN PER TEST DRIVE, ON EVERY BACKEND. # -# rig_claim_drive [wait] # 0 held, 6 busy, 2 cannot arbitrate +# rig_claim_drive [wait] +# # 0 held, 6 busy, 2 cannot arbitrate # rig_drop_drive # close it in this shell or subshell # # The bridge lock is about the wire and skips SLIRP, so two runs of one tag in @@ -383,8 +384,10 @@ rig_drop_bridge() { # Two checkouts with the same tag are two drives and stay parallel. # # Blocking and bounded like the bridge: [wait], else AMINETXDUO_DRIVE_WAIT, -# else 1800 seconds, then 6. 2 is no flock(1) or a lock file that cannot be -# made. Inherited by the emulator, so the drive stays claimed while a guest +# else 1800 seconds, then 6. 2 is a lock file that cannot be made, or no +# flock(1) on a bridged backend. WITHOUT flock(1) A NON-BRIDGED RUN DEGRADES: +# one warning and 0, unlocked, so a SLIRP or no-network run on a host with no +# flock (macOS) still runs; the wire needs arbitration, a lone drive does not. Inherited by the emulator, so the drive stays claimed while a guest # outlives a killed launcher; long-lived helpers call rig_drop_drive first. # # LOCK ORDER, EVERYWHERE: DRIVE BEFORE BRIDGE. A path that held the bridge @@ -412,11 +415,16 @@ rig_drive_path() { # dir "$(rig_drive_realpath "$1" | cksum | cut -d' ' -f1)" } -rig_claim_drive() { # dir who [wait] +rig_claim_drive() { # dir who backend [wait] [ -n "${1:-}" ] || { echo "rig_claim_drive: no drive named" >&2; return 2; } [ -z "$RIG_DRIVE_FD" ] || return 0 - rig_have_flock || { rig_no_flock; return 2; } - local who="${2:-$$}" limit="${3:-${AMINETXDUO_DRIVE_WAIT:-1800}}" + if ! rig_have_flock; then + rig_backend_bridged "${3:-}" && { rig_no_flock; return 2; } + echo "no flock: drive $(rig_drive_realpath "$1") not locked;" \ + "do not run two of this tag at once" >&2 + return 0 + fi + local who="${2:-$$}" limit="${4:-${AMINETXDUO_DRIVE_WAIT:-1800}}" local f fd rc start waited real mkdir -p "$(dirname "$1")" 2> /dev/null || true real=$(rig_drive_realpath "$1") From aaf15f88a4ce05b79848125fbd3ad28210715c08 Mon Sep 17 00:00:00 2001 From: Tinic Uro Date: Thu, 1 Oct 2026 08:40:18 -0700 Subject: [PATCH 4/5] run-workbench.sh: an anxnet run installs with the board present An anxnet run stages anxnet.device and no vendor file, so the Installer's driver-file fallback preselects nothing. The install boot had no board, so InstallNetProbe found nothing either: the card page came up with every box clear, installdrive pressed Proceed, the script answered "Select at least one network device." and the pair repeated until the timeout (ariadne2 drawer AVERAGE run at 24ade38b, ~140 clicks, status 20). Boot the install with the board, as -M already does, so the probe preselects it. NO_CARD keeps its board-less boot. Co-Authored-By: Claude Opus 5.5 --- install/test/run-workbench.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/install/test/run-workbench.sh b/install/test/run-workbench.sh index 9d388cc1..c3c7b628 100755 --- a/install/test/run-workbench.sh +++ b/install/test/run-workbench.sh @@ -1886,9 +1886,16 @@ fi # Installer spinning on its "select at least one" page. Ordinary scenarios # keep their old driver-file fallback, and the explicit no-card fixture still # boots with no board. +# +# An anxnet run is the same case: the staged file is anxnet.device, which is +# not in the Installer's driver-file fallback, so with no board present the +# card page comes up with nothing ticked and installdrive's Proceed loops on +# "Select at least one network device." INSTALL_CARD="" if [ "$MANUAL_CARD" = 1 ]; then INSTALL_CARD=net +elif [ "$SANA2_SEL_SOURCE" = anxnet ] && [ "$NO_CARD" = 0 ]; then + INSTALL_CARD=net fi boot install "$INSTALL_TIMEOUT" "$INSTALL_CARD" INSTALL_STATUS=$BOOT_STATUS From 9b9a0e28c29c78dff1109e07dc652845e6ad1089 Mon Sep 17 00:00:00 2001 From: Tinic Uro Date: Thu, 1 Oct 2026 08:47:19 -0700 Subject: [PATCH 5/5] run-workbench.sh: refuse -B with -D The drawer arm installs twice and marks S:Network-Startup between the runs; a boot-disabled install writes no S:Network-Startup, so the mark fails and the run ends at status 20 after a correct install. Co-Authored-By: Claude Opus 5.5 --- install/test/run-workbench.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/install/test/run-workbench.sh b/install/test/run-workbench.sh index c3c7b628..7e078d73 100755 --- a/install/test/run-workbench.sh +++ b/install/test/run-workbench.sh @@ -161,6 +161,11 @@ if [ "$EXPERT_CUSTOM" = "1" ] && [ "$LEVEL" != "EXPERT" ]; then echo "-E needs -l EXPERT: unit and interface name are Expert-only pages" >&2 exit 2 fi +if [ "$NO_BOOT" = "1" ] && [ "$DRAWER" = "1" ]; then + echo "-B and -D conflict: the drawer arm installs twice and marks the" >&2 + echo "S:Network-Startup that a boot-disabled install does not write." >&2 + exit 2 +fi if [ "$NO_BOOT" = "1" ] && [ "$TERMINAL" = "1" ]; then echo "-B and -H conflict: httpd-at-boot is not asked when boot networking" >&2 echo "is disabled." >&2