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