From d856b93b72237fb9f3a460c41b1773885f70a723 Mon Sep 17 00:00:00 2001 From: AI Dev Date: Fri, 9 Oct 2026 01:59:00 +0000 Subject: [PATCH 1/2] majestic: start it again after a crash, and stop after five in ten minutes Nothing restarted majestic when it died: a camera that crashed once was without video until someone restarted it or power-cycled it. S95majestic now runs it under a small supervisor (this script, `supervise`) which starts it again when it dies of a fault -- SIGSEGV, SIGBUS, SIGILL, SIGFPE, SIGABRT, or SIGKILL from the out-of-memory killer -- after 5 s, then 30 s, then 2 min. After five crashes in ten minutes it stops trying and leaves /etc/crash/majestic.loop (when, how many, which signal), which the web interface reports, so a majestic that dies at every start does not hold the camera in a restart loop. A majestic stopped on purpose (SIGTERM, SIGINT) or exiting by itself is not started again, nor one that crashes while sysupgrade holds its lock: the upgrade brings majestic back itself. stop ends the supervisor first, also while it waits, so nothing starts majestic behind it. A start clears the loop note. The daemon is still found by executable, keeps argv[0], -s and the SIGHUP ignore, and pidof majestic is unchanged. .github/scripts/test_majestic_supervise.sh runs the script against a stub majestic and start-stop-daemon, in the shipped and the stripped form. --- .github/scripts/test_majestic_supervise.sh | 163 +++++++++++++++++++++ .github/workflows/shell-tests.yml | 16 ++ general/package/majestic/files/S95majestic | 100 ++++++++++++- 3 files changed, 272 insertions(+), 7 deletions(-) create mode 100755 .github/scripts/test_majestic_supervise.sh diff --git a/.github/scripts/test_majestic_supervise.sh b/.github/scripts/test_majestic_supervise.sh new file mode 100755 index 0000000000..5af84008d5 --- /dev/null +++ b/.github/scripts/test_majestic_supervise.sh @@ -0,0 +1,163 @@ +#!/bin/bash +# Regression tests for S95majestic's supervisor: majestic started again after +# a crash, and the camera never held in a restart loop. +# +# Everything it gets wrong is silent, and on a camera nobody is watching: a +# majestic left dead after a crash (no video until a power cycle), one that +# dies at every start restarted forever, one stopped on purpose -- by +# `S95majestic stop`, by sysupgrade -- started again behind the stopper's +# back, or a stop that returns while a restart is still on its way. None of +# it happens on demand without a majestic that crashes. +# +# Run the real script in a sandbox: its paths are rewritten under $SB, its +# waits shortened, and majestic and start-stop-daemon are stubs. The stub +# start-stop-daemon does what busybox's does with the flags the script uses: +# -S runs the daemon in place, -K signals it, -t -K says whether it runs. +# +# Lightweight: pure shell, no root, runs in a few seconds. + +set -u + +SRC=${SRC:-general/package/majestic/files/S95majestic} +fail=0 +ok() { echo "ok $*"; } +bad() { echo "FAIL $*"; fail=$((fail + 1)); } + +[ -f "$SRC" ] || { echo "FAIL cannot find $SRC — run me from the repo root"; exit 1; } + +SB=$(mktemp -d) +trap 'pkill -f "$SB/" 2>/dev/null; rm -rf "$SB"' EXIT +mkdir -p "$SB/bin" "$SB/run" "$SB/crash" + +sed -e "s|DAEMON_PATH=\"/usr/bin/\$DAEMON\"|DAEMON_PATH=\"$SB/bin/majestic\"|" \ + -e "s|/var/run/|$SB/run/|g" \ + -e "s|CRASH=/etc/crash|CRASH=$SB/crash|" \ + -e "s|UPGRADE_LOCK=/tmp/sysupgrade.lock|UPGRADE_LOCK=$SB/sysupgrade.lock|" \ + -e "s|BACKOFF=\"5 30 120\"|BACKOFF=\"\${TEST_BACKOFF:-0 0 0}\"|" \ + "$SRC" > "$SB/S95majestic" +for p in 'DAEMON_PATH="/usr/bin/$DAEMON"' /var/run/ CRASH=/etc/crash UPGRADE_LOCK=/tmp/sysupgrade.lock 'BACKOFF="5 30 120"'; do + grep -qF "$p" "$SRC" || bad "$p is gone from S95majestic; this test is testing nothing there" +done +chmod +x "$SB/S95majestic" + +# majestic: counts its starts, then does what $SB/mode says -- dies of a +# signal, exits, or runs until it is stopped. +cat > "$SB/bin/majestic" <> "$SB/starts" +case "\$(cat "$SB/mode")" in + segv) kill -SEGV \$\$ ;; + abrt) kill -ABRT \$\$ ;; + term) kill -TERM \$\$ ;; + exit) exit 0 ;; + run) exec sleep 1000 ;; +esac +EOF +cat > "$SB/bin/start-stop-daemon" <<'EOF' +#!/bin/sh +mode= test= sig=TERM exe= +while [ $# -gt 0 ]; do + case "$1" in + -S) mode=start ;; -K) mode=stop ;; -t) test=1 ;; -q) ;; + -s) shift; sig=$1 ;; -x) shift; exe=$1 ;; -a) shift ;; + --) shift; break ;; + esac + shift +done +case "$mode" in + start) exec "$exe" "$@" ;; + stop) + pids=$(pgrep -f "^/bin/sh $exe|^sleep 1000" 2>/dev/null) + [ -n "$pids" ] || exit 1 + [ -n "$test" ] && exit 0 + kill -s "$sig" $pids 2>/dev/null + exit 0 ;; +esac +EOF +printf '#!/bin/sh\necho "$*" >> "%s/log"\n' "$SB" > "$SB/bin/logger" +chmod +x "$SB/bin"/* +export PATH="$SB/bin:$PATH" + +starts() { [ -f "$SB/starts" ] && wc -l < "$SB/starts" | tr -d ' ' || echo 0; } +reset() { rm -f "$SB/starts" "$SB/log" "$SB/crash"/* "$SB/sysupgrade.lock" "$SB/run"/*; echo "$1" > "$SB/mode"; } +supervise() { timeout 20 sh "$SB/S95majestic" supervise; } + +# 1. A majestic that dies at every start: started again, then given up on. +reset segv +supervise +if [ "$(starts)" = 5 ] && [ -f "$SB/crash/majestic.loop" ]; then + ok "a majestic that crashes at every start is run five times, then left stopped" +else + bad "crash loop: $(starts) starts, loop note: $(ls "$SB/crash")" +fi +grep -q '^signal=11$' "$SB/crash/majestic.loop" 2>/dev/null && grep -q '^crashes=5$' "$SB/crash/majestic.loop" \ + && grep -q '^utc=' "$SB/crash/majestic.loop" \ + && ok "the loop note says how often, by what signal and when" || bad "loop note: $(cat "$SB/crash/majestic.loop" 2>/dev/null)" +grep -q 'not starting it again' "$SB/log" 2>/dev/null && ok "and the log says so" || bad "log: $(cat "$SB/log" 2>/dev/null)" +[ ! -f "$SB/run/majestic-supervise.pid" ] && ok "the supervisor leaves no pid behind" || bad "pid file left" + +# 2. An abort is a crash too. +reset abrt +supervise +[ "$(starts)" = 5 ] && grep -q '^signal=6$' "$SB/crash/majestic.loop" && ok "SIGABRT is restarted like SIGSEGV" \ + || bad "abort: $(starts) starts" + +# 3. Stopped on purpose, or ending by itself: never started again. +for m in term exit; do + reset $m + supervise + [ "$(starts)" = 1 ] && [ ! -f "$SB/crash/majestic.loop" ] && ok "a majestic that ends with '$m' is not started again" \ + || bad "$m: $(starts) starts" +done + +# 4. An upgrade has the camera: a crash then is left to it. +reset segv +touch "$SB/sysupgrade.lock" +supervise +[ "$(starts)" = 1 ] && [ ! -f "$SB/crash/majestic.loop" ] && ok "during an upgrade a crash is not restarted" \ + || bad "upgrade: $(starts) starts" + +# 5. start and stop: the supervisor goes with majestic, and the pid with it. +reset run +echo "utc=2026-01-01 00:00:00" > "$SB/crash/majestic.loop" +sh "$SB/S95majestic" start > /dev/null +for _ in $(seq 50); do [ "$(starts)" -ge 1 ] && break; sleep 0.1; done +sleep 0.3 +[ ! -f "$SB/crash/majestic.loop" ] && ok "a start clears the note that the last run was given up on" || bad "loop note survived a start" +pgrep -f "$SB/S95majestic supervise" > /dev/null && ok "start leaves a supervisor running majestic" || bad "no supervisor after start" +out=$(sh "$SB/S95majestic" start) +[ "$out" = "Starting majestic: OK (already running)" ] && ok "a second start starts nothing" || bad "second start: $out" +sh "$SB/S95majestic" stop > /dev/null +sleep 0.5 +if ! pgrep -f "$SB/S95majestic supervise" > /dev/null && ! pgrep -f "^sleep 1000" > /dev/null && [ "$(starts)" = 1 ]; then + ok "stop ends the supervisor and majestic, and nothing starts it again" +else + bad "after stop: supervisor $(pgrep -f "$SB/S95majestic supervise"), starts $(starts)" +fi +[ ! -f "$SB/run/majestic-supervise.pid" ] && ok "stop removes the supervisor's pid" || bad "pid file left after stop" + +# 6. A stop while it waits to start majestic again ends the wait. +reset segv +TEST_BACKOFF="30" sh "$SB/S95majestic" start > /dev/null +for _ in $(seq 50); do [ "$(starts)" -ge 1 ] && break; sleep 0.1; done +sleep 0.3 +t0=$(date +%s) +sh "$SB/S95majestic" stop > /dev/null +sleep 0.5 +if ! pgrep -f "$SB/S95majestic supervise" > /dev/null && [ $(( $(date +%s) - t0 )) -lt 5 ] && [ "$(starts)" = 1 ]; then + ok "a stop during the wait after a crash ends it at once" +else + bad "stop during the wait: supervisor $(pgrep -f "$SB/S95majestic supervise"), starts $(starts)" +fi + +# 7. A pid file left by something else is not the supervisor. +reset run +sleep 999 & +other=$! +echo "$other" > "$SB/run/majestic-supervise.pid" +sh "$SB/S95majestic" stop > /dev/null +kill -0 "$other" 2>/dev/null && ok "stop does not kill whatever reused the supervisor's pid" || bad "stop killed pid $other" +kill "$other" 2>/dev/null + +[ "$fail" -eq 0 ] && echo "all passed" || echo "$fail failed" +exit "$fail" diff --git a/.github/workflows/shell-tests.yml b/.github/workflows/shell-tests.yml index da81e5d6c0..55b87697bd 100644 --- a/.github/workflows/shell-tests.yml +++ b/.github/workflows/shell-tests.yml @@ -70,6 +70,22 @@ jobs: general/package/openipc-failsafe/files/S98crashlog > /tmp/S98crashlog.stripped SRC=/tmp/S98crashlog.stripped bash .github/scripts/test_crashlog.sh + majestic-supervise: + name: majestic restarted after a crash, never in a loop + if: >- + github.repository == 'OpenIPC/firmware' || + github.event_name == 'workflow_dispatch' || + (github.event_name == 'pull_request' && !github.event.repository.private) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - run: bash .github/scripts/test_majestic_supervise.sh + - name: Re-run against the comment-stripped form + run: | + awk -f general/scripts/strip-shell-comments.awk \ + general/package/majestic/files/S95majestic > /tmp/S95majestic.stripped + SRC=/tmp/S95majestic.stripped bash .github/scripts/test_majestic_supervise.sh + camera-mac: name: the MAC the camera gives itself if: >- diff --git a/general/package/majestic/files/S95majestic b/general/package/majestic/files/S95majestic index 2757d014b8..11ba4dc8e4 100755 --- a/general/package/majestic/files/S95majestic +++ b/general/package/majestic/files/S95majestic @@ -10,6 +10,20 @@ DAEMON="majestic" DAEMON_PATH="/usr/bin/$DAEMON" DAEMON_ARGS="-s" +# majestic is run by a small supervisor (this script, `supervise`), which +# starts it again when it dies of a fault -- before, a crash left the camera +# without video until someone restarted it by hand. It waits longer after each +# crash, and after five in ten minutes it stops trying and leaves +# /etc/crash/majestic.loop, which the web interface reports: a majestic that +# dies at every start would otherwise hold the camera in a restart loop. The +# dump majestic writes as it dies stays in /etc/crash for the crash report. +SUPERVISOR=/var/run/majestic-supervise.pid +CRASH=/etc/crash +UPGRADE_LOCK=/tmp/sysupgrade.lock +BACKOFF="5 30 120" +WINDOW=600 +LIMIT=5 + # Find the daemon by executable, never through a pidfile. Given -p, busybox # start-stop-daemon inspects only the pid that file holds and never scans # /proc, so the moment that pid is stale it can no longer see the live daemon: @@ -40,19 +54,87 @@ running() { # without this the daemon is dead every run; with it, it survives every run. start() { echo -n "Starting $DAEMON: " + if running || supervising; then + echo "OK (already running)" + return 0 + fi + if [ ! -x "$DAEMON_PATH" ]; then + echo "FAIL" + return 1 + fi + # A start, by hand or at boot, is a fresh run: the note that the last one + # was given up on is about a majestic that is no longer there. + rm -f "$CRASH/majestic.loop" ( trap '' HUP - start-stop-daemon -b -S -q -x "$DAEMON_PATH" -a "$DAEMON" -- $DAEMON_ARGS - ) - case $? in - 0) echo "OK"; return 0 ;; - 1) echo "OK (already running)"; return 0 ;; - *) echo "FAIL"; return 1 ;; + exec sh "$0" supervise + ) /dev/null 2>&1 & + echo "OK" +} + +# The supervisor's pid, if it is the supervisor still: a pid in a file can +# have been reused by anything since. +supervising() { + pid=$(cat "$SUPERVISOR" 2>/dev/null) + [ -n "$pid" ] && tr '\0' ' ' < "/proc/$pid/cmdline" 2>/dev/null | grep -q " supervise" +} + +# The signals a fault ends majestic with: SIGILL, SIGABRT, SIGBUS (7, and 10 +# on MIPS), SIGFPE, SIGSEGV -- and SIGKILL, which is how the kernel's +# out-of-memory killer ends it. SIGTERM and SIGINT are someone stopping it, +# and an exit of its own is its own decision: neither is restarted. +crashed() { + case "$1" in + 4 | 6 | 7 | 8 | 9 | 10 | 11) return 0 ;; esac + return 1 +} + +supervise() { + trap '' HUP + # A stop while it waits ends the wait, not just the run after it. + trap 'kill $waiting 2>/dev/null; rm -f "$SUPERVISOR"; exit 0' TERM INT + echo $$ > "$SUPERVISOR" + waiting= + crashes= + while :; do + start-stop-daemon -S -q -x "$DAEMON_PATH" -a "$DAEMON" -- $DAEMON_ARGS + rc=$? + [ "$rc" -gt 128 ] && crashed $((rc - 128)) || break + sig=$((rc - 128)) + # An upgrade has the camera: it brings majestic back itself. + [ -f "$UPGRADE_LOCK" ] && break + now=$(cut -d. -f1 /proc/uptime) + recent= + n=0 + for t in $crashes $now; do + if [ $((now - t)) -lt "$WINDOW" ]; then + recent="$recent $t" + n=$((n + 1)) + fi + done + crashes=$recent + if [ "$n" -ge "$LIMIT" ]; then + mkdir -p "$CRASH" + printf 'utc=%s\ncrashes=%s\nsignal=%s\n' "$(date -u '+%Y-%m-%d %H:%M:%S')" "$n" "$sig" > "$CRASH/majestic.loop" + logger -t "$DAEMON" "crashed $n times in $((WINDOW / 60)) minutes (signal $sig); not starting it again" 2>/dev/null + break + fi + delay=$(echo $BACKOFF | cut -d' ' -f"$n") + [ -n "$delay" ] || delay=${BACKOFF##* } + logger -t "$DAEMON" "crashed (signal $sig); starting it again in ${delay}s" 2>/dev/null + sleep "$delay" & + waiting=$! + wait "$waiting" + waiting= + done + rm -f "$SUPERVISOR" } stop() { echo -n "Stopping $DAEMON: " + # The supervisor first, so it does not start what is being stopped. + supervising && kill "$pid" 2>/dev/null start-stop-daemon -K -q -x "$DAEMON_PATH" i=0 while running && [ $i -lt 10 ]; do @@ -63,7 +145,7 @@ stop() { echo "FAIL" return 1 fi - rm -f "/var/run/$DAEMON.pid" + rm -f "/var/run/$DAEMON.pid" "$SUPERVISOR" echo "OK" } @@ -72,6 +154,10 @@ case "$1" in $1 ;; + supervise) + supervise + ;; + restart) stop || exit 1 sleep 3 From b5d23210d85a66750b6815d19147c02cd83dfd6a Mon Sep 17 00:00:00 2001 From: AI Dev Date: Fri, 9 Oct 2026 02:15:03 +0000 Subject: [PATCH 2/2] majestic: start and stop one at a time, and no restart into an upgrade - start and stop take a lock holding the holder's pid, taken over only from a holder that is gone; start returns once the supervisor it launched is the process the pid file names, so two starts at once leave one supervisor and a stop right after a start finds it. - The supervisor asks for the upgrade lock before every start, so an upgrade that begins while it waits after a crash is not started over. - The supervisor is recognised by its last two arguments, this script and `supervise`, not by any command line that contains the word. - ci-matrix classifies the new test as no-build. --- .github/scripts/ci-matrix.py | 3 +- .github/scripts/test_majestic_supervise.sh | 37 +++++++++++++++++- general/package/majestic/files/S95majestic | 45 +++++++++++++++++++++- 3 files changed, 81 insertions(+), 4 deletions(-) diff --git a/.github/scripts/ci-matrix.py b/.github/scripts/ci-matrix.py index ebabe06723..cccc4ae000 100644 --- a/.github/scripts/ci-matrix.py +++ b/.github/scripts/ci-matrix.py @@ -193,7 +193,7 @@ "lint-workflow-shell.py", "push_build.py", "soc_aliases.py", "test_automount.sh", "test_check_mac.sh", "test_excludes_report.sh", "test_load_hisilicon.sh", "test_push_build.py", "test_shell_parse.sh", - "test_strip_shell_comments.sh", "test_sysupgrade.sh", "test_crashlog.sh", + "test_strip_shell_comments.sh", "test_sysupgrade.sh", "test_crashlog.sh", "test_majestic_supervise.sh", # build-one.yml's request parser and its test: dispatch and shell-tests # only, never part of a board build. "build_request.sh", "test_build_request.sh", @@ -959,6 +959,7 @@ def self_test(): ([".github/scripts/test_sysupgrade.sh"], 0, "shell-tests fixture"), ([".github/scripts/test_check_mac.sh"], 0, "shell-tests: MAC derivation"), ([".github/scripts/test_crashlog.sh"], 0, "shell-tests: crash log capture"), + ([".github/scripts/test_majestic_supervise.sh"], 0, "shell-tests: majestic supervisor"), ([".github/scripts/test_strip_shell_comments.sh"], 0, "shell-tests: stripper"), ([".github/scripts/test_automount.sh"], 0, "shell-tests: automount"), ([".github/scripts/test_excludes_report.sh"], 0, "shell-tests: excludes"), diff --git a/.github/scripts/test_majestic_supervise.sh b/.github/scripts/test_majestic_supervise.sh index 5af84008d5..4d3deec1d5 100755 --- a/.github/scripts/test_majestic_supervise.sh +++ b/.github/scripts/test_majestic_supervise.sh @@ -114,8 +114,13 @@ done reset segv touch "$SB/sysupgrade.lock" supervise -[ "$(starts)" = 1 ] && [ ! -f "$SB/crash/majestic.loop" ] && ok "during an upgrade a crash is not restarted" \ +[ "$(starts)" = 0 ] && [ ! -f "$SB/crash/majestic.loop" ] && ok "during an upgrade majestic is not started" \ || bad "upgrade: $(starts) starts" +# ...nor when the upgrade begins while the supervisor waits after a crash. +reset segv +(sleep 1; touch "$SB/sysupgrade.lock") & +TEST_BACKOFF="2" supervise +[ "$(starts)" = 1 ] && ok "an upgrade that begins during the wait is not started over" || bad "upgrade in the wait: $(starts) starts" # 5. start and stop: the supervisor goes with majestic, and the pid with it. reset run @@ -159,5 +164,35 @@ sh "$SB/S95majestic" stop > /dev/null kill -0 "$other" 2>/dev/null && ok "stop does not kill whatever reused the supervisor's pid" || bad "stop killed pid $other" kill "$other" 2>/dev/null +# 8. Nor whatever reused it with "supervise" at the end of its arguments. +sh -c 'sleep 998; :' other supervise & +other=$! +echo "$other" > "$SB/run/majestic-supervise.pid" +sh "$SB/S95majestic" stop > /dev/null +kill -0 "$other" 2>/dev/null && ok "nor one whose last argument is supervise" || bad "stop killed pid $other" +# Its sleep too: left behind, it would hold this script's output open. +pkill -P "$other" 2>/dev/null +kill "$other" 2>/dev/null + +# 9. Two starts at once, then a stop: nothing is left to start majestic. +reset run +sh "$SB/S95majestic" start > /dev/null & +sh "$SB/S95majestic" start > /dev/null & +wait +sh "$SB/S95majestic" stop > /dev/null +sleep 0.5 +if ! pgrep -f "$SB/S95majestic supervise" > /dev/null && ! pgrep -f "^sleep 1000" > /dev/null; then + ok "two starts at once leave one supervisor, which a stop ends" +else + bad "after two starts and a stop: supervisors $(pgrep -f "$SB/S95majestic supervise" | tr '\n' ' ')" +fi + +# 10. A stop at once after a start finds the supervisor. +reset run +sh "$SB/S95majestic" start > /dev/null; sh "$SB/S95majestic" stop > /dev/null +sleep 1 +! pgrep -f "$SB/S95majestic supervise" > /dev/null && ! pgrep -f "^sleep 1000" > /dev/null \ + && ok "a stop right after a start leaves nothing running" || bad "start then stop left something running" + [ "$fail" -eq 0 ] && echo "all passed" || echo "$fail failed" exit "$fail" diff --git a/general/package/majestic/files/S95majestic b/general/package/majestic/files/S95majestic index 11ba4dc8e4..73156fdf15 100755 --- a/general/package/majestic/files/S95majestic +++ b/general/package/majestic/files/S95majestic @@ -54,11 +54,14 @@ running() { # without this the daemon is dead every run; with it, it survives every run. start() { echo -n "Starting $DAEMON: " + lock if running || supervising; then + unlock echo "OK (already running)" return 0 fi if [ ! -x "$DAEMON_PATH" ]; then + unlock echo "FAIL" return 1 fi @@ -69,14 +72,47 @@ start() { trap '' HUP exec sh "$0" supervise ) /dev/null 2>&1 & + echo $! > "$SUPERVISOR" + # Returned only once the supervisor is the process the pid names, so a + # stop that follows at once finds it. + i=0 + while ! supervising && [ $i -lt 50 ]; do + i=$((i + 1)) + sleep 0.1 + done + unlock echo "OK" } +# start and stop one at a time: two starts at once would leave a supervisor +# no pid names, which a stop could then not end. The lock holds its holder's +# pid, and is taken over only once that process is gone -- a start that was +# killed -- never from one still running. +lock() { + until mkdir "$SUPERVISOR.lock" 2>/dev/null; do + holder=$(cat "$SUPERVISOR.lock/pid" 2>/dev/null) + if [ -n "$holder" ] && ! kill -0 "$holder" 2>/dev/null; then + rm -rf "$SUPERVISOR.lock" + continue + fi + sleep 1 + done + echo $$ > "$SUPERVISOR.lock/pid" +} +unlock() { + rm -rf "$SUPERVISOR.lock" +} + # The supervisor's pid, if it is the supervisor still: a pid in a file can # have been reused by anything since. supervising() { pid=$(cat "$SUPERVISOR" 2>/dev/null) - [ -n "$pid" ] && tr '\0' ' ' < "/proc/$pid/cmdline" 2>/dev/null | grep -q " supervise" + [ -n "$pid" ] || return 1 + # This script, run as `supervise`: its last two arguments. + set -- $(tr '\0' ' ' < "/proc/$pid/cmdline" 2>/dev/null) + [ $# -ge 2 ] || return 1 + eval "script=\${$(($# - 1))} last=\${$#}" + [ "$last" = supervise ] && [ "${script##*/}" = "${0##*/}" ] } # The signals a fault ends majestic with: SIGILL, SIGABRT, SIGBUS (7, and 10 @@ -98,11 +134,13 @@ supervise() { waiting= crashes= while :; do + # An upgrade has the camera: it brings majestic back itself. Asked + # before every start, the first and each one after a wait. + [ -f "$UPGRADE_LOCK" ] && break start-stop-daemon -S -q -x "$DAEMON_PATH" -a "$DAEMON" -- $DAEMON_ARGS rc=$? [ "$rc" -gt 128 ] && crashed $((rc - 128)) || break sig=$((rc - 128)) - # An upgrade has the camera: it brings majestic back itself. [ -f "$UPGRADE_LOCK" ] && break now=$(cut -d. -f1 /proc/uptime) recent= @@ -133,6 +171,7 @@ supervise() { stop() { echo -n "Stopping $DAEMON: " + lock # The supervisor first, so it does not start what is being stopped. supervising && kill "$pid" 2>/dev/null start-stop-daemon -K -q -x "$DAEMON_PATH" @@ -142,10 +181,12 @@ stop() { sleep 1 done if running; then + unlock echo "FAIL" return 1 fi rm -f "/var/run/$DAEMON.pid" "$SUPERVISOR" + unlock echo "OK" }