#!/usr/local/gnubin/bash
# 05-Jan-92 Noah Friedman <friedman@prep.ai.mit.edu>
# Last modified 03-Feb-93
#
# pidctrl, a shell script for frobnicating processes (probably upon logout)
#
# There are a few known bugs.  For BSDish systems, when the STAT field has
# embedded spaces, the printed program name will be wrong. 
# 
# Public domain.
#

function usage ()
{
    if [ $# -gt 0 ]; then
       echo -e "${progname}: $*\n" 1>&2
    fi

    cat 1>&2 <<EOF
Usage: ${progname} {-D} {-k} {-v} {proc1} {proc2} {...}
       ${progname} {--debug} {--kill} {--verbose} [proc1] {proc2} {...}

proc1, proc2, ... may be program names or PIDs. 

EOF

   exit 1
}

function main ()
{
    progname="${0##*/}"
    initialize_variables "$@"
    parse_command_args "$@"
    shift $?

    test -n "${debug+set}" && set -x

    frobnicate_processes;
}

function initialize_variables ()
{
    tmpfile="/tmp/${progname}.$$"
    pretmpfile="${tmpfile}~"

    bq="\`"  # To prevent hairy quoting and escaping later.
    eq="'"

    operating_system=$(uname -s)
    case ${operating_system} in
       HP-UX | hp-ux | HPUX | hpux | AIX | aix )
          ps_args="-u ${EUID}"
         ;;
       * )
          ps_args="-xc"
         ;;
    esac
}

function uname () 
{ 
 local uname;

    uname="$(command uname "$@" 2> /dev/null)";
    echo "${uname:-unknown}"
}

function parse_command_args ()
{
 local orig_number_options=$#

    # unset option variables to make sure they weren't accidentally
    # exported 
    unset debug force_kill verbose proglist

    # If you add new commands be sure to change the wildcards below to make
    # sure they are unambiguous (i.e. only match one possible long option)
    # Be sure to show at least one instance of the full long option name to
    # document what the long option is canonically called. 
    while [ $# -gt 0 ]; do
       case z$1 in
          z-D | z--debug | z--d* )
             debug=t
             shift
            ;;
          z-k | z--kill | z--k* )
             force_kill=t
             shift
            ;;
          z-h* | z--help | z--h* )
             usage
            ;;
          z-v | z--verbose | z--v* )
             verbose=t
             shift
            ;;
          z-- )
             shift
             break
            ;;
          z-* )
             usage "${bq}${1}${eq} is not a valid option."
            ;;
          * )
             proglist="${proglist} $1"
             shift
            ;;
       esac
    done

    # Return number of shifted arguments so calling function can shift
    # appropriate amount.
    return $[ orig_number_options - $# ]
}
 
function get_process_list ()
{
 local ignore_regexp="$(cons_regexp $$ ${progname} 'ps' 'egrep' 'selection_svc')"
 local find_regexp="$(cons_regexp ${proglist:-'.*'})"

    ps ${ps_args} \
       | egrep -v "${ignore_regexp}" \
       | egrep "${find_regexp}" > "${pretmpfile}" 2>&1

    eval $(awk '
        NR == 1 {
           for ( i = 1; i <= NF ; i++ ) {
              if ( $i == "CMD" || $i == "COMMAND" )
                 printf("cmd_field=%d ", i);
              if ($i == "PID" )
                 printf("pid_field=%d ", i);
           }         
        }
        END {
           printf("num_procs=%i ", NR - 1);
        }' "${pretmpfile}")

    # guess, if necessary. (bad, since it's really quite variable)
    # `5' is the usual cmd field on bsd/sunos/aix, `4' for hpux 
    # FIXME: STAT field output in bsd/sunos ps breaks things. 
    cmd_field=${cmd_field:-"5"}
    pid_field=${pid_field:-"1"} 

    sed "1d" "${pretmpfile}" > "${tmpfile}"
    rm -f "${pretmpfile}"
}

function cons_regexp ()
{
 local whitespace_regexp="[ \t]*"
 local need_closeparen
 
    if [ $# -gt 1 ]; then
       need_closeparen=t
       echo -n "("
    fi

    while [ $# -ne 0 ]; do
       echo -ne "${whitespace_regexp}${1}${whitespace_regexp}"
       test $# -gt 1 && echo -n "|"
       shift
    done

    test "${need_closeparen}" && echo -n ")"
    echo ""
}

function frobnicate_processes ()
{
 local i=1

    get_process_list;

    while [ ${i} -le ${num_procs} ]; do
       set -- $(sed -n "${i}p" "${tmpfile}")
       eval pid="\$${pid_field}"
       eval cmd="\$${cmd_field}"

       if [ "${force_kill}" ]; then
          builtin kill -KILL "${pid}"
          if [ "${verbose}" ]; then
             echo "Process ${pid} \"${cmd}\" killed." 1>&2
          fi
       else
          get_user_command "${pid}" "${cmd}"
       fi

       let i="i + 1"
    done

    rm -f "${tmpfile}"
}

function get_user_command ()
{
 local help="[ (E)xit (K)ill (S)ignal (R)enice (I)gnore ] "
 local pid="$1"
 local cmd="$2"

    while : ; do
       echo -ne "Process \"${pid}\t${cmd}\"\t\tOption (h=help): "
       read ans
       case "${ans}" in
          h* | H* )
             echo "${help}"
             continue
            ;;
          e* | E* )
             exit 0
            ;;
          k* | K* )
             builtin kill -KILL ${pid}
             if [ $? -eq 0 -a "${verbose}" ]; then
                echo "${progname}: process ${pid} \"${cmd}\" killed." 1>&2
             fi
             return 0
            ;;
          i* | I* )
             return 0
            ;;
          s* | S* )
             do_signal "${pid}" "${cmd}" ${ans}
             return $?
            ;;
          r* | R* )
             do_renice "${pid}" "${cmd}" ${ans}
             return $?
            ;;
       esac 
    done
}

# Invocation: do_signal [pid] [cmd] s {sig}
function do_signal ()
{
 local pid="$1"
 local cmd="$2"
 local sig

    shift 2;
    set -- "$@"
    if [ $# -eq 1 ]; then
       while : ; do
          echo -n "Enter signal to send (h=help): "
          read sig
          case ${sig} in
             e | q ) 
                exit 0
               ;;
             h | H )
                kill -l
               ;;
             * )
                break
               ;;
          esac
       done
    else
       sig="$2"
    fi

    kill "-${sig}" "${pid}"

    if [ "${verbose}" ]; then
       echo "${progname}: sent pid ${bq}${pid}${eq} signal ${bq}-${sig}${eq}" 1>&2
    fi

}

# Invocation: do_signal [pid] [cmd] r {renice_value}
function do_renice ()
{
 local pid="$1"
 local cmd="$2"
 local renice_value

    shift 2;
    set -- "$@"
    if [ $# -eq 1 ]; then
       echo -n "Enter nice value: "
       read renice_value
    else
       renice_value="$2"
    fi

    if [ ! "${renice_value}" ]; then
       return 1
    fi

    if [ ! "${verbose}" ]; then
       renice ${renice_value} -p ${pid} > /dev/null 2>&1
    else
       renice ${renice_value} -p ${pid}
    fi

    return $?
}

function exit ()
{
 rm -f "${tmpfile}"
 builtin exit "$@"
}

main "$@"

#
# eof
#
