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