#!/bin/ksh
# @(#) maim.ksh 1.6 94/05/22
# 88/03/01-1992 john h. dubois iii (john@armory.com)
# 93/04/20 Protect $PPID
# 93/05/16 Added -f option & multiple process-names, use getopts
# 94/02/08 Added -a option
# 94/02/19 Allow multiple user names
# 94/02/25 Added -t option
# 94/05/22 If -a given, interpret args as user names.  Understand -signame.
#          Added -l.

# maim: kill processes by name.
# I believe the name 'maim' originated with Jon Luini (falcon@echo.com).

alias istrue="test 0 -ne"
alias isfalse="test 0 -eq"

name=${0##*/}
Usage=\
"Usage: $name [-afhl] [-SIGNAME] [-<signal>] [-t<tty[,tty...]>]
       [-u<user[,user...]>] <process-name> ..."

typeset -i x=0 KillAll=0

# By default, kill matching processes regardless of tty
TTYPat=\*

while getopts :ahls:fu:t:xA:B:C:E:F:H:I:K:P:Q:S:T:S:U:V:W: opt; do
    case $opt in
    h)
    print \
"$name: kill processes by name.  
$Usage
$name kills procesess owned by the invoking user that match any of the
given names.  Korn shell patterns may be used in process names if they are
escaped from the shell.  Each matching process is first sent signal 1
(SIGHUP).  If the process has not exited after two seconds, it is sent
signal 15 (SIGTERM).  If the process has still not exited after two more
seconds, it is sent signal 9 (SIGKILL).  Neither $name nor its parent
process are killed even if their names match.
Options:
-a: Kill all processes.  This is equivalent to a process name pattern of '*'.
    If -a is given, any arguments are taken to be user names and are acted
    on as they are when given with -u.
-f: The process' own idea of its name (as listed by ps -f) is used for
    comparison to the process names given, instead of the name recorded
    for system accounting purposes (as listed by ps without arguments).
-h: Print this help.
-l: List all signals by name and number.
-s<signal>: If a signal is given, it is sent instead of the default signals.
    More than one signal may be specified, in which case the specified signals
    will be sent with two-second pauses between them.  Signals may be specified
    by number or by name (in capital letters), e.g. -HUP.
-SIGNAME: This is a shorthand form of -s signal-name.
-t: Limit process selection to those running on the specified tty(s).
-u: Processes owned by the specified user(s) are killed, instead of those
    owned by the invoking user."
	exit 0
	;;
    a)
	KillAll=1
	;;
    f)
	f=-f
	;;
    l)
	kill -list
	exit 0;;
    x)
	x=1
	;;
    s)
	signals="$signals $OPTARG"
	;;
    t)
	# Convert comma-separated list to ksh pattern
	TTYPat="?(tty)@($(IFS=,; set -- $OPTARG; IFS=\|; echo "$*"))"
	;;
    u)
	UserList=$OPTARG
	;;
    [ABCEFHIKPQSTSUVW0123456789])
	signals="$signals $opt$OPTARG"
	;;
    +?)
	print -u2 "$name: options should not be preceded by a '+'."
	exit 1
	;;
    ?) 
	print -u2 "$name: $OPTARG: bad option.  Use -h for help."
	exit 1
	;;
    esac
done
 
# remove args that were options
let OPTIND=OPTIND-1
shift $OPTIND

# If killing all procs, make the pattern be '*',
# and take any args to be user names
if istrue KillAll; then
    # Only set user list if args given since even setting it to a space
    # will prevent it from being set to the invoking user
    [ $# -gt 0 ] && UserList="$UserList $*"
    set -- '*'
fi

if [ $# -lt 1 ]; then
    print -u2 "$Usage\nUse -h for help."
    exit
fi

[ -z "$signals" ] && signals="1 15 9"

# If UserList not set, set it
if [ -z "$UserList" ]; then
    if [ -n "$USER" ]; then
	UserList=$USER
    else
	id=`id`
	UserList=${id%\) *}
	UserList=${UserList#*\(}
    fi
fi

pat=$1
shift
for pname; do
    pat="$pat|$pname"
done
pat="@($pat)"

istrue x && print "pattern: $pat"

typeset -R8 cmd2

set -o noglob

print "Maiming:"
ps $f -u "$UserList" | while read psline; do
    set -- $psline
    if [ -z "$f" ]; then
	cmd=$4
	tty=$2
    else
	if [[ "$5" = ??? ]]; then
	    cmd=$9
	    tty=$7
	else
	    cmd=$8
	    tty=$6
	fi
	cmd=${cmd##*/}
    fi
    [ "$cmd" = COMMAND ] && continue	# ignore header
    # Using && in [[ ]] fails if eval'ed, so use separate tests
    if eval [[ \"$cmd\" = "$pat" ]] && eval [[ tty$tty = "$TTYPat" ]]; then
	# Make sure that if "maim sh" [ksh, etc] is given,
	# neither this process nor its parent will not be killed
	if [ $1 != $$ -a $1 != $PPID ]; then
	    print -u2 -- "$psline"
	    [ -z "$f" ] && pids="$pids $1" || pids="$pids $2"
	elif [ -n "$f" ]; then
	    print -u2 "Saved: $psline"
	fi
    elif [ -n "$f" ]; then
	cmd2=$cmd
	print -u2 "No match: $psline"
    fi
done

istrue x && print "process ids: {$pids}"

if [ -z "$pids" ]; then
    print -u2 "Nothing to maim."
    exit 1
fi

for signal in 0 $signals 0; do
    newpids=
    for proc in $pids; do
	result=`kill -$signal $proc 2>&1`
	case "$result" in
	*denied*) print "$result";;
	*"no such process"*)
	    if [ -n "$lastsig" ]; then
		print "Process $proc killed by signal $lastsig."
	    else
		print "$result"
	    fi;;
	"") 
	    if [ -n "$lastsig" ]; then
		if [ "$signal" = 0 ]; then
		    print "Process $proc still exists."
		else
		    print "Signal $signal sent to process $proc."
		fi
	    fi
	    newpids="$newpids $proc";;
	*) print "$result"
	   newpids="$newpids $proc";;
	esac
    done
    lastsig=$signal
    [ -z "$newpids" ] && break
    pids=$newpids
    [ "$signal" != 0 ] && sleep 2
done
print Done.
