#!/bin/sh
#/ 
#/ \file
#/
#/ \brief       control script for typical services
#/ 
#/ \author      Arthur
#/
#/ \date        January 28, 2013
#/ 
#/ \version     0.2
#/


################################################################################
#                                                                              #
#                              C O N S T A N T S                               #
#                                                                              #
################################################################################


RC_NAME=`basename $0 2>/dev/null`
RC_DIR=`dirname \`readlink -qe $0\` 2>/dev/null`
RC_VER="0.2"
RC_ID="service control"

STATE_DIR="/var/run"            # system's state directory
PID_EXT="pid"                   # PID file extension (w/o dot)

CONFIG_DIR="/etc/svc.d"         # service configuration directory

MAX_DAEMONS=8                   # max. daemon configurations supported


################################################################################
#                                                                              #
#                               I N C L U D E S                                #
#                                                                              #
################################################################################


. $RC_DIR/include/service_utils
. $RC_DIR/include/rc_utils


################################################################################
#                                                                              #
#                              F U N C T I O N S                               #
#                                                                              #
################################################################################


#-------------------------------------------------------------------------------


#/
#/ Output script usage and exit.
#/
#/ \returns
#/
#/ Always exit code 1.
#/
usage()
{
    $ECHO "Usage: $RC_NAME [-l][-n][-q][-v][-h][-V] [ACTION] [SERVICE]"

    exit 1
}


#-------------------------------------------------------------------------------


#/
#/ Output script version and exit.
#/
#/ \returns
#/
#/ Always exit code 1.
#/
version()
{
    $ECHO "$RC_NAME $RC_VER"

    exit 1
}


#-------------------------------------------------------------------------------


#/ 
#/ Startup a service. A service consists of one or more daemon processes which
#/ are defined in the service's config file located in <tt>CONFIG_DIR</tt>. The
#/ daemons are started sequentially, and in increasing numerical order. Each
#/ daemon can have a pre-start and a post-start command associated with it.
#/ The pre-start command is executed just prior to the daemon process itself is
#/ is executed, while the post-start command is executed just after the
#/ daemon process has been executed. Both the pre-start command and the
#/ post-start command as well as the daemon process must execute without errors
#/ else the service is aborted prematurely.
#/
#/ Apart from a daemon-specific pre- and post-start command, the service as a
#/ whole has a pre- and post-start command as well. The pre-start command in
#/ this case is execute before the first daemon process is started, while the 
#/ post-start command is executed after the last daemon of the service was
#/ started. Note that the pre-start command will not execute if the service was
#/ already running, and also, the post-start command will not execute if any of
#/ the daemons failed to execute.
#/
#/ \returns
#/ 
#/ Return code 0 if service started successfully; code 1 if it failed to start
#/ or any other error condition occurred.
#/ If the service is already running, it won't be started again so this is
#/ considered an error condition.
#/ 
start()
{
    assert start "$START_SETTLE -ge 0"

    service=""              # service identifier
    exec=""                 # full path daemon executable
    name=""                 # daemon basename
    defs=""
    num_daemons=0
    num_started=0
    num=0
    ret=0

    service=`trim "$ID"`

    num_daemons=`num_service_daemons`
    num_started=0

    is_service_running
    if test $? -ne 0 -a $? -ne 2
    then
        $ECHO_N "Starting $service service..."

        # service pre-start command:
        do_commands $SERVICE 0 START PRE 1
        ret=$?
        if test $ret -ne 0
        then
            $ECHO
            error "$SERVICE: pre-start commands failed ($ret)"
        fi

        if test $ret -eq 0
        then
            #
            # Start daemons one by one; abort as soon as one fails.
            #
            defs=`list_daemon_defs`
            for num in $defs
            do
                eval exec=\$DAEMON${num}_EXEC
                name=`basename $exec 2>/dev/null`

                # daemon N pre-start command:
                do_commands $SERVICE $num START PRE 1
                ret=$?
                if test $ret -ne 0
                then
                    $ECHO
                    error "$SERVICE: DAEMON$num: pre-start commands failed ($ret)"
                    break
                fi

                # start daemon N:
                $DRY_RUN execute_daemon $num
                ret=$?
                if test $ret -eq 0
                then
                    sleep $START_SETTLE
                    $ECHO_N " $name"
                else
                    break
                fi

                # daemon N post-start command:
                do_commands $SERVICE $num START POST 1
                ret=$?
                if test $ret -ne 0
                then
                    $ECHO
                    error "$SERVICE: DAEMON$num: post-start commands failed ($ret)"
                    break
                fi

                num_started=`expr "$num_started" + "1"`
            done
            $ECHO
        fi

        # service post-start command:
        if test $ret -eq 0
        then
            do_commands $SERVICE 0 START POST 1
            ret=$?
            if test $ret -ne 0
            then
                $ECHO
                error "$SERVICE: post-start commands failed ($ret)"
            fi
        fi
    else
        $ECHO "Service $service already running."
    fi


    if test $num_started -ge 1 -a $num_started -eq $num_daemons
    then
        ret=0       # all daemons (>=1) have started successfully
    else
        ret=1       # nothing started or some failed to start
    fi
    return $ret
}


#-------------------------------------------------------------------------------


#/ 
#/ Shutdown a service. This function behaves the same but with opposite effect
#/ as the start() function. Pre- and post-stop commands can be associated with
#/ a daemon process as well as to the service as a whole, and they are all
#/ executed at the appropriate moment. However, the function does not abort
#/ prematurely when an error occurs. Instead, it continues stopping daemons
#/ trying to shutdown the service as much as possible.
#/ 
#/ \returns
#/ 
#/ Return code 0 if service has been completely stopped; code 1 in case the
#/ service was not running or any other error condition.
#/
stop()
{
    assert stop "$STOP_SETTLE -ge 0"

    service=""              # service identifier
    exec=""                 # full path daemon executable
    name=""                 # daemon basename
    defs=""
    num_daemons=0
    num_stopped=0
    num=0
    ret=0

    service=`trim "$ID"`

    num_daemons=`num_service_daemons`
    num_stopped=0

    is_service_running
    if test $? -eq 0 -o $? -eq 2
    then
        $ECHO_N "Stopping $service service..."

        # service pre-stop command:
        do_commands $SERVICE 0 STOP PRE 0
        ret=$?
        if test $ret -ne 0
        then
            $ECHO
            warning "$SERVICE: pre-stop commands failed ($ret)"
        fi

        if test $ret -eq 0
        then
            #
            # Stop daemons one by one; ignore failures.
            #
            defs=`list_daemon_defs`
            for num in $defs
            do
                eval exec=\$DAEMON${num}_EXEC
                name=`basename $exec 2>/dev/null`

                # daemon N pre-stop command:
                do_commands $SERVICE $num STOP PRE 0
                ret=$?
                if test $ret -ne 0
                then
                    $ECHO
                    warning "$SERVICE: DAEMON$num: pre-stop commands failed ($ret)"
                fi

                # stop daemon N:
                $DRY_RUN terminate_daemon $num
                ret=$?
                if test $ret -eq 0
                then
                    sleep $STOP_SETTLE
                    $ECHO_N " $name"
                fi

                # daemon N post-stop command:
                do_commands $SERVICE $num STOP POST 0
                ret=$?
                if test $ret -ne 0
                then
                    $ECHO
                    warning "$SERVICE: DAEMON$num: post-stop commands failed ($ret)"
                fi

                num_stopped=`expr "$num_stopped" + "1"`
            done
            $ECHO
        fi

        # service post-stop command:
        do_commands $SERVICE 0 STOP POST 0
        ret=$?
        if test $ret -ne 0
        then
            $ECHO
            warning "$SERVICE: post-stop commands failed ($ret)"
        fi
    else
        $ECHO "Service $service not running."
    fi


    if test $num_stopped -ge 1 -a $num_stopped -eq $num_daemons
    then
        ret=0       # all daemons (>=1) have stopped successfully
    else
        ret=1       # nothing stopped or some failed to stop
    fi
    return $ret
}


#-------------------------------------------------------------------------------


#/ 
#/ Shutdown and restart a service. If the service is not running, it is simply
#/ started as if a \c start action was given.
#/ 
#/ \returns
#/ 
#/ Return code 0 if restart sequence succeeded, otherwise code 1.
#/ 
restart()
{
    assert restart "$RESTART_PAUSE -ge 0"

    service=""
    ret=0

    service=`trim "$ID"`

    # stop service if it's running:
    is_service_running
    if test $? -eq 0
    then
        stop
        sleep $RESTART_PAUSE
    fi
    # ...and start it again:
    start
    ret=$?

    return $ret
}


#-------------------------------------------------------------------------------


#/
#/ Reload service configuration.
#/
#/ \returns
#/
#/ Return code 0 if reloading succeeded; code 1 in case reloading failed or
#/ any other error occurred.
#/
#/ \todo
#/
#/ * Support for services that don't support Reload.
#/ * Support alternative Reload mechanism (i.e. something else than signals).
#/
reload()
{
    service=""
    exec=""
    reload=""
    defs=""
    pid=0
    num=0
    ret=0
    _dummy=0            # padding to prevent "unalignment trap"...

    service=`trim "$ID"`

    is_service_running
    if test $? -eq 0 -o $? -eq 2
    then
        $ECHO_N "Signaling $service service to reload configuration..."

        #
        # After determining the number of daemons of this service, we can
        # access the appropriate DAEMON<num>_RELOAD variable and signal each
        # daemon to reload its config, if a "reload" signal is defined.
        #
        defs=`list_daemon_defs`
        for num in $defs
        do
            eval exec=\$DAEMON${num}_EXEC
            eval reload=\$DAEMON${num}_RELOAD

            if test -n "$reload"
            then
                pid=`get_pid $num`

                $DRY_RUN kill -$reload $pid
                if test $? -ne 0
                then
                    ret=1
                fi
            fi
        done

        # if $ret still 0, then all went good:
        if test "$ret" = "0"
        then
            $ECHO " done"
        else
            $ECHO " failed"
        fi
    else
        $ECHO "Service $service not running."
        ret=1
    fi

    return $ret
}


#-------------------------------------------------------------------------------


#/ 
#/ Show current running state of service.
#/ 
#/ \returns
#/ 
#/ Return code 0 if service is running; code 1 if service is not running or
#/ any other error condition.
#/ 
status()
{
    service=""
    exec=""
    name=""
    defs=""
    pid=0
    num=0
    ret=0

    service=`trim "$ID"`

    is_service_running
    if test $? -eq 0 -o $? -eq 2
    then
        $ECHO_N "Service $service is running:"

        #
        # Assume service is fully running and
        # there is no problem.
        #
        ret=0

        defs=`list_daemon_defs`
        for num in $defs
        do
            eval exec=\$DAEMON${num}_EXEC
            name=`basename $exec 2>/dev/null`

            pid=`get_pid $num`
            if test -n "$pid"
            then
                $ECHO_N " $name"
            else
                ret=1
            fi
        done
        $ECHO
    else
        $ECHO "Service $service is not running."
        ret=1
    fi

    return $ret
}


#-------------------------------------------------------------------------------


#/ 
#/ Show services configured on the system. Only the services defined in
#/ \c CONFIG_DIR are listed regardless of whether they are currently running.
#/ 
#/ \returns
#/ 
#/ Does not return; exit status always 1.
#/ 
list_services()
{
    cfgs=""
    svc=""
    id=""

    cfgs=`find_configs`
    for svc in $cfgs
    do
        read_config $svc

        id=$ID
        if test -z "$id"
        then
            id="<unknown>"
        fi

        $ECHO "$svc\t-- $id"
    done

    exit 1
}


#-------------------------------------------------------------------------------


################################################################################
#                                                                              #
#                         S T A R T  O F  S C R I P T                          #
#                                                                              #
#                                                                              #
# Operation:                                                                   #
#                                                                              #
# o Process command line arguments, determine action and service.              #
# o Include service configuration.                                             #
# o Call appropriate ACTION function.                                          #
#                                                                              #
################################################################################

QUIET=0                             # quiet flag (0 or 1)
VERBOSE=0                           # verbose flag (0 or 1)
DRY_RUN=""                          # default is to actually execute commands

ACTION=""                           # action to perform...
SERVICE=""                          # ...on this service


#
# Handle command line OPTIONS.
#
while test $# -gt 0
do
    case "$1" in
    -l|--list)
        list_services
        ;;
    -n|--dry-run)
        DRY_RUN="$ECHO -e \n> "     # ensure we begin on a new line
        ;;
    -q|--quiet)
        QUIET=1
        exec >/dev/null             # mute stdout
        ;;
    -v|--verbose)
        VERBOSE=1
        ECHO_N=$ECHO                # don't inhibit newline;
                                    # this prevents clobbering of text
        ;;
    -h|--help)
        usage
        ;;
    -V|--version)
        version
        ;;
    -*)
        fatal "unknown option: $1"
        ;;
    *)                              # first non-OPTIONS argument
        break
        ;;
    esac
    shift
done

#
# Handle influential environment variables.
#
if test "$RC_VERBOSE" = "0" -o "$RC_VERBOSE" = "1"
then
    VERBOSE=$RC_VERBOSE
fi


#
# Determine what to do for who (ACTION and SERVICE).
#

# try to derive ACTION and SERVICE from script name: 
ACTION=`get_action $RC_NAME`
SERVICE=`get_facility $RC_NAME`

if test $# -le 2
then
    # command line parameters MAY override the
    # derived ACTION and SERVICE, if they exist:
    if test -n "$1"
    then
        ACTION=$1
    fi
    if test -n "$2"
    then
        SERVICE=$2
    fi

    if test -n "$ACTION"
    then
        if test -n "$SERVICE"
        then
            # fetch service configuration and do ACTION:
            read_config $SERVICE
            if test $? -eq 0
            then
                case "$ACTION" in
                start)   start   ;;
                stop)    stop    ;;
                restart) restart ;;
                reload)  reload  ;;
                status)  status  ;;
                *)
                    fatal "unknown action: $ACTION"
                    ;;
                esac
            else
                fatal "configuration not found: $SERVICE"
            fi
        else
            fatal "missing service"
        fi
    else
        fatal "missing action"
    fi
else
    fatal "syntax error: $*"
fi
