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