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