home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 98 / Skunkware 98.iso / osr5 / sco / scripts / maim < prev    next >
Encoding:
Korn shell script  |  1997-08-26  |  25.5 KB  |  909 lines

  1. #!/bin/ksh
  2. # @(#) maim.ksh 2.0 97/06/29
  3. # 88/03/01-1992 john h. dubois iii (john@armory.com)
  4. # 93/04/20 Protect $PPID
  5. # 93/05/16 Added -f option & multiple process-names, use getopts
  6. # 94/02/08 Added -a option
  7. # 94/02/19 Allow multiple user names
  8. # 94/02/25 Added -t option
  9. # 94/05/22 If -a given, interpret args as user names.  Understand -signame.
  10. #          Added -l.
  11. # 94/09/28 Understand -signum.  Added -n.
  12. # 94/11/06 -t w/no procs implies -a.  Added -k.
  13. # 94/12/09 Warn about process names longer than 8 chars.  Added v option.
  14. # 95/01/28 Do one kill for all procs.
  15. # 95/02/24 Let [-?] be given for tty to match proc w/o controlling tty
  16. # 95/04/11 Added p option, ! versions of [ktup].
  17. # 95/05/03 Added r option.
  18. # 95/06/08 Added s option.
  19. # 95/08/26 Put quotes around all the stuff that ps returns when it is used on
  20. #          the LHS of a [ a = b ] test, since it might start with (or be ) -.
  21. # 95/12/30 v5 port: Use "PID" to recognize header instead of COMMAND
  22. # 96/01/28 Under v5, ps -l will list up to 14 chars (was 8).
  23. # 96/01/31 Make tty name match whether given with leading /dev/ or not, and
  24. #          whether ps gives leading 'tty' part or not.
  25. #          Added iq options.
  26. # 96/02/13 Explicitly disallow -ta.
  27. # 96/04/13 Get user name from id output more reliably.
  28. # 96/11/17 Check once per second while sleeping for whether procs are dead.
  29. # 97/04/24 Changed -i to -b; added new -i (interactive) and -m options.
  30. # 97/05/29 Added o option.
  31. # 97/06/03 Let sleep times be specified individually.
  32. # 97/06/29 Skip ps process, to avoid having it clutter output
  33.  
  34. # maim: kill processes by name.
  35. # I believe the name 'maim' originated with Jon Luini (falcon@echo.com).
  36.  
  37. alias istrue="test 0 -ne"
  38. alias isfalse="test 0 -eq"
  39.  
  40. function doArgs {
  41.     typeset opt Arg
  42.     # Puts args in array to make -signum easier to process.
  43.     set -A Args -- "$@"
  44.  
  45.     # 0-9 are given as not expecting a value because getopts does not allow
  46.     # optional values.  If a multi-digit signal number is given, it will be
  47.     # dealt with by a hack.
  48.     while getopts \
  49.     :irzafhblmno:vxp:qk:s:t:u:A:B:C:E:F:H:I:K:P:Q:S:T:S:U:V:W:0123456789 \
  50.     opt "${Args[@]}"; do
  51.     case "$opt" in
  52.     h)
  53.     print \
  54. "$name: kill processes by name.  
  55. $Usage
  56. $name kills procesess owned by the invoking user that match any of the
  57. given names.  Korn shell patterns may be used in process names if they are
  58. escaped from the shell.  Each matching process is first sent signal 1
  59. (SIGHUP).  If the process has not exited after $defSleep seconds, it is sent
  60. signal 15 (SIGTERM).  If the process has still not exited after $defSleep more
  61. seconds, it is sent signal 9 (SIGKILL).  Neither $name nor its parent
  62. process are killed even if their names match.
  63. Options:
  64. -a: Kill all processes.  This is equivalent to a process name pattern of '*'.
  65.     The shell that $name is run from is protected from being killed.
  66.     If -a is given, any arguments are taken to be user names and are acted
  67.     on as they are when given with -u.
  68. -f: The process' own idea of its name (as listed by ps -f) is used for
  69.     comparison to the process names given, instead of the name recorded
  70.     for system accounting purposes (as listed by ps without arguments).
  71. -h: Print this help.
  72. -b: Quit immediately after sending the last signal.  $name normally pauses for
  73.     the sleep period after the last signal, and then checks for processes that
  74.     have not died so that the user can be informed of them.
  75. -l: List all signals by name and number.
  76. -n: List the processes that would be killed, but do not signal them.
  77. -<signal>: If a signal is given, it is sent instead of the default signals.
  78.     More than one signal may be specified, in which case the specified signals
  79.     will be sent with $defSleep-second pauses between them.  Signals may be
  80.     specified by number or by name (in capital letters), e.g. -HUP.
  81. -s<sleeptime>: Set the sleep between signals, after the last signal, and before
  82.     restarting the process (if -r is given), to <sleeptime> seconds instead of
  83.     the default of $defSleep seconds.  To set these individually, the alternate
  84.     form -sb,a,r may be used, where b is the time between signals, a is the
  85.     time after the last signal, and r is the time before restart.
  86. -q: Be quiet.  Only error messages are printed.
  87. -r: Restart the process.  Only one process name may be given.  It is first
  88.     killed (the signal delivered defaults to signal 9 only, but can be set with
  89.     -<signal>); then, after a wait of $defSleep seconds, the named process is
  90.     started up.  It should be in the command search path, and should be a
  91.     process that will automatically background itself.
  92. -i: Interactive operation.  Information on each matching process is printed,
  93.     and then a response from the user determines whether it is killed or not.
  94.     If -i is given without any process selection options, all processes are
  95.     selected and asked about (as though -a had been given).
  96. -m: Menu operation.  Information on all matching processes are printed along
  97.     with menu line numbers, which are used to select among them.
  98. -v: Tell what processes are skipped in addition to those being killed.
  99. -t<tty-list>: Limit process selection to those running on the specified tty(s).
  100.     A list of whitespace or comma separated tty names may be given.  If a tty
  101.     name of '-' or '?' is given, processes which have no controlling tty are
  102.     matched.  If -t is given and no process patterns are given, -a is implied.
  103. -k<tty-list>: Like -t, except that processes are killed regardless of user ID.
  104. -o<age>: Limit process selection to those that are at least <age> old.  <age>
  105.     is given as a an integer followed by a unit, where the unit specifiers are
  106.     d, h, m, and s for days, hours, minutes, and seconds.  Example: -o5h
  107.     would select processes at least 5 hours.  This option causes process
  108.     start times to be given in GMT.
  109. -u<user-list>: Processes owned by the specified user(s) are killed, instead of
  110.     those owned by the invoking user.  A list of whitespace or comma separated
  111.     user names may be given.
  112. -p<process-ID-list>: Limit process selection to those given in the comma-
  113.     or whitespace-separated list.
  114. If the argument to -t, -k, -u, or -p begins with a '!', then any process that
  115. matches the given criterion is skipped.  Only one version of any option should
  116. be used.  Note: -u!userlist and -o<age> turn on the -f option too (a ps
  117. limitation)."
  118.         exit 0
  119.         ;;
  120.     m)
  121.         Menu=true
  122.         Query=true
  123.         ;;
  124.     i)
  125.         Interactive=true
  126.         Query=true
  127.         ;;
  128.     a)
  129.         KillAll=true
  130.         ;;
  131.     v)
  132.         Verbose=true
  133.         ;;
  134.     n)
  135.         NoKill=true
  136.         ;;
  137.     r)
  138.         Restart=true
  139.         ;;
  140.     f)
  141.         f=-f
  142.         ;;
  143.     b)
  144.         testSig=
  145.         ;;
  146.     l)
  147.         kill -list
  148.         exit 0;;
  149.     x)
  150.         x=1
  151.         ;;
  152.     s)
  153.         OIFS=$IFS
  154.         IFS=,
  155.         set -- $OPTARG
  156.         IFS=$OIFS
  157.         bSleep=$1
  158.         aSleep=$1
  159.         rSleep=$1
  160.         [ $# -gt 1 ] && aSleep=$2
  161.         [ $# -gt 2 ] && rSleep=$3
  162.         ;;
  163.     o)
  164.         OTZ=$TZ
  165.         export TZ=0    # avoid DST issues
  166.         makeAge "$OPTARG"
  167.         date '+%Y %j' | read cur_year cur_day
  168.         mstart_setup
  169.         f=-f
  170.         checkAge=true
  171.         ;;
  172.     p)
  173.         if [[ "$OPTARG" = !* ]]; then
  174.         PIDpat="$PPID $$ ${OPTARG#?}"
  175.         NotPID=true
  176.         else
  177.         PIDpat=$OPTARG
  178.         NotPID=false
  179.         fi
  180.         # Convert comma-separated list to ksh pattern
  181.         OIFS=$IFS
  182.         IFS=" ,"
  183.         set -- $PIDpat
  184.         IFS=\|
  185.         PIDpat="@($*)"
  186.         IFS=$OIFS
  187.         ;;
  188.     q)
  189.         exec >/dev/null
  190.         ;;
  191.     u)
  192.         u=true
  193.         if [[ "$OPTARG" = !* ]]; then
  194.         # Convert comma-separated list to ksh pattern
  195.         OIFS=$IFS
  196.         IFS=" ,"
  197.         set -- ${OPTARG#?}
  198.         IFS=\|
  199.         NotUserPat="@($*)"
  200.         IFS=$OIFS
  201.         AllUsers=true
  202.         f=-f
  203.         else
  204.         UserList=$OPTARG
  205.         fi
  206.         ;;
  207.     [kt])
  208.         if [[ "$OPTARG" = !* ]]; then
  209.         TTYPat=${OPTARG#?}
  210.         NotTTY=true
  211.         else
  212.         TTYPat=$OPTARG
  213.         NotTTY=false
  214.         fi
  215.         # Convert comma-separated list to ksh pattern
  216.         OIFS=$IFS
  217.         IFS=" ,"
  218.         set -- $TTYPat
  219.         IFS=$OIFS
  220.         TTYPat=
  221.         for tty; do
  222.         case "$tty" in
  223.         [-?])    # Convert - to ? since that's the way ps gives it
  224.             TTYPat="$TTYPat|\\?"
  225.             ;;
  226.         *)
  227.             tty=${tty#/dev/}
  228.             TTYPat="$TTYPat|${tty#tty}"
  229.             ;;
  230.         esac
  231.         done
  232.         # Some versions of ps strip leading 'tty' from tty name; some
  233.         # don't.  Make it optional.
  234.         TTYPat="?(tty)@(${TTYPat#?})"
  235.         [ $opt = k ] && AllUsers=true
  236.         ;;
  237.     [ABCEFHIKPQSTSUVW])
  238.         signals="$signals $opt$OPTARG"
  239.         ;;
  240.     [0123456789])
  241.         Arg=${Args[OPTIND-1]}
  242.         # Get rid of everything up to the option we are dealing with.
  243.         Arg=${Arg##-*([!$opt])}
  244.         signals="$signals $Arg"
  245.         # Replace the rest of the arg with a noop so that if more than one
  246.         # digit was given, the rest of them aren't processed separately.
  247.         # Changing OPTIND in a getopts loop does not have the desired
  248.         # effect.  Also, if getopts thought there was going to be another
  249.         # option, there better be one or it will barf.  So, tack on a z
  250.         # to replace anything we might have disposed of.
  251.         Args[OPTIND-1]=${Args[OPTIND-1]%$Arg}${opt}z
  252.         ;;
  253.     z)    # noop
  254.         ;;
  255.     +?)
  256.         print -u2 "$name: options should not be preceded by a '+'."
  257.         exit 1
  258.         ;;
  259.     ?) 
  260.         print -u2 "$name: $OPTARG: bad option.  Use -h for help."
  261.         exit 1
  262.         ;;
  263.     esac
  264.     done
  265.  
  266.     # OPTIND seems to be local to functions, so return # of args procced
  267.     return $OPTIND
  268. }
  269.  
  270. # Set functions  
  271. # 95/01/28 John H. DuBois III
  272.  
  273. # Usage: SubtractSet setname element ... 
  274. # setname is the name of an array that holds a set.
  275. # A set is stored in an array using contiguous indices starting with 0,
  276. # in ascending order by lexicographic value.
  277. # Any named element that is in setname is removed.
  278. # The named elements do not have to be sorted.
  279. # It is not an error for an element to not be in the set.
  280. function SubtractSet {
  281.     typeset SetName=$1 Elements Set
  282.     typeset -i i=0 NumElem
  283.  
  284.     shift
  285.     set -s    # sort elements
  286.     # Save elements, because the next set -A will nuke them
  287.     set -A Elements -- "$@"
  288.     # Save the set into a copy to make it easier to work with
  289.     eval set -A Set -- \"\${$SetName[@]}\"
  290.     # Restore elements as positional params, to make them easier to work with
  291.     set -- "${Elements[@]}"
  292.     NumElem=${#Set[*]}
  293.     while [ $# -gt 0 -a i -lt NumElem ]; do
  294.     while [[ $# -gt 0 && "$1" < "${Set[i]}" ]]; do
  295.         shift
  296.     done
  297.     [[ "$1" = "${Set[i]}" ]] && unset Set[i]
  298.     let i+=1
  299.     done
  300.     eval set -A $SetName -- '"${Set[@]}"'
  301. }
  302.  
  303. # Sets _OSRelease to the entire OS release (e.g. 3.2v5.0.0}
  304. # Sets _OSVersion to the version part (e.g. 5.0.0)
  305. # Returns the major part of the version (e.g. 5)
  306. # If an OSVersion value is given as an arg, returns 0 if the system OS version
  307. # is lexicographically less than it; 1 if they are equal; 2 if the system OS
  308. # version is greater.
  309. function OSVersion {
  310.     typeset arg=$1
  311.     if [ -z "$_OSRelease" ]; then
  312.     # Name of release field is different in different langs
  313.     set -- $(LANG=english_us.ascii uname -X)
  314.     while [ "$1" != Release -a $# -ge 3 ]; do
  315.         shift 3
  316.     done
  317.     [ $# -lt 3 ] && return 1
  318.     _OSRelease=$3
  319.     _OSVersion=${3##*v}
  320.     fi
  321.     [ -z "$arg" ] && return ${_OSVersion%%.*}
  322.     [[ "$_OSVersion" > "$arg" ]] && return 2
  323.     [[ "$_OSVersion" < "$arg" ]] && return 0
  324.     return 1
  325. }
  326.  
  327. # Usage: dosleep <seconds> <pid> ...
  328. # Sleeps for <seconds> or until no pids are still alive, whichever occurs
  329. # first, checking each second.
  330. function dosleep {
  331.     typeset -i sleeptime=$1 pid
  332.     typeset return
  333.     shift
  334.     while [ sleeptime -gt 0 ]; do
  335.     # kill exits with status 0 for successful delivery, 2 for permission
  336.     # denied or no such process
  337.     return=true
  338.     for pid; do
  339.         if kill -0 $pid 2>/dev/null; then
  340.         return=false
  341.         fi
  342.     done
  343.     $return && return
  344.     let sleeptime=sleeptime-1
  345.     sleep 1
  346.     done
  347. }
  348.  
  349. # Converts time in the format h:m:s to seconds & returns it in hsm2sec
  350. # Returns failure status if $1 does not have 3 fields
  351. typeset -i hms2sec
  352. function hms2sec {
  353.     typeset IFS
  354.     IFS=:
  355.  
  356.     set -- $1
  357.     hms2sec="$1*3600+$2*60+$3"
  358.     [ $# -eq 3 ]
  359. }
  360.  
  361. # Convert relative time spec of the form n[dhms] to a UNIX epoch time
  362. # and returns it in max_stime.
  363. typeset -i max_stime
  364. function makeAge {
  365.     typeset -i i sec
  366.  
  367.     if [[ "$1" != *([0-9])[dhms] ]]; then
  368.     print -ru2 -- "$name: Bad age: $1"
  369.     exit 1
  370.     fi
  371.     i=${1%?}
  372.     if [ i -eq 0 ]; then
  373.     print -ru2 -- "$name: Age value must be a positive integer."
  374.     exit 1
  375.     fi
  376.     case "$1" in
  377.     *d)
  378.     sec='i*86400'
  379.     ;;
  380.     *h)
  381.     sec='i*3600'
  382.     ;;
  383.     *m)
  384.     sec='i*60'
  385.     ;;
  386.     *s)
  387.     sec=i
  388.     ;;
  389.     esac
  390.     # Convert relative values to absolute, using current time
  391.     curtime
  392.     max_stime=curtime-sec
  393.     istrue x && type unixtime > /dev/null &&
  394.     print -u2 "Will kill processes older than: $(TZ=$OTZ unixtime $max_stime)"
  395. }
  396.  
  397. # Uses globals max_stime, PS_month, PS_day, and PS_time
  398. function doCheckAge {
  399.     $checkAge || return 0
  400.  
  401.     typeset -i time day year proctime
  402.  
  403.     curtime
  404.     if [ -n "$PS_month" ]; then
  405.     date2doy $PS_month $PS_day
  406.     [ date2doy -lt cur_day ] && year=cur_year || year=cur_year-1
  407.     unixdays $year $PS_month $PS_day
  408.     proctime=unixdays*86400
  409.     else    # max age was specified as seconds
  410.     hms2sec "$PS_time" || {
  411.         print -u2 "Bad time field: $PS_time"
  412.         return 1
  413.     }
  414.     day=curtime/86400
  415.     time=curtime%86400
  416.     [ hms2sec -gt time ] && let day-=1
  417.     proctime='day*86400+hms2sec'
  418.     fi
  419.     istrue x && type unixtime > /dev/null && print -u2 "Process time "\
  420. "'$PS_month $PS_day $PS_time' converted to: $(TZ=$OTZ unixtime $proctime)"
  421.     # Return true if process time is earlier than max start time
  422.     [ proctime -lt max_stime ]
  423. }
  424.  
  425. ### start of doy-date lib
  426. # 97/06/29 john@armory.com
  427. typeset -i MStart
  428. function mstart_setup {
  429.     if [[ $(( `date +%y` % 4 )) = 0 ]]; then
  430.     set -A MStart 0 31 60 91 121 152 182 213 244 274 305 335 366
  431.     else
  432.     set -A MStart 0 31 59 90 120 151 181 212 243 273 304 334 365
  433.     fi
  434. }
  435.  
  436. function initMonths {
  437.     set -A mNum2Month "" jan feb mar apr may jun jul aug sep oct nov dec
  438. }
  439.  
  440. function month2num {
  441.     typeset -lL3 month=$1
  442.     typeset -i i=1
  443.     [ ${#mNum2Month} -eq 0 ] && initMonths
  444.     while [ i -le 12 ]; do
  445.     [ ${mNum2Month[i]} = "$month" ] && return $i
  446.     let i+=1
  447.     done
  448.     return 0
  449. }
  450.  
  451. # doy2date day-of-year
  452. # Returns the day-of-year converted to month & day (separated by a space)
  453. # in global doy2date
  454. # assumes conversion is for the current year
  455. function doy2date {
  456.     typeset -i M
  457.     M=$1/32+1
  458.     [ $1 -gt MStart[M] ] && M=M+1
  459.     doy2date="$M $(($1 - MStart[M-1]))"
  460. }
  461.  
  462. # date2doy month day-of-month
  463. # Returns the month & day-of-month converted to day-of-year in global date2doy
  464. # assumes conversion is for the current year
  465. function date2doy {
  466.     typeset -i month
  467.     [[ $1 = +([0-9]) ]] && month=$1 || {
  468.     month2num "$1" && return 0
  469.     month=$?
  470.     }
  471.     date2doy=$((MStart[month-1] + $2))
  472. }
  473.  
  474. # date2days year month day-of-month
  475. # Returns the number of complete days that passed from 1900 Jan 1 to the start
  476. # of the given date in global date2days.
  477. # The month may be given in numeric form (Jan=1) or by name, in which case at
  478. # least the first 3 characters must be passed (case is ignored).
  479. # Works from 1901 to 2099.
  480. # If year < 100, it is assumed to be part of the 1900 century
  481. typeset -i date2days
  482. function date2days {
  483.     typeset -i year=$1 day=$3 leap_days MDays month
  484.     [ year -ge 100 ] && let year-=1900
  485.     let leap_days=year/4+1
  486.     [[ $2 = +([0-9]) ]] && month=$2 || {
  487.     month2num "$2" && return -1
  488.     month=$?
  489.     }
  490.     [[ month -le 2 && $((year%4)) = 0 ]] && let leap_days-=1
  491.     [ ${#MDays[*]} -eq 0 ] &&
  492.     set -A MDays 0 0 31 59 90 120 151 181 212 243 273 304 334 365
  493.     date2days="year*365+MDays[month]+day-1+leap_days"
  494. }
  495.  
  496. # unixdays year month day-of-month
  497. # Returns the number of complete days that passed from 1970 Jan 1
  498. # to the start of the given date in global unixdays
  499. typeset -i unixdays
  500. function unixdays {
  501.     date2days "$@"
  502.     unixdays=date2days-25568
  503. }
  504.  
  505. # diffdays year1 month1 day-of-month1 year2 month2 day-of-month2 {
  506. # prints the number of complete days that passed from date 1 to date 2
  507. typeset -i diffdays
  508. function diffdays {
  509.     date2days $4 $5 $6
  510.     diffdays=date2days
  511.     date2days $1 $2 $3
  512.     diffdays=diffdays-date2days
  513. }
  514.  
  515. # Returns the UNIX epoch time in global curtime
  516. # Depends on SECONDS not being messed with
  517. # If all date utilities had %s this wouldn't be neccessary...
  518. typeset -i curtime _shell_start=-1
  519. function curtime {
  520.     typeset -i Y m d H M S
  521.     if [ _shell_start -eq -1 ]; then
  522.     TZ=0 date '+%Y %m %d %H %M %S' | read Y m d H M S
  523.     unixdays $Y $m $d
  524.     curtime="unixdays*86400+H*3600+M*60+S"
  525.     _shell_start=curtime-SECONDS
  526.     else
  527.     curtime=_shell_start+SECONDS
  528.     fi
  529. }
  530. ### end of doy-date lib
  531. # start of main program
  532.  
  533. name=${0##*/}
  534. Usage=\
  535. "Usage: $name [-afhilqnv] [-<signal>] [-[tk][!]<tty[,tty...]>] [-o<age>]
  536. [-u[!]<user[,user...]>] [-p[!]<process-ID,[process-ID,...>] <process-name> ..."
  537.  
  538. typeset -i x=0 
  539. KillAll=false
  540. NoKill=false
  541. AllUsers=false
  542. Verbose=false
  543. Restart=false
  544. testSig=0
  545. defSleep=3
  546. bSleep=$defSleep
  547. aSleep=$defSleep
  548. rSleep=$defSleep
  549. NotUserPat=-
  550. Interactive=false
  551. Menu=false
  552. Query=false
  553. checkAge=false
  554. u=false
  555.  
  556. set -o noglob
  557.  
  558. # Make sure that if "maim sh" [ksh, etc] is given,
  559. # neither this process nor its parent will be killed
  560. PIDpat="@($$|$PPID)"
  561. NotPID=true
  562.  
  563. typeset -i cur_year cur_day
  564. doArgs "$@"
  565. shift $(($?-1))        # remove args that were options
  566.  
  567. if [ -n "$TTYPat" ]; then
  568.     # Do not allow this because if both -t and -a are given, it isn't clear
  569.     # whether the user meant args to be user names or tty names.
  570.     if $KillAll; then
  571.     print -u2 "Cannot give both -a and -t."\
  572. "  -t implies -a, so you probably should use just -t."
  573.     exit 1
  574.     fi
  575.     # If a tty pattern is given w/o process names, all processes on the tty are
  576.     # candidates for being killed.  If -a was given along with user names,
  577.     # $# will not be 0, so this test will fail (incorrectly, since there are
  578.     # no process patterns), but the process pattern will be set to * anyway
  579.     # in the KillAll block.
  580.     [ $# -lt 1 ] && set -- '*'
  581. else
  582.     # By default, kill matching processes regardless of tty
  583.     TTYPat=\*
  584.     NotTTY=false
  585. fi
  586.  
  587. # If killing all procs (ignoring process name),
  588. # make the pattern be '*', and take any args to be user names.
  589. # If Interactive is set and no process selection options given, do KillAll.
  590. if $KillAll || [ $# -lt 1 -a $Query = true ]; then
  591.     if $Restart; then    # Must have proc name for restart
  592.     print -u2 "$name: Cannot give -r and -a options together."
  593.     exit 1
  594.     fi
  595.     # Only set user list if args given since even setting it to a space
  596.     # will prevent it from being set to the invoking user
  597.     [ $# -gt 0 ] && UserList="$UserList $*"
  598.     set -- '*'
  599. fi
  600.  
  601. if [ $# -lt 1 ]; then
  602.     print -u2 "$name: No non-option arguments given."
  603.     $u && print -u2 "You gave -u; perhaps you also meant to give -a?"
  604.     print -u2 \
  605. "$Usage
  606. Use -h for help."
  607.     exit
  608. fi
  609.  
  610. if [ -z "$signals" ]; then
  611.     $Restart && signals=9 || signals="1 15 9"
  612. fi
  613.  
  614. # If UserList not set, set it
  615. if [ -z "$UserList" ]; then
  616.     $AllUsers || {
  617.     if [ -n "$USER" ]; then
  618.         UserList=$USER
  619.     else
  620.         id=$(id)
  621.         UserList=${id%%\)*}
  622.         UserList=${UserList##*\(}
  623.         istrue x && print -u2 "id output: <$id>.  User: $UserList"
  624.     fi
  625.     }
  626. else    # A list of users has been given.
  627.     if $AllUsers; then
  628.     print -u2 \
  629. "$name: User names must not be given with -k.
  630. $Usage
  631. Use -h for help."
  632.     exit
  633.     fi
  634. fi
  635.  
  636. if $Restart; then
  637.     if [ $# -gt 1 ]; then
  638.     # Only allow one because it's difficult to keep track of which procs
  639.     # were killed.
  640.     print -u2 "$name: Must not give more than one process name with -r."
  641.     exit 1
  642.     fi
  643.     RestartProc=$1
  644. fi
  645.  
  646. pat=
  647. typeset -i maxName m MenuPIDs
  648.  
  649. for pname; do
  650.     # Only run uname if actually neccessary
  651.     if [[ -z "$f" && ${#pname} -gt 8 && "$pname" != *[][*?()]* ]]; then
  652.     ((maxName)) || { OSVersion 5 && maxName=8 || maxName=14; }
  653.     if [ ${#pname} -gt maxName ]; then
  654.         print -u2 \
  655. "$name: Warning: process names longer than $maxName characters will not match
  656. any processes unless -f is given.  Skipping: $pname"
  657.     else
  658.         pat="$pat|$pname"
  659.     fi
  660.     else
  661.     pat="$pat|$pname"
  662.     fi
  663. done
  664. if [ -z "$pat" ]; then
  665.     print -u2 "$name: No patterns.  Aborting."
  666.     exit 1
  667. fi
  668. # Get rid of the leading |
  669. pat="@(${pat#?})"
  670.  
  671. $AllUsers && set -A psArgs -- $f -e || set -A psArgs -- $f "-u$UserList"
  672.  
  673. istrue x && print -u2 \
  674. "Process name pattern: <$pat>
  675. TTY pattern: <$TTYPat>
  676. NotTTY: $NotTTY
  677. Process pattern: <$PIDpat>
  678. NotPID: $NotPID
  679. User list: <$UserList>
  680. User exclusion pattern: $NotUserPat
  681. Signals: <$signals>
  682. ps command: ps ${psArgs[*]}"
  683.  
  684. if $Query; then
  685.     exec 3<&0    # save stdin
  686.     if $Interactive; then
  687.     print -r \
  688. "For each process, enter 'y' to kill it, 'n' or return to skip it,
  689. 'q' to stop prompting and kill all processes that 'y' was entered for,
  690. or 'a' to abort without killing anything."
  691.     fi
  692. else
  693.     $NoKill || print "Maiming:"
  694. fi
  695.  
  696. typeset -i pspid=0
  697. # Want IFS set to nothing when reads are done so that leading whitespace will
  698. # not be stripped (so that psline can be printed nicely)
  699. # Start up ps as coprocess so we can get its pid
  700. ps "${psArgs[@]}" |&
  701. pspid=$!
  702. while IFS="" && read -r psline; do
  703.     IFS=" "
  704.     set -- $psline
  705.     if [ -z "$f" ]; then    # if doing plain ps
  706.     PS_cmd=$4
  707.     PS_tty=$2
  708.     PS_pid=$1
  709.     else            # if doing ps -f
  710.     if [[ "$5" = ??? ]]; then    # if start time given as month&day
  711.         PS_cmd=$9
  712.         PS_tty=$7
  713.         PS_month=$5
  714.         PS_day=$6
  715.         PS_time=
  716.     else                # if start time given as time of day
  717.         PS_cmd=$8
  718.         PS_tty=$6
  719.         PS_month=
  720.         PS_day=
  721.         PS_time=$5
  722.         [ "$PS_time" = - ] && continue    # zombie
  723.     fi
  724.     PS_cmd=${PS_cmd##*/}
  725.     PS_pid=$2
  726.     PS_user=$1
  727.     fi
  728.     [ "$PS_tty" = - ] && continue    # zombie
  729.     # ignore header & line for ps process itself
  730.     [[ "$PS_pid" = PID || "$PS_pid" -eq pspid ]] && continue
  731.     istrue x && print -ru2 -- \
  732. "Command: $PS_cmd
  733. TTY: $PS_tty
  734. PID: $PS_pid"
  735.     if eval "[[ '$PS_cmd' = $pat && 
  736.     ( $NotTTY = true && '$PS_tty' != $TTYPat ||
  737.     $NotTTY = false && '$PS_tty' = $TTYPat ) &&
  738.     '$PS_user' != $NotUserPat &&
  739.     ( $NotPID = true && '$PS_pid' != $PIDpat ||
  740.     $NotPID = false && '$PS_pid' = $PIDpat ) ]]" && doCheckAge; then
  741.     if $Menu; then
  742.         let m+=1
  743.         MenuLines[m]=$psline
  744.         MenuPIDs[m]=$PS_pid
  745.     else
  746.         print -r -- "$psline"    # Tell what we're maiming
  747.         if $Interactive; then
  748.         print -nr -- "Kill? [nyqa] "
  749.         read -u3 resp
  750.         case "$resp" in
  751.         [yY]*)
  752.             ;;
  753.         [nN]|"")
  754.             continue
  755.             ;;
  756.         [qQ])
  757.             break
  758.             ;;
  759.         [aA])
  760.             print Aborting.
  761.             exit 0
  762.             ;;
  763.         *)
  764.             print Skipping.
  765.             continue
  766.             ;;
  767.         esac
  768.         fi
  769.         pids="$pids $PS_pid"
  770.     fi
  771.     elif $Verbose; then
  772.     print -u2 "No match: $psline"
  773.     SkippedProcs=true
  774.     fi
  775. done <&p
  776. IFS="     
  777. "
  778.  
  779. if $Menu && [ ${#MenuLines[*]} -gt 0 ]; then
  780.     typeset -i num numElem
  781.     PS3=\
  782. "Enter: process numbers (NOT PIDs) to kill; q or EOF to quit and kill selected
  783. processes; a to abort without killing anything; or press <return> to reprint
  784. the process list: "
  785.     DoCont=true
  786.     while $DoCont; do
  787.     DoCont=false
  788.     if [ ${#MenuLines[*]} -eq 0 ]; then
  789.         print -n "All processes have been selected.  Kill them? [ny] "
  790.         read line
  791.         [[ "$line" != [yY]* ]] && exit 0
  792.         break
  793.     fi
  794.     select line in "${MenuLines[@]}"; do
  795.         case "$REPLY" in
  796.         # EOF in case it's taken literally!
  797.         [qQ]*|EOF)
  798.         break
  799.         ;;
  800.         [aA])
  801.         exit 0
  802.         ;;
  803.         +([0-9     ]))
  804.         numElem=${#MenuPIDs[*]}
  805.         for num in $REPLY; do
  806.             if [ num -eq 0 -o num -gt numElem ]; then
  807.             print "Process number out of range (skipping): $num"
  808.             else
  809.             # Avoid leading whitespace in PID list
  810.             [ -n "$pids" ] && pids="$pids ${MenuPIDs[num]}" ||
  811.             pids=${MenuPIDs[num]}
  812.             KillLines="$KillLines
  813. ${MenuLines[num]}"
  814.             unset MenuPIDs[num] MenuLines[num]
  815.             fi
  816.         done
  817.         ;;
  818.         *)
  819.         print "Unrecognized input; not processed."
  820.         ;;
  821.         esac
  822.         if [ -n "$KillLines" ]; then
  823.         print -r -- "Kill list (PIDs $pids):$KillLines
  824. "
  825.         fi
  826.         # Compact array
  827.         set -A MenuPIDs -- "" "${MenuPIDs[@]}"
  828.         set -A MenuLines -- "" "${MenuLines[@]}"
  829.         unset MenuPIDs[0] MenuLines[0]
  830.         DoCont=true
  831.         break
  832.     done
  833.     done
  834.     print ""    # In case EOF is hit
  835. fi
  836.  
  837. # Sort pids so they can be operated on as a set
  838. set -s $pids
  839. set -A KillPIDs -- $pids
  840.  
  841. istrue x && print -u2 "process ids: ${KillPIDs[*]}"
  842.  
  843. if [ -z "$pids" ]; then
  844.     print -u2 "Nothing to maim."
  845.     exit 1
  846. fi
  847.  
  848. $NoKill && exit 0
  849.  
  850. set -A sigList 0 $signals $testSig
  851. numSig=${#sigList[*]}
  852. istrue x && print -u2 \
  853. "$numSig signals to send (including initial & test signals): ${sigList[*]}"
  854. typeset -i signum=0
  855. while [ signum -lt numSig ]; do
  856.     signal=${sigList[signum]}
  857.     istrue x && print -u2 "Processing signal $signal..."
  858.     DonePIDs=
  859.     KilledPIDs=
  860.     kill -$signal "${KillPIDs[@]}" 2>&1 | while read result; do
  861.     # Output of kill looks like this (one process per line):
  862.     # kill: 1: permission denied
  863.     # kill: 30000: no such process
  864.     # Lines for processes are printed in the same order as PIDs given.
  865.     set -- $result
  866.     pid=${2%:}
  867.     case "$result" in
  868.     *denied*) print -u2 "$result";;
  869.     *"no such process"*)
  870.         # If this is the 2nd or later signal being sent,
  871.         # announce that the previous signal killed the process.
  872.         if [ -n "$lastsig" ]; then
  873.         KilledPIDs="$KilledPIDs $pid"
  874.         else    # If 1st signal, announce that process didn't exist.
  875.         print -u2 "$result"
  876.         fi
  877.         DonePIDs="$DonePIDs $pid"
  878.         ;;
  879.     *) print -u2 "$result"
  880.         ;;
  881.     esac
  882.     done
  883.     [ -n "$KilledPIDs" ] &&
  884.     print Processes killed by signal $lastsig: $KilledPIDs
  885.     SubtractSet KillPIDs $DonePIDs
  886.     [ ${#KillPIDs[*]} -eq 0 ] && break
  887.  
  888.     if [ -n "$lastsig" ]; then
  889.     if [ "$signal" = 0 ]; then
  890.         # If there was a previous signal, and this is the final test
  891.         # signal, warn about any processes still alive.
  892.         print "Processes not killed: ${KillPIDs[*]}"
  893.     else
  894.         print "Processes sent signal $signal: ${KillPIDs[*]}"
  895.     fi
  896.     fi
  897.  
  898.     lastsig=$signal
  899.     let signum+=1
  900.     # Don't sleep after first (test) and final signals.
  901.     [ signum -gt 1 -a signum -lt numSig ] && dosleep $bSleep ${KillPIDs[*]}
  902. done
  903. if $Restart; then
  904.     sleep $rSleep
  905.     print "Restarting '$RestartProc'..."
  906.     $RestartProc
  907. fi
  908. print Done.
  909.