#!/bin/sh
# Pt-baselayout/src/svc-script/svc.sh
# 
#  Copyright: ©2007–2014, Güralp Systems Ltd.
#  Author: R.J.Dunlop    <rdunlop@guralp.com>
#  License: GPLv3
#

PATH="/sbin:/usr/sbin:/bin:/usr/bin"

# Some system constants
CONFDIR="/etc/conf.d"
CONFDIR2="/etc/conf.local"
INITDIR="/etc/init.d"
INITDIR2="/etc/init.local"
VARDIR="/var/run/svc"
SVC="//sbin/svc"
MONITOR_PIDFILE="/var/run/svc_monitor.pid"


# Error echo function that we can turn on and off depending on the script mode
# we are in.
rc_error() {
    [ "${scriptmode}" != "daemon" -a "${scriptmode}" != "monitor" ] \
        && echo $*
}


########################################################################
# Overrideable command functions.
########################################################################
# These are the barebone (often NOOP) defaults

# You can call "requires" to state that another package is required and should
# be started before this service, and "depends_on" which is a stronger
# dependency which will cause this service to be stopped if the one that it
# depends on is stopped.  A non zero return will abort any startup so you can
# make your own tests.
#
# You may also call the functions "std_depends" and "std_requires" which have
# the same semantics as "depends_on" and "requires", but which take the
# following arguments:
#  $1 -> application name (as per gcs_deptool)
#  $2 -> path to config file
# These will use gcs_deptool to evaluate and register all dependencies for the
# service in question, based on its configuration file.
depend() {
    : NOOP
}

start() {
    : NOOP
}

stop() {
    : NOOP
}

restart() {
    svc_stop
    svc_start
}

reload() {
    svc_restart
}

zap() {
    # Since zap is the emergency version for when the regular stop fails
    # this may not make much sense
    svc_stop
}

stopped() {
    svc_zap
}

# This is a return code only routine for programs to use
running() {
    if [ -r "${VARDIR}/${servicename}.pid" ]
    then
        if [ -d "/proc/`cat "${VARDIR}/${servicename}.pid"`" ]
        then
            return 0
        fi
    elif [ -r "${VARDIR}/${servicename}.started" \
                -a -r "${VARDIR}/${servicename}.nopid" ]
    then
        return 0
    fi
    return 1
}

status() {
    if [ -r "${VARDIR}/${servicename}.started" ]
    then
        echo "Started: `cat "${VARDIR}/${servicename}.started"`"
    fi

    if running
    then
        if [  -r "${VARDIR}/${servicename}.pid" ]
        then
            echo "Process ID:  `cat "${VARDIR}/${servicename}.pid"`"
        fi
        echo "Running"
        depend
        return 0
    else
        echo "Not running"
        return 1
    fi
}

list() {
    [ -n "${MYLISTING}" ] && printf "%-30s - ${MYLISTING}\n" "${servicename}"
}

mrstatus() {
    local started pid status listing grp
    local startfile="${VARDIR}/${servicename}.started"
    local uptime

    if [ -r "${startfile}" ]
    then
        started="`cat "${startfile}"`"
        uptime="$((`date +%s` - `stat -c %Y "$startfile"`))"
    else
        started="unknown"
        uptime="-1"
    fi

    if running
    then
        if [ "${uptime}" -ge 0 -a "${uptime}" -lt 20 ]
        then
            status="restarting"
        else
            status="running"
        fi
        if [ -r "${VARDIR}/${servicename}.pid" ]
        then
            pid="$(cat "${VARDIR}/${servicename}.pid")"
        else
            pid="unknown"
        fi
    else
        if [ "${uptime}" -ge 0 -a "${uptime}" -lt 20 ]
        then
            status="restarting"
        else
            status="stopped"
        fi
        pid="none"
    fi

    listing="${MYLISTING}"
    [ -z "${listing}" ] && listing="${servicename}"

    # If the script doesn't specify a group directly guess based on the
    # service name (most use the generic conf group)
    grp="${CONTROL_GROUP}"
    if [ -z "${grp}" ]
    then
        case "${servicename}" in
        net_*)
            grp="netconf"
            ;;

        postgres*)
            grp="postgres"
            ;;

        *)
            grp="conf"
            ;;
        esac
    fi

    echo "${status}%${grp}%${pid}%${started}%${servicename}%${listing}"
}

usage() {
    [ -z "${MYUSAGE}" ] && MYUSAGE="{start|stop|reload|restart|status}"
    echo "usage: ${servicename} ${MYUSAGE}"
}

help() {
    if [ -n "${MYHELP}" ]
    then
        echo "${MYHELP}"
    else
        usage
    fi
}


########################################################################
# End of overrideable functions
########################################################################


# Housekeeping functions

check_dirs() {
    # We're called early in the boot sequence so ${VARDIR} may not exist
    # especially if /var is on a fresh tmpfs
    [ ! -d "${VARDIR}" ] && mkdir -p -m 755 "${VARDIR}"
    [ ! -d "${VARDIR}.restart" ] && mkdir -p -m 755 "${VARDIR}.restart"
}

clean_files() {
    rm -f "${VARDIR}/${servicename}.started"
    rm -f "${VARDIR}/${servicename}.restart"
    rm -f "${VARDIR}/${servicename}.monitor"
    rm -f "${VARDIR}/${servicename}.pid"
    rm -f "${VARDIR}/${servicename}.nopid"
    if [ -f "${VARDIR}/${servicename}.dependents" ]
    then
        mv -f "${VARDIR}/${servicename}.dependents" \
            "${VARDIR}/${servicename}.resume"
    fi
}

# Since this is commonly used to stop a service the error return indicates
# whether a process exists/persists after the kill not the success/failure
# of the kill itself.
kill_pid() {
    local i
    local pid
    local sig

    if [ -r "${VARDIR}/${servicename}.pid" ]
    then
        # Move file aside so svc_monitor does not attempt immediate restart
        mv "${VARDIR}/${servicename}.pid" "${VARDIR}/${servicename}.pid.exit"
        pid="`cat "${VARDIR}/${servicename}.pid.exit"`"

        # Take three shots at it
        for sig in term term kill
        do
            if kill -sig${sig} "${pid}" > /dev/null 2>&1
            then
                # Kill issued ok.  Give it a moment and check the process died
                for i in 1 2 3 4 5
                do
                    if [ -d "/proc/${pid}" ]
                    then
                        sleep 1
                    else
                        rm -f "${VARDIR}/${servicename}.pid.exit"
                        return 0
                    fi
                done
            else
                # Only reason for the kill to fail is pid does not exist
                rm -f "${VARDIR}/${servicename}.pid.exit"
                return 0
            fi
        done
        rc_error "Failed to kill process for ${servicename}"

        # Restore .pid file for someone else (svc_monitor) to retry
        mv "${VARDIR}/${servicename}.pid.exit" "${VARDIR}/${servicename}.pid"
        return 1
    else
        rc_error "No process to kill for ${servicename}"
    fi
    return 0
}


# Wait for up to 7 seconds for a file to appear
wait_for_file() {
    local file="$1"
    local i

    if [ -n "${file}" ]
    then
        for i in 1 2 3 4 5 6 7
        do
            if [ -e "${file}" ]
            then
                return 0
            fi
            sleep 1
        done
        [ -e "${file}" ]
    fi
}


# Dependency functions
#
# Called from the service scripts depends() function their exact behaviour
# depends on the command being processed

register_depends_on() {
    local d="${VARDIR}/${1}.dependents"

    if [ -r "${d}" ] && grep '^'${servicename}'$' "${d}" > /dev/null 2>&1
    then
        : Already in the file
    else
        echo "${servicename}" >> "${d}"
    fi
}

deregister_depends_on() {
    local f

    for f in "${VARDIR}/${1}.dependents" "${VARDIR}/${1}.resume"
    do
        if [ -r "${f}" ] && grep '^'${servicename}'$' "${f}" > /dev/null 2>&1
        then
            sed -i -e '/^'${servicename}'$/d' "${f}"
        fi
    done
}

requires() {
    local service="$1"

    if [ "${command}" = "svc_start" ]
    then
        if ${SVC} "${service}" running
        then
            # Okay it's already running
            return 0
        else
            ${SVC} ${svc_modeflag} "${service}" start
            return $?
        fi
    elif [ "${command}" = "status" ]
    then
        if ${SVC} "${service}" running
        then
            echo "Dependency ${service} is running"
            return 0
        else
            echo "Dependency ${service} is NOT RUNNING"
            return 1
        fi
    fi
    return 0
}

std_requires() {
    local appname="$1"
    local cfgfile="$2"
    local depsvc

    for depsvc in `gcs_deptool -a "${appname}" -c "${cfgfile}" show`
    do
        requires "${depsvc}"
    done
}

depends_on() {
    local service="$1"
    local ret="0"

    if [ "${command}" = "svc_start" ]
    then
        # Start things we depend on but making sure they don't trigger
        # a restart of ourselves
        deregister_depends_on "${service}"
        requires "${service}"
        ret="$?"
        register_depends_on "${service}"

    elif [ "${command}" = "svc_stop" ]
    then
        deregister_depends_on "${service}"

    elif [ "${command}" = "status" ]
    then
        requires "${service}"
        ret="$?"
    fi
    return ${ret}
}

std_depends() {
    local appname="$1"
    local cfgfile="$2"
    local depsvc

    for depsvc in `gcs_deptool -a "${appname}" -c "${cfgfile}" show`
    do
        depends_on "${depsvc}"
    done
}

resume_dependents() {
    local dservice

    if [ -r "${VARDIR}/${servicename}.resume" ] && running
    then
        while read dservice
        do
            ${SVC} ${svc_modeflag} "${dservice}" start
        done < "${VARDIR}/${servicename}.resume"

        rm -f "${VARDIR}/${servicename}.resume"
    fi
}

stop_dependents() {
    local dservice

    if [ -r "${VARDIR}/${servicename}.dependents" ]
    then
        while read dservice
        do
            if ${SVC} "${dservice}" running
            then
                ${SVC} ${svc_modeflag} "${dservice}" stop_dep
            fi
        done < "${VARDIR}/${servicename}.dependents"
    fi
}

# Utility for service script start() functions.  Starts a command as a daemon
# unless we've been called from a supervising daemon in which case we exec
# the command.  To make sense in this case daemonize() must be that last
# entry in the service start() function.
# Original version for commands that don't support GSL daemonizing options
daemonize_orig() {
    local pid

    if [ "${scriptmode}" = "daemon" ]
    then
        echo $$ > "${VARDIR}/${servicename}.pid"
        rm -f "${VARDIR}/${servicename}.nopid"
        exec $*
    else
        ( eval exec $* > /dev/null 2>&1 < /dev/null ) &
        pid="$!"

        echo "${pid}" > "${VARDIR}/${servicename}.pid"
        rm -f "${VARDIR}/${servicename}.nopid"
    fi
}

# Version for GSL daemons that support daemonizing options
daemonize() {
    local pid

    if [ "${scriptmode}" = "daemon" ]
    then
        echo $$ > "${VARDIR}/${servicename}.pid"
        rm -f "${VARDIR}/${servicename}.nopid"
        exec $* --pid-file "${VARDIR}/${servicename}.pid"
    else
        eval $* --pid-file "${VARDIR}/${servicename}.pid" --daemon
        rm -f "${VARDIR}/${servicename}.nopid"
    fi
}


# Service functions
#
# Wrappers around some of the redefinable functions above performing the
# dependency checks and other housekeeping

# Determine if we should perform the task startup [when called from
# task-startup or the svc_monitor] or signal the svc_monitor to do it for us
# [when called by the user (direct or via CGI)].
is_real_start() {
    [ "${scriptmode}" == "monitor" ]
}

ask_svc_monitor_to_start() {
    local act="$1"
    local pid

    echo "${act}" > "${VARDIR}.restart/${servicename}"

    if [ -r "${MONITOR_PIDFILE}" ]
    then
        pid="`cat "${MONITOR_PIDFILE}"`"
        [ -n "${pid}" ] && kill -sigusr1 "${pid}"
    fi
}

svc_start() {
    if is_real_start
    then
        if running
        then
            rc_error "Service ${servicename} is already running"
            resume_dependents
            return 1
        fi
        check_dirs
        if depend
        then
            date > "${VARDIR}/${servicename}.nopid"
            if start
            then
                date > "${VARDIR}/${servicename}.started"
            else
                rc_error "Startup for ${servicename} FAILED"
            return 1
            fi
        else
            rc_error "Startup dependancies for ${servicename} not satisfied"
            return 1
        fi
        resume_dependents
    else
        ask_svc_monitor_to_start start
    fi
}

stop_dep=""

svc_stop() {
    if running
    then
        # If this is a "normal" stop then deregister ourselves from all the
        # processes we depend on
        [ -z "${stop_dep}" ] && depend

        stop_dependents
        if stop
        then
            clean_files
            return 0
        else
            rc_error "FAILED to stop ${servicename}"
            return 1
        fi
    elif [ -r "${VARDIR}/${servicename}.started" ]
    then
        stop_dependents
        clean_files
        return 0
    else
        rc_error "Service ${servicename} is not running"
        return 1
    fi
}

svc_zap() {
    stop_dependents
    stop
    clean_files
}


# Called from the service monitor to perform a restart when a process has
# died for unknown reasons.
svc_stopped() {
    svc_zap
    if svc_start
    then
        resume_dependents
    fi
}

svc_restart() {
    if is_real_start
    then
        if running
        then
            restart
        else
            rc_error "Service ${servicename} is not running. Attempting start"
            svc_start
        fi
    else
        ask_svc_monitor_to_start restart
    fi
}

svc_reload() {
    if running
    then
        reload
    else
        rc_error "Service ${servicename} is not running"
        return 1
    fi
}


# Simple info commands

do_usage() {
    # This is sanitised removing the scriptmode flags and other options
    # intended for program use only
    echo "usage: svc service_name {start|stop|reload|restart|status}"
    echo "usage: svc --help {service_name}"
    echo "usage: svc --list"
    echo "usage: svc --status"
}

do_help() {
    echo "This program is used to control system services."
    echo
    echo "Use 'svc --list' to obtain a list of available services."
    echo "Use 'svc --status' for a status summary."
    echo "Use 'svc --help service' for information about an individual service."
    echo
    do_usage
}

do_list() {
    local s

    for s in "${INITDIR}/"* "${INITDIR2}/"*
    do
        # Non-executables in $INITDIR are not intended to be run by the
        # user and won't be listed
        if [ -f "${s}" -a -x "${s}" ]
        then
            if [ -r "${s}" ] && grep "MYLISTING" "${s}" > /dev/null 2>&1
                then
                "${s}" list
            fi
        fi
    done
}

do_status() {
    local s

    for s in "${INITDIR}/"* "${INITDIR2}/"*
    do
        if [ -f "${s}" -a -x "${s}" ]
        then
            # Call function within script for now, as it is twice as fast,
            # however if anyone overrides the running function in their script
            # This will need to revert to calling the script recursively
            #if $SVC $s running

            servicename="`basename "${s}"`"
            if running
            then
                printf "%-30s - Running\n" "${s}"
            else
                printf "%-30s - Not running\n" "${s}"
            fi
        fi
    done
}

do_mrstatus() {
    local s

    for s in "${INITDIR}/"* "${INITDIR2}/"*
    do
        if [ -f "${s}" -a -x "${s}" ]
        then
            MYLISTING=""
            CONTROL_GROUP=""
            source "${s}"
            servicename="`basename ${s}`"
            mrstatus
        fi
    done
}

# Check program arguments

# Grab any scriptmode flag (also the special --list and --help options)
# Users envoking the services via /etc/init.d/name will not be able to set
# these which is good as they are intended for program use only.
scriptmode="user"
svc_modeflag="$1"
case "${svc_modeflag}" in
--daemon)
    scriptmode="daemon"
    shift
    ;;

--monitor)
    scriptmode="monitor"
    shift
    ;;

--list)
    scriptmode="list"
    shift
    ;;

--help)
    scriptmode="help"
    shift
    ;;

--status)
    scriptmode="status"
    shift
    ;;

--mrstatus)
    scriptmode="mrstatus"
    shift
    ;;

*)
    svc_modeflag=""
    ;;
esac

export svc_modeflag="${svc_modeflag}"

# First parameter is the service name which may have spurious directory info
# depending on how we were envoked.  Scripts MUST reside in $INITDIR or $INITDIR2
if [ -z "$1" ]
then
    # Only help, list and status are allowed not to have a service name
    if [ "${scriptmode}" = "list" ]
    then
        do_list
        exit 0
    elif [ "${scriptmode}" = "help" ]
    then
        do_help
        exit 0
    elif [ "${scriptmode}" = "status" ]
    then
        do_status
        exit 0
    elif [ "${scriptmode}" = "mrstatus" ]
    then
        do_mrstatus
        exit 0
    else
        do_usage
        exit 1
    fi
fi
# RC scripts may use a symbolic link with a letter+numeric prefix so read the
# link and use the resolved name
if [ -L "$1" ]
then
    servicename="`readlink "$1"`"
    servicename="`basename "${servicename}"`"
else
    servicename="`basename $1`"
fi
servicefile="${INITDIR2}/${servicename}"
[ ! -f "${servicefile}" ] && servicefile="${INITDIR}/${servicename}"
if [ ! -f "${servicefile}" ]
then
    case "$2" in
    stop|zap|stopped)
        # Not fatal for these three (perform default actions)
        ;;
    *)
        echo "Service '${servicename}' does not exist"
        echo
        do_usage
        exit 1
        ;;
    esac
fi

# Next parameter should be the command.  All further parameters are ignored.
# Note there are several more commands than we own up to in the usage info.
# List and help modes have already set the command.
if [ "${scriptmode}" = "list" ]
then
    command="list"
elif [ "${scriptmode}" = "help" ]
then
    command="help"
else
    case "$2" in
    task-start)
        scriptmode="monitor"
        svc_modeflag="--monitor"
        command="svc_start"
        ;;

    start|stop|zap|stopped|restart|reload)
        # Call via a service wrapper
        command="svc_$2"
        ;;

    stop_dep)
        stop_dep="true"
        command="svc_stop"
        ;;

    status|running|list|help|usage|mrstatus)
        # Info commands can be called direct
        command="$2"
        ;;

    *)
        command="usage"
        ;;
    esac
fi

#echo "svc.sh called"
#echo "scriptmode  ${scriptmode}"
#echo "servicename ${servicename}"
#echo "command     ${command}"


# Load any service overrides and then call the command
[ -r "${servicefile}" ] && source "${servicefile}"
${command}

# vim: ts=4:sw=4:expandtab
