#!/bin/sh
# Readiness of keepalived, as far as a package can measure it without
# knowledge of the instance.
#
# Two stages, and the second is the real one:
#
# 1. The VRRP CHILD PROCESS runs. keepalived reads the configuration first and
#    then forks the VRRP process; once it exists, the configuration was read
#    and the instance initialised. "The parent process exists" does not prove
#    that -- it also exists while it is still rejecting the configuration.
#
# 2. The VRRP STATE is reached (MASTER or BACKUP). Only keepalived can say
#    that, through a notify script or notify_fifo -- i.e. through
#    CONFIGURATION, which Puppet manages here. The package cannot set it
#    without meddling with the catalogue. Hence the contract: if someone
#    writes the state to $KEEPALIVED_STATE_FILE, that file counts. If it is
#    missing, stage 1 applies -- and the difference is named, not hidden.
#
#    The default is /run/keepalived.state, because EXACTLY THERE the sister
#    package keepalived-notify (from 0.1.3) writes:
#        case "$STATE" in MASTER|BACKUP|FAULT) echo "$STATE" >/run/keepalived.state
#    In 0.1.4 this said /run/keepalived/state -- a file IN a directory instead
#    of next to it --, and stage 2 could never be reached (Puppet session,
#    23.09.2026). Two packages of one family, two paths for the same fact: the
#    mistake does not show, because the fallback to stage 1 works.
#
#    The default is HERE and not in data/default.env: measured the same day,
#    data/check does NOT see the variables from default.env. s6-notifyoncheck
#    starts the check with the supervisor's environment, and run-script-daemon
#    reads default.env only in the child -- after the check was started. A
#    KEEPALIVED_STATE_FILE in default.env would have had no effect.
#
#    Since 0.1.9 stage 2 is REQUIRED when the configuration names
#    keepalived-notify.sh (data/notify-configured): run removes the state file
#    before every start, so the file only exists after a transition of THIS
#    run, and falling back to stage 1 would report "ready" as soon as the VRRP
#    process forks -- before the election. Without keepalived-notify.sh in the
#    configuration nothing writes the file, and stage 1 stays the answer.
#
# Why this is needed at all: until 0.1.3 the unit had no notification-fd, so
# s6 reported "up" as soon as the process ran. On a router that is too early:
# Puppet sees a finished service while the VRRP election is still running,
# and the router acceptance relies on the state.

set -u

STATE_FILE=${KEEPALIVED_STATE_FILE:-/run/keepalived.state}

if [ -r "$STATE_FILE" ]; then
    read -r _s < "$STATE_FILE" || _s=''
    case ${_s:-} in
    MASTER|BACKUP) exit 0 ;;
    FAULT) exit 1 ;;   # state reached, but faulty -- not ready
    *) exit 1 ;;
    esac
fi

# The configuration promises a state report: wait for it, no stage 1.
"${0%/*}/notify-configured" && exit 1

# Stage 1: does the parent process have a child?
_pid=$(cat /run/keepalived.pid 2>/dev/null || true)
case ${_pid:-} in
'' | *[!0-9]*)
    # Without a pid file do not guess the process tree: report not ready, so
    # that s6-notifyoncheck keeps asking instead of claiming readiness.
    exit 1
    ;;
esac
[ -d "/proc/$_pid" ] || exit 1

for _c in /proc/[0-9]*/stat; do
    # Field 4 is the PPID. One child of the keepalived parent is enough.
    set -- $(cat "$_c" 2>/dev/null) || continue
    [ "${4:-}" = "$_pid" ] && exit 0
done
exit 1
