#!/bin/sh

# whatap_infra service
#


export WHATAP_HOME=/usr/whatap/infra/conf
PATH=/sbin:/usr/sbin:/usr/bin:/usr/lib/netsvc:/usr/lib/netsvc/yp
export PATH

BIN=/usr/whatap/infra/whatap_infrad
CONF=/usr/whatap/infra/conf/whatap.conf
PID=/usr/whatap/infra/conf/whatap.pid


whatapinfra_start() {
    export WHATAP_HOME=/usr/whatap/infra/conf
    PATH=/sbin:/usr/sbin:/usr/bin:/usr/lib/netsvc:/usr/lib/netsvc/yp
    export PATH

    BIN=/usr/whatap/infra/whatap_infrad
    CONF=/usr/whatap/infra/conf/whatap.conf
    PID=/usr/whatap/infra/conf/whatap.pid
    if [ -f "$PID" ]; then
        OLD_PID=$(cat "$PID" 2>/dev/null | tr -d ' \t\r\n')

        # SERVER-1901: compare the recorded pid exactly against the pids of THIS
        # install. The previous check grepped the whole ps line for the pid as a
        # substring, so an unrelated column (ppid, a time field, a longer pid)
        # could satisfy it; and unlike stop()/term() it skipped the tr -d above,
        # so a stray CR in the pid file made the match fail. Both error
        # directions matter now that a sweep follows: a false "running" blocks a
        # legitimate start, and a false "not running" drops a healthy master
        # into the sweep below and restarts it for no reason.
        if is_valid_pid "$OLD_PID" \
            && whatap_infrad_pids | grep -x "$OLD_PID" >/dev/null 2>&1; then
            echo "whatap-infra already running (PID $OLD_PID)."
            exit 1
        else
            echo "Stale PID file found (PID $OLD_PID). Removing $PID"
            rm -f "$PID"
        fi
    fi


    if [ ! -r $CONF ]; then
        echo "$CONF does not appear to exist. Abort."
        exit 1
    fi

    if [ -s $PID ]; then
        echo "whatap-infra appears to already be running?\nTo override, you could delete pid file like following.\n\nrm -f $PID \n"
        exit 1
    fi

    # SERVER-1901: reaching here means no tracked master is running, yet workers
    # orphaned by an earlier crash / kill -9 / stop-without-cleanup may still be
    # alive - stop() removes the pid file, so the checks above cannot see them.
    # Starting on top of those leftovers is exactly how the orphan count grew one
    # process per restart cycle on HP-UX. Sweep before spawning a new master.
    # (Defined further down; sh resolves functions at call time.)
    kill_whatap_infrad_processes

    echo "Starting whatap-infra daemon..."
    if [ -x $BIN ]; then
        $BIN
    fi
}


# SERVER-1901: reject anything that is not a positive decimal pid before it
# reaches kill. An empty / corrupt pid file (e.g. "-1" or garbage) would
# otherwise turn "kill" into a broadcast (kill -1 signals every process the
# user owns) or signal a recycled, unrelated pid.
is_valid_pid() {
    case "$1" in
        ''|*[!0-9]*) return 1 ;;
        *) [ "$1" -gt 0 ] ;;
    esac
}

# SERVER-1901: the pid file only records the master process. The master forks a
# "foreground" worker child whose pid is never tracked, and the service scripts
# used to stop the master with SIGQUIT - a signal the agent does not register a
# handler for - so it died via the default action WITHOUT running its child-kill
# logic, and dumped core on the way out. Worker children orphaned this way (or
# by a kill -9 on the master) linger and accumulate across restart/patch/
# reinstall, which is the zombie/orphan buildup observed on HP-UX. We now send
# SIGTERM, which the agent does handle.
#
# List the pids of every whatap_infrad process belonging to THIS install. We
# intentionally do NOT scope by parent pid: orphans from earlier cycles have
# already been reparented to init (ppid=1), so ppid matching would miss exactly
# the leaked workers we must reap.
#
# The match is an exact whitespace-delimited token equal to the absolute $BIN
# path - not a bare "whatap_infrad" name and not a substring:
#   - the install root is NOT fixed: install.sh rewrites the default prefix in
#     this very file to $INSTALL_ROOT at install time (which is also why this
#     comment must not spell the default prefix out - the sed would rewrite the
#     comment too and invert its meaning). A bare-name match therefore also hits
#     agents of a different install root on the same host, and any unrelated
#     process that merely mentions the binary (an editor, a tail, a wrapper);
#   - a substring match on the full path still hits siblings that share it as a
#     prefix - the patch script leaves "whatap_infrad.bak-<timestamp>" files
#     right beside the binary, so this is not hypothetical.
# Both were reproduced on HP-UX. The master runs as "<python> $BIN" and its
# worker child as "<python> $BIN foreground" (the child inherits argv[0] from
# the master), so an exact token match covers every process we own and nothing
# we do not. awk's own argv holds "bin=<path>", never a bare "<path>" token, so
# the matcher cannot select itself.
whatap_infrad_pids() {
    ps -ef 2>/dev/null | awk -v bin="$BIN" '
        { for (i = 1; i <= NF; i++) if ($i == bin) { print $2; break } }'
}

# Poll for the processes to drain rather than sleeping a fixed amount. The
# agent's SIGTERM handler kills its child and then sleeps 5s before exiting, so
# a shorter wait would SIGKILL the master mid-shutdown and lose its termination
# log; older/slower boxes need more headroom still. Polling keeps the common
# case near 1s while allowing up to 10s before we escalate to SIGKILL.
wait_whatap_infrad_exit() {
    waited=0
    while [ "$waited" -lt 10 ]; do
        if [ -z "$(whatap_infrad_pids)" ]; then
            return 0
        fi
        sleep 1
        waited=$((waited + 1))
    done
    return 1
}

# Terminate every whatap_infrad process of this install. Signal masters first so
# their own handler can kill their live child, then force-kill any survivors.
# This also guarantees the master itself is gone before we drop the pid file, so
# a following start cannot race into a second instance.
kill_whatap_infrad_processes() {
    for p in $(whatap_infrad_pids); do
        is_valid_pid "$p" || continue
        echo "Stopping whatap-infra process (PID $p)"
        kill -TERM "$p" 2>/dev/null
    done

    if wait_whatap_infrad_exit; then
        return 0
    fi

    for p in $(whatap_infrad_pids); do
        is_valid_pid "$p" || continue
        echo "whatap-infra process (PID $p) still alive, sending SIGKILL"
        kill -9 "$p" 2>/dev/null
    done

    # SIGKILL 도 즉시 반영되지 않는다. 마지막으로 한 번 더 확인해서 이 함수의
    # 종료 코드가 "정말 다 없어졌는가" 를 뜻하게 한다. 이 확인이 없으면
    # 반환값은 위 루프의 마지막 kill 이 남긴 우연한 값이 되고 - 대상이 이미
    # 사라져 루프가 한 번도 돌지 않으면 0 - 레거시 경로가 잔존을 성공으로
    # 보고하게 된다.
    wait_whatap_infrad_exit
}


# SERVER-1901: stop/term no longer signal the pid from the pid file directly.
# That kill was unverified - unlike start(), it never checked that the recorded
# pid still belongs to this install, so a stale pid file whose number had been
# recycled meant signalling an unrelated process. It was also redundant:
# kill_whatap_infrad_processes() already signals every process matching $BIN,
# which includes the master, and doing both delivered a second SIGTERM that
# could re-enter the agent's handler while it was still shutting down.
whatapinfra_term() {
    echo "Shutdown whatap-infra quickly..."
    kill_whatap_infrad_processes
    rm -f $PID
}


# 이 버전부터 whatap_infrad 가 control-stop 을 이해한다.
# 그 아래 버전에 control-stop 을 주면 에러가 아니라 무한 대기가 된다.
MIN_CONTROL_VERSION=1.5.0

# X.Y.Z 형태의 버전 문자열을 정수로 바꾼다. 각 구성요소를 3자리 폭으로
# 묶는다 - 2자리였을 때 1.4.100 처럼 세 자리 컴포넌트가 앞자리로 넘쳐
# (overflow) 1.5.0 보다 큰 것으로 비교되는 결함이 있었다("판단이 안 서면
# 옛 길로" 가야 하는데 거꾸로 새 길을 타 버린다). 숫자가 아닌 값은 0으로
# 막아 printf 가 stderr 에 에러를 흘리지 않게 한다.
version_num() {
    major=$(echo "$1" | cut -d. -f1)
    minor=$(echo "$1" | cut -d. -f2)
    patch=$(echo "$1" | cut -d. -f3)
    case "$major" in ''|*[!0-9]*) major=0 ;; esac
    case "$minor" in ''|*[!0-9]*) minor=0 ;; esac
    case "$patch" in ''|*[!0-9]*) patch=0 ;; esac
    printf "%d%03d%03d" "$major" "$minor" "$patch"
}

agent_supports_control() {
    [ -x "$BIN" ] || return 1
    # 탐침에도 타임아웃을 건다. 실패 양상이 행이라 타임아웃이 없으면
    # 탐침 자체가 멈춘다. 워처(watcher)의 표준출력/에러는 /dev/null 로
    # 돌린다 - 안 그러면 명령치환의 파이프 쓰기측을 워처가 쥐고 있어서,
    # 탐침이 곧바로 끝나는 신버전 빠른 경로에서도 워처가 잠들어 있는 5초
    # 내내 v=$(...) 가 EOF 를 못 보고 기다린다(실측: 매 stop/restart 마다
    # 5초가 그냥 더 붙는다). wait 가 끝나는 즉시 워처를 죽여 - probe pid
    # 가 재사용된 뒤 엉뚱한 프로세스를 5초 뒤에 kill -9 하는 사고도 막고,
    # 워처가 불필요하게 남아있지 않게 한다.
    v=$("$BIN" version 2>/dev/null &
        probe=$!
        ( sleep 5; kill -9 $probe 2>/dev/null ) >/dev/null 2>&1 &
        watcher=$!
        wait $probe
        rc=$?
        kill -9 $watcher 2>/dev/null
        wait $watcher 2>/dev/null
        exit $rc) || return 1
    # 첫 줄만 쓴다 - 버전 뒤에 부가 출력이 붙어도 오탐하지 않는다.
    v=$(printf '%s\n' "$v" | head -n 1)
    [ -n "$v" ] || return 1
    [ "$(version_num "$v")" -ge "$(version_num "$MIN_CONTROL_VERSION")" ]
}

# 정지의 결과를 삼키지 않는다.
#
# 예전에는 `rm -f $PID` 가 이 함수의 마지막 명령이라 함수의 - 그리고
# `whatap-infra stop` 의 - 종료 코드가 항상 rm 의 것(=0)이었다. ER710007/8
# 로 끝난 정지도 호출자에게는 성공으로 보였다: infra_agent_patch_hpux.sh 의
# `if [ $? -ne 0 ]` 는 발화할 수 없었고, restart 는 아직 살아 있는 에이전트
# 위에 2세대를 띄웠다. 진실은 상태 파일(control_status.json)에만 남는데 그것을
# 읽는 출하 호출자는 하나도 없다.
#
# 실패했을 때 pid 파일을 남기는 것도 같은 목적이다. whatapinfra_start() 가
# 그 파일로 "이미 실행 중" 을 판정하므로, 실패한 정지 뒤에 이어지는
# start/restart 가 2세대를 띄우지 못하게 막아 준다.
whatapinfra_stop() {
    echo "Shutdown whatap-infra gracefully..."
    if agent_supports_control; then
        "$BIN" control-stop
        rc=$?
    else
        kill_whatap_infrad_processes
        rc=$?
    fi
    if [ $rc -eq 0 ]; then
        rm -f $PID
    else
        echo "whatap-infra stop failed (rc=$rc); keeping $PID"
    fi
    return $rc
}


whatapinfra_restart() {
    whatapinfra_stop
    rc=$?
    # 정지가 실패했으면 기동하지 않는다. 살아 있는 에이전트 위에 2세대를
    # 띄우는 것이 이 브랜치가 막으려는 바로 그 사고다.
    if [ $rc -ne 0 ]; then
        echo "whatap-infra restart aborted: stop failed (rc=$rc)"
        return $rc
    fi
    sleep 3
    whatapinfra_start
}


case "$1" in
    start)
        whatapinfra_start
        ;;
    stop)
        whatapinfra_stop
        ;;
    # SERVER-1901: whatapinfra_term existed but had no case branch, so it was
    # unreachable. Wire it up rather than leave dead code behind.
    term)
        whatapinfra_term
        ;;
    restart)
        whatapinfra_restart
        ;;
    *)
        echo "usage: `basename $0` {start|stop|term|restart}"
esac

