#
# scriptlib2
#
# Framework and functions used to create resource type action scripts.

# Copyright (c) 2000 Silicon Graphics, Inc., SuSE, Inc., Joshua Rodman.  
#                    All Rights Reserved.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of version 2 of the GNU General Public License as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it would be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# Further, this software is distributed without any warranty that it is
# free of the rightful claim of any third person regarding infringement
# or the like.  Any license provided herein, whether implied or
# otherwise, applies only to this software file.  Patent licenses, if
# any, provided herein do not apply to combinations of this program with
# other software, or any other product whatsoever.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write the Free Software Foundation,
# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA.
#
# Contact information: Silicon Graphics, Inc., 1600 Amphitheatre Pkwy,
# Mountain View, CA 9404, or:
#
# http://www.sgi.com;
#
# SuSE, Inc., 580 Second Street, Oakland, CA 94607
#
# For further information regarding this notice, see:
#
# http://oss.sgi.com/projects/GenInfo/NoticeExplan

#
#ident "$Id: scriptlib,v 1.1 2000/08/31 19:16:32 vasa Exp $"
#

# Variable definitions
#
# Output:
#   None
#
# Globals set:
#   Many.  See body.
#
# Returns:
#   Success
s2_set_defaults()
{
    HA_ACTION=$(basename $0)                   # action to take on resources
    HA_RESOURCE_TYPE=$(basename $(dirname $0)) # resource type to act on
    HA_HOSTNAME=$(uname -n)                    # node's hostname

    HA_SCRIPTNAME=$HA_RESOURCE_TYPE-$HA_ACTION # Name for logging, etc.

    # Commands used by scripts. command location
    # TODO: these should be configuerd elsewhere
    HA_CMDSPATH=/usr/lib/failsafe/bin
    HA_PRIVCMDSPATH=/usr/lib/sysadm/privbin    # Location of Rhino framework
    HA_LOGCMD=ha_cilog                         # command to log messages
    HA_RESOURCEQUERYCMD=resourceQuery          # command to get attributes
    HA_RETRY_CMD_ERR=12                        # A return value for
                                               #    resourceQuery

    # Retry resourceQuery command $HA_RETRY_CMD_MAX times
    # TODO: This should be configurable elsewhere
    HA_RETRY_CMD_MAX=5

    # TODO: This should be configurable elsewhere
    HA_SCRIPTTMPDIR=/var/run/failsafe/tmp

    # CDB location
    # TODO: This should be configurable elsewhere
    HA_CDB=/var/lib/failsafe/cdb/cdb.db

    # Script log variables
    # TODO: use these with ha_cilog
    HA_SCRIPTGROUP=ha_script
    HA_SCRIPTSUBSYS=script

    # Script log levels
    HA_NORMLVL=1
    HA_DBGLVL=10

    # TODO: This should be dynamically acquired from the CHAOS daemons
    HA_CURRENT_LOGLEVEL=2
    # TODO: This should be used with $HA_LOGCMD or removed
    HA_CURRENT_LOGFILE="/var/log/failsafe/script"

    # Script logging commands
    HA_LOG=s2_log
    HA_DBGLOG=s2_log_debug

    # Script error values
    HA_SUCCESS=0
    HA_NOT_RUNNING=0
    HA_INVAL_ARGS=1
    HA_CMD_FAILED=2
    HA_RUNNING=2
    HA_NOTSUPPORTED=3
    HA_NOCFGINFO=4
}

# Formats and logs a message
#
# Output:
#   None
#
# Returns:
#   Return value of logging command
s2_log_internal()
{
    # Arguments
    local MESSAGE="$1"        # The message to log

    # Script log header
    local log_header=$(date "+%a %b %e %H:%M:%S")

    local formatted_message="$log_header <N $HA_SCRIPTNAME script $$:0> $MESSAGE"
    $HA_LOGCMD "$formatted_message"
}

# Log a normal resource-script message.
# When LOGLEVEL is below NORMLVL, message is dropped
#
# Output:
#   None
#
# Returns:
#   Return value of logging command, or success when logging is disabled
s2_log()
{
    # Arguments
    local MESSAGE="$*"        # The message to log

    if [ ${HA_CURRENT_LOGLEVEL} -ge ${HA_NORMLVL} ]; then
        s2_log_internal "$MESSAGE"
    fi
}

# Log a debug resourc-script message. 
# When LOGLEVEL is below DEBUGLVL, message is dropped
#
# Output:
#   None
#
# Returns:
#   Return value of logging command, or success when logging is disabled
#
s2_log_debug()
{
    # Arguments
    local MESSAGE="$*"        # The message to log

    if [ ${HA_CURRENT_LOGLEVEL} -ge ${HA_DBGLVL} ]; then
        s2_log_internal "$MESSAGE"
    fi
}

# Run a shell command with nice logging and output available
# On error, a provided command description and the command output are
# logged A LOGLVL of DEBUGLVL provides additional logging
#
# Output:
#   COMMAND standard output
#   COMMAND standard error on standard error
#
# Returns:
#   return value of COMMAND
s2_run_command()
{
    # Option kludge
    # TODO: learn to use getopt, the bash man page is not helpful enough
    # to avoid errors
    if [ "$1" == "-u" ] && [ $# -eq 4 ]; then
        local COMMAND_USER=$2
        shift 2
    fi

    # Arguments
    local COMMAND=$1          # command to run
    local COMMAND_TEXT=$2     # description of command for logging

    local exit_code
    local filename_string

    filename_string=$HA_SCRIPTTMPDIR/run_command-stderr.$HA_SCRIPTNAME.XXXXXXX
    if ! local errorfile=$(mktemp $filename_string); then
        $HA_LOG "error creating errorfile $errorfile"
        return $HA_CMD_FAILED
    fi

    filename_string=$HA_SCRIPTTMPDIR/run_command-stdout.$HA_SCRIPTNAME.XXXXXXX
    if ! local outfile=$(mktemp $filename_string); then
        $HA_LOG "error creating outfile $outfile"
        rm -f $errorfile
        return $HA_CMD_FAILED
    fi

    if [ "$COMMAND_USER" ]; then
    filename_string=$HA_SCRIPTTMPDIR/run_command-exitcode.$HA_SCRIPTNAME.XXXXXXX
        if local exitcodefile=$(mktemp $filename_string); then
            $HA_LOG "error creating exitcodefile $exitcodfile"
            rm -f $errorfile $outfile
            return $HA_CMD_FAILED
        fi
        chown $COMMAND_USER $errorfile $outfile $exitcodefile
    fi

    $HA_DBGLOG $COMMAND_TEXT

    if [ "$COMMAND_USER" ]; then
        local command_string
        command_string="$COMMAND >$outfile 2>$errorfile; echo \$? >$exitcodefile"
        if ! su $COMMAND_USER -c "$command_string"; then
            $HA_LOG "su to $COMMAND_USER failed"
            exit_code=$HA_CMD_FAILED
        else
            exit_code=$(cat $exitcodefile)
        fi
    else
        $COMMAND >$outfile 2>$errorfile
        exit_code=$?
    fi

    if [ $exit_code -ne 0 ]; then
        $HA_LOG "$COMMAND_TEXT failed"
        $HA_LOG $(cat $outfile)
        $HA_LOG $(cat $errorfile)
    fi

    $HA_DBGLOG "$COMMAND exited with status $exit_code"

    # Copy the output to standard out and the standard error output to
    # standard error
    cat $outfile
    cat $errorfile 1>&2

    rm -f $outfile $errorfile $exitcodefile
    return $exit_code
}


# Set the status for one of the resources being handled.
#
# The current implementation writes this data to a return information 
# file named in $HA_OUTFILE.
#
# Output:
#   None
#
# Returns:
#   return value of echo to OUTFILE
s2_write_resource_status()
{
    # Arguments
    local RESOURCE_STATUS=$1      # status to set the resource to
    local RESOURCE_NAMES=$*       # list of resource names to be set

    local resource
    for resource in $RESOURCE_NAMES; do
        echo $resource $RESOURCE_STATUS >> $HA_OUTFILE;
    done
}

# Read the list of resources from the failsafe INFILE into a list
#
# Output:
#   list of resources
#
# Returns:
#   return value of echo to stdout
s2_parse_infile()
{
    # Arguments
    local INFILE=$1               # File to read resources from

    # File consists of resource names, one per line
    # These stored in a single string
    echo $(cat $INFILE)
}

# Render a list of resource names as FailSafe infile format 
#
# Output:
#   stream of resources, one per line
#
# Returns:
#   return value of echo to stdout
s2_print_infile()
{
    # Arguments
    local RESOURCE_NAMES="$*"     # resource names to render

    local resource 

    # Loop over input, printing each as a line
    for resource in $RESOURCE_NAMES; do
        echo $resource
    done
}


# This has the germ of reading and writing resource files.
# both of which are currently unimportant.  TODO: fix later.
#
### Render state variables as FailSafe resourcefile format 
## 
## Output:
##    list of name/value parameter pairs
#print_paramfile()
#{
#    # Arguments
#    local PARAM_FILE=$1
#
#    local parameter value
#
#    # loop over input from PARAMFILE
#    while read parameter value; do
#
#    done < $PARAM_FILE
#    for string in $HA_RES_NAMES; do
#        if   [ "$stringtype" == "key" ]; then
#            key="$string"
#            stringtype="value"
#        elif [ "$stringtype" == "value" ]; then
#            echo $key $string
#            stringtype="key"
#        fi
#    done
#}


# Obtain attribute values for a given attribute name
#
# Output:
#   list of values for attribute name
#
# Returns:
#   Success if any found, failure if none found
s2_get_attribute_value()
{
    #Arguments
    local ATTR_NAME=$1         # The attribute name to look up
    local ATTR_LIST=$2         # List of attribute value pairs
    local GET_ALL=$3           # Flag to get all values of the given attribute name
                               # this may or may not be set.
    local name value result

    set -- $ATTR_LIST                            # list -> positional args

    while [ $# -gt 0 ]; do                       # walk the positional args
        name=$1 value=$2

        if [ "$ATTR_NAME" == "$name" ]; then     # if its the one we want
            if [ -z "$result" ]; then            # first match
                result=$value
                if [ ! "$GET_ALL" ]; then        # if we only want one
                    break                        #   search complete
                fi
            else                                 # successive matches
                result="$result $value"
            fi
        fi

        shift 2                                  # next pair
    done

    if [ -z "$result" ]; then                    # attribute was not found
        return 1                                 #    indicate failure
    else                                         # attribute was found
        echo $result                             #    return value(s)
        return 0                                 #    indicate success
    fi
}

# Read resource information from the Cluster DataBase (CDB) without retries
#
# Output:
#   attribute/value pair list for a given resource
#
# Returns:
#   Return value of the resource query command
s2_get_attributes_debug()
{
    local HA_RETRY_CMD_MAX=1

    s2_get_attributes "$@"
    return
}

# Read resource information from the Cluster DataBase (CDB) with retries
#
# Output:
#   attribute/value pair list for a given resource
#
# Returns:
#   Return value of the resource query command
s2_get_attributes()
{
    # Arguments
    local RESOURCE_TYPE=$1
    local RESOURCE_NAME=$2
    local GET_DEPENDENCIES=$3

    # If this file exists, do not retry.. for debug purposes!
    if [ -f /var/run/failsafe/resourceQuery.debug ]; then
        local HA_RETRY_CMD_MAX=1
    fi

    local exit_code
    lcoal command_output
    local command="$HA_PRIVCMDSPATH/$HA_RESOURCEQUERYCMD _CDB_DB=$HA_CDB \
                   _RESOURCE=$RESOURCE_NAME _RESOURCE_TYPE=$RESOURCE_TYPE"

    if [ "$GET_DEPENDENCIES" ]; then
        command="$command _ALL=true"
    fi

    if [ "$HA_CLUSTERNAME" ]; then
        command="$command _CLUSTER=$HA_CLUSTERNAME"
    fi

    # attempt to retreive information from daemons
    # retry when specified up to HA_RETRY_CMD_MAX times
    for retry in $(seq 1 $HA_RETRY_CMD_MAX); do
        command_output=$($command)               # Run the command; get the output
        exit_code=$?                             #  and the error code

        if [ $exit_code -ne $HA_SUCCESS ]; then  # log errors
            $HA_LOG "$HA_RESOURCEQUERYCMD: resource name $RESOURCE_NAME resource type $RESOURCE_TYPE"
            $HA_LOG "Failed with error: $command_output"
        fi

        if [ $exit_code -ne $HA_RETRY_CMD_ERR ]; then
            break;                               # only retry when told to
        fi
    done

    # When dependencies are requested, they must be acquired from the
    # bottom of the output..  (this sucks)
    # This is achieved by removing all text before the beginning of the
    # dependency data.  (ditto)
    #  TODO: fix the resource query command to have more reasonable behavior
    if [ "$GET_DEPENDENCIES" ]; then
        # this removes all text up to a string 'esource dependencies' 
        # which covers the positive case "Resource.." and the negative 
        # "No resource .."
        command_output=${command_output#*esource dependencies}
    fi

    echo "$command_output"               # return resulting resource string
    return $exit_code                    # and the return code
}

# Verify the arguments to the script
#
# Output:
#   None
#
# Globals set:
#   HA_INFILE       : File containing resource list
#   HA_OUTFILE      : File in which resource status is written
#   HA_PARAMFILE    : File in which parameters are passed from SRMD
#
# Returns:
#   Failure if an error is found in the arguments, otherwise Success
s2_check_args()
{
    # Arguments
    HA_INFILE=$1    # the list of resources
    HA_OUTFILE=$2   # resource status is written to this
    HA_PARAMFILE=$3 # as of sep.28.2000 - Contains name of the cluster, 
                    # might have more stuff someday..

    # Build a comma seperated string of all script arguments for logging
    local arg arglist=""
    for arg in "$@"; do
        arglist="${arglist}${arg}, "
    done
    # Remove the trailing comma and space
    arglist=${arglist%, }

    $HA_DBGLOG "$HA_SCRIPTNAME called with arguments: $arglist"

    if  [ $# -ne 2 ] && [ $# -ne 3 ]; then
        ${HA_LOG} "Incorrect number of arguments"
        return 1;
    fi

    if [ ! -r $HA_INFILE ]; then
        ${HA_LOG} "infile $HA_INFILE is not readable or does not exist"
        return 1;
    fi

    if [ ! -s $HA_INFILE ]; then
        ${HA_LOG} "infile $HA_INFILE is empty"
        return 1;
    fi

    if [ $# -ne 3 ]; then
        unset HA_PARAMFILE
    else
        if [ ! -r $HA_PARAMFILE ]; then
            ${HA_LOG} "file $HA_PARAMFILE is not readable or does not exist"
            return 1;
        fi
        # Read the cluster name from the paramfile
        # TODO: This code shouldn't be in check_args
        #       Create paramfile parser and use it in the init()
        local ITEM VALUE
        HA_CLUSTERNAME=""

        while read ITEM VALUE; do
            if [ "$ITEM" == "ClusterName" ]; then
                HA_CLUSTERNAME="$VALUE"
            fi
        done < $HA_PARAMFILE
    fi
    return 0;
}

# Initialize the framework
#
# Output:
#   None
#
# Globals Set:
#   See s2_set_defaults(), s2_check_args()
#
# Returns:
#   return value of s2_check_args
s2_init()
{
    #Arguments 
    # - passed through arguments from script start

    s2_set_defaults

    if ! s2_check_args "$@"; then
        #TODO: unforgiveably bad style to exit within function?
        exit $HA_INVAL_ARGS
    fi
}

# Take action on the list of resources
#
# Output:
#   Output of user-defined <resource_type>_execute function
#
# Returns:
#   return value of the last run of user-defined 
#          <resource_type>_execute function
s2_execute()
{
    # Arguments
    #  none

    local resource_name

    # Read HA_INFILE
    resources=$(s2_parse_infile $HA_INFILE)

    for resource_name in $resources; do
        resource_attr=$(s2_get_attributes $HA_RESOURCE_TYPE $resource_name)
        process_resource $HA_ACTION $resource_name "$resource_attr"
    done
}

# Main
s2_init "$@"
