#!/bin/sh -e

. /usr/lib/s6-policy-common/lib/svctl-common.sh

if test -z "$1" ; then
  echo "svctl-restart: usage: svctl-restart service..." 1>&2
  exit 100
fi

if ! test -L /run/s6-rc/compiled ; then
  echo "svctl-restart: fatal: s6-rc is not running" 1>&2
  exit 2
fi

list=$(s6-rc-db atomics "$@")

# Oneshots have to be taken down before they can run again: "s6-rc -u change" on
# an already-active oneshot is a no-op, so until 2026-09-22 "svctl restart" on a
# oneshot did nothing at all and still exited 0 -- every "notify => Service[...]"
# on a oneshot was silently lost (S6-SYSTEM.md, and the Puppet provider reported
# "Triggered 'refresh'" for it).
#
# Note what this implies: taking a oneshot down takes its dependants down too,
# and "s6-rc -u change" below brings the named service back up, not them. That is
# s6-rc's dependency semantics, and it is the reason this only happens for
# oneshots that are actually up -- restarting a stopped one would have no
# meaning either way.
downagain=""
for i in $list ; do
  t=$(s6-rc-db type "$i")
  if test x"$t" = x"longrun" ; then
    # E4: WARTEN, wie "svctl start" es tut.
    #
    # "s6-svc -r" schickt das Signal und kehrt sofort zurueck; der anschliessende
    # "s6-rc -u change" ist fuer einen Dienst, den s6-rc schon als aktiv fuehrt,
    # ein No-op. Bis 1.1.3 war "svctl restart" damit fertig, bevor der Dienst
    # wieder oben war. Der Provider hat das ausgeglichen, Handgriffe und fremde
    # postinsts nicht.
    #
    # 1.2.3: -wR/-wr, NICHT -wU/-wu. 1.2.0 bis 1.2.2 benutzten -wU/-wu, und das
    # wartet auf einen ZUSTAND: s6-svlisten1 liest den Ausgangszustand, bevor es
    # das Signal schickt, und ein Dienst, der schon oben und bereit ist, erfuellt
    # "-wU" sofort. Gemessen (Container kat, 23.09.2026): restart einer Unit mit
    # 5 s bis zur Bereitschaft kehrte nach 9 ms mit rc 0 zurueck, der Dienst stand
    # "up 0 s, nicht bereit" -- E4 wirkte nie. -wR/-wr warten auf das EREIGNIS
    # "neu gestartet (und bereit)": dieselbe Unit 5008 ms, danach bereit.
    #
    # -wR bei notification-fd, sonst -wr: ohne notification-fd meldet s6 nie
    # "bereit" (s6-svstat -o ready bleibt false), und -wR liefe ins Limit.
    #
    # Nur fuer einen Dienst, der JETZT oben ist: "s6-svc -r" auf einen gestoppten
    # Dienst tut nichts, und -wr wartet dann bis zum Limit (gemessen: rc 99 nach
    # 5010 ms). Einen gestoppten Dienst bringt das "s6-rc -u change" unten hoch,
    # und das wartet selbst -- so wie "svctl start".
    #
    # -T nimmt dasselbe Limit wie jede andere Transition, im dpkg-Lauf also
    # SVCTL_TIMEOUT_DPKG (svctl_timeout entscheidet das an DPKG_RUNNING_VERSION).
    _rdir="/run/s6-rc/servicedirs/$i"

    # E11 (1.2.6): ein bewusster Neustart ist bei einer permafail-Unit ein
    # RESET. s6-permafailon zaehlt die Tode im Fenster; ohne Loeschen des
    # Zaehlers stirbt der Dienst nach dem Neustart beim ersten Fehler sofort
    # wieder dauerhaft -- oder, wie in Container cab gemessen, ein sauberer
    # Neustart (SIGTERM) fuehrte zu "dauerhaft unten", weil die alten Tode noch
    # im Fenster lagen. Erkannt an s6-permafailon im finish (dh-s6 .permafail
    # erzeugt genau das; s6-rc legt es als finish.user ab).
    if grep -qs 's6-permafailon' "$_rdir/finish.user" "$_rdir/finish" ; then
      if s6-svdt-clear "$_rdir" 2>/dev/null ; then
        echo "svctl-restart: $i: Todeszaehler geloescht (permafail-Unit, ein Neustart ist ein Reset)" 1>&2
      fi
    fi

    if [ -e "$_rdir/notification-fd" ]; then
      _rwait="-wR"; _uwait="-wU"
      _rword="bereit"
    else
      _rwait="-wr"; _uwait="-wu"
      _rword="oben"
    fi
    _rlim="$(svctl_timeout)"
    _rrc=0
    if [ "$(s6-svstat -o up "$_rdir" 2>/dev/null)" != "true" ]; then
      # Unten. Fuehrt s6-rc den Dienst NICHT als aktiv, bringt ihn das
      # "s6-rc -u change" unten hoch. Fuehrt s6-rc ihn als aktiv -- der Fall nach
      # permafail (exit 125: up=false wantedup=false) oder nach einem Absturz
      # mit gesenktem Wunsch --, ist jenes change ein No-op, und bis 1.2.5
      # meldete restart rc 0 und tat NICHTS (gemessen in kat: nach permafail
      # und behobener Ursache blieb der Dienst unten, Zaehler 3). Dann hier
      # direkt hochholen und warten, wie svctl start im Nachschub.
      if s6-rc -a list 2>/dev/null | grep -qFx "$i" ; then
        s6-svc -u "$_uwait" -T "$_rlim" "/run/s6-rc/scandir/$i" || _rrc=$?
      else
        continue
      fi
    else
      s6-svc -r "$_rwait" -T "$_rlim" "/run/s6-rc/scandir/$i" || _rrc=$?
    fi
    if [ "$_rrc" -ne 0 ]; then
      echo "svctl-restart: fatal: $i ist nach dem Neustart nicht wieder ${_rword} geworden" 1>&2
      echo "svctl-restart:        (Limit ${_rlim} ms, s6-svc rc=${_rrc}). Zustand:" 1>&2
      s6-svstat "$_rdir" 2>&1 | sed 's/^/svctl-restart:        /' 1>&2
      exit "$_rrc"
    fi
  elif test x"$t" = x"oneshot" ; then
    if s6-rc -a list | grep -qFx "$i" ; then
      downagain="$downagain $i"
    fi
  fi
done

timeout="$(svctl_timeout)"

if test -n "$downagain" ; then
  # shellcheck disable=SC2086
  rc=0
  s6-rc -t "$timeout" -d change $downagain || rc=$?
  if [ "$rc" -ne 0 ]; then
    echo "svctl-restart: fatal: das Herunterfahren der Oneshots ist fehlgeschlagen oder hat" 1>&2
    echo "svctl-restart:        das Limit von ${timeout} ms gerissen (s6-rc rc=${rc})." 1>&2
    # shellcheck disable=SC2086
    svctl_report_pending down $downagain
    exit "$rc"
  fi
fi

# Mit Zeitlimit: ein Dienst, der beim Neustart haengt, darf kein postinst und
# keinen Puppet-Lauf blockieren (siehe svctl-update.unlocked).
rc=0
s6-rc -t "$timeout" -u change "$@" || rc=$?
if [ "$rc" -ne 0 ]; then
  echo "svctl-restart: fatal: der Zustandswechsel ist fehlgeschlagen oder hat das Limit von" 1>&2
  echo "svctl-restart:        ${timeout} ms gerissen (s6-rc rc=${rc})." 1>&2
  if [ "$rc" -eq 111 ]; then
    echo "svctl-restart: rc=111 ist der Sperrfehler: eine andere s6-rc-Transition haelt" 1>&2
    echo "svctl-restart: /run/s6-rc, das Limit wurde nicht erreicht." 1>&2
  fi
  svctl_report_pending up "$@"
  exit "$rc"
fi
