#!/bin/sh
set -eu

# This file is installed outside /usr/bin so a package transaction cannot
# replace the recovery procedure before it has quiesced the old daemon.
VPX_CLI=${VPX_CLI:-/usr/bin/vpx}
DATA_DIR=${VPX_DATA_DIR:-/var/lib/vpx}
RESTART_MARKER=${VPX_RESTART_MARKER:-/run/vpx-package-restart}
ARCH_RESTART_MARKER=${VPX_ARCH_RESTART_MARKER:-/run/vpx-arch-upgrade-restart}
SERVICE_INTENT=${VPX_SERVICE_INTENT:-$DATA_DIR/service-restart.intent}
UPDATE_INTENT=${VPX_UPDATE_INTENT:-$DATA_DIR/update-recovery-pending}
TIMEOUT=${VPX_RECOVERY_TIMEOUT:-30}
STATUS_TIMEOUT=${VPX_STATUS_TIMEOUT:-2}
SYNC=${VPX_SYNC:-sync}

# This marker is understood only by daemon versions which implement the
# recovery-only startup path. An upgrade from an older release cannot make the
# already-installed daemon honor it; power-loss safety becomes complete only
# after the first adopting install.
arm_service_intent() {
    mkdir -p "$DATA_DIR" || return 1
    chmod 700 "$DATA_DIR" || return 1
    intent_temporary="$DATA_DIR/.service-restart.intent.$$"
    rm -f "$intent_temporary" || return 1
    umask 077
    if ! printf '%s\n' restart > "$intent_temporary" \
        || ! chmod 600 "$intent_temporary" \
        || ! "$SYNC" \
        || ! mv -f "$intent_temporary" "$SERVICE_INTENT" \
        || ! "$SYNC"; then
        rm -f "$intent_temporary"
        return 1
    fi
}

clear_service_intent() {
    rm -f "$SERVICE_INTENT" || return 1
    "$SYNC"
}

service_running() {
    if [ -d /run/systemd/system ]; then
        systemctl is-active --quiet vpxd
    elif command -v rc-service >/dev/null 2>&1; then
        rc-service vpxd status >/dev/null 2>&1
    elif [ -x /etc/init.d/vpxd ]; then
        /etc/init.d/vpxd status >/dev/null 2>&1
    else
        return 1
    fi
}

daemon_running() {
    service_running || vpxd_process_running
}

vpxd_process_running() {
    for comm in /proc/[0-9]*/comm; do
        [ -r "$comm" ] || continue
        IFS= read -r process_name < "$comm" || continue
        [ "$process_name" = vpxd ] || continue
        process_directory=${comm%/comm}
        process_stat=$process_directory/stat
        if [ -r "$process_stat" ]; then
            if IFS= read -r process_stat_line < "$process_stat"; then
                process_stat_fields=${process_stat_line##*) }
                process_state=${process_stat_fields%% *}
                [ "$process_state" = Z ] && continue
                return 0
            fi
        fi
        [ ! -d "$process_directory" ] || return 0
    done
    return 1
}

terminate_vpxd_processes() {
    for comm in /proc/[0-9]*/comm; do
        [ -r "$comm" ] || continue
        IFS= read -r process_name < "$comm" || continue
        [ "$process_name" = vpxd ] || continue
        process_id=${comm#/proc/}
        process_id=${process_id%/comm}
        kill -TERM "$process_id" 2>/dev/null || true
    done
}

wait_for_stop() {
    stop_deadline=$((TIMEOUT + 1))
    while daemon_running && [ "$stop_deadline" -gt 0 ]; do
        sleep 1
        stop_deadline=$((stop_deadline - 1))
    done
    ! daemon_running
}

stop_service() {
    stop_status=0
    if [ -d /run/systemd/system ]; then
        systemctl stop vpxd || stop_status=$?
    elif command -v rc-service >/dev/null 2>&1; then
        rc-service vpxd stop || stop_status=$?
    elif [ -x /etc/init.d/vpxd ]; then
        /etc/init.d/vpxd stop || stop_status=$?
    fi
    terminate_vpxd_processes
    return "$stop_status"
}

start_service() {
    if [ -d /run/systemd/system ]; then
        systemctl start vpxd
    elif command -v rc-service >/dev/null 2>&1; then
        rc-service vpxd start
    elif [ -x /etc/init.d/vpxd ]; then
        /etc/init.d/vpxd start
    else
        echo "Error: cannot identify the vpxd service manager." >&2
        return 1
    fi
}

require_recovery_complete() {
    for recovery_file in \
        "$DATA_DIR/ipv6_guard.json" \
        "$DATA_DIR/ipv6_routes.json" \
        "$DATA_DIR/router_cleanup.json"; do
        if [ -e "$recovery_file" ]; then
            echo "Error: network recovery is still pending at $recovery_file; the package transaction was aborted." >&2
            return 1
        fi
    done
}

recovery_pending() {
    [ -e "$DATA_DIR/ipv6_guard.json" ] \
        || [ -e "$DATA_DIR/ipv6_routes.json" ] \
        || [ -e "$DATA_DIR/router_cleanup.json" ]
}

daemon_state_matches() {
    expected_state=$1
    local_identity=$("$VPX_CLI" --internal-build-identity 2>/dev/null) || return 1
    daemon_state=$("$VPX_CLI" --internal-daemon-state \
        http://127.0.0.1:9847 2>/dev/null) || return 1
    [ "$daemon_state" = "$local_identity $expected_state" ]
}

run_cli_with_timeout() {
    cli_command=$1
    cli_timeout=$2
    case "$cli_command" in
        internal-recovery-probe) daemon_state_matches recovery & ;;
        internal-full-probe) daemon_state_matches full & ;;
        *) "$VPX_CLI" --daemon http://127.0.0.1:9847 "$cli_command" >/dev/null 2>&1 & ;;
    esac
    cli_pid=$!
    (
        sleep "$cli_timeout"
        kill -TERM "$cli_pid" 2>/dev/null || exit 0
        sleep 1
        kill -KILL "$cli_pid" 2>/dev/null || true
    ) &
    cli_timeout_pid=$!
    if wait "$cli_pid"; then cli_status=0; else cli_status=$?; fi
    kill "$cli_timeout_pid" 2>/dev/null || true
    wait "$cli_timeout_pid" 2>/dev/null || true
    return "$cli_status"
}

wait_for_recovery_only() {
    recovery_deadline=$((TIMEOUT + 1))
    while [ "$recovery_deadline" -gt 0 ]; do
        if daemon_running && [ -f "$SERVICE_INTENT" ] \
            && ! recovery_pending \
            && run_cli_with_timeout internal-recovery-probe "$STATUS_TIMEOUT"; then
            return 0
        fi
        sleep 1
        recovery_deadline=$((recovery_deadline - 1))
    done
    return 1
}

wait_for_full_daemon() {
    recovery_deadline=$((TIMEOUT + 1))
    while [ "$recovery_deadline" -gt 0 ]; do
        if daemon_running && [ ! -e "$SERVICE_INTENT" ] \
            && ! recovery_pending \
            && run_cli_with_timeout internal-full-probe "$STATUS_TIMEOUT"; then
            return 0
        fi
        sleep 1
        recovery_deadline=$((recovery_deadline - 1))
    done
    return 1
}

preserve_recovery_only() {
    if ! arm_service_intent; then
        echo "Error: vpxd recovery failed and durable restart intent could not be restored." >&2
        return 1
    fi
    stop_service >/dev/null 2>&1 || true
    wait_for_stop >/dev/null 2>&1 || true
    start_service >/dev/null 2>&1 || true
}

complete_running_transaction() {
    if ! start_service || ! wait_for_recovery_only; then
        echo "Error: vpxd did not reach recovery-only readiness; restart intent was preserved." >&2
        return 1
    fi
    if [ -e "$UPDATE_INTENT" ]; then
        rm -f "$RESTART_MARKER" "$ARCH_RESTART_MARKER"
        return 0
    fi
    if ! clear_service_intent; then
        echo "Error: durable restart intent could not be cleared after recovery proof." >&2
        return 1
    fi
    if ! stop_service || ! wait_for_stop \
        || ! start_service || ! wait_for_full_daemon; then
        preserve_recovery_only || true
        echo "Error: vpxd did not reach full readiness; restart intent was preserved." >&2
        return 1
    fi
    rm -f "$RESTART_MARKER" "$ARCH_RESTART_MARKER"
}

complete_stopped_transaction() {
    if ! start_service || ! wait_for_recovery_only; then
        echo "Error: vpxd did not reach recovery-only readiness; restart intent was preserved." >&2
        return 1
    fi
    if [ -e "$UPDATE_INTENT" ]; then
        rm -f "$ARCH_RESTART_MARKER"
        return 0
    fi
    if ! stop_service || ! wait_for_stop; then
        echo "Error: vpxd could not be returned to its stopped state; restart intent was preserved." >&2
        return 1
    fi
    if ! clear_service_intent; then
        echo "Error: durable restart intent could not be cleared after recovery proof." >&2
        return 1
    fi
    rm -f "$RESTART_MARKER" "$ARCH_RESTART_MARKER"
}

restore_stopped_service() {
    complete_running_transaction
}

recover_stopped_daemon() {
    umask 077
    restart_after_recovery=${1:-1}
    had_arch_restart=0
    [ ! -f "$ARCH_RESTART_MARKER" ] || had_arch_restart=1
    printf '%s\n' restart > "$RESTART_MARKER"
    arm_service_intent || return 1
    if [ "$restart_after_recovery" -eq 1 ]; then
        complete_running_transaction || return 1
    else
        complete_stopped_transaction || return 1
    fi
    if [ "$had_arch_restart" -eq 1 ]; then
        printf '%s\n' recover > "$ARCH_RESTART_MARKER"
    fi
}

disconnect_with_timeout() {
    "$VPX_CLI" --daemon http://127.0.0.1:9847 disconnect &
    disconnect_pid=$!
    (
        sleep "$TIMEOUT"
        kill -TERM "$disconnect_pid" 2>/dev/null || exit 0
        sleep 1
        kill -KILL "$disconnect_pid" 2>/dev/null || true
    ) &
    timeout_pid=$!
    if wait "$disconnect_pid"; then
        disconnect_status=0
    else
        disconnect_status=$?
    fi
    kill "$timeout_pid" 2>/dev/null || true
    wait "$timeout_pid" 2>/dev/null || true
    return "$disconnect_status"
}

case "${1:-}" in
    quiesce)
        if ! daemon_running; then
            if require_recovery_complete; then
                if ! arm_service_intent; then
                    echo "Error: could not persist service restart intent; the package transaction was aborted." >&2
                    exit 1
                fi
                exit 0
            fi
            restart_after_recovery=0
            [ ! -f "$RESTART_MARKER" ] || restart_after_recovery=1
            recover_stopped_daemon "$restart_after_recovery" || true
            exit 1
        fi
        was_running=0
        if service_running; then
            was_running=1
        fi
        [ "$was_running" -eq 0 ] || rm -f "$RESTART_MARKER"
        if [ ! -x "$VPX_CLI" ]; then
            echo "Error: cannot safely replace running VPX because $VPX_CLI is missing." >&2
            exit 1
        fi
        if ! disconnect_with_timeout; then
            echo "Error: vpxd Disconnect failed or timed out; the package transaction was aborted." >&2
            exit 1
        fi
        require_recovery_complete
        if ! arm_service_intent; then
            echo "Error: could not persist service restart intent; the package transaction was aborted." >&2
            exit 1
        fi
        if [ "$was_running" -eq 1 ]; then
            umask 077
            printf '%s\n' restart > "$RESTART_MARKER"
        fi
        if ! stop_service; then
            if ! daemon_running; then
                recover_stopped_daemon "$was_running" || true
            fi
            echo "Error: vpxd service stop failed; the package transaction was aborted." >&2
            exit 1
        fi
        if ! wait_for_stop; then
            echo "Error: vpxd did not stop; the package transaction was aborted." >&2
            exit 1
        fi
        if ! require_recovery_complete; then
            recover_stopped_daemon "$was_running" || true
            exit 1
        fi
        [ "$was_running" -eq 1 ] || exit 0
        ;;
    restart)
        if [ ! -f "$RESTART_MARKER" ] && [ ! -e "$SERVICE_INTENT" ]; then
            rm -f "$ARCH_RESTART_MARKER"
            exit 0
        fi
        require_recovery_complete
        if [ ! -e "$SERVICE_INTENT" ] && ! arm_service_intent; then
            echo "Error: could not persist service restart intent; volatile intent was preserved." >&2
            exit 1
        fi
        if [ -f "$RESTART_MARKER" ]; then
            complete_running_transaction
        else
            complete_stopped_transaction
        fi
        ;;
    recover)
        umask 077
        printf '%s\n' restart > "$RESTART_MARKER"
        intent_was_pending=0
        [ ! -e "$SERVICE_INTENT" ] || intent_was_pending=1
        if ! arm_service_intent; then
            echo "Error: could not persist service restart intent; volatile intent was preserved." >&2
            exit 1
        fi
        if daemon_running && [ "$intent_was_pending" -eq 0 ]; then
            require_recovery_complete
            echo "Error: vpxd is still running; recovery intent was preserved." >&2
            exit 1
        fi
        complete_running_transaction
        ;;
    clear-marker)
        rm -f "$RESTART_MARKER" "$ARCH_RESTART_MARKER"
        ;;
    finalize-remove)
        if daemon_running; then
            echo "Error: vpxd is still running; durable restart intent was preserved." >&2
            exit 1
        fi
        require_recovery_complete
        if [ -e "$UPDATE_INTENT" ]; then
            echo "Error: an update still owns recovery; durable restart intent was preserved." >&2
            exit 1
        fi
        if ! clear_service_intent; then
            echo "Error: durable restart intent could not be retired after service removal." >&2
            exit 1
        fi
        rm -f "$RESTART_MARKER" "$ARCH_RESTART_MARKER"
        ;;
    *)
        echo "usage: $0 {quiesce|restart|recover|clear-marker|finalize-remove}" >&2
        exit 2
        ;;
esac
