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