]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - usr.bin/top/commands.c
heimdal: Fix multiple security vulnerabilities
[FreeBSD/FreeBSD.git] / usr.bin / top / commands.c
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