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