#!/bin/sh
# svctl state <unit> [--json]  -- E2 (1.2.0)
#
# EINE Zeile, die die Frage "wie geht es diesem Dienst?" beantwortet.
#
# Warum es das gibt: bis 1.1.3 brauchte diese Frage FUENF Aufrufe und drei
# Werkzeuge (svctl isup, svctl isenabled, svctl iswanted, s6-svstat, s6-svdt), und
# das einzige maschinenlesbare Zusammenfuehren lag in aep-zabbix-checks -- also in
# einem UEBERWACHUNGSpaket und nicht im Steuerwerkzeug. Wer an der Maschine sitzt,
# soll nicht fuenf Kommandos tippen und im Kopf zusammensetzen muessen.
#
# JEDES FELD IST -1, WENN NICHT MESSBAR, und niemals 0: bei updownfor,
# deaths_window und den Wahrheitswerten ist 0 eine gueltige Aussage. Ein 0 fuer
# "ich habe nicht hingesehen" ist genau das gruene Licht, das diese Woche
# mehrfach in die Irre gefuehrt hat.
#
# Rechte: die Felder aus s6-svstat/s6-svdt brauchen Leserecht auf
# servicedirs/<unit>/supervise (drwx------ root root), also root. Ohne root
# stehen sie auf -1 -- der Aufruf schlaegt nicht fehl, er sagt nur weniger.
set -u

PROG="svctl-state"
unit=""
json=false
for a in "$@" ; do
  case "$a" in
    --json) json=true ;;
    -*) echo "$PROG: unbekannte Option: $a" 1>&2; exit 100 ;;
    *) if [ -z "$unit" ]; then unit="$a"; else echo "$PROG: zu viele Argumente" 1>&2; exit 100; fi ;;
  esac
done
if [ -z "$unit" ]; then
  echo "$PROG: usage: svctl state <unit> [--json]" 1>&2
  exit 100
fi
case "$unit" in
  *[!A-Za-z0-9_.-]*) echo "$PROG: ungueltiger Unitname: $unit" 1>&2; exit 100 ;;
esac
if ! test -L /run/s6-rc/compiled ; then
  echo "$PROG: fatal: s6-rc is not running" 1>&2
  exit 2
fi
if ! s6-rc-db list services 2>/dev/null | grep -qFx "$unit" ; then
  if s6-rc-db list bundles 2>/dev/null | grep -qFx "$unit" ; then
    echo "$PROG: $unit ist ein Bundle, kein Dienst -- svctl isup zeigt Bundles" 1>&2
    exit 100
  fi
  echo "$PROG: unknown atomic service: $unit" 1>&2
  exit 4
fi

DIR="/run/s6-rc/servicedirs/$unit"
TAI64_EPOCH=4611686018427387904
TAI_UTC_OFFSET=37
WINDOW=${SVCTL_DEATH_WINDOW:-600}

up=-1 ready=-1 has_notification=0 wanted=-1 enabled=-1 normallyup=-1
updownfor=-1 deaths_window=-1 tally_full=-1 restart_pending=-1 permafail_suspect=-1

# --- Wunsch und Startsatz: brauchen kein root ------------------------------
if s6-rc -a list 2>/dev/null | grep -qFx "$unit" ; then wanted=1; else wanted=0; fi
if /usr/lib/s6-policy-common/bin/svctl-isenabled "$unit" >/dev/null 2>&1 ; then
  enabled=1
else
  case $? in
    3|1) enabled=0 ;;
    *) enabled=-1 ;;
  esac
fi

# --- Supervisionszustand: braucht root -------------------------------------
if [ -d "$DIR" ]; then
  [ -e "$DIR/notification-fd" ] && has_notification=1
  if _st=$(s6-svstat -o up,ready,normallyup,updownfor "$DIR" 2>/dev/null) ; then
    # shellcheck disable=SC2086
    set -- $_st
    if [ $# -ge 4 ]; then
      [ "$1" = true ] && up=1 || up=0
      if [ "$has_notification" = 1 ]; then
        [ "$2" = true ] && ready=1 || ready=0
      else
        # Ohne notification-fd meldet s6 ready == up. Das ist keine Aussage
        # ueber Bereitschaft, also wird hier auch keine behauptet.
        ready=-1
      fi
      [ "$3" = true ] && normallyup=1 || normallyup=0
      case "$4" in ''|*[!0-9]*) updownfor=-1 ;; *) updownfor=$4 ;; esac
    fi
  fi

  # --- Todeszaehler ---------------------------------------------------------
  # Gezaehlt werden FEHLSCHLAEGE, nicht Tode: ein geordnetes Beenden ist kein
  # Fehlschlag. Gemessen am 24.09.2026: "svctl restart"/"svctl stop" hinterlassen
  # "signal SIGTERM", ein Dienst, der SIGTERM abfaengt, "exitcode 0", und ein
  # Dienst, der von sich aus stirbt, "exitcode <n>". Wer nur zaehlt, erklaert
  # jeden zweiten Neustart zum Absturz -- dieser Fehler hat den Provider zwei
  # Fassungen gekostet.
  _tmax=100
  if [ -r "$DIR/max-death-tally" ]; then
    read -r _m < "$DIR/max-death-tally" 2>/dev/null || _m=''
    case ${_m:-} in ''|*[!0-9]*) ;; *) _tmax=$_m ;; esac
  fi
  if _svdt=$(s6-svdt -n "$_tmax" "$DIR" 2>/dev/null) ; then
    _now=$(( $(date -u +%s) + TAI_UTC_OFFSET ))
    _n=0 _f=0 _pf=0
    while IFS= read -r _line ; do
      case "$_line" in @*) ;; *) continue ;; esac
      _n=$((_n + 1))
      _lbl=${_line#@}
      _sec=$(printf '%.16s' "$_lbl")
      _t=$(( 0x$_sec - TAI64_EPOCH ))
      [ $(( _now - _t )) -le "$WINDOW" ] || continue
      case "$_line" in
        *'signal SIGTERM'*) continue ;;
        *'exitcode 0'*) continue ;;
      esac
      _f=$((_f + 1))
      case "$_line" in *'exitcode 125'*) _pf=1 ;; esac
    done <<EOF2
$_svdt
EOF2
    deaths_window=$_f
    [ "$_n" -ge "$_tmax" ] && tally_full=1 || tally_full=0
    permafail_suspect=$_pf
  fi

  # --- restart_pending -----------------------------------------------------
  # s6-rc umhuellt das run-Skript und legt das Original als run.user daneben;
  # verglichen wird deshalb dessen mtime, nicht die von run.
  #
  # GRENZE, benannt statt versteckt: s6-rc-update schreibt bei jeder Aenderung den
  # GANZEN Baum neu, also tragen nach der Installation eines beliebigen s6-Pakets
  # alle run.user eine frische mtime. Dieses Feld sagt darum "die Datei ist neuer
  # als der Prozess" und nicht "der Inhalt hat sich geaendert". Wer letzteres
  # braucht, nimmt restart_pending aus aep-zabbix-checks (>= 0.4.5), das einen
  # Hash-Stand mitfuehrt.
  if [ "$up" = 1 ] && [ "$updownfor" -ge 0 ] 2>/dev/null ; then
    _rf="$DIR/run.user"
    [ -e "$_rf" ] || _rf="$DIR/run"
    if [ -r "$_rf" ] && _mt=$(stat -c %Y "$_rf" 2>/dev/null) ; then
      if [ "$_mt" -gt $(( $(date -u +%s) - updownfor )) ] 2>/dev/null ; then
        restart_pending=1
      else
        restart_pending=0
      fi
    fi
  fi
fi

if $json ; then
  printf '{"unit":"%s","up":%s,"ready":%s,"has_notification":%s,"wanted":%s,"enabled":%s,"normallyup":%s,"updownfor":%s,"deaths_window":%s,"tally_full":%s,"restart_pending":%s,"permafail_suspect":%s}\n' \
    "$unit" "$up" "$ready" "$has_notification" "$wanted" "$enabled" "$normallyup" \
    "$updownfor" "$deaths_window" "$tally_full" "$restart_pending" "$permafail_suspect"
else
  printf '%s up=%s ready=%s has_notification=%s wanted=%s enabled=%s normallyup=%s updownfor=%s deaths_window=%s tally_full=%s restart_pending=%s permafail_suspect=%s\n' \
    "$unit" "$up" "$ready" "$has_notification" "$wanted" "$enabled" "$normallyup" \
    "$updownfor" "$deaths_window" "$tally_full" "$restart_pending" "$permafail_suspect"
fi
