. "$BST_LIB" require pstree.psmisc psmisc/present # psmisc ships its binaries .psmisc-suffixed and every case below calls them that # way ON PURPOSE. busybox carries its own killall and fuser applets, and busybox # ash resolves a built-in applet AHEAD of $PATH -- so a bare `killall` is # busybox's even with the update-alternatives symlink in place. Invoking # killall.psmisc / fuser.psmisc is the only way to prove THIS port rather than # busybox. pstree has no busybox applet but is called the same way for symmetry. # # All three tools opendir("/proc") and walk every pid, and fuser additionally # reads /proc//{fd,maps,cwd,root,exe} per process -- the shape # brintos/linux#9 breaks for procps, whose top/vmstat/pgrep and full `ps -e` scan # hang and are recorded as skips in 85-procps.test. It does NOT bite psmisc on # this base: each scan below was measured 8/8 clean over repeated runs. So these # cases RUN rather than being skipped, which is the point -- if #9 ever reaches # this scan, these are the cases that will say so instead of hiding it. # --- 1. identity: all three tools run, and they are psmisc's ---------------- check_out psmisc/pstree-version "PSmisc" pstree.psmisc --version check_out psmisc/killall-version "PSmisc" killall.psmisc --version check_out psmisc/fuser-version "PSmisc" fuser.psmisc -V # --- 2. packaging: the bare names resolve to this port ---------------------- check_eq psmisc/alt-pstree "pstree.psmisc" "$(readlink /usr/bin/pstree)" check_eq psmisc/alt-killall "killall.psmisc" "$(readlink /usr/bin/killall)" check_eq psmisc/alt-fuser "fuser.psmisc" "$(readlink /usr/bin/fuser)" # --- 3. pstree: a full /proc walk that renders a tree ----------------------- check psmisc/pstree-runs pstree.psmisc check_out psmisc/pstree-pid1 "(1)" pstree.psmisc -p # pstree must see its OWN pid -- proof the tree is a live scan of /proc rather # than a cached or static answer. out=$(pstree.psmisc -p 2>&1) case "$out" in *pstree.psmisc*) bst_ok psmisc/pstree-self ;; *) bst_notok psmisc/pstree-self "own pid absent from tree: $(_bst_1line "$out")" ;; esac # --- 4. a process WE create, then found by name and by the file it holds ---- # This is what psmisc adds over procps: procps answers questions ABOUT # processes, psmisc acts on them BY NAME and resolves them FROM A FILE. Build a # process with a unique comm holding a known fd, so both claims are checked # against a target whose identity we control rather than an ambient process. tmp=${TMPDIR:-/tmp} uniq=bstpsm$$ # unique comm (<=15 chars, so it survives /proc//comm) lnk=$tmp/$uniq # comm comes from the exec'd basename, so a symlink is enough dat=$tmp/bstpsm-dat.$$ fifo=$tmp/bstpsm-fifo.$$ unused=$tmp/bstpsm-unused.$$ rm -f "$lnk" "$dat" "$fifo" "$unused" echo psmisc-selftest > "$dat" : > "$unused" mkfifo "$fifo" 2>/dev/null ln -sf "$(command -v cat)" "$lnk" # The child holds fd 3 on $dat (a regular file), then blocks opening $fifo, # which has no writer. `exec` keeps its pid, so $child is the process psmisc # must find. ( exec 3< "$dat"; exec "$lnk" "$fifo" ) > /dev/null 2>&1 & child=$! # Settle WITHOUT sleep: relative timers are broken on this runtime # (linux-in-the-browser#120 -- `sleep N` returns in ~1s and `timeout` hangs), so # spin on the wall clock, which does advance. Fork-free: read is a builtin. read u _ < /proc/uptime; t0=${u%.*} while :; do read u _ < /proc/uptime; [ $(( ${u%.*} - t0 )) -ge 2 ] && break; done # killall matches by NAME across a full /proc scan; -s 0 signals nothing. check psmisc/killall-by-name killall.psmisc -s 0 "$uniq" # ...and pstree sees that same process in its tree. out=$(pstree.psmisc -p 2>&1) case "$out" in *"$uniq"*) bst_ok psmisc/pstree-sees-child ;; *) bst_notok psmisc/pstree-sees-child "$uniq absent: $(_bst_1line "$out")" ;; esac # fuser resolves a process from a file it holds open -- the question nothing # else on this image can ask. Compare the reported pid to the one we spawned; # parse it out rather than substring-matching, so the pid cannot be matched # accidentally against the path. fout=$(fuser.psmisc "$dat" 2>&1) fpid=$(printf '%s\n' "$fout" | sed 's/^[^:]*://' | tr -dc '0-9') check_eq psmisc/fuser-open-fd "$child" "$fpid" # fuser reports pid 1 for the executable it is running, with the 'e' access type. check_out psmisc/fuser-exe "1e" fuser.psmisc /bin/busybox # Discrimination, both directions: a file nobody holds reports nothing (rc 1)... check_rc psmisc/fuser-absent 1 fuser.psmisc "$unused" # ...and a process blocked INSIDE open(2) has no fd installed yet, so fuser # correctly does not attribute the fifo to it. Asserted so that a future change # making this "find" something is noticed rather than welcomed. check_rc psmisc/fuser-blocked-open 1 fuser.psmisc "$fifo" # killall really delivers the signal, not just a match: SIGTERM by name exits 0. check psmisc/killall-signals killall.psmisc "$uniq" read u _ < /proc/uptime; t0=${u%.*} while :; do read u _ < /proc/uptime; [ $(( ${u%.*} - t0 )) -ge 2 ] && break; done # ...and the target is no longer running. Read the state out of /proc instead of # calling `wait`: on this runtime a blocking waitpid can return EINTR rather # than harvesting the zombie it already has (brintos/linux#39, the same stale # signal-mask family as #9), which would hang this suite. `Z` (terminated, # awaiting reap) and a vanished entry are both "the signal landed"; only a still # -sleeping target means killall failed to deliver. For the same reason this does # NOT assert `killall -s 0` now fails -- a zombie's /proc entry still answers, # so that check would be a false negative rather than a real one. st=$(cut -d' ' -f3 "/proc/$child/stat" 2>/dev/null) case "$st" in ""|Z) bst_ok psmisc/killall-target-dead ;; *) bst_notok psmisc/killall-target-dead "target state=$st (expected Z or gone)" ;; esac rm -f "$lnk" "$dat" "$fifo" "$unused"