Two of Section F. Hooks are the pressure valve. "Can Panama also do X when the theme changes" is now a five-line file in ~/.config/panama/hooks rather than a fork, a feature request, or a patch somebody rebases forever. Each name takes a single file and a .d directory so several things can react without fighting over one, and a broken hook is reported and stepped over: somebody's script must never cost a theme change, an upgrade or a login. Wired at theme-set, post-upgrade and post-migrate. This is the thirty-line version of the plugin host the upstream ledger defers, and it has no API to keep stable beyond "we will run your script and tell you what happened". Testing it caught a real bug the reading would not have: run_one captured the script path but never shifted it off, so every hook got its own filename as $1 and the real arguments arrived one place late. A hook reading $1 as the colour scheme got a path. The crash watcher notices when a program dumps core and says so. Under GNOME, ABRT does this; here nothing did, and applications died silently, which is most of how "Linux is flaky" gets earned. Once per program per session is the entire design, not a nicety. This machine's portal backend crashes between eleven and sixty times a day, and a notification per crash would be one every few minutes for something nobody can act on. The first is news; the fortieth is why people turn notifications off. The health page keeps the running count. It waits for the notification server before reporting, because the crash most worth hearing about is the one that took the shell with it, and it names the executable rather than the kernel's comm field, which truncates at fifteen characters. Verified against real segfaults.
119 lines
5.2 KiB
Bash
Executable File
119 lines
5.2 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
# Telling somebody when a program crashes.
|
|
#
|
|
# On GNOME, ABRT says so. Under a hand-assembled Hyprland desktop nothing did,
|
|
# and applications died silently, which is most of how "Linux is flaky" gets
|
|
# earned. Fedora ships systemd-coredump by default, so the information was
|
|
# already there and nobody was reading it.
|
|
#
|
|
# What must hold:
|
|
#
|
|
# 1. Once per program per session. This machine's portal backend crashes
|
|
# between eleven and sixty times a day; a notification per crash would be
|
|
# one every few minutes for something nobody can act on. The first is
|
|
# news, the fortieth is why people turn notifications off.
|
|
# 2. Another user's crash is not reported. It is not this session's business
|
|
# and it would leak what they are running.
|
|
# 3. The program is named by its executable, not by the kernel's comm field,
|
|
# which is truncated to fifteen characters -- "panama-test-cra" for a
|
|
# program called panama-test-crasher.
|
|
# 4. It waits for the notification server. The crash most worth reporting is
|
|
# the one that took the shell down with it.
|
|
# 5. It follows from now rather than replaying the boot, so a session that
|
|
# starts after a crash does not open with a notification about something
|
|
# already lived through.
|
|
|
|
set -uo pipefail
|
|
|
|
repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
|
watcher="$repo_dir/bin/panama-crash-watch"
|
|
unit="$repo_dir/config/local/share/systemd/user/panama-crash-watch.service"
|
|
autostart="$repo_dir/config/dot/hypr/autostart.lua"
|
|
|
|
findings=()
|
|
note() { findings+=("$1"); }
|
|
|
|
[[ -x "$watcher" ]] || { printf 'crash watch contract: %s is not executable\n' "$watcher" >&2; exit 1; }
|
|
|
|
work="$(mktemp -d)"
|
|
trap 'rm -rf "$work"' EXIT
|
|
calls="$work/calls"
|
|
stub="$work/bin"
|
|
mkdir -p "$stub"
|
|
|
|
# Two crashes of one program, one of another, and one belonging to somebody
|
|
# else. journalctl is replaced by a stub that emits them and exits, so the
|
|
# watcher's follow loop terminates instead of hanging the test.
|
|
uid="$(id -u)"
|
|
cat >"$stub/journalctl" <<STUB
|
|
#!/usr/bin/env bash
|
|
printf '%s\n' \\
|
|
'{"COREDUMP_UID":"$uid","COREDUMP_EXE":"/usr/bin/panama-test-crasher","COREDUMP_COMM":"panama-test-cra"}' \\
|
|
'{"COREDUMP_UID":"$uid","COREDUMP_EXE":"/usr/bin/panama-test-crasher","COREDUMP_COMM":"panama-test-cra"}' \\
|
|
'{"COREDUMP_UID":"$uid","COREDUMP_EXE":"/usr/bin/other-program","COREDUMP_COMM":"other-program"}' \\
|
|
'{"COREDUMP_UID":"99999","COREDUMP_EXE":"/usr/bin/someone-elses","COREDUMP_COMM":"someone-elses"}'
|
|
STUB
|
|
chmod +x "$stub/journalctl"
|
|
|
|
cat >"$stub/notify-send" <<STUB
|
|
#!/usr/bin/env bash
|
|
printf '%s\n' "\$*" >>"$calls"
|
|
STUB
|
|
chmod +x "$stub/notify-send"
|
|
|
|
# The bus is already up, so the wait loop falls straight through.
|
|
cat >"$stub/busctl" <<'STUB'
|
|
#!/usr/bin/env bash
|
|
exit 0
|
|
STUB
|
|
chmod +x "$stub/busctl"
|
|
|
|
: >"$calls"
|
|
PATH="$stub:$PATH" timeout 20 "$watcher" >/dev/null 2>&1
|
|
|
|
# ── 1. Once per program ─────────────────────────────────────────────────────
|
|
|
|
crasher_notices="$(grep -c 'panama-test-crasher' "$calls" || true)"
|
|
(( crasher_notices == 1 )) \
|
|
|| note "a program that crashed twice produced $crasher_notices notifications; it must produce one per session"
|
|
|
|
# A different program is still news.
|
|
grep -q 'other-program' "$calls" \
|
|
|| note 'a second, different program crashing was not reported'
|
|
|
|
# ── 2. Somebody else's crash is not ours ────────────────────────────────────
|
|
|
|
grep -q 'someone-elses' "$calls" \
|
|
&& note "another user's crash was reported, which leaks what they are running"
|
|
|
|
# ── 3. The name is not the truncated one ────────────────────────────────────
|
|
|
|
grep -q 'panama-test-cra ' "$calls" \
|
|
&& note 'the notification uses the truncated kernel comm field rather than the executable name'
|
|
|
|
# ── 4 & 5. How it listens ───────────────────────────────────────────────────
|
|
|
|
grep -q 'org.freedesktop.Notifications' "$watcher" \
|
|
|| note 'the watcher does not wait for the notification server, so a shell crash would report to nobody'
|
|
grep -q -- '-f -n 0' "$watcher" \
|
|
|| note 'the watcher replays the journal rather than following from now, so a session would open with old crashes'
|
|
grep -q 'MESSAGE_ID=' "$watcher" \
|
|
|| note 'the watcher matches on log text rather than the coredump message id'
|
|
|
|
# ── Installed and started ───────────────────────────────────────────────────
|
|
|
|
[[ -r "$unit" ]] || note 'there is no user unit for the crash watcher'
|
|
grep -q 'panama-crash-watch' "$autostart" \
|
|
|| note 'nothing starts the crash watcher at login'
|
|
grep -q 'PANAMA_PATH' "$unit" \
|
|
|| note 'the unit hardcodes the repository path, so a clone elsewhere would not start'
|
|
|
|
if (( ${#findings[@]} > 0 )); then
|
|
printf 'crash watch contract: %d finding(s)\n' "${#findings[@]}" >&2
|
|
printf ' - %s\n' "${findings[@]}" >&2
|
|
exit 1
|
|
fi
|
|
|
|
printf 'crash watch contract: PASS\n'
|