Annotation of src/usr.bin/top/commands.c, Revision 1.20
1.20 ! otto 1: /* $OpenBSD: commands.c,v 1.19 2005/12/04 23:10:06 tedu Exp $ */
1.1 downsj 2:
3: /*
4: * Top users/processes display for Unix
5: * Version 3
6: *
1.6 deraadt 7: * Copyright (c) 1984, 1989, William LeFebvre, Rice University
8: * Copyright (c) 1989, 1990, 1992, William LeFebvre, Northwestern University
1.1 downsj 9: *
1.6 deraadt 10: * Redistribution and use in source and binary forms, with or without
11: * modification, are permitted provided that the following conditions
12: * are met:
13: * 1. Redistributions of source code must retain the above copyright
14: * notice, this list of conditions and the following disclaimer.
15: * 2. Redistributions in binary form must reproduce the above copyright
16: * notice, this list of conditions and the following disclaimer in the
17: * documentation and/or other materials provided with the distribution.
18: *
19: * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
20: * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22: * IN NO EVENT SHALL THE AUTHOR OR HIS EMPLOYER BE LIABLE FOR ANY DIRECT, INDIRECT,
23: * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
24: * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26: * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28: * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
1.1 downsj 29: */
30:
31: /*
32: * This file contains the routines that implement some of the interactive
33: * mode commands. Note that some of the commands are implemented in-line
34: * in "main". This is necessary because they change the global state of
35: * "top" (i.e.: changing the number of processes to display).
36: */
37:
1.2 downsj 38: #include <sys/types.h>
39: #include <stdio.h>
1.1 downsj 40: #include <ctype.h>
1.2 downsj 41: #include <errno.h>
42: #include <stdlib.h>
43: #include <string.h>
1.1 downsj 44: #include <signal.h>
1.2 downsj 45: #include <unistd.h>
1.1 downsj 46: #include <sys/time.h>
47: #include <sys/resource.h>
48:
1.2 downsj 49: #include "top.h"
50:
1.1 downsj 51: #include "sigdesc.h" /* generated automatically */
52: #include "boolean.h"
53: #include "utils.h"
1.2 downsj 54: #include "machine.h"
1.1 downsj 55:
1.9 deraadt 56: static char *next_field(char *);
57: static int scanint(char *, int *);
58: static char *err_string(void);
59: static size_t str_adderr(char *, size_t, int);
60: static size_t str_addarg(char *, size_t, char *, int);
61: static int err_compar(const void *, const void *);
1.1 downsj 62:
63: /*
64: * show_help() - display the help screen; invoked in response to
65: * either 'h' or '?'.
66: */
1.2 downsj 67: void
1.8 pvalchev 68: show_help(void)
1.1 downsj 69: {
1.9 deraadt 70: printf("Top version %s, %s\n", version_string(), copyright);
71: fputs("\n\n"
72: "A top users display for Unix\n"
73: "\n"
74: "These single-character commands are available:\n"
75: "\n"
76: "^L - redraw screen\n"
1.13 jmc 77: "h or ? - help; show this text\n"
78: "q - quit\n", stdout);
1.9 deraadt 79:
80: /* not all commands are available with overstrike terminals */
81: if (overstrike) {
82: fputs("\n"
83: "Other commands are also available, but this terminal is not\n"
84: "sophisticated enough to handle those commands gracefully.\n\n",
85: stdout);
86: } else {
87: fputs(
88: "d - change number of displays to show\n"
89: "e - list errors generated by last \"kill\" or \"renice\" command\n"
1.20 ! otto 90: "g - filter on command name (+ selects all commands)\n"
1.9 deraadt 91: "i - toggle the displaying of idle processes\n"
92: "I - same as 'i'\n"
93: "k - kill processes; send a signal to a list of processes\n"
94: "n or # - change number of processes to display\n", stdout);
95: fputs(
1.18 hshoexer 96: "o - specify sort order (size, res, cpu, time, pri)\n"
1.16 otto 97: "p - display one process (+ selects all processes)\n",
1.9 deraadt 98: stdout);
99: fputs(
100: "r - renice a process\n"
101: "s - change number of seconds to delay between updates\n"
1.13 jmc 102: "S - toggle the display of system processes\n"
1.9 deraadt 103: "u - display processes for only one user (+ selects all users)\n"
1.19 tedu 104: "T - toggle the display of threads\n"
1.17 markus 105: "C - toggle the display of the command line arguments\n"
1.9 deraadt 106: "\n\n", stdout);
107: }
1.1 downsj 108: }
109:
110: /*
111: * Utility routines that help with some of the commands.
112: */
1.8 pvalchev 113: static char *
114: next_field(char *str)
1.1 downsj 115: {
1.9 deraadt 116: if ((str = strchr(str, ' ')) == NULL)
117: return (NULL);
118:
119: *str = '\0';
120: while (*++str == ' ') /* loop */
121: ;
122:
123: /* if there is nothing left of the string, return NULL */
124: /* This fix is dedicated to Greg Earle */
125: return (*str == '\0' ? NULL : str);
1.1 downsj 126: }
127:
1.14 deraadt 128: static int
1.9 deraadt 129: scanint(char *str, int *intp)
1.1 downsj 130: {
1.9 deraadt 131: int val = 0;
132: char ch;
1.1 downsj 133:
1.9 deraadt 134: /* if there is nothing left of the string, flag it as an error */
135: /* This fix is dedicated to Greg Earle */
136: if (*str == '\0')
137: return (-1);
138:
139: while ((ch = *str++) != '\0') {
140: if (isdigit(ch))
141: val = val * 10 + (ch - '0');
142: else if (isspace(ch))
143: break;
144: else
145: return (-1);
1.1 downsj 146: }
1.9 deraadt 147: *intp = val;
148: return (0);
1.1 downsj 149: }
150:
151: /*
152: * Some of the commands make system calls that could generate errors.
153: * These errors are collected up in an array of structures for later
154: * contemplation and display. Such routines return a string containing an
155: * error message, or NULL if no errors occurred. The next few routines are
156: * for manipulating and displaying these errors. We need an upper limit on
157: * the number of errors, so we arbitrarily choose 20.
158: */
159:
160: #define ERRMAX 20
161:
1.9 deraadt 162: struct errs { /* structure for a system-call error */
1.15 marc 163: int err; /* value of errno (that is, the actual error) */
1.9 deraadt 164: char *arg; /* argument that caused the error */
1.1 downsj 165: };
166:
167: static struct errs errs[ERRMAX];
1.9 deraadt 168: static int errcnt;
169: static char *err_toomany = " too many errors occurred";
170: static char *err_listem =
1.1 downsj 171: " Many errors occurred. Press `e' to display the list of errors.";
172:
173: /* These macros get used to reset and log the errors */
174: #define ERR_RESET errcnt = 0
1.9 deraadt 175: #define ERROR(p, e) \
176: if (errcnt >= ERRMAX) { \
177: return(err_toomany); \
178: } else { \
179: errs[errcnt].arg = (p); \
1.15 marc 180: errs[errcnt++].err = (e); \
1.9 deraadt 181: }
182:
183: #define STRMAX 80
1.1 downsj 184:
185: /*
186: * err_string() - return an appropriate error string. This is what the
187: * command will return for displaying. If no errors were logged, then
188: * return NULL. The maximum length of the error string is defined by
189: * "STRMAX".
190: */
1.8 pvalchev 191: static char *
192: err_string(void)
1.1 downsj 193: {
1.9 deraadt 194: int cnt = 0, first = Yes, currerr = -1;
195: static char string[STRMAX];
196: struct errs *errp;
197:
198: /* if there are no errors, return NULL */
199: if (errcnt == 0)
200: return (NULL);
201:
202: /* sort the errors */
1.12 deraadt 203: qsort(errs, errcnt, sizeof(struct errs), err_compar);
1.9 deraadt 204:
205: /* need a space at the front of the error string */
206: string[0] = ' ';
207: string[1] = '\0';
208:
209: /* loop thru the sorted list, building an error string */
210: while (cnt < errcnt) {
211: errp = &(errs[cnt++]);
1.15 marc 212: if (errp->err != currerr) {
1.9 deraadt 213: if (currerr != -1) {
214: if (str_adderr(string, sizeof string, currerr) >
215: sizeof string - 2)
216: return (err_listem);
217:
218: /* we know there's more */
219: (void) strlcat(string, "; ", sizeof string);
220: }
1.15 marc 221: currerr = errp->err;
1.9 deraadt 222: first = Yes;
1.1 downsj 223: }
1.9 deraadt 224: if (str_addarg(string, sizeof string, errp->arg, first) >=
225: sizeof string)
226: return (err_listem);
227:
228: first = No;
1.1 downsj 229: }
230:
1.9 deraadt 231: /* add final message */
232: if (str_adderr(string, sizeof string, currerr) >= sizeof string)
233: return (err_listem);
1.1 downsj 234:
1.9 deraadt 235: /* return the error string */
236: return (string);
1.1 downsj 237: }
238:
239: /*
240: * str_adderr(str, len, err) - add an explanation of error "err" to
241: * the string "str".
242: */
1.8 pvalchev 243: static size_t
244: str_adderr(char *str, size_t len, int err)
1.1 downsj 245: {
1.9 deraadt 246: size_t msglen;
247: char *msg;
1.1 downsj 248:
1.9 deraadt 249: msg = err == 0 ? "Not a number" : strerror(err);
1.8 pvalchev 250:
1.9 deraadt 251: if ((msglen = strlcat(str, ": ", len)) >= len)
252: return (msglen);
1.8 pvalchev 253:
1.9 deraadt 254: return (strlcat(str, msg, len));
1.1 downsj 255: }
256:
257: /*
258: * str_addarg(str, len, arg, first) - add the string argument "arg" to
259: * the string "str". This is the first in the group when "first"
260: * is set (indicating that a comma should NOT be added to the front).
261: */
1.8 pvalchev 262: static size_t
263: str_addarg(char *str, size_t len, char *arg, int first)
1.1 downsj 264: {
1.9 deraadt 265: size_t msglen;
1.1 downsj 266:
1.9 deraadt 267: if (!first) {
268: if ((msglen = strlcat(str, ", ", len)) >= len)
269: return (msglen);
270: }
271: return (strlcat(str, arg, len));
1.1 downsj 272: }
273:
274: /*
275: * err_compar(p1, p2) - comparison routine used by "qsort"
276: * for sorting errors.
277: */
1.8 pvalchev 278: static int
279: err_compar(const void *e1, const void *e2)
1.1 downsj 280: {
1.9 deraadt 281: const struct errs *p1 = (struct errs *) e1;
282: const struct errs *p2 = (struct errs *) e2;
283: int result;
284:
1.15 marc 285: if ((result = p1->err - p2->err) == 0)
1.9 deraadt 286: return (strcmp(p1->arg, p2->arg));
287: return (result);
1.1 downsj 288: }
289:
290: /*
291: * error_count() - return the number of errors currently logged.
292: */
1.8 pvalchev 293: int
294: error_count(void)
1.1 downsj 295: {
1.9 deraadt 296: return (errcnt);
1.1 downsj 297: }
298:
299: /*
300: * show_errors() - display on stdout the current log of errors.
301: */
1.8 pvalchev 302: void
303: show_errors(void)
1.1 downsj 304: {
1.9 deraadt 305: struct errs *errp = errs;
306: int cnt = 0;
1.1 downsj 307:
1.9 deraadt 308: printf("%d error%s:\n\n", errcnt, errcnt == 1 ? "" : "s");
309: while (cnt++ < errcnt) {
310: printf("%5s: %s\n", errp->arg,
1.15 marc 311: errp->err == 0 ? "Not a number" : strerror(errp->err));
1.9 deraadt 312: errp++;
313: }
1.1 downsj 314: }
315:
316: /*
317: * kill_procs(str) - send signals to processes, much like the "kill"
318: * command does; invoked in response to 'k'.
319: */
1.8 pvalchev 320: char *
321: kill_procs(char *str)
1.1 downsj 322: {
1.10 millert 323: int signum = SIGTERM, procnum;
1.9 deraadt 324: struct sigdesc *sigp;
1.11 jfb 325: uid_t uid, puid;
1.9 deraadt 326: char *nptr;
327:
328: /* reset error array */
329: ERR_RESET;
330:
331: /* remember our uid */
332: uid = getuid();
333:
334: /* skip over leading white space */
335: while (isspace(*str))
336: str++;
337:
338: if (str[0] == '-') {
339: /* explicit signal specified */
340: if ((nptr = next_field(str)) == NULL)
341: return (" kill: no processes specified");
342:
343: if (isdigit(str[1])) {
344: (void) scanint(str + 1, &signum);
345: if (signum <= 0 || signum >= NSIG)
346: return (" invalid signal number");
347: } else {
348: /* translate the name into a number */
349: for (sigp = sigdesc; sigp->name != NULL; sigp++) {
350: if (strcmp(sigp->name, str + 1) == 0) {
351: signum = sigp->number;
352: break;
353: }
354: }
355:
356: /* was it ever found */
357: if (sigp->name == NULL)
358: return (" bad signal name");
359: }
360: /* put the new pointer in place */
361: str = nptr;
1.1 downsj 362: }
1.9 deraadt 363: /* loop thru the string, killing processes */
364: do {
365: if (scanint(str, &procnum) == -1) {
366: ERROR(str, 0);
367: } else {
368: /* check process owner if we're not root */
1.11 jfb 369: puid = proc_owner(procnum);
370: if (puid == (uid_t)(-1)) {
371: ERROR(str, ESRCH);
372: } else if (uid && (uid != puid)) {
1.9 deraadt 373: ERROR(str, EACCES);
374: } else if (kill(procnum, signum) == -1) {
375: ERROR(str, errno);
376: }
1.1 downsj 377: }
1.9 deraadt 378: } while ((str = next_field(str)) != NULL);
1.1 downsj 379:
1.9 deraadt 380: /* return appropriate error string */
381: return (err_string());
1.1 downsj 382: }
383:
384: /*
385: * renice_procs(str) - change the "nice" of processes, much like the
386: * "renice" command does; invoked in response to 'r'.
387: */
1.8 pvalchev 388: char *
389: renice_procs(char *str)
1.1 downsj 390: {
1.10 millert 391: uid_t uid;
1.9 deraadt 392: char negate;
1.10 millert 393: int prio, procnum;
1.9 deraadt 394:
395: ERR_RESET;
396: uid = getuid();
397:
398: /* allow for negative priority values */
399: if ((negate = (*str == '-')) != 0) {
400: /* move past the minus sign */
401: str++;
402: }
403: /* use procnum as a temporary holding place and get the number */
404: procnum = scanint(str, &prio);
405:
406: /* negate if necessary */
407: if (negate)
408: prio = -prio;
1.1 downsj 409:
410: #if defined(PRIO_MIN) && defined(PRIO_MAX)
1.9 deraadt 411: /* check for validity */
412: if (procnum == -1 || prio < PRIO_MIN || prio > PRIO_MAX)
413: return (" bad priority value");
1.1 downsj 414: #endif
415:
1.9 deraadt 416: /* move to the first process number */
417: if ((str = next_field(str)) == NULL)
418: return (" no processes specified");
419:
420: /* loop thru the process numbers, renicing each one */
421: do {
422: if (scanint(str, &procnum) == -1) {
423: ERROR(str, 0);
424: }
425: /* check process owner if we're not root */
426: else if (uid && (uid != proc_owner(procnum))) {
427: ERROR(str, EACCES);
428: } else if (setpriority(PRIO_PROCESS, procnum, prio) == -1) {
429: ERROR(str, errno);
430: }
431: } while ((str = next_field(str)) != NULL);
1.1 downsj 432:
1.9 deraadt 433: /* return appropriate error string */
434: return (err_string());
1.1 downsj 435: }