commands.c revision 24140
1/*
2 *  Top users/processes display for Unix
3 *  Version 3
4 *
5 *  This program may be freely redistributed,
6 *  but this entire comment MUST remain intact.
7 *
8 *  Copyright (c) 1984, 1989, William LeFebvre, Rice University
9 *  Copyright (c) 1989, 1990, 1992, William LeFebvre, Northwestern University
10 */
11
12/*
13 *  This file contains the routines that implement some of the interactive
14 *  mode commands.  Note that some of the commands are implemented in-line
15 *  in "main".  This is necessary because they change the global state of
16 *  "top" (i.e.:  changing the number of processes to display).
17 */
18
19#include "os.h"
20#include <ctype.h>
21#include <signal.h>
22#include <errno.h>
23#include <sys/time.h>
24#include <sys/resource.h>
25
26#include "sigdesc.h"		/* generated automatically */
27#include "boolean.h"
28#include "utils.h"
29
30extern int  errno;
31
32extern char *copyright;
33
34/* imported from screen.c */
35extern int overstrike;
36
37int err_compar();
38char *err_string();
39
40/*
41 *  show_help() - display the help screen; invoked in response to
42 *		either 'h' or '?'.
43 */
44
45show_help()
46
47{
48    printf("Top version %s, %s\n", version_string(), copyright);
49    fputs("\n\n\
50A top users display for Unix\n\
51\n\
52These single-character commands are available:\n\
53\n\
54^L      - redraw screen\n\
55q       - quit\n\
56h or ?  - help; show this text\n", stdout);
57
58    /* not all commands are availalbe with overstrike terminals */
59    if (overstrike)
60    {
61	fputs("\n\
62Other commands are also available, but this terminal is not\n\
63sophisticated enough to handle those commands gracefully.\n\n", stdout);
64    }
65    else
66    {
67	fputs("\
68d       - change number of displays to show\n\
69e       - list errors generated by last \"kill\" or \"renice\" command\n\
70i       - toggle the displaying of idle processes\n\
71I       - same as 'i'\n\
72k       - kill processes; send a signal to a list of processes\n\
73n or #  - change number of processes to display\n", stdout);
74#ifdef ORDER
75	fputs("\
76o       - specify sort order (size, res, cpu, time)\n", stdout);
77#endif
78	fputs("\
79r       - renice a process\n\
80s       - change number of seconds to delay between updates\n\
81u       - display processes for only one user (+ selects all users)\n\
82\n\
83\n", stdout);
84    }
85}
86
87/*
88 *  Utility routines that help with some of the commands.
89 */
90
91char *next_field(str)
92
93register char *str;
94
95{
96    if ((str = strchr(str, ' ')) == NULL)
97    {
98	return(NULL);
99    }
100    *str = '\0';
101    while (*++str == ' ') /* loop */;
102
103    /* if there is nothing left of the string, return NULL */
104    /* This fix is dedicated to Greg Earle */
105    return(*str == '\0' ? NULL : str);
106}
107
108scanint(str, intp)
109
110char *str;
111int  *intp;
112
113{
114    register int val = 0;
115    register char ch;
116
117    /* if there is nothing left of the string, flag it as an error */
118    /* This fix is dedicated to Greg Earle */
119    if (*str == '\0')
120    {
121	return(-1);
122    }
123
124    while ((ch = *str++) != '\0')
125    {
126	if (isdigit(ch))
127	{
128	    val = val * 10 + (ch - '0');
129	}
130	else if (isspace(ch))
131	{
132	    break;
133	}
134	else
135	{
136	    return(-1);
137	}
138    }
139    *intp = val;
140    return(0);
141}
142
143/*
144 *  Some of the commands make system calls that could generate errors.
145 *  These errors are collected up in an array of structures for later
146 *  contemplation and display.  Such routines return a string containing an
147 *  error message, or NULL if no errors occurred.  The next few routines are
148 *  for manipulating and displaying these errors.  We need an upper limit on
149 *  the number of errors, so we arbitrarily choose 20.
150 */
151
152#define ERRMAX 20
153
154struct errs		/* structure for a system-call error */
155{
156    int  errno;		/* value of errno (that is, the actual error) */
157    char *arg;		/* argument that caused the error */
158};
159
160static struct errs errs[ERRMAX];
161static int errcnt;
162static char *err_toomany = " too many errors occurred";
163static char *err_listem =
164	" Many errors occurred.  Press `e' to display the list of errors.";
165
166/* These macros get used to reset and log the errors */
167#define ERR_RESET   errcnt = 0
168#define ERROR(p, e) if (errcnt >= ERRMAX) \
169		    { \
170			return(err_toomany); \
171		    } \
172		    else \
173		    { \
174			errs[errcnt].arg = (p); \
175			errs[errcnt++].errno = (e); \
176		    }
177
178/*
179 *  err_string() - return an appropriate error string.  This is what the
180 *	command will return for displaying.  If no errors were logged, then
181 *	return NULL.  The maximum length of the error string is defined by
182 *	"STRMAX".
183 */
184
185#define STRMAX 80
186
187char *err_string()
188
189{
190    register struct errs *errp;
191    register int  cnt = 0;
192    register int  first = Yes;
193    register int  currerr = -1;
194    int stringlen;		/* characters still available in "string" */
195    static char string[STRMAX];
196
197    /* if there are no errors, return NULL */
198    if (errcnt == 0)
199    {
200	return(NULL);
201    }
202
203    /* sort the errors */
204    qsort((char *)errs, errcnt, sizeof(struct errs), err_compar);
205
206    /* need a space at the front of the error string */
207    string[0] = ' ';
208    string[1] = '\0';
209    stringlen = STRMAX - 2;
210
211    /* loop thru the sorted list, building an error string */
212    while (cnt < errcnt)
213    {
214	errp = &(errs[cnt++]);
215	if (errp->errno != currerr)
216	{
217	    if (currerr != -1)
218	    {
219		if ((stringlen = str_adderr(string, stringlen, currerr)) < 2)
220		{
221		    return(err_listem);
222		}
223		(void) strcat(string, "; ");	  /* we know there's more */
224	    }
225	    currerr = errp->errno;
226	    first = Yes;
227	}
228	if ((stringlen = str_addarg(string, stringlen, errp->arg, first)) ==0)
229	{
230	    return(err_listem);
231	}
232	first = No;
233    }
234
235    /* add final message */
236    stringlen = str_adderr(string, stringlen, currerr);
237
238    /* return the error string */
239    return(stringlen == 0 ? err_listem : string);
240}
241
242/*
243 *  str_adderr(str, len, err) - add an explanation of error "err" to
244 *	the string "str".
245 */
246
247str_adderr(str, len, err)
248
249char *str;
250int len;
251int err;
252
253{
254    register char *msg;
255    register int  msglen;
256
257    msg = err == 0 ? "Not a number" : errmsg(err);
258    msglen = strlen(msg) + 2;
259    if (len <= msglen)
260    {
261	return(0);
262    }
263    (void) strcat(str, ": ");
264    (void) strcat(str, msg);
265    return(len - msglen);
266}
267
268/*
269 *  str_addarg(str, len, arg, first) - add the string argument "arg" to
270 *	the string "str".  This is the first in the group when "first"
271 *	is set (indicating that a comma should NOT be added to the front).
272 */
273
274str_addarg(str, len, arg, first)
275
276char *str;
277int  len;
278char *arg;
279int  first;
280
281{
282    register int arglen;
283
284    arglen = strlen(arg);
285    if (!first)
286    {
287	arglen += 2;
288    }
289    if (len <= arglen)
290    {
291	return(0);
292    }
293    if (!first)
294    {
295	(void) strcat(str, ", ");
296    }
297    (void) strcat(str, arg);
298    return(len - arglen);
299}
300
301/*
302 *  err_compar(p1, p2) - comparison routine used by "qsort"
303 *	for sorting errors.
304 */
305
306err_compar(p1, p2)
307
308register struct errs *p1, *p2;
309
310{
311    register int result;
312
313    if ((result = p1->errno - p2->errno) == 0)
314    {
315	return(strcmp(p1->arg, p2->arg));
316    }
317    return(result);
318}
319
320/*
321 *  error_count() - return the number of errors currently logged.
322 */
323
324error_count()
325
326{
327    return(errcnt);
328}
329
330/*
331 *  show_errors() - display on stdout the current log of errors.
332 */
333
334show_errors()
335
336{
337    register int cnt = 0;
338    register struct errs *errp = errs;
339
340    printf("%d error%s:\n\n", errcnt, errcnt == 1 ? "" : "s");
341    while (cnt++ < errcnt)
342    {
343	printf("%5s: %s\n", errp->arg,
344	    errp->errno == 0 ? "Not a number" : errmsg(errp->errno));
345	errp++;
346    }
347}
348
349/*
350 *  kill_procs(str) - send signals to processes, much like the "kill"
351 *		command does; invoked in response to 'k'.
352 */
353
354char *kill_procs(str)
355
356char *str;
357
358{
359    register char *nptr;
360    int signum = SIGTERM;	/* default */
361    int procnum;
362    struct sigdesc *sigp;
363    int uid;
364
365    /* reset error array */
366    ERR_RESET;
367
368    /* remember our uid */
369    uid = getuid();
370
371    /* skip over leading white space */
372    while (isspace(*str)) str++;
373
374    if (str[0] == '-')
375    {
376	/* explicit signal specified */
377	if ((nptr = next_field(str)) == NULL)
378	{
379	    return(" kill: no processes specified");
380	}
381
382	if (isdigit(str[1]))
383	{
384	    (void) scanint(str + 1, &signum);
385	    if (signum <= 0 || signum >= NSIG)
386	    {
387		return(" invalid signal number");
388	    }
389	}
390	else
391	{
392	    /* translate the name into a number */
393	    for (sigp = sigdesc; sigp->name != NULL; sigp++)
394	    {
395		if (strcmp(sigp->name, str + 1) == 0)
396		{
397		    signum = sigp->number;
398		    break;
399		}
400	    }
401
402	    /* was it ever found */
403	    if (sigp->name == NULL)
404	    {
405		return(" bad signal name");
406	    }
407	}
408	/* put the new pointer in place */
409	str = nptr;
410    }
411
412    /* loop thru the string, killing processes */
413    do
414    {
415	if (scanint(str, &procnum) == -1)
416	{
417	    ERROR(str, 0);
418	}
419	else
420	{
421	    /* check process owner if we're not root */
422	    if (uid && (uid != proc_owner(procnum)))
423	    {
424		ERROR(str, EACCES);
425	    }
426	    /* go in for the kill */
427	    else if (kill(procnum, signum) == -1)
428	    {
429		/* chalk up an error */
430		ERROR(str, errno);
431	    }
432	}
433    } while ((str = next_field(str)) != NULL);
434
435    /* return appropriate error string */
436    return(err_string());
437}
438
439/*
440 *  renice_procs(str) - change the "nice" of processes, much like the
441 *		"renice" command does; invoked in response to 'r'.
442 */
443
444char *renice_procs(str)
445
446char *str;
447
448{
449    register char negate;
450    int prio;
451    int procnum;
452    int uid;
453
454    ERR_RESET;
455    uid = getuid();
456
457    /* allow for negative priority values */
458    if ((negate = (*str == '-')) != 0)
459    {
460	/* move past the minus sign */
461	str++;
462    }
463
464    /* use procnum as a temporary holding place and get the number */
465    procnum = scanint(str, &prio);
466
467    /* negate if necessary */
468    if (negate)
469    {
470	prio = -prio;
471    }
472
473#if defined(PRIO_MIN) && defined(PRIO_MAX)
474    /* check for validity */
475    if (procnum == -1 || prio < PRIO_MIN || prio > PRIO_MAX)
476    {
477	return(" bad priority value");
478    }
479#endif
480
481    /* move to the first process number */
482    if ((str = next_field(str)) == NULL)
483    {
484	return(" no processes specified");
485    }
486
487    /* loop thru the process numbers, renicing each one */
488    do
489    {
490	if (scanint(str, &procnum) == -1)
491	{
492	    ERROR(str, 0);
493	}
494
495	/* check process owner if we're not root */
496	else if (uid && (uid != proc_owner(procnum)))
497	{
498	    ERROR(str, EACCES);
499	}
500	else if (setpriority(PRIO_PROCESS, procnum, prio) == -1)
501	{
502	    ERROR(str, errno);
503	}
504    } while ((str = next_field(str)) != NULL);
505
506    /* return appropriate error string */
507    return(err_string());
508}
509
510