xref: /openbsd-src/usr.bin/top/commands.c (revision 799f675f6700f14e59124f9825c723e9f2ce19dc)
1 /* $OpenBSD: commands.c,v 1.21 2007/01/05 09:46:12 otto Exp $	 */
2 
3 /*
4  *  Top users/processes display for Unix
5  *  Version 3
6  *
7  * Copyright (c) 1984, 1989, William LeFebvre, Rice University
8  * Copyright (c) 1989, 1990, 1992, William LeFebvre, Northwestern University
9  *
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.
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 
38 #include <sys/types.h>
39 #include <stdio.h>
40 #include <ctype.h>
41 #include <errno.h>
42 #include <stdlib.h>
43 #include <string.h>
44 #include <signal.h>
45 #include <unistd.h>
46 #include <sys/time.h>
47 #include <sys/resource.h>
48 
49 #include "top.h"
50 
51 #include "sigdesc.h"		/* generated automatically */
52 #include "boolean.h"
53 #include "utils.h"
54 #include "machine.h"
55 
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 *);
62 
63 /*
64  *  show_help() - display the help screen; invoked in response to
65  *		either 'h' or '?'.
66  */
67 void
68 show_help(void)
69 {
70 	printf("Top version %s, %s\n", version_string(), copyright);
71 	puts("\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"
77 	    "h or ?  - help; show this text\n"
78 	    "q       - quit");
79 
80 	/* not all commands are available with overstrike terminals */
81 	if (overstrike) {
82 		puts("\n"
83 		    "Other commands are also available, but this terminal is not\n"
84 		    "sophisticated enough to handle those commands gracefully.\n");
85 	} else {
86 		puts(
87 		    "C       - toggle the display of the command line arguments\n"
88 		    "d       - change number of displays to show\n"
89 		    "e       - list errors generated by last \"kill\" or \"renice\" command\n"
90 		    "g       - filter on command name (+ selects all commands)\n"
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"
95 		    "o       - specify sort order (size, res, cpu, time, pri)\n"
96 		    "p       - display one process (+ selects all processes)\n"
97 		    "r       - renice a process\n"
98 		    "s       - change number of seconds to delay between updates\n"
99 		    "S       - toggle the display of system processes\n"
100 		    "T       - toggle the display of threads\n"
101 		    "u       - display processes for only one user (+ selects all users)\n"
102 		    "\n");
103 	}
104 }
105 
106 /*
107  *  Utility routines that help with some of the commands.
108  */
109 static char *
110 next_field(char *str)
111 {
112 	if ((str = strchr(str, ' ')) == NULL)
113 		return (NULL);
114 
115 	*str = '\0';
116 	while (*++str == ' ')	/* loop */
117 		;
118 
119 	/* if there is nothing left of the string, return NULL */
120 	/* This fix is dedicated to Greg Earle */
121 	return (*str == '\0' ? NULL : str);
122 }
123 
124 static int
125 scanint(char *str, int *intp)
126 {
127 	int val = 0;
128 	char ch;
129 
130 	/* if there is nothing left of the string, flag it as an error */
131 	/* This fix is dedicated to Greg Earle */
132 	if (*str == '\0')
133 		return (-1);
134 
135 	while ((ch = *str++) != '\0') {
136 		if (isdigit(ch))
137 			val = val * 10 + (ch - '0');
138 		else if (isspace(ch))
139 			break;
140 		else
141 			return (-1);
142 	}
143 	*intp = val;
144 	return (0);
145 }
146 
147 /*
148  *  Some of the commands make system calls that could generate errors.
149  *  These errors are collected up in an array of structures for later
150  *  contemplation and display.  Such routines return a string containing an
151  *  error message, or NULL if no errors occurred.  The next few routines are
152  *  for manipulating and displaying these errors.  We need an upper limit on
153  *  the number of errors, so we arbitrarily choose 20.
154  */
155 
156 #define ERRMAX 20
157 
158 struct errs {			/* structure for a system-call error */
159 	int             err;	/* value of errno (that is, the actual error) */
160 	char           *arg;	/* argument that caused the error */
161 };
162 
163 static struct errs errs[ERRMAX];
164 static int      errcnt;
165 static char    *err_toomany = " too many errors occurred";
166 static char    *err_listem =
167 	" Many errors occurred.  Press `e' to display the list of errors.";
168 
169 /* These macros get used to reset and log the errors */
170 #define ERR_RESET   errcnt = 0
171 #define ERROR(p, e) \
172 	if (errcnt >= ERRMAX) { \
173 		return(err_toomany); \
174 	} else { \
175 		errs[errcnt].arg = (p); \
176 		errs[errcnt++].err = (e); \
177 	}
178 
179 #define STRMAX 80
180 
181 /*
182  *  err_string() - return an appropriate error string.  This is what the
183  *	command will return for displaying.  If no errors were logged, then
184  *	return NULL.  The maximum length of the error string is defined by
185  *	"STRMAX".
186  */
187 static char *
188 err_string(void)
189 {
190 	int cnt = 0, first = Yes, currerr = -1;
191 	static char string[STRMAX];
192 	struct errs *errp;
193 
194 	/* if there are no errors, return NULL */
195 	if (errcnt == 0)
196 		return (NULL);
197 
198 	/* sort the errors */
199 	qsort(errs, errcnt, sizeof(struct errs), err_compar);
200 
201 	/* need a space at the front of the error string */
202 	string[0] = ' ';
203 	string[1] = '\0';
204 
205 	/* loop thru the sorted list, building an error string */
206 	while (cnt < errcnt) {
207 		errp = &(errs[cnt++]);
208 		if (errp->err != currerr) {
209 			if (currerr != -1) {
210 				if (str_adderr(string, sizeof string, currerr) >
211 				    sizeof string - 2)
212 					return (err_listem);
213 
214 				/* we know there's more */
215 				(void) strlcat(string, "; ", sizeof string);
216 			}
217 			currerr = errp->err;
218 			first = Yes;
219 		}
220 		if (str_addarg(string, sizeof string, errp->arg, first) >=
221 		    sizeof string)
222 			return (err_listem);
223 
224 		first = No;
225 	}
226 
227 	/* add final message */
228 	if (str_adderr(string, sizeof string, currerr) >= sizeof string)
229 		return (err_listem);
230 
231 	/* return the error string */
232 	return (string);
233 }
234 
235 /*
236  *  str_adderr(str, len, err) - add an explanation of error "err" to
237  *	the string "str".
238  */
239 static size_t
240 str_adderr(char *str, size_t len, int err)
241 {
242 	size_t msglen;
243 	char *msg;
244 
245 	msg = err == 0 ? "Not a number" : strerror(err);
246 
247 	if ((msglen = strlcat(str, ": ", len)) >= len)
248 		return (msglen);
249 
250 	return (strlcat(str, msg, len));
251 }
252 
253 /*
254  *  str_addarg(str, len, arg, first) - add the string argument "arg" to
255  *	the string "str".  This is the first in the group when "first"
256  *	is set (indicating that a comma should NOT be added to the front).
257  */
258 static size_t
259 str_addarg(char *str, size_t len, char *arg, int first)
260 {
261 	size_t msglen;
262 
263 	if (!first) {
264 		if ((msglen = strlcat(str, ", ", len)) >= len)
265 			return (msglen);
266 	}
267 	return (strlcat(str, arg, len));
268 }
269 
270 /*
271  *  err_compar(p1, p2) - comparison routine used by "qsort"
272  *	for sorting errors.
273  */
274 static int
275 err_compar(const void *e1, const void *e2)
276 {
277 	const struct errs *p1 = (struct errs *) e1;
278 	const struct errs *p2 = (struct errs *) e2;
279 	int result;
280 
281 	if ((result = p1->err - p2->err) == 0)
282 		return (strcmp(p1->arg, p2->arg));
283 	return (result);
284 }
285 
286 /*
287  *  error_count() - return the number of errors currently logged.
288  */
289 int
290 error_count(void)
291 {
292 	return (errcnt);
293 }
294 
295 /*
296  *  show_errors() - display on stdout the current log of errors.
297  */
298 void
299 show_errors(void)
300 {
301 	struct errs *errp = errs;
302 	int cnt = 0;
303 
304 	printf("%d error%s:\n\n", errcnt, errcnt == 1 ? "" : "s");
305 	while (cnt++ < errcnt) {
306 		printf("%5s: %s\n", errp->arg,
307 		    errp->err == 0 ? "Not a number" : strerror(errp->err));
308 		errp++;
309 	}
310 }
311 
312 /*
313  *  kill_procs(str) - send signals to processes, much like the "kill"
314  *		command does; invoked in response to 'k'.
315  */
316 char *
317 kill_procs(char *str)
318 {
319 	int signum = SIGTERM, procnum;
320 	struct sigdesc *sigp;
321 	uid_t uid, puid;
322 	char *nptr;
323 
324 	/* reset error array */
325 	ERR_RESET;
326 
327 	/* remember our uid */
328 	uid = getuid();
329 
330 	/* skip over leading white space */
331 	while (isspace(*str))
332 		str++;
333 
334 	if (str[0] == '-') {
335 		/* explicit signal specified */
336 		if ((nptr = next_field(str)) == NULL)
337 			return (" kill: no processes specified");
338 
339 		if (isdigit(str[1])) {
340 			(void) scanint(str + 1, &signum);
341 			if (signum <= 0 || signum >= NSIG)
342 				return (" invalid signal number");
343 		} else {
344 			/* translate the name into a number */
345 			for (sigp = sigdesc; sigp->name != NULL; sigp++) {
346 				if (strcmp(sigp->name, str + 1) == 0) {
347 					signum = sigp->number;
348 					break;
349 				}
350 			}
351 
352 			/* was it ever found */
353 			if (sigp->name == NULL)
354 				return (" bad signal name");
355 		}
356 		/* put the new pointer in place */
357 		str = nptr;
358 	}
359 	/* loop thru the string, killing processes */
360 	do {
361 		if (scanint(str, &procnum) == -1) {
362 			ERROR(str, 0);
363 		} else {
364 			/* check process owner if we're not root */
365 			puid = proc_owner(procnum);
366 			if (puid == (uid_t)(-1)) {
367 				ERROR(str, ESRCH);
368 			} else if (uid && (uid != puid)) {
369 				ERROR(str, EACCES);
370 			} else if (kill(procnum, signum) == -1) {
371 				ERROR(str, errno);
372 			}
373 		}
374 	} while ((str = next_field(str)) != NULL);
375 
376 	/* return appropriate error string */
377 	return (err_string());
378 }
379 
380 /*
381  *  renice_procs(str) - change the "nice" of processes, much like the
382  *		"renice" command does; invoked in response to 'r'.
383  */
384 char *
385 renice_procs(char *str)
386 {
387 	uid_t uid;
388 	char negate;
389 	int prio, procnum;
390 
391 	ERR_RESET;
392 	uid = getuid();
393 
394 	/* allow for negative priority values */
395 	if ((negate = (*str == '-')) != 0) {
396 		/* move past the minus sign */
397 		str++;
398 	}
399 	/* use procnum as a temporary holding place and get the number */
400 	procnum = scanint(str, &prio);
401 
402 	/* negate if necessary */
403 	if (negate)
404 		prio = -prio;
405 
406 #if defined(PRIO_MIN) && defined(PRIO_MAX)
407 	/* check for validity */
408 	if (procnum == -1 || prio < PRIO_MIN || prio > PRIO_MAX)
409 		return (" bad priority value");
410 #endif
411 
412 	/* move to the first process number */
413 	if ((str = next_field(str)) == NULL)
414 		return (" no processes specified");
415 
416 	/* loop thru the process numbers, renicing each one */
417 	do {
418 		if (scanint(str, &procnum) == -1) {
419 			ERROR(str, 0);
420 		}
421 		/* check process owner if we're not root */
422 		else if (uid && (uid != proc_owner(procnum))) {
423 			ERROR(str, EACCES);
424 		} else if (setpriority(PRIO_PROCESS, procnum, prio) == -1) {
425 			ERROR(str, errno);
426 		}
427 	} while ((str = next_field(str)) != NULL);
428 
429 	/* return appropriate error string */
430 	return (err_string());
431 }
432