diff --git a/apps/wolfsshd/test/run_all_sshd_tests.sh b/apps/wolfsshd/test/run_all_sshd_tests.sh index b1b6ae13..edfc8dd6 100755 --- a/apps/wolfsshd/test/run_all_sshd_tests.sh +++ b/apps/wolfsshd/test/run_all_sshd_tests.sh @@ -97,12 +97,45 @@ port_in_use() { (exec 3<>"/dev/tcp/127.0.0.1/$1") 2>/dev/null } +# Where blocks are claimed. Sticky and world-writable like /tmp itself, so a +# run as the invoking user and a run under sudo can claim from the same pool. +PORT_LOCK_ROOT="${TMPDIR:-/tmp}/wolfssh-sshd-ports" +mkdir -p "$PORT_LOCK_ROOT" 2>/dev/null +chmod 1777 "$PORT_LOCK_ROOT" 2>/dev/null || true + +# Take a block by creating its directory. mkdir is atomic, so of two runners +# racing for one block exactly one wins; probing alone could not do this, +# because both could see the same eight ports free before either had bound +# one. The claim is held for the whole run and released in run_teardown. +claim_port_block() { + local lock owner + lock="$PORT_LOCK_ROOT/$1" + if mkdir "$lock" 2>/dev/null; then + echo $$ > "$lock/pid" 2>/dev/null + return 0 + fi + # A claim whose owner is gone is stale. Without this a run killed before + # its teardown would take its block out of circulation permanently. Two + # runners can reach this at once; the loser's mkdir just fails and it + # moves on to the next block. + owner=`cat "$lock/pid" 2>/dev/null` + if [ -n "$owner" ] && ! kill -0 "$owner" 2>/dev/null; then + rm -rf "$lock" 2>/dev/null + if mkdir "$lock" 2>/dev/null; then + echo $$ > "$lock/pid" 2>/dev/null + return 0 + fi + fi + return 1 +} + find_port_block() { local start i n base busy start=$(( $$ % PORT_BLOCK_COUNT )) for (( i = 0; i < PORT_BLOCK_COUNT; i++ )); do base=$(( PORT_BLOCK_FIRST \ + ((start + i) % PORT_BLOCK_COUNT) * PORT_BLOCK_SIZE )) + claim_port_block "$base" || continue busy=0 for (( n = 0; n < PORT_BLOCK_SIZE; n++ )); do if port_in_use $(( base + n )); then @@ -114,11 +147,15 @@ find_port_block() { printf '%s' "$base" return 0 fi + # Claimed but unusable: something outside the suite holds a port in + # it. Give the claim back rather than sit on a block we cannot use. + rm -rf "$PORT_LOCK_ROOT/$base" 2>/dev/null done return 1 } PORT_BASE=`find_port_block` +PORT_LOCK_DIR="$PORT_LOCK_ROOT/$PORT_BASE" if [ -z "$PORT_BASE" ]; then echo "Error: no free block of $PORT_BLOCK_SIZE ports found starting at" \ "$PORT_BLOCK_FIRST." @@ -172,6 +209,9 @@ run_teardown() { stop_all_wolfsshd || true fi rm -f "$WOLFSSHD_TEST_PIDFILE" || true + if [ -n "$PORT_BASE" ]; then + rm -rf "$PORT_LOCK_DIR" || true + fi } trap run_teardown EXIT diff --git a/apps/wolfsshd/test/sshd_ossh_cert_test.sh b/apps/wolfsshd/test/sshd_ossh_cert_test.sh index 937f2753..a2049935 100755 --- a/apps/wolfsshd/test/sshd_ossh_cert_test.sh +++ b/apps/wolfsshd/test/sshd_ossh_cert_test.sh @@ -49,6 +49,13 @@ command -v ssh-keygen >/dev/null 2>&1 || \ skip "ssh-keygen not found, skipping OpenSSH cert test" WORK=$(mktemp -d) +# Checked before the trap below is installed: an empty WORK would reduce its +# pattern to "wolfsshd .*", which matches every wolfsshd on the machine and is +# exactly the host-wide teardown this suite no longer does. +if [ -z "$WORK" ] || [ ! -d "$WORK" ]; then + echo "FAIL: could not create a work directory for the OpenSSH cert test" + exit 1 +fi # Matched on $WORK, this run's own mktemp dir, not on the config basename: # "sshd_config_ossh" appears in every concurrent run's command line too, # so the basename pattern tore down another run's daemon along with this diff --git a/apps/wolfsshd/test/start_sshd.sh b/apps/wolfsshd/test/start_sshd.sh index 729607f5..599ef0bf 100755 --- a/apps/wolfsshd/test/start_sshd.sh +++ b/apps/wolfsshd/test/start_sshd.sh @@ -134,8 +134,10 @@ EOF sleep 0.1 done # A pid file left by a daemon that has since died is worse than none: - # stop_wolfsshd would kill whatever has been given that pid since. - if [ -n "$PID" ] && ! sudo kill -0 "$PID" 2>/dev/null; then + # stop_wolfsshd would kill whatever has been given that pid since. Ask + # what the process is, not merely whether it exists: "kill -0" answers + # the second question only, and the pid may have been recycled. + if [ -n "$PID" ] && ! pgrep -x wolfsshd | grep -qx -- "$PID"; then PID="" fi else @@ -157,6 +159,22 @@ EOF sleep 0.1 done fi + # wolfSSHd writes its PID file in StartSSHD() immediately before + # tcp_listen(), so the pid appearing does not mean the socket accepts yet. + # A caller that connects the moment this returns -- several do, with no + # sleep -- would be refused, and the daemon's log would show no connection + # at all. Wait for the daemon's own listening line, matched on its pid so + # that a previous daemon's line in this appended log cannot satisfy it. + if [ -n "$PID" ]; then + for i in $(seq 1 100); do + if sudo grep -qF "[PID $PID]: [SSHD] Listening on port" \ + ./log.txt 2>/dev/null; then + break + fi + sleep 0.1 + done + fi + printf "SSHD running on PID $PID\n" # Record the daemon in the run's registry, if the runner set one up. Test