diff --git a/server/service b/server/service index 7a48358..6b35706 100755 --- a/server/service +++ b/server/service @@ -87,19 +87,27 @@ installed() { esac } -# Whether the service fell over, as opposed to being stopped on purpose. +# What OpenRC's `status` exit code means. Measured on OpenRC 0.63.3 in a +# guest built for the purpose, not read from documentation: # -# OpenRC prints `crashed` *and* exits non-zero for this, so the word is -# read rather than the exit code -- leaning on the code would report -# "couldn't check", which is a different and less useful thing to say. -# The `running` check above stays on its exit code, which already worked -# and does not depend on wording. -crashed() { - case "$MANAGER" in - systemd) systemctl --user --quiet is-failed "$NAME" ;; - openrc) rc-service --user "$NAME" status 2>/dev/null | grep -qw crashed ;; - esac -} +# 0 started 3 stopped 32 crashed +# 1 could not find out +# +# 1 covers every way the question cannot be answered -- an unknown +# service, XDG_RUNTIME_DIR unset, or a user softlevel that was never +# initialised ("openrc did not boot this system"). Distinguishing it is +# the whole reason to read the code rather than the text. +# +# And the text must not be read. OpenRC prints `* status: crashed` to +# **stderr**, so the obvious `status 2>/dev/null | grep -qw crashed` +# throws away precisely the word it is searching for, finds nothing, and +# falls through to `stopped` -- reporting a service that fell over as one +# somebody chose to stop. That was this script's bug until it was +# measured, and the same shape is worth checking wherever a status is +# parsed rather than counted. +OPENRC_STARTED=0 +OPENRC_STOPPED=3 +OPENRC_CRASHED=32 require_binary() { [ -x "$BINARY" ] && return 0 @@ -212,19 +220,46 @@ case "${1:-}" in status) if ! installed; then echo not-installed - elif case "$MANAGER" in - systemd) systemctl --user --quiet is-active "$NAME" ;; - openrc) rc-service --user "$NAME" status >/dev/null 2>&1 ;; - esac then - echo running - elif crashed; then - # Before `stopped`, because a crashed service satisfies - # neither of the other two and would otherwise be reported as - # a state somebody chose. - echo failed - else - echo stopped + exit 0 fi + case "$MANAGER" in + systemd) + if systemctl --user --quiet is-active "$NAME"; then + echo running + # Before `stopped`, because a service that fell over + # satisfies neither of the other two and would otherwise + # be reported as a state somebody chose. + elif systemctl --user --quiet is-failed "$NAME"; then + echo failed + else + echo stopped + fi + ;; + openrc) + # `|| code=$?` rather than a bare call: `set -e` is on, and + # every answer except "running" is a non-zero exit, so a + # plain invocation kills the script before it can say what + # the code meant. + code=0 + rc-service --user "$NAME" status >/dev/null 2>&1 || code=$? + case $code in + "$OPENRC_STARTED") echo running ;; + "$OPENRC_CRASHED") echo failed ;; + "$OPENRC_STOPPED") echo stopped ;; + # Anything else is "could not find out", which is a + # real state and not one of the other three. Saying so + # costs a non-zero exit and buys the caller the truth; + # guessing `stopped` here is what the contract's + # `failed` state exists to stop. + *) + echo "could not ask OpenRC about $NAME -- is" \ + "XDG_RUNTIME_DIR set and the user softlevel" \ + "initialised?" >&2 + exit 1 + ;; + esac + ;; + esac ;; *) echo "usage: $0 install|uninstall|start|stop|restart|status|logs" >&2