diff --git a/install/test/run-nfsmount.sh b/install/test/run-nfsmount.sh index 607f23294..50503a4b9 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" "$BACKEND" || 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 8fa158fcd..66bd47dcb 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" "$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 5f6c9f125..7e078d73f 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 @@ -410,15 +415,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 +457,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 @@ -579,6 +602,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 @@ -1181,21 +1211,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 +1275,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 @@ -1820,9 +1891,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 @@ -2441,7 +2519,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 +2783,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 diff --git a/tests/tools/console-instance.sh b/tests/tools/console-instance.sh index 5d988f9ff..58fe08d40 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" "$BACKEND" 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 58cc23de4..b2b582694 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" "$BACKEND" || { + 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 a6f507fae..324fc2242 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" none || 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 c6e3dadf4..27a93f0bc 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,14 @@ 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" "$_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 $? fi @@ -661,6 +669,7 @@ cleanup() { rig_release_port rig_drop_bridge rig_drop_standing + rig_drop_drive return 0 } trap cleanup EXIT INT TERM HUP @@ -700,7 +709,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 +764,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 872487a59..e348aa58a 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 @@ -258,10 +258,31 @@ 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" ] && - 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 +504,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 +577,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 594dce1d1..44323cdba 100755 --- a/tools/classicwb.sh +++ b/tools/classicwb.sh @@ -198,6 +198,13 @@ 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" "$BACKEND" 0 || + exit $? + say model "$MODEL" say variant "$VARIANT" say classicwb "$DIST" @@ -256,7 +263,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 +415,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 +866,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 +915,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 +925,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 25873c757..0aa7fd70d 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" "$BACKEND" 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 637e4da5e..ff031f3ed 100755 --- a/tools/emu-rig-lock.sh +++ b/tools/emu-rig-lock.sh @@ -367,6 +367,114 @@ 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 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 +# 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 backend [wait] + [ -n "${1:-}" ] || { echo "rig_claim_drive: no drive named" >&2; return 2; } + [ -z "$RIG_DRIVE_FD" ] || return 0 + 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") + 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() {