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