# SccsId[] = "%W% (USL function) %G%"
              TRM_name="TERMINATE"
              if [ ".${SECONDS}" = "." ]; then # Bourne function already loaded?
                 [ ."`set|egrep '^$TRM_name\(\)\{$'`" != . ] && TRM_loaded=1
              else # Korn or Bash shell and function already loaded?
                 if [ `expr "\`uname -s\`" : "[Ll][Ii][Nn][Uu][Xx]"` -eq 0 ]; then
                    [ ."`typeset +f|awk '/^'$TRM_name'[=\(]?/'`" != . ] && TRM_loaded=1
                 else # Linux
                    [ ."`typeset -F|awk '/^'$TRM_name'[=\(]?/'`" != . ] && TRM_loaded=1
                 fi
              fi
              if [ 0${TRM_loaded} -eq 0 ]; then
              #----------------------------------------------------------------------#
              TERMINATE() # Function documentation located at bottom, but briefly,   #
              #           # $1=PID, $2=PIDfile, $3=MaxProcessSeconds, and $4=PSopts  #
              #----------------------------------------------------------------------#
              { [ ."${AWK}" = . ] && { { [ -x /usr/bin/nawk ] && AWK=/usr/bin/nawk; } \
                                  ||   { [ -x /bin/gawk     ] && AWK=/bin/gawk    ; } \
                                  ||   { [ -x /usr/bin/awk  ] && AWK=/usr/bin/awk ; }; }

                if [ .${SHLIB} = . ]; then SHLIB=/usr/local/scripts; export SHLIB; fi

                . $SHLIB/kill_pid.sh       # Function dependencies
                . $SHLIB/isint.sh
                . $SHLIB/email_msg.sh      # Calls $SHLIB/exit.sh
                . $SHLIB/touch_exit_err.sh

                #------------------------------------------------------------#
                # If the following variables are not set, use these defaults.#
                #------------------------------------------------------------#
                : ${id_num=`id|sed 's/^\(uid=\)\([0-9]*\)\(.*\)/\2/'`}
                : ${id_hex=`echo "obase=16;$id_num"|bc`}
                : ${script_name:=`basename $0`}
                : ${sp:="                    "} # 20 spaces
                : ${stderr_sp:="             STDERR="}
                : ${verbose:=0}
                : ${TRM_max_time:=600}
                : ${bin_dir:=/bin}  # Ensure we get the commands we want
                : ${ECHO:="$bin_dir/echo"}
                : ${tmp:=/var/tmp}

                : ${OZ:=`uname -s 2> /dev/null|tr '[A-Z]' '[a-z]' 2> /dev/null`}
                if [ ."$OZ" != ."linux" ]; then
                   : ${AWK:=nawk}
                   TRM_ps_opts='-eaf -o pid,time,args'
                else # Linux
                   : ${AWK:=gawk}
                   TRM_ps_opts='-aux'
                fi
                TRM_KILLPID_opts="-x"

                : ${log:="$name_root.log"}
                [ ."${teelog}" = . ] && teelog="cat"

                TRM_ID="$script_name($TRM_name)"

                #------------------------------#
                # Parse any function options.  #
                #------------------------------#
                TRM_optn=0        # Initialize as false (0)
                TRM_invalid_opt=0
                TRM_opt_M=0
                TRM_opt_S=0
                TRM_opt_T=0
                TRM_opt_x=""

                TRM_root=$tmp/$name_root"_TRM_go_"$id_hex
                TRM_err=$TRM_root"."$Xtimestamp
                while getopts MSTx TRM_opt 2>> $TRM_err
                do
                   case $TRM_opt in
                      M ) TRM_opt_M=1  # Mail (-M) option
                          TRM_optn=1
                          ;;
                      S ) TRM_KILLPID_opts="${TRM_KILLPID_opts}S"
                          TRM_optn=1
                          ;;
                      T ) TRM_KILLPID_opts="${TRM_KILLPID_opts}T"
                          TRM_optn=1
                          ;;
                      x ) TRM_KILLPID_opts="${TRM_KILLPID_opts}x"
                          TRM_optn=1
                          ;;
                     \? ) TRM_m1="Invalid Option: -`sed 's/^.*-- //' $TRM_err`"
                          TRM_invalid_opt=1
                          ;;
                      * ) ;;
                   esac
                done
                #----------------------------------------------------------#
                # Shift to remaining arguments (arguments solve nothing :) #
                #----------------------------------------------------------#
                [ $TRM_optn -eq 1 ] && shift `expr $OPTIND - 1`
                [ ."$TRM_root" != . ] && \rm -f $TRM_root* > /dev/null 2>&1

                #----------------------------------------------------------------#
                # Must reset this dog if this function is apt to be called again #
                OPTIND=1  # (try and find this fact documented anywhere else).   #
                #----------------------------------------------------------------#

                TRM_usage="Usage: $TRM_name -MS PID PIDfile MaxTime [PSopts]"
                if [ $# -lt 1 -o $TRM_invalid_opt -eq 1 ]; then
                   : ${TRM_m1:="Insufficient args."}
                   [ $TRM_opt_x -eq 1 ]                   \
                     && TRM_m2="$script_name terminated." \
                     || TRM_m2="$TRM_name terminated."
                   EMAIL_MSG "ERROR: (Function) $TRM_ID"                           \
                     "${sp}$TRM_m1\n"                                              \
                     "${sp}$TRM_usage\n"                                           \
                     "${sp}         -M      = Notify of process termination"       \
                     "${sp}                   (assassination) via e-mail."         \
                     "${sp}         -S      = Use sudo.\n"                         \
                     "${sp}         -T      = Test only--terminate nothing."       \
                     "${sp}                   Passes -T option to KILL_PID."       \
                     "${sp}         -x      = Exit if kill fails.\n"               \
                     "${sp}         PID     = Required: Process ID of the process" \
                     "${sp}                   we are to monitor and terminate if"  \
                     "${sp}                   it exceeds our time limit."          \
                     "${sp}         PIDfile = Used to communicate back to the"     \
                     "${sp}                   caller regarding process completion" \
                     "${sp}                   (normal completion or killed)."      \
                     "${sp}         MaxTime = Maximum time process allowed (if a"  \
                     "${sp}                   $max_proc_time is assigned AND if"   \
                     "${sp}                   MaxTime argument is not supplied,"   \
                     "${sp}                   we will use $max_proc_time, else we" \
                     "${sp}                   warn the user and use $TRM_max_time" \
                     "${sp}                   seconds as a failsafe default)."     \
                     "${sp}         PSopts  = Specific 'ps' options.  We use"      \
                     "${sp}                   These to see if the process is"      \
                     "${sp}                   still running. If not supplied"      \
                     "${sp}                   we use defaults based on OS."        \
                     "${sp}$TRM_m2"
                   if [ $TRM_opt_l -eq 1 ]; then # Logger option? (1 = yes)
                      WRITE_ERR_TO_SYSLOGS -p "$TRM_pri" -t "$TRM_tag" \
                        "ABORT: $TRM_ID $TRM_m1 $TRM_m2"
                   fi
                   $TRM_exit $failure
                fi

                #----------------------------------------------------#
                # Validate PID (make sure it is an integer).         #
                #----------------------------------------------------#
                ISINT "$1"
                if [ $? -ne 1 ]; then
                   EMAIL_MSG "ERROR: $TRM_ID"                       \
                     "${sp}$TRM_ID PID (\$1) argument not numeric!" \
                     "${sp}$TRM_usage"                              \
                     "${sp}$script_name terminated."
                   EXIT 1
                else
                   TRM_target_pid=$1
                   shift
                fi

                #----------------------------------------------------#
                # We absolutely must have a file to hold our PID.    #
                # We use it to pass whether or not the process       #
                # completed normally or had to be killed.            #
                #----------------------------------------------------#
                TOUCH_EXIT_ERR $1 # Touch confirms we can access file.
                TRM_pidfil="$1"
                shift

                #----------------------------------------------------#
                # If the user doesn't supply this one, use default,  #
                # and fuss at the user for not supplying it.         #
                #----------------------------------------------------#
                if [ ."$1" = . ]; then
                   EMAIL_MSG "ERROR: $TRM_ID"                                     \
                     "${sp}$TRM_ID Max Process time (seconds) argument missing!"  \
                     "${sp}Using $TRM_max_time as a default, but fix it will'ya." \
                     "${sp}$script_name continues."
                   TRM_max_sss=$TRM_max_time
                else
                   ISINT $1
                   if [ $? -ne 1 ]; then
                      EMAIL_MSG "ERROR: $TRM_ID"                                \
                        "${sp}$TRM_ID Max Process time argument non-numeric!"   \
                        "${sp}Using $TRM_max_time instead, but fix it will'ya." \
                        "${sp}$script_name continues."
                      TRM_max_sss=$TRM_max_time
                   else
                      TRM_max_sss=$1
                   fi
                   shift
                fi

                [ ."$1" != . ] && TRM_ps_opts="$1"

              #--------------------------------------------------------------------#
              TRM_PSA() # Nested/internal PS|Awk function.                         #
              #--------------------------------------------------------------------#
              { ps $TRM_ps_opts | $AWK -v pid=$1 -v sp="$sp" \
                  'BEGIN {n=0}
                   $1 == pid {
                               gsub(/[\t ]+/, " ", $0)
                               sub(/^[\t ]*/, sp,  $0)
                               if ($0 !~ /<defunct>/)
                               {
                                 print
                                 n++
                               }
                             }
                   END {exit n}'
              } # TRM_PSA()

                #----------------------------------------------------------------#
                #     Now wait for the dog to complete (or be assassinated).     #
                #                                                                #
                if [ ."$verbose" = ."1" ]; then # If verbose, show the process
                   echo "`date '+%Y-%m-%d %T'`"            \
                     "Waiting up to $TRM_max_sss seconds"  \
                     "for pid $TRM_target_pid to complete." | $teelog

                   TRM_PSA $TRM_target_pid | $teelog
                fi

                #------------------------------------------------------------#
                # The following background task will terminate with extreme  #
                # prejudice (-9) the process if, after sleeping, we see      #
                # that the dog is still running.  We do this by first        #
                # removing the file, $TRM_pidfil, then killing the entire    #
                # background process as well.                                #
                #                                                            #
                (
                   [ ."$verbose" = ."1" ] \
                     && echo "${sp}echo $TRM_target_pid > $TRM_pidfil" | $teelog
                        echo            $TRM_target_pid > $TRM_pidfil
                   #-------------------------------------------------------#
                   # While-loop uses multiple short sleep processes so it  #
                   # won't hang around long after the assassin process     #
                   # itself is terminated.                                 #
                   #-------------------------------------------------------#
                   TRM_secs=0
                   TRM_sleep=15
                   while [ $TRM_secs -lt $TRM_max_sss ]
                   do
                      sleep $TRM_sleep
                      TRM_secs=`expr $TRM_secs + $TRM_sleep`
                      [ ! -f $TRM_pidfil ] && exit # If file's gone
                   done

                   if [ -f $TRM_pidfil ]; then
                      #------------------------------------------------#
                      # If the process is running, show it.            #
                      #------------------------------------------------#
                      if [ ."$verbose" = ."1" ]; then
                         TRM_PSA $TRM_target_pid | $teelog
                      fi

                      echo "${sp}Terminating $TRM_target_pid" \
                        "(failed to complete in $TRM_max_sss seconds)." | $teelog

                      if [ ."$verbose" = ."1" ]; then
                         if [ ."$stderr" != . ]; then
                            echo  "`date '+%Y-%m-%d %T'`" \
                              "KILL_PID $TRM_KILLPID_opts $TRM_target_pid" >> $stderr
                         else
                            $ECHO "`date '+%Y-%m-%d %T'`" \
                              "KILL_PID $TRM_KILLPID_opts $TRM_target_pid" | $teelog
                         fi
                      fi

                      KILL_PID $TRM_KILLPID_opts $TRM_target_pid 2>&1
                    # echo "KILLED $TRM_target_pid" >> $TRM_pidfil
                      sleep 5             # Time to receive termination response.
                      echo "" | $teelog
                      echo "" $TRM_pidfil # Trying to ensure the file is rehashed.

                      TRM_subj="NOTICE: $script_name"
                      TRM_msg="Terminating (timed out) process (pid=$TRM_target_pid)."
                      TRM_msg="$TRM_msg\n${sp}$script_name continues."

                      if [ $TRM_opt_M -eq 1 ]; then
                         EMAIL_MSG "$TRM_subject" \
                                   "$TRM_msg"
                      else
                         if [ ."$stderr" != . ]; then
                            $ECHO "`date '+%Y-%m-%d %T'` $TRM_msg" >> $stderr
                         fi
                         $ECHO "`date '+%Y-%m-%d %T'` $TRM_msg" | $teelog
                      fi
                   fi # if [ -f $TRM_pidfil ]; then
                ) 2>&1 &

                TRM_assassin_pid=$!

                [ ."$verbose" = ."1" ] \
                  && echo "${sp}Assassin pid=$TRM_assassin_pid" | $teelog
                #                                                            #
                #------------------------------------------------------------#

                #--------------------------------------------------------------#
                # Now wait for target process to complete (or be assassinated).#
                # If it completes in time we'll terminate the assassin.        #
                #--------------------------------------------------------------#
                [ ."$verbose" = ."1" ] \
                  && echo "`date '+%Y-%m-%d %T'` wait $TRM_target_pid" | $teelog
                wait $TRM_target_pid  # Add NO pipes|redirection with 'wait'
                TRM_target_status=$?

                #------------------------------------------------------------#
                # Save and pass along completed target's exit status.        #
                #------------------------------------------------------------#
                if [ ."$stderr" != . -a $TRM_target_status -ne 0 ]; then
                   $ECHO "`date '+%Y-%m-%d %T'`"                   \
                     "Target process ($TRM_target_pid) completion" \
                     "status=$TRM_target_status." >> $stderr
                else
                   $ECHO "`date '+%Y-%m-%d %T'`"                   \
                     "Target process ($TRM_target_pid) completion" \
                     "status=$TRM_target_status." | $teelog
                fi

                #------------------------------------------------------------#
                # If our assassin is still out there, then terminate him.    #
                #------------------------------------------------------------#
                TRM_PSA $TRM_assassin_pid # No $teelog
                if [ $? -gt 0 ]; then
                   #---------------------------------------------------------#
                   # The "\c" below ensures thet any "$assassin_pid Killed"  #
                   # response from kill command is captured on the same      #
                   # line (no "| $teelog" in the echo command).              #
                   #---------------------------------------------------------#
                   $ECHO "`date '+%Y-%m-%d %T'`" \
                     "kill -9 $TRM_assassin_pid (BG assassin process) -- \c"
                      kill -9 $TRM_assassin_pid 2>&1
                   sleep 5 # Time to receive "killed" message.
                   echo "" # Because of the \c in kill echo (above).
                fi

                TRM_death_certificate=`sed -n '/KILLED/p' $TRM_pidfil`

                #------------------------------------------------------------#
                # When the command completes (either normally or otherwise,  #
                # remove $TRM_pidfil before returning to caller.             #
                #------------------------------------------------------------#
                [ ."$verbose" = ."1" ] && echo "`date '+%Y-%m-%d %T'`" \
                  "rm -f $TRM_pidfil"                             | $teelog
                  \rm -f $TRM_pidfil 2>&1 | sed "s/^/$stderr_sp/" | $teelog

                [ ."$TRM_death_certificate" = . -a TRM_target_status -eq 0 ] \
                  && return 0 || return 1
              } # "TRM_" prefix identifies this function's local variables.
              fi

              #======================================================================#
              #                       D O C U M E N T A T I O N                      #
              #======================================================================#
              #                                                                      #
              #      Author: Bob Orlando                                             #
              #                                                                      #
              #        Date: February 8, 2002                                        #
              #                                                                      #
              #  Program ID: terminate.sh                                            #
              #                                                                      #
              #       Usage: TERMINATE -MSTx PID PIDfile MaxTime [PSopts]            #
              #                                                                      #
              #                 -M      = Notify of process termination              #
              #                           (assassination) via e-mail.                #
              #                 -S      = Use sudo.                                  #
              #                                                                      #
              #                 -T      = Test only--terminate nothing.              #
              #                           Passes -T option to KILL_PID.              #
              #                 -x      = Exit if kill fails.                        #
              #                                                                      #
              #                 PID     = Required: Process ID of the process        #
              #                           we are to monitor and terminate if         #
              #                           it exceeds our time limit.                 #
              #                 PIDfile = Used to communicate back to the            #
              #                           caller regarding process completion        #
              #                           (normal completion or killed).             #
              #                 MaxTime = Maximum time process allowed (if a         #
              #                           $max_proc_time is assigned AND if          #
              #                           MaxTime argument is not supplied,          #
              #                           we will use $max_proc_time, else we        #
              #                           warn the user and use $TRM_max_time        #
              #                           seconds as a failsafe default).            #
              #                 PSopts  = Specific 'ps' options.  We use             #
              #                           These to see if the process is             #
              #                           still running. If not supplied             #
              #                           we use defaults based on OS.               #
              #                                                                      #
              #     Purpose: Terminate any process by PID that runs longer than      #
              #              the given time limit.  (Actual termination executed     #
              #              by shell library function, KILL_PID.)                   #
              #                                                                      #
              # Description: ......................................................  #
              #              ......................................................  #
              #                                                                      #
              #     Globals: No global variables assigned from this function.        #
              #              "TRM_" prefix identifies local function variables.      #
              #                                                                      #
              #       Calls: EXIT, ISINT, KILL_PID, EMAIL_MSG, and TOUCH_EXIT_ERR    #
              #              shell library functions.                                #
              #                                                                      #
              # Exit_status: Exits with failure (1) on show-stopper error, else      #
              #              returns 1 when the target process is terminated, or     #
              #              zero when if the process completes normally.            #
              #                                                                      #
              #       Notes: .....................................................   #
              #              .....................................................   #
              #                                                                      #
              #    Modified: 2004-09-17 Bob Orlando                                  #
              #                 v1.16 * Add -T (test) option (calls KILL_PID         #
              #                         with -T option).                             #
              #                                                                      #
              #----------------------------------------------------------------------#
            
Artificial Intelligence is no match for natural stupidity.
©Copyright Bob Orlando, 2002-2011
All rights reserved.
http://www.OrlandoKuntao.com
E-mail: Bob@OrlandoKuntao.com
Last update: Jan. 26, 2011
by Bob Orlando