commands.c revision 37451
124139Sjoerg/* 224139Sjoerg * Top users/processes display for Unix 324139Sjoerg * Version 3 424139Sjoerg * 524139Sjoerg * This program may be freely redistributed, 624139Sjoerg * but this entire comment MUST remain intact. 724139Sjoerg * 824139Sjoerg * Copyright (c) 1984, 1989, William LeFebvre, Rice University 924139Sjoerg * Copyright (c) 1989, 1990, 1992, William LeFebvre, Northwestern University 1024139Sjoerg */ 1124139Sjoerg 1224139Sjoerg/* 1324139Sjoerg * This file contains the routines that implement some of the interactive 1424139Sjoerg * mode commands. Note that some of the commands are implemented in-line 1524139Sjoerg * in "main". This is necessary because they change the global state of 1624139Sjoerg * "top" (i.e.: changing the number of processes to display). 1724139Sjoerg */ 1824139Sjoerg 1924139Sjoerg#include "os.h" 2024139Sjoerg#include <ctype.h> 2124139Sjoerg#include <signal.h> 2224139Sjoerg#include <errno.h> 2324139Sjoerg#include <sys/time.h> 2424139Sjoerg#include <sys/resource.h> 2524139Sjoerg 2624139Sjoerg#include "sigdesc.h" /* generated automatically */ 2737451Sbde#include "top.h" 2824139Sjoerg#include "boolean.h" 2924139Sjoerg#include "utils.h" 3024139Sjoerg 3124139Sjoergextern int errno; 3224139Sjoerg 3324139Sjoergextern char *copyright; 3424139Sjoerg 3524139Sjoerg/* imported from screen.c */ 3624139Sjoergextern int overstrike; 3724139Sjoerg 3824139Sjoergint err_compar(); 3924139Sjoergchar *err_string(); 4024139Sjoerg 4124139Sjoerg/* 4224139Sjoerg * show_help() - display the help screen; invoked in response to 4324139Sjoerg * either 'h' or '?'. 4424139Sjoerg */ 4524139Sjoerg 4624139Sjoergshow_help() 4724139Sjoerg 4824139Sjoerg{ 4924139Sjoerg printf("Top version %s, %s\n", version_string(), copyright); 5024139Sjoerg fputs("\n\n\ 5124139SjoergA top users display for Unix\n\ 5224139Sjoerg\n\ 5324139SjoergThese single-character commands are available:\n\ 5424139Sjoerg\n\ 5524139Sjoerg^L - redraw screen\n\ 5624139Sjoergq - quit\n\ 5724139Sjoergh or ? - help; show this text\n", stdout); 5824139Sjoerg 5924139Sjoerg /* not all commands are availalbe with overstrike terminals */ 6024139Sjoerg if (overstrike) 6124139Sjoerg { 6224139Sjoerg fputs("\n\ 6324139SjoergOther commands are also available, but this terminal is not\n\ 6424139Sjoergsophisticated enough to handle those commands gracefully.\n\n", stdout); 6524139Sjoerg } 6624139Sjoerg else 6724139Sjoerg { 6824139Sjoerg fputs("\ 6924139Sjoergd - change number of displays to show\n\ 7024139Sjoerge - list errors generated by last \"kill\" or \"renice\" command\n\ 7124139Sjoergi - toggle the displaying of idle processes\n\ 7224139SjoergI - same as 'i'\n\ 7324139Sjoergk - kill processes; send a signal to a list of processes\n\ 7424139Sjoergn or # - change number of processes to display\n", stdout); 7524139Sjoerg#ifdef ORDER 7624139Sjoerg fputs("\ 7724139Sjoergo - specify sort order (size, res, cpu, time)\n", stdout); 7824139Sjoerg#endif 7924139Sjoerg fputs("\ 8024139Sjoergr - renice a process\n\ 8124139Sjoergs - change number of seconds to delay between updates\n\ 8224139Sjoergu - display processes for only one user (+ selects all users)\n\ 8324139Sjoerg\n\ 8424139Sjoerg\n", stdout); 8524139Sjoerg } 8624139Sjoerg} 8724139Sjoerg 8824139Sjoerg/* 8924139Sjoerg * Utility routines that help with some of the commands. 9024139Sjoerg */ 9124139Sjoerg 9224139Sjoergchar *next_field(str) 9324139Sjoerg 9424139Sjoergregister char *str; 9524139Sjoerg 9624139Sjoerg{ 9724139Sjoerg if ((str = strchr(str, ' ')) == NULL) 9824139Sjoerg { 9924139Sjoerg return(NULL); 10024139Sjoerg } 10124139Sjoerg *str = '\0'; 10224139Sjoerg while (*++str == ' ') /* loop */; 10324139Sjoerg 10424139Sjoerg /* if there is nothing left of the string, return NULL */ 10524139Sjoerg /* This fix is dedicated to Greg Earle */ 10624139Sjoerg return(*str == '\0' ? NULL : str); 10724139Sjoerg} 10824139Sjoerg 10924139Sjoergscanint(str, intp) 11024139Sjoerg 11124139Sjoergchar *str; 11224139Sjoergint *intp; 11324139Sjoerg 11424139Sjoerg{ 11524139Sjoerg register int val = 0; 11624139Sjoerg register char ch; 11724139Sjoerg 11824139Sjoerg /* if there is nothing left of the string, flag it as an error */ 11924139Sjoerg /* This fix is dedicated to Greg Earle */ 12024139Sjoerg if (*str == '\0') 12124139Sjoerg { 12224139Sjoerg return(-1); 12324139Sjoerg } 12424139Sjoerg 12524139Sjoerg while ((ch = *str++) != '\0') 12624139Sjoerg { 12724139Sjoerg if (isdigit(ch)) 12824139Sjoerg { 12924139Sjoerg val = val * 10 + (ch - '0'); 13024139Sjoerg } 13124139Sjoerg else if (isspace(ch)) 13224139Sjoerg { 13324139Sjoerg break; 13424139Sjoerg } 13524139Sjoerg else 13624139Sjoerg { 13724139Sjoerg return(-1); 13824139Sjoerg } 13924139Sjoerg } 14024139Sjoerg *intp = val; 14124139Sjoerg return(0); 14224139Sjoerg} 14324139Sjoerg 14424139Sjoerg/* 14524139Sjoerg * Some of the commands make system calls that could generate errors. 14624139Sjoerg * These errors are collected up in an array of structures for later 14724139Sjoerg * contemplation and display. Such routines return a string containing an 14824139Sjoerg * error message, or NULL if no errors occurred. The next few routines are 14924139Sjoerg * for manipulating and displaying these errors. We need an upper limit on 15024139Sjoerg * the number of errors, so we arbitrarily choose 20. 15124139Sjoerg */ 15224139Sjoerg 15324139Sjoerg#define ERRMAX 20 15424139Sjoerg 15524139Sjoergstruct errs /* structure for a system-call error */ 15624139Sjoerg{ 15731939Sjb int error; /* value of errno (that is, the actual error) */ 15824139Sjoerg char *arg; /* argument that caused the error */ 15924139Sjoerg}; 16024139Sjoerg 16124139Sjoergstatic struct errs errs[ERRMAX]; 16224139Sjoergstatic int errcnt; 16324139Sjoergstatic char *err_toomany = " too many errors occurred"; 16424139Sjoergstatic char *err_listem = 16524139Sjoerg " Many errors occurred. Press `e' to display the list of errors."; 16624139Sjoerg 16724139Sjoerg/* These macros get used to reset and log the errors */ 16824139Sjoerg#define ERR_RESET errcnt = 0 16924139Sjoerg#define ERROR(p, e) if (errcnt >= ERRMAX) \ 17024139Sjoerg { \ 17124139Sjoerg return(err_toomany); \ 17224139Sjoerg } \ 17324139Sjoerg else \ 17424139Sjoerg { \ 17524139Sjoerg errs[errcnt].arg = (p); \ 17631939Sjb errs[errcnt++].error = (e); \ 17724139Sjoerg } 17824139Sjoerg 17924139Sjoerg/* 18024139Sjoerg * err_string() - return an appropriate error string. This is what the 18124139Sjoerg * command will return for displaying. If no errors were logged, then 18224139Sjoerg * return NULL. The maximum length of the error string is defined by 18324139Sjoerg * "STRMAX". 18424139Sjoerg */ 18524139Sjoerg 18624139Sjoerg#define STRMAX 80 18724139Sjoerg 18824139Sjoergchar *err_string() 18924139Sjoerg 19024139Sjoerg{ 19124139Sjoerg register struct errs *errp; 19224139Sjoerg register int cnt = 0; 19324139Sjoerg register int first = Yes; 19424139Sjoerg register int currerr = -1; 19524139Sjoerg int stringlen; /* characters still available in "string" */ 19624139Sjoerg static char string[STRMAX]; 19724139Sjoerg 19824139Sjoerg /* if there are no errors, return NULL */ 19924139Sjoerg if (errcnt == 0) 20024139Sjoerg { 20124139Sjoerg return(NULL); 20224139Sjoerg } 20324139Sjoerg 20424139Sjoerg /* sort the errors */ 20524139Sjoerg qsort((char *)errs, errcnt, sizeof(struct errs), err_compar); 20624139Sjoerg 20724139Sjoerg /* need a space at the front of the error string */ 20824139Sjoerg string[0] = ' '; 20924139Sjoerg string[1] = '\0'; 21024139Sjoerg stringlen = STRMAX - 2; 21124139Sjoerg 21224139Sjoerg /* loop thru the sorted list, building an error string */ 21324139Sjoerg while (cnt < errcnt) 21424139Sjoerg { 21524139Sjoerg errp = &(errs[cnt++]); 21631939Sjb if (errp->error != currerr) 21724139Sjoerg { 21824139Sjoerg if (currerr != -1) 21924139Sjoerg { 22024139Sjoerg if ((stringlen = str_adderr(string, stringlen, currerr)) < 2) 22124139Sjoerg { 22224139Sjoerg return(err_listem); 22324139Sjoerg } 22424139Sjoerg (void) strcat(string, "; "); /* we know there's more */ 22524139Sjoerg } 22631939Sjb currerr = errp->error; 22724139Sjoerg first = Yes; 22824139Sjoerg } 22924139Sjoerg if ((stringlen = str_addarg(string, stringlen, errp->arg, first)) ==0) 23024139Sjoerg { 23124139Sjoerg return(err_listem); 23224139Sjoerg } 23324139Sjoerg first = No; 23424139Sjoerg } 23524139Sjoerg 23624139Sjoerg /* add final message */ 23724139Sjoerg stringlen = str_adderr(string, stringlen, currerr); 23824139Sjoerg 23924139Sjoerg /* return the error string */ 24024139Sjoerg return(stringlen == 0 ? err_listem : string); 24124139Sjoerg} 24224139Sjoerg 24324139Sjoerg/* 24424139Sjoerg * str_adderr(str, len, err) - add an explanation of error "err" to 24524139Sjoerg * the string "str". 24624139Sjoerg */ 24724139Sjoerg 24824139Sjoergstr_adderr(str, len, err) 24924139Sjoerg 25024139Sjoergchar *str; 25124139Sjoergint len; 25224139Sjoergint err; 25324139Sjoerg 25424139Sjoerg{ 25524139Sjoerg register char *msg; 25624139Sjoerg register int msglen; 25724139Sjoerg 25824139Sjoerg msg = err == 0 ? "Not a number" : errmsg(err); 25924139Sjoerg msglen = strlen(msg) + 2; 26024139Sjoerg if (len <= msglen) 26124139Sjoerg { 26224139Sjoerg return(0); 26324139Sjoerg } 26424139Sjoerg (void) strcat(str, ": "); 26524139Sjoerg (void) strcat(str, msg); 26624139Sjoerg return(len - msglen); 26724139Sjoerg} 26824139Sjoerg 26924139Sjoerg/* 27024139Sjoerg * str_addarg(str, len, arg, first) - add the string argument "arg" to 27124139Sjoerg * the string "str". This is the first in the group when "first" 27224139Sjoerg * is set (indicating that a comma should NOT be added to the front). 27324139Sjoerg */ 27424139Sjoerg 27524139Sjoergstr_addarg(str, len, arg, first) 27624139Sjoerg 27724139Sjoergchar *str; 27824139Sjoergint len; 27924139Sjoergchar *arg; 28024139Sjoergint first; 28124139Sjoerg 28224139Sjoerg{ 28324139Sjoerg register int arglen; 28424139Sjoerg 28524139Sjoerg arglen = strlen(arg); 28624139Sjoerg if (!first) 28724139Sjoerg { 28824139Sjoerg arglen += 2; 28924139Sjoerg } 29024139Sjoerg if (len <= arglen) 29124139Sjoerg { 29224139Sjoerg return(0); 29324139Sjoerg } 29424139Sjoerg if (!first) 29524139Sjoerg { 29624139Sjoerg (void) strcat(str, ", "); 29724139Sjoerg } 29824139Sjoerg (void) strcat(str, arg); 29924139Sjoerg return(len - arglen); 30024139Sjoerg} 30124139Sjoerg 30224139Sjoerg/* 30324139Sjoerg * err_compar(p1, p2) - comparison routine used by "qsort" 30424139Sjoerg * for sorting errors. 30524139Sjoerg */ 30624139Sjoerg 30724139Sjoergerr_compar(p1, p2) 30824139Sjoerg 30924139Sjoergregister struct errs *p1, *p2; 31024139Sjoerg 31124139Sjoerg{ 31224139Sjoerg register int result; 31324139Sjoerg 31431939Sjb if ((result = p1->error - p2->error) == 0) 31524139Sjoerg { 31624139Sjoerg return(strcmp(p1->arg, p2->arg)); 31724139Sjoerg } 31824139Sjoerg return(result); 31924139Sjoerg} 32024139Sjoerg 32124139Sjoerg/* 32224139Sjoerg * error_count() - return the number of errors currently logged. 32324139Sjoerg */ 32424139Sjoerg 32524139Sjoergerror_count() 32624139Sjoerg 32724139Sjoerg{ 32824139Sjoerg return(errcnt); 32924139Sjoerg} 33024139Sjoerg 33124139Sjoerg/* 33224139Sjoerg * show_errors() - display on stdout the current log of errors. 33324139Sjoerg */ 33424139Sjoerg 33524139Sjoergshow_errors() 33624139Sjoerg 33724139Sjoerg{ 33824139Sjoerg register int cnt = 0; 33924139Sjoerg register struct errs *errp = errs; 34024139Sjoerg 34124139Sjoerg printf("%d error%s:\n\n", errcnt, errcnt == 1 ? "" : "s"); 34224139Sjoerg while (cnt++ < errcnt) 34324139Sjoerg { 34424139Sjoerg printf("%5s: %s\n", errp->arg, 34531939Sjb errp->error == 0 ? "Not a number" : errmsg(errp->error)); 34624139Sjoerg errp++; 34724139Sjoerg } 34824139Sjoerg} 34924139Sjoerg 35024139Sjoerg/* 35124139Sjoerg * kill_procs(str) - send signals to processes, much like the "kill" 35224139Sjoerg * command does; invoked in response to 'k'. 35324139Sjoerg */ 35424139Sjoerg 35524139Sjoergchar *kill_procs(str) 35624139Sjoerg 35724139Sjoergchar *str; 35824139Sjoerg 35924139Sjoerg{ 36024139Sjoerg register char *nptr; 36124139Sjoerg int signum = SIGTERM; /* default */ 36224139Sjoerg int procnum; 36324139Sjoerg struct sigdesc *sigp; 36424139Sjoerg int uid; 36524139Sjoerg 36624139Sjoerg /* reset error array */ 36724139Sjoerg ERR_RESET; 36824139Sjoerg 36924139Sjoerg /* remember our uid */ 37024139Sjoerg uid = getuid(); 37124139Sjoerg 37224139Sjoerg /* skip over leading white space */ 37324139Sjoerg while (isspace(*str)) str++; 37424139Sjoerg 37524139Sjoerg if (str[0] == '-') 37624139Sjoerg { 37724139Sjoerg /* explicit signal specified */ 37824139Sjoerg if ((nptr = next_field(str)) == NULL) 37924139Sjoerg { 38024139Sjoerg return(" kill: no processes specified"); 38124139Sjoerg } 38224139Sjoerg 38324139Sjoerg if (isdigit(str[1])) 38424139Sjoerg { 38524139Sjoerg (void) scanint(str + 1, &signum); 38624139Sjoerg if (signum <= 0 || signum >= NSIG) 38724139Sjoerg { 38824139Sjoerg return(" invalid signal number"); 38924139Sjoerg } 39024139Sjoerg } 39124139Sjoerg else 39224139Sjoerg { 39324139Sjoerg /* translate the name into a number */ 39424139Sjoerg for (sigp = sigdesc; sigp->name != NULL; sigp++) 39524139Sjoerg { 39624139Sjoerg if (strcmp(sigp->name, str + 1) == 0) 39724139Sjoerg { 39824139Sjoerg signum = sigp->number; 39924139Sjoerg break; 40024139Sjoerg } 40124139Sjoerg } 40224139Sjoerg 40324139Sjoerg /* was it ever found */ 40424139Sjoerg if (sigp->name == NULL) 40524139Sjoerg { 40624139Sjoerg return(" bad signal name"); 40724139Sjoerg } 40824139Sjoerg } 40924139Sjoerg /* put the new pointer in place */ 41024139Sjoerg str = nptr; 41124139Sjoerg } 41224139Sjoerg 41324139Sjoerg /* loop thru the string, killing processes */ 41424139Sjoerg do 41524139Sjoerg { 41624139Sjoerg if (scanint(str, &procnum) == -1) 41724139Sjoerg { 41824139Sjoerg ERROR(str, 0); 41924139Sjoerg } 42024139Sjoerg else 42124139Sjoerg { 42224139Sjoerg /* check process owner if we're not root */ 42324139Sjoerg if (uid && (uid != proc_owner(procnum))) 42424139Sjoerg { 42524139Sjoerg ERROR(str, EACCES); 42624139Sjoerg } 42724139Sjoerg /* go in for the kill */ 42824139Sjoerg else if (kill(procnum, signum) == -1) 42924139Sjoerg { 43024139Sjoerg /* chalk up an error */ 43124139Sjoerg ERROR(str, errno); 43224139Sjoerg } 43324139Sjoerg } 43424139Sjoerg } while ((str = next_field(str)) != NULL); 43524139Sjoerg 43624139Sjoerg /* return appropriate error string */ 43724139Sjoerg return(err_string()); 43824139Sjoerg} 43924139Sjoerg 44024139Sjoerg/* 44124139Sjoerg * renice_procs(str) - change the "nice" of processes, much like the 44224139Sjoerg * "renice" command does; invoked in response to 'r'. 44324139Sjoerg */ 44424139Sjoerg 44524139Sjoergchar *renice_procs(str) 44624139Sjoerg 44724139Sjoergchar *str; 44824139Sjoerg 44924139Sjoerg{ 45024139Sjoerg register char negate; 45124139Sjoerg int prio; 45224139Sjoerg int procnum; 45324139Sjoerg int uid; 45424139Sjoerg 45524139Sjoerg ERR_RESET; 45624139Sjoerg uid = getuid(); 45724139Sjoerg 45824139Sjoerg /* allow for negative priority values */ 45924139Sjoerg if ((negate = (*str == '-')) != 0) 46024139Sjoerg { 46124139Sjoerg /* move past the minus sign */ 46224139Sjoerg str++; 46324139Sjoerg } 46424139Sjoerg 46524139Sjoerg /* use procnum as a temporary holding place and get the number */ 46624139Sjoerg procnum = scanint(str, &prio); 46724139Sjoerg 46824139Sjoerg /* negate if necessary */ 46924139Sjoerg if (negate) 47024139Sjoerg { 47124139Sjoerg prio = -prio; 47224139Sjoerg } 47324139Sjoerg 47424139Sjoerg#if defined(PRIO_MIN) && defined(PRIO_MAX) 47524139Sjoerg /* check for validity */ 47624139Sjoerg if (procnum == -1 || prio < PRIO_MIN || prio > PRIO_MAX) 47724139Sjoerg { 47824139Sjoerg return(" bad priority value"); 47924139Sjoerg } 48024139Sjoerg#endif 48124139Sjoerg 48224139Sjoerg /* move to the first process number */ 48324139Sjoerg if ((str = next_field(str)) == NULL) 48424139Sjoerg { 48524139Sjoerg return(" no processes specified"); 48624139Sjoerg } 48724139Sjoerg 48824139Sjoerg /* loop thru the process numbers, renicing each one */ 48924139Sjoerg do 49024139Sjoerg { 49124139Sjoerg if (scanint(str, &procnum) == -1) 49224139Sjoerg { 49324139Sjoerg ERROR(str, 0); 49424139Sjoerg } 49524139Sjoerg 49624139Sjoerg /* check process owner if we're not root */ 49724139Sjoerg else if (uid && (uid != proc_owner(procnum))) 49824139Sjoerg { 49924139Sjoerg ERROR(str, EACCES); 50024139Sjoerg } 50124139Sjoerg else if (setpriority(PRIO_PROCESS, procnum, prio) == -1) 50224139Sjoerg { 50324139Sjoerg ERROR(str, errno); 50424139Sjoerg } 50524139Sjoerg } while ((str = next_field(str)) != NULL); 50624139Sjoerg 50724139Sjoerg /* return appropriate error string */ 50824139Sjoerg return(err_string()); 50924139Sjoerg} 51024139Sjoerg 511