#!/usr/bin/env bash # verify-kill-all-processes.sh # Falsification script for https://heatware.net/linux/kill-all-processes/ # # Runs standalone. It creates its own scratch processes (named hw_w13_verify__*), # signals only those, and removes them on exit. It NEVER runs `kill -9 -1`, `pkill -u` # or a bare `killall`. If the article HTML is not sitting next to it, the content # regression greps are SKIPped, not failed. # # Exit status: 0 unless a claim in the article is contradicted. set -u set +m # keep bash job-control chatter ("Killed: 9") out of the PASS/FAIL stream PASS=0; FAIL=0; SKIP=0; CONTENT=0 TAG="hw_w13_verify_$$" WORK="$(mktemp -d "${TMPDIR:-/tmp}/${TAG}.XXXXXX")" ART="$(cd "$(dirname "$0")" 2>/dev/null && pwd)/kill-all-processes.html" OS="$(uname -s)" cleanup() { pkill -f "$TAG" >/dev/null 2>&1 sleep 0.3 rm -rf "$WORK" } trap cleanup EXIT INT TERM pass() { PASS=$((PASS+1)); echo "PASS $*"; } fail() { FAIL=$((FAIL+1)); echo "FAIL $*"; } skip() { SKIP=$((SKIP+1)); echo "SKIP $*"; } content() { CONTENT=$((CONTENT+1)); echo "CONTENT $*"; } echo "# platform: $OS $(uname -m) | scratch tag: $TAG" echo # ---------------------------------------------------------------- 1. kill -u # Article: "kill -u username ... does not exist. kill takes a signal and PIDs. # It has no user filter, in either the bash builtin or the standalone binary." out="$( { kill -u nobody; } 2>&1 )" if printf '%s' "$out" | grep -qiE 'invalid signal|unknown signal|illegal|usage|not a valid'; then pass "shell builtin rejects 'kill -u nobody': $(printf '%s' "$out" | head -1)" else fail "shell builtin accepted 'kill -u nobody' (article says there is no -u option); output: $out" fi if [ -x /bin/kill ]; then out="$(/bin/kill -u nobody 2>&1 | head -1)" if printf '%s' "$out" | grep -qiE 'invalid signal|unknown signal|illegal|usage'; then pass "/bin/kill rejects -u: $out" else fail "/bin/kill accepted -u; output: $out" fi else skip "/bin/kill not present on this system" fi # ------------------------------------------------- 2. pkill -f over-matching # Article: "-f matches the pattern as a substring of the entire command line", # so a log tail and an unrelated supervisor die alongside the intended target. mkw() { printf '#!/bin/sh\nwhile :; do sleep 1; done\n' > "$WORK/$1" chmod +x "$WORK/$1" } mkw "${TAG}_worker" mkw "${TAG}_worker_supervisor" : > "$WORK/${TAG}_worker.log" "$WORK/${TAG}_worker" >/dev/null 2>&1 & disown 2>/dev/null || true "$WORK/${TAG}_worker_supervisor" >/dev/null 2>&1 & disown 2>/dev/null || true tail -f "$WORK/${TAG}_worker.log" >/dev/null 2>&1 & disown 2>/dev/null || true sleep 1 before="$(pgrep -f "$TAG" 2>/dev/null | wc -l | tr -d ' ')" if [ "${before:-0}" -ge 3 ]; then pkill -f "${TAG}_worker" >/dev/null 2>&1 sleep 1 after="$(pgrep -f "$TAG" 2>/dev/null | wc -l | tr -d ' ')" if [ "${after:-1}" -eq 0 ]; then pass "pkill -f killed all $before processes whose command line contained the pattern, not just the target" else fail "expected every command-line match to die; $after of $before survived" fi else skip "could not start 3 scratch processes (found $before) - pkill over-match not tested" fi # ------------------------------------- 3. SIGKILL skips the program's cleanup # Article: "after plain kill : [none] / after kill -9 : hw_w13_job...tmp ...lock" cat > "$WORK/${TAG}_job" </dev/null echo "partial results" > "\$TMP" cleanup() { rm -f "\$TMP"; rmdir "\$LOCK" 2>/dev/null; exit 0; } trap cleanup TERM INT while :; do sleep 1; done JOB chmod +x "$WORK/${TAG}_job" leftovers() { ls -d "$WORK/${TAG}_job."*.tmp "$WORK/${TAG}_job.lock" 2>/dev/null | wc -l | tr -d ' '; } "$WORK/${TAG}_job" >/dev/null 2>&1 & JOBPID=$! disown "$JOBPID" 2>/dev/null || true sleep 1 during="$(leftovers)" kill "$JOBPID" 2>/dev/null; sleep 1 after_term="$(leftovers)" "$WORK/${TAG}_job" >/dev/null 2>&1 & JOBPID=$! disown "$JOBPID" 2>/dev/null || true sleep 1 kill -9 "$JOBPID" 2>/dev/null; sleep 1 after_kill="$(leftovers)" rm -f "$WORK/${TAG}_job."*.tmp; rmdir "$WORK/${TAG}_job.lock" 2>/dev/null if [ "${during:-0}" -lt 2 ]; then skip "scratch job never created its lock/temp files - SIGTERM vs SIGKILL cleanup not tested" elif [ "${after_term:-9}" -eq 0 ] && [ "${after_kill:-0}" -ge 1 ]; then pass "SIGTERM ran the trap and removed $during leftovers; SIGKILL left $after_kill behind (lock file survives the process)" else fail "expected 0 leftovers after SIGTERM and >=1 after SIGKILL; got $after_term and $after_kill" fi # --------------------------------------------- 4. pgrep/pkill and ancestors # Article: BSD excludes the caller's ancestors by default; procps-ng does not. case "$OS" in Darwin|*BSD) noanc="$(pgrep -u "$(id -u)" 2>/dev/null | grep -cx "$$")" anc="$(pgrep -a -u "$(id -u)" 2>/dev/null | grep -cx "$$")" if [ "${noanc:-1}" -eq 0 ] && [ "${anc:-0}" -ge 1 ]; then pass "BSD pgrep excludes the calling shell (PID $$) by default and includes it under -a, as the man page states" else fail "expected the calling shell to be absent without -a and present with -a; got $noanc and $anc" fi ;; Linux) if pgrep --help 2>&1 | grep -q -- '--ignore-ancestors'; then pass "procps-ng pgrep documents ancestor exclusion as the opt-in flag -A/--ignore-ancestors, i.e. not the default" else skip "this pgrep does not advertise --ignore-ancestors; ancestor default not asserted" fi ;; *) skip "ancestor-exclusion check not defined for $OS" ;; esac # ------------------------------------------------ 5. killall is not portable # Article: BSD takes -m for a regex and rejects -r; GNU psmisc is the reverse. if command -v killall >/dev/null 2>&1; then m_out="$(killall -s -m "${TAG}_absent" 2>&1)" r_out="$(killall -s -r "${TAG}_absent" 2>&1)" case "$OS" in Darwin|*BSD) if ! printf '%s' "$m_out" | grep -qi 'unknown signal\|illegal option\|invalid option' \ && printf '%s' "$r_out" | grep -qi 'unknown signal\|illegal option\|invalid option'; then pass "BSD killall accepts -m and rejects -r: $(printf '%s' "$r_out" | head -1)" else fail "expected BSD killall to accept -m and reject -r; -m said [$m_out], -r said [$r_out]" fi ;; Linux) if printf '%s' "$r_out" | grep -qvi 'invalid option' \ && printf '%s' "$m_out" | grep -qi 'invalid option\|unrecognized'; then pass "GNU killall accepts -r and rejects -m, the mirror image of the BSD flags" else skip "this killall did not split -r/-m the documented GNU way; -m said [$m_out], -r said [$r_out]" fi ;; *) skip "killall flag check not defined for $OS" ;; esac if man killall 2>/dev/null | col -b 2>/dev/null | tr '\n\t' ' ' | tr -s ' ' \ | grep -q "a single dot will match any process running under the real UID of the caller"; then pass "local killall man page carries the quoted warning: 'a single dot will match any process running under the real UID of the caller'" else skip "local killall man page not readable, or does not carry the -m warning quoted in the article" fi else skip "killall not installed" fi # ----------------------------------------------------- 6. ps aux --sort=-%cpu # Article: --sort is a procps-ng extension; it fails on macOS and the BSDs. if ps aux --sort=-%cpu >/dev/null 2>&1; then case "$OS" in Linux) pass "ps aux --sort=-%cpu works here, as the article says it does on Linux" ;; *) fail "ps aux --sort=-%cpu succeeded on $OS; the article says BSD ps rejects it" ;; esac else case "$OS" in Linux) fail "ps aux --sort=-%cpu failed on Linux; the article says procps-ng supports it" ;; *) pass "ps aux --sort=-%cpu rejected on $OS, as the article says (--sort is procps-ng only)" ;; esac fi # ------------------------------------------------- 7. article content greps echo if [ -f "$ART" ]; then check_text() { if grep -qF "$2" "$ART"; then content "$1"; else fail "article text missing: $1"; fi } check_text "leads with the warning not to run kill -9 -1" \ "Do not run kill -9 -1." check_text "states -1 means every process the caller may signal" \ 'it means "every process I am allowed to signal", and -9 means they get no chance to save anything' check_text "states kill -u username does not exist" \ "That second one does not exist." check_text "publishes the command that produced the process count" \ '$ pgrep -u "$(id -u)" | wc -l' check_text "publishes 584 as the measured count, not a rounded illustration" \ "That is the blast radius of kill -9 -1" check_text "publishes the 584 vs 590 ancestor measurement as a whole claim" \ "pgrep -u UID returned 584 PIDs and did not include the calling shell; pgrep -a -u UID returned 590 and did" check_text "attributes the Linux pkill behaviour to the man page, not to a run" \ "Linux side cited from the man page, not executed" check_text "quotes the BSD killall System V paragraph" \ "is intended to be used by the system shutdown process only" check_text "quotes the -m caution verbatim" \ "a single dot will match any process running under the real UID of the caller" check_text "states kill -9 -1 was not executed" \ "kill -9 -1 was deliberately not run" check_text "links to the single-process article rather than repeating it" \ 'href="/linux/force-quit-process-ubuntu/"' else skip "kill-all-processes.html not next to this script - content regression greps not run" fi echo echo "PASS=$PASS FAIL=$FAIL SKIP=$SKIP CONTENT=$CONTENT" [ "$FAIL" -eq 0 ] || exit 1 exit 0