diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-19 23:28:22 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-09-19 23:28:22 -0300 |
| commit | ba996acfcad1698adbf4a1834fe50e73b1c6cab9 (patch) | |
| tree | 282ba00ce5b10d7416aecb9f2f0f0a439340a57d /9player/test/adv_ns_process.sh | |
| parent | b05abcba3ea09ea106ad28364c6e40a3ec31b890 (diff) | |
| download | cloud9-ba996acfcad1698adbf4a1834fe50e73b1c6cab9.tar.gz cloud9-ba996acfcad1698adbf4a1834fe50e73b1c6cab9.zip | |
Rename programs: 9player -> 9ns, introspect -> 9proc, app -> web (9web)
Directories, binaries, build options (-D9ns, -D9proc), step names, module
name (9proc), thread and fs names, env var NINEPLAYER_MOUNT -> NINE_MOUNT,
docs and test scripts. Browser assets move to web/static.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Diffstat (limited to '9player/test/adv_ns_process.sh')
| -rwxr-xr-x | 9player/test/adv_ns_process.sh | 202 |
1 files changed, 0 insertions, 202 deletions
diff --git a/9player/test/adv_ns_process.sh b/9player/test/adv_ns_process.sh deleted file mode 100755 index 4ce0ae8..0000000 --- a/9player/test/adv_ns_process.sh +++ /dev/null @@ -1,202 +0,0 @@ -#!/usr/bin/env bash -# Adversarial regression tests for 9player/src/ns.zig and 9player/src/main.zig: process, -# namespace, signal and CLI handling. Real namespaces, real FUSE. -# Usage: bash 9player/test/adv_ns_process.sh <9player> <introspect> (part of zig build 9player-adv) -# Exit 0 on success (or when the machine cannot run the tests), 1 on failure. -set -u - -PLAYER=$(realpath "${1:?path to 9player}") -INTROSPECT=$(realpath "${2:?path to introspect}") -# Unix socket paths are limited to ~107 bytes; keep the temp dir short. -TMP=$(mktemp -d "${TMPDIR:-/tmp}/9padv.XXXXXX") -PIDS=() -FAILED=0 -PASSED=0 - -cleanup() { - for p in "${PIDS[@]:-}"; do [ -n "$p" ] && kill "$p" 2>/dev/null; done - rm -rf "$TMP" -} -trap cleanup EXIT - -if ! unshare -Urm true 2>/dev/null; then echo "SKIP: unprivileged user namespaces unavailable"; exit 0; fi -if [ ! -c /dev/fuse ]; then echo "SKIP: /dev/fuse missing"; exit 0; fi - -pass() { PASSED=$((PASSED + 1)); echo "ok - $1"; } -fail() { FAILED=$((FAILED + 1)); echo "FAIL - $1"; shift; [ $# -gt 0 ] && printf ' %s\n' "$@"; } -expect_eq() { if [ "$2" = "$3" ]; then pass "$1"; else fail "$1" "expected: $(printf %q "$2")" "actual: $(printf %q "$3")"; fi; } -expect_contains() { case "$3" in *"$2"*) pass "$1" ;; *) fail "$1" "missing: $(printf %q "$2")" "in: $(printf %q "$3")" ;; esac; } - -SOCK=$TMP/s -"$INTROSPECT" --unix "$SOCK" & -PIDS+=($!) -for _ in $(seq 1 100); do [ -S "$SOCK" ] && break; sleep 0.05; done -[ -S "$SOCK" ] || { echo "introspect did not create $SOCK"; exit 1; } -MI_BEFORE=$(grep -v " $TMP" /proc/self/mountinfo | sort) - -TIMEOUT=$(command -v timeout) -run() { "$TIMEOUT" 60 "$PLAYER" --unix "$SOCK" "$@"; } - -echo "# CLI" -expect_eq "--help goes to stdout, exit 0" "Usage: 9player" "$(run --help 2>/dev/null | head -1 | cut -c1-14; )" -expect_eq "--help exit code" "0" "$("$PLAYER" --help >/dev/null 2>&1; echo $?)" -expect_eq "--version on stdout" "9player" "$("$PLAYER" --version 2>/dev/null | cut -d' ' -f1)" -expect_eq "single-dash typo is a usage error, not a program" "125" "$(run -mount /x -- true 2>/dev/null; echo $?)" -expect_contains "single-dash typo message" "unknown option -mount" "$(run -mount /x -- true 2>&1)" -expect_eq "--unix= empty is a usage error" "125" "$("$PLAYER" --unix= -- true 2>/dev/null; echo $?)" -expect_contains "--unix= message" "socket path" "$("$PLAYER" --unix= -- true 2>&1)" -expect_eq "--mount '' is a usage error" "125" "$(run --mount '' -- true 2>/dev/null; echo $?)" -expect_eq "--msize huge rejected" "125" "$(run --msize 4294967295 -- true 2>/dev/null; echo $?)" -expect_eq "--msize 16 MiB accepted" "ok" "$(run --msize 16777216 -- sh -c 'echo ok')" -expect_contains "empty program name is reported" "empty program name" "$(run -- '' 2>&1)" -expect_eq "empty program name exit" "125" "$(run -- '' 2>/dev/null; echo $?)" -expect_eq "empty \$SHELL falls back to /bin/sh" "0" "$(SHELL= run -- </dev/null >/dev/null 2>&1; echo $?)" -expect_eq "--fd with a closed descriptor fails early" "125" "$("$PLAYER" --fd 987 -- true 2>/dev/null; echo $?)" -expect_contains "--fd bad descriptor message" "--fd 987: EBADF" "$("$PLAYER" --fd 987 -- true 2>&1)" - -echo "# exec failures" -expect_eq "not found is 127" "127" "$(run -- no-such-program-9player 2>/dev/null; echo $?)" -expect_eq "PATH element that is a file: still 127" "127" "$(PATH=/etc/passwd run -- true 2>/dev/null; echo $?)" -expect_contains "PATH element that is a file: message" "exec true: E" "$(PATH=/etc/passwd run -- true 2>&1)" -printf '#!/bin/sh\necho no\n' >"$TMP/nx"; chmod 644 "$TMP/nx" -expect_eq "non-executable is 126" "126" "$(run -- "$TMP/nx" 2>/dev/null; echo $?)" -mkdir -p "$TMP/p1" "$TMP/p2"; cp "$TMP/nx" "$TMP/p1/prog"; printf '#!/bin/sh\necho right\n' >"$TMP/p2/prog"; chmod 755 "$TMP/p2/prog" -expect_eq "non-executable first in PATH, executable later" "right" "$(PATH=$TMP/p1:$TMP/p2 run -- prog)" -expect_eq "non-executable only in PATH is 126" "126" "$(PATH=$TMP/p1 run -- prog 2>/dev/null; echo $?)" -expect_eq "argv[0] preserved" "sh" "$(run -- sh -c 'echo $0')" -expect_eq "PATH unset uses default" "ok" "$(env -u PATH "$PLAYER" --unix "$SOCK" -- sh -c 'echo ok')" - -echo "# fd hygiene" -# 9player passes inherited descriptors through untouched, so compare with what a -# plain child of this script sees (the runner may itself hold extra fds). -FD_LIST='ls /proc/self/fd | grep -v "^3$" | sort -n | tr "\n" " " | sed "s/ $//"' -FD_BASE=$(sh -c "$FD_LIST") -expect_eq "no extra fds in the program (unix)" "$FD_BASE" "$(run -- sh -c "$FD_LIST")" -expect_eq "no extra fds in the program (spawn)" "$FD_BASE" "$("$TIMEOUT" 60 "$PLAYER" --spawn "$INTROSPECT --stdio" -- sh -c "$FD_LIST")" -expect_eq "--fd transport does not leak into the program" "0 1 2" "$(python3 - "$PLAYER" "$SOCK" <<'EOF' -import socket, subprocess, sys, os -s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM); s.connect(sys.argv[2]) -r = subprocess.run([sys.argv[1], "--fd", str(s.fileno()), "--", "sh", "-c", - 'ls /proc/self/fd | grep -v "^3$" | sort -n | tr "\n" " " | sed "s/ $//"'], - pass_fds=[s.fileno()], capture_output=True, text=True) -print(r.stdout.strip()) -EOF -)" - -echo "# signals" -expect_eq "SIGTERM forwarded" "143" "$(run -- sh -c 'kill -TERM $PPID; sleep 5; echo alive' >/dev/null 2>&1; echo $?)" -expect_eq "SIGHUP forwarded" "129" "$(run -- sh -c 'kill -HUP $PPID; sleep 5; echo alive' >/dev/null 2>&1; echo $?)" -expect_eq "SIGINT to 9player is ignored while the child lives" "still-here" "$(run -- sh -c 'kill -INT $PPID; sleep 0.3; echo still-here')" -# Ctrl-C from the tty must not kill the --spawn server (same process group). -expect_eq "Ctrl-C on the tty leaves the --spawn server alive" "ok" "$(timeout 30 python3 - "$PLAYER" "$INTROSPECT" <<'EOF' -import os, pty, sys, time, select -P, I = sys.argv[1], sys.argv[2] -prog = ["python3", "-c", """ -import os, signal, sys, time -signal.signal(signal.SIGINT, lambda *a: None) -m = os.environ['NINEPLAYER_MOUNT'] -open(m + '/build/optimize').read() -sys.stdin.readline() -try: - open(m + '/build/optimize').read(); print('ok', flush=True) -except Exception as e: - print('mount dead:', e, flush=True) -"""] -pid, fd = pty.fork() -if pid == 0: - os.execv(P, [P, "--spawn", I + " --stdio", "--"] + prog) -out = b"" -def rd(t): - global out - end = time.time() + t - while time.time() < end: - r, _, _ = select.select([fd], [], [], 0.1) - if r: - try: d = os.read(fd, 4096) - except OSError: return - if not d: return - out += d -rd(1.5); os.write(fd, b"\x03"); rd(0.7); os.write(fd, b"\n"); rd(3) -os.waitpid(pid, 0) -print(out.decode(errors="replace").replace("^C", "").strip().splitlines()[-1] if out.strip() else "no output") -EOF -)" -# The --spawn server dying mid-session is reaped (no zombie) and does not end the session. -OUT=$("$TIMEOUT" 60 "$PLAYER" --spawn "$INTROSPECT --stdio" -- sh -c 'srv=$(cat $NINEPLAYER_MOUNT/runtime/pid); kill -TERM $srv; sleep 0.5; st=$(ps -o stat= -p $srv 2>/dev/null); echo "${st:-gone}"; exit 5' 2>/dev/null); RC=$? -expect_eq "server death mid-session: exit status still the child's, server reaped (no zombie)" "5 gone" "$RC $OUT" -# A server that never answers: once the child is dead, SIGTERM must end 9player. -cat >"$TMP/hang.py" <<'EOF' -import struct, os, sys, time -def rd(n): - b = b"" - while len(b) < n: - c = os.read(0, n - len(b)) - if not c: sys.exit(0) - b += c - return b -while True: - size, = struct.unpack("<I", rd(4)); body = rd(size - 4) - typ, tag = struct.unpack("<BH", body[:3]) - if typ == 100: - msize, = struct.unpack("<I", body[3:7]); v = b"9P2000" - r = struct.pack("<BHI", 101, tag, msize) + struct.pack("<H", len(v)) + v - elif typ == 104: - r = struct.pack("<BH", 105, tag) + bytes([0x80]) + struct.pack("<IQ", 0, 0) - else: - time.sleep(3600) - os.write(1, struct.pack("<I", 4 + len(r)) + r) -EOF -"$PLAYER" --spawn "python3 $TMP/hang.py" -- true 2>/dev/null & -HP=$! -sleep 1; kill -TERM $HP -START=$(date +%s) -for _ in $(seq 1 100); do kill -0 $HP 2>/dev/null || break; sleep 0.1; done -if kill -0 $HP 2>/dev/null; then kill -KILL $HP; RC=hung; else wait $HP; RC=$?; fi -expect_eq "hung server: one SIGTERM ends 9player once the child is dead (watchdog)" "143" "$RC" -expect_eq "hung server: exit was prompt" "yes" "$([ $(( $(date +%s) - START )) -lt 8 ] && echo yes)" -pkill -f "$TMP/hang.py" 2>/dev/null - -echo "# child/parent protocol" -if command -v strace >/dev/null 2>&1 && strace -qq -e trace=none true 2>/dev/null; then - expect_eq "child killed before handoff" "125" "$(strace -f -qq -e trace=unshare -e inject=unshare:signal=KILL -o /dev/null timeout 20 "$PLAYER" --unix "$SOCK" -- true 2>/dev/null; echo $?)" - expect_contains "child killed before handoff: message" "child exited before reporting" "$(strace -f -qq -e trace=unshare -e inject=unshare:signal=KILL -o /dev/null timeout 20 "$PLAYER" --unix "$SOCK" -- true 2>&1)" - expect_eq "status handoff fails" "125" "$(strace -f -qq -e trace=sendmsg -e inject=sendmsg:error=EPIPE -o /dev/null timeout 20 "$PLAYER" --unix "$SOCK" -- true 2>/dev/null; echo $?)" - # recvmsg skipped (returns 1 without the fd): the child must be killed, not exec'd onto a dead mount. - OUT=$(strace -f -qq -e trace=recvmsg -e inject=recvmsg:retval=1:when=1 -o /dev/null timeout 20 "$PLAYER" --unix "$SOCK" -- sh -c 'echo child-ran' 2>&1; echo "rc=$?") - expect_contains "truncated fd handoff: child not exec'd" "rc=125" "$OUT" - expect_eq "truncated fd handoff: program never ran" "no" "$(case "$OUT" in *child-ran*) echo yes;; *) echo no;; esac)" - expect_contains "fuse mount failure is reported" "mount fuse: EPERM" "$(strace -f -qq -e trace=mount -e inject=mount:error=EPERM:when=2 -o /dev/null timeout 20 "$PLAYER" --unix "$SOCK" --mount "$TMP/mp" -- true 2>&1)" -else - echo "skip - strace unavailable (child failure injection)" -fi -expect_contains "fork failure is reported" "fork: E" "$(python3 -c " -import resource, os -resource.setrlimit(resource.RLIMIT_NPROC, (1, 1)) -os.execv('$PLAYER', ['$PLAYER', '--unix', '$SOCK', '--', 'true'])" 2>&1)" - -echo "# mountpoint policy" -ln -s /nonexistent "$TMP/dangling" -expect_contains "dangling symlink mountpoint" "dangling symlink" "$(run --mount "$TMP/dangling" -- true 2>&1)" -expect_eq "refuse to shadow / via /proc/self/root" "125" "$(run --mount /proc/self/root/x9p -- true 2>/dev/null; echo $?)" -expect_contains "refuse to shadow / via /proc/self/root: message" "refusing to shadow /" "$(run --mount /proc/self/root/x9p -- true 2>&1)" -expect_eq "refuse to shadow under /proc" "125" "$(run --mount /proc/self/fd/x9p -- true 2>/dev/null; echo $?)" -if [ "$(ls -A /usr/lib | wc -l)" -gt 4096 ]; then - expect_contains "parent with >4096 entries refused" "more than 4096 entries" "$(run --mount /usr/lib/x9p -- true 2>&1)" -else - echo "skip - no root-owned directory with >4096 entries" -fi -expect_eq "shadowed /run keeps its entries" "$(ls -A /run | sort | tr '\n' ' ')" "$(run --mount /run/x9p -- sh -c 'ls -A /run | grep -v "^x9p$" | sort | tr "\n" " "')" -expect_eq "mountpoint with spaces" "ok" "$(mkdir -p "$TMP/with space" && run --mount "$TMP/with space" -- sh -c '[ -f "$NINEPLAYER_MOUNT/README" ] && echo ok')" -expect_eq "mountpoint is a file" "125" "$(run --mount "$TMP/nx" -- true 2>/dev/null; echo $?)" - -echo "# leaks" -for i in $(seq 1 30); do run -- sh -c 'cat $NINEPLAYER_MOUNT/build/optimize >/dev/null' 2>/dev/null; done -BG=(); for i in $(seq 1 10); do ( run -- sh -c 'cat $NINEPLAYER_MOUNT/build/optimize >/dev/null' 2>/dev/null ) & BG+=($!); done; wait "${BG[@]}" # not a bare wait: that would also wait for the server -sleep 0.3 -expect_eq "no stray 9player processes" "" "$(pgrep -f "^$PLAYER " | tr '\n' ' ')" -expect_eq "no stray --stdio servers" "" "$(pgrep -f "$INTROSPECT --stdio" | tr '\n' ' ')" -expect_eq "host mount table untouched" "same" "$([ "$MI_BEFORE" = "$(grep -v " $TMP" /proc/self/mountinfo | sort)" ] && echo same || echo changed)" - -echo -echo "passed=$PASSED failed=$FAILED" -[ "$FAILED" -eq 0 ] |
