]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/top/commands.c
Merge branch 'releng/11.3' into releng-CDN/11.3
[FreeBSD/FreeBSD.git] / contrib / 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 "os.h"
22
23 #include <sys/time.h>
24 #include <sys/resource.h>
25
26 #include <ctype.h>
27 #include <errno.h>
28 #include <signal.h>
29 #include <unistd.h>
30
31 #include "commands.h"
32 #include "sigdesc.h"            /* generated automatically */
33 #include "top.h"
34 #include "boolean.h"
35 #include "utils.h"
36 #include "machine.h"
37
38 extern int  errno;
39
40 extern char *copyright;
41
42 /* imported from screen.c */
43 extern int overstrike;
44
45 int err_compar();
46 char *err_string();
47 static int str_adderr(char *str, int len, int err);
48 static int str_addarg(char *str, int len, char *arg, int first);
49
50 /*
51  *  show_help() - display the help screen; invoked in response to
52  *              either 'h' or '?'.
53  */
54
55 void
56 show_help()
57
58 {
59     printf("Top version %s, %s\n", version_string(), copyright);
60     fputs("\n\n\
61 A top users display for Unix\n\
62 \n\
63 These single-character commands are available:\n\
64 \n\
65 ^L      - redraw screen\n\
66 q       - quit\n\
67 h or ?  - help; show this text\n", stdout);
68
69     /* not all commands are availalbe with overstrike terminals */
70     if (overstrike)
71     {
72         fputs("\n\
73 Other commands are also available, but this terminal is not\n\
74 sophisticated enough to handle those commands gracefully.\n\n", stdout);
75     }
76     else
77     {
78         fputs("\
79 C       - toggle the displaying of weighted CPU percentage\n\
80 d       - change number of displays to show\n\
81 e       - list errors generated by last \"kill\" or \"renice\" command\n\
82 H       - toggle the displaying of threads\n\
83 i or I  - toggle the displaying of idle processes\n\
84 j       - toggle the displaying of jail ID\n\
85 J       - display processes for only one jail (+ selects all jails)\n\
86 k       - kill processes; send a signal to a list of processes\n\
87 m       - toggle the display between 'cpu' and 'io' modes\n\
88 n or #  - change number of processes to display\n", stdout);
89 #ifdef ORDER
90         if (displaymode == DISP_CPU)
91                 fputs("\
92 o       - specify sort order (pri, size, res, cpu, time, threads, jid, pid)\n",
93             stdout);
94         else
95                 fputs("\
96 o       - specify sort order (vcsw, ivcsw, read, write, fault, total, jid, pid)\n",
97             stdout);
98 #endif
99         fputs("\
100 P       - toggle the displaying of per-CPU statistics\n\
101 r       - renice a process\n\
102 s       - change number of seconds to delay between updates\n\
103 S       - toggle the displaying of system processes\n\
104 a       - toggle the displaying of process titles\n\
105 t       - toggle the display of this process\n\
106 u       - display processes for only one user (+ selects all users)\n\
107 w       - toggle the display of swap use for each process\n\
108 z       - toggle the displaying of the system idle process\n\
109 \n\
110 \n", stdout);
111     }
112 }
113
114 /*
115  *  Utility routines that help with some of the commands.
116  */
117
118 char *next_field(str)
119
120 register char *str;
121
122 {
123     if ((str = strchr(str, ' ')) == NULL)
124     {
125         return(NULL);
126     }
127     *str = '\0';
128     while (*++str == ' ') /* loop */;
129
130     /* if there is nothing left of the string, return NULL */
131     /* This fix is dedicated to Greg Earle */
132     return(*str == '\0' ? NULL : str);
133 }
134
135 int
136 scanint(str, intp)
137
138 char *str;
139 int  *intp;
140
141 {
142     register int val = 0;
143     register char ch;
144
145     /* if there is nothing left of the string, flag it as an error */
146     /* This fix is dedicated to Greg Earle */
147     if (*str == '\0')
148     {
149         return(-1);
150     }
151
152     while ((ch = *str++) != '\0')
153     {
154         if (isdigit(ch))
155         {
156             val = val * 10 + (ch - '0');
157         }
158         else if (isspace(ch))
159         {
160             break;
161         }
162         else
163         {
164             return(-1);
165         }
166     }
167     *intp = val;
168     return(0);
169 }
170
171 /*
172  *  Some of the commands make system calls that could generate errors.
173  *  These errors are collected up in an array of structures for later
174  *  contemplation and display.  Such routines return a string containing an
175  *  error message, or NULL if no errors occurred.  The next few routines are
176  *  for manipulating and displaying these errors.  We need an upper limit on
177  *  the number of errors, so we arbitrarily choose 20.
178  */
179
180 #define ERRMAX 20
181
182 struct errs             /* structure for a system-call error */
183 {
184     int  errnum;        /* value of errno (that is, the actual error) */
185     char *arg;          /* argument that caused the error */
186 };
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 *err_string()
216
217 {
218     register struct errs *errp;
219     register int  cnt = 0;
220     register int  first = Yes;
221     register 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 != -1)
246             {
247                 if ((stringlen = str_adderr(string, stringlen, currerr)) < 2)
248                 {
249                     return(err_listem);
250                 }
251                 (void) strcat(string, "; ");      /* we know there's more */
252             }
253             currerr = errp->errnum;
254             first = Yes;
255         }
256         if ((stringlen = str_addarg(string, stringlen, errp->arg, first)) ==0)
257         {
258             return(err_listem);
259         }
260         first = No;
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(str, len, err)
277
278 char *str;
279 int len;
280 int err;
281
282 {
283     register char *msg;
284     register int  msglen;
285
286     msg = err == 0 ? "Not a number" : errmsg(err);
287     msglen = strlen(msg) + 2;
288     if (len <= msglen)
289     {
290         return(0);
291     }
292     (void) strcat(str, ": ");
293     (void) strcat(str, msg);
294     return(len - msglen);
295 }
296
297 /*
298  *  str_addarg(str, len, arg, first) - add the string argument "arg" to
299  *      the string "str".  This is the first in the group when "first"
300  *      is set (indicating that a comma should NOT be added to the front).
301  */
302
303 static int
304 str_addarg(str, len, arg, first)
305
306 char *str;
307 int  len;
308 char *arg;
309 int  first;
310
311 {
312     register int arglen;
313
314     arglen = strlen(arg);
315     if (!first)
316     {
317         arglen += 2;
318     }
319     if (len <= arglen)
320     {
321         return(0);
322     }
323     if (!first)
324     {
325         (void) strcat(str, ", ");
326     }
327     (void) strcat(str, arg);
328     return(len - arglen);
329 }
330
331 /*
332  *  err_compar(p1, p2) - comparison routine used by "qsort"
333  *      for sorting errors.
334  */
335
336 int
337 err_compar(p1, p2)
338
339 register struct errs *p1, *p2;
340
341 {
342     register int result;
343
344     if ((result = p1->errnum - p2->errnum) == 0)
345     {
346         return(strcmp(p1->arg, p2->arg));
347     }
348     return(result);
349 }
350
351 /*
352  *  error_count() - return the number of errors currently logged.
353  */
354
355 int
356 error_count()
357
358 {
359     return(errcnt);
360 }
361
362 /*
363  *  show_errors() - display on stdout the current log of errors.
364  */
365
366 void
367 show_errors()
368
369 {
370     register int cnt = 0;
371     register struct errs *errp = errs;
372
373     printf("%d error%s:\n\n", errcnt, errcnt == 1 ? "" : "s");
374     while (cnt++ < errcnt)
375     {
376         printf("%5s: %s\n", errp->arg,
377             errp->errnum == 0 ? "Not a number" : errmsg(errp->errnum));
378         errp++;
379     }
380 }
381
382 /*
383  *  kill_procs(str) - send signals to processes, much like the "kill"
384  *              command does; invoked in response to 'k'.
385  */
386
387 char *kill_procs(str)
388
389 char *str;
390
391 {
392     register char *nptr;
393     int signum = SIGTERM;       /* default */
394     int procnum;
395     struct sigdesc *sigp;
396     int uid;
397
398     /* reset error array */
399     ERR_RESET;
400
401     /* remember our uid */
402     uid = getuid();
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(" kill: no processes specified");
413         }
414
415         if (isdigit(str[1]))
416         {
417             (void) scanint(str + 1, &signum);
418             if (signum <= 0 || signum >= NSIG)
419             {
420                 return(" invalid signal number");
421             }
422         }
423         else 
424         {
425             /* translate the name into a number */
426             for (sigp = sigdesc; sigp->name != NULL; sigp++)
427             {
428                 if (strcmp(sigp->name, str + 1) == 0)
429                 {
430                     signum = sigp->number;
431                     break;
432                 }
433             }
434
435             /* was it ever found */
436             if (sigp->name == NULL)
437             {
438                 return(" bad signal name");
439             }
440         }
441         /* put the new pointer in place */
442         str = nptr;
443     }
444
445     /* loop thru the string, killing processes */
446     do
447     {
448         if (scanint(str, &procnum) == -1)
449         {
450             ERROR(str, 0);
451         }
452         else
453         {
454             /* check process owner if we're not root */
455             if (uid && (uid != proc_owner(procnum)))
456             {
457                 ERROR(str, EACCES);
458             }
459             /* go in for the kill */
460             else if (kill(procnum, signum) == -1)
461             {
462                 /* chalk up an error */
463                 ERROR(str, errno);
464             }
465         }
466     } while ((str = next_field(str)) != NULL);
467
468     /* return appropriate error string */
469     return(err_string());
470 }
471
472 /*
473  *  renice_procs(str) - change the "nice" of processes, much like the
474  *              "renice" command does; invoked in response to 'r'.
475  */
476
477 char *renice_procs(str)
478
479 char *str;
480
481 {
482     register char negate;
483     int prio;
484     int procnum;
485     int uid;
486
487     ERR_RESET;
488     uid = getuid();
489
490     /* allow for negative priority values */
491     if ((negate = (*str == '-')) != 0)
492     {
493         /* move past the minus sign */
494         str++;
495     }
496
497     /* use procnum as a temporary holding place and get the number */
498     procnum = scanint(str, &prio);
499
500     /* negate if necessary */
501     if (negate)
502     {
503         prio = -prio;
504     }
505
506 #if defined(PRIO_MIN) && defined(PRIO_MAX)
507     /* check for validity */
508     if (procnum == -1 || prio < PRIO_MIN || prio > PRIO_MAX)
509     {
510         return(" bad priority value");
511     }
512 #endif
513
514     /* move to the first process number */
515     if ((str = next_field(str)) == NULL)
516     {
517         return(" no processes specified");
518     }
519
520     /* loop thru the process numbers, renicing each one */
521     do
522     {
523         if (scanint(str, &procnum) == -1)
524         {
525             ERROR(str, 0);
526         }
527
528         /* check process owner if we're not root */
529         else if (uid && (uid != proc_owner(procnum)))
530         {
531             ERROR(str, EACCES);
532         }
533         else if (setpriority(PRIO_PROCESS, procnum, prio) == -1)
534         {
535             ERROR(str, errno);
536         }
537     } while ((str = next_field(str)) != NULL);
538
539     /* return appropriate error string */
540     return(err_string());
541 }
542