xref: /openbsd-src/usr.bin/top/commands.c (revision db3296cf5c1dd9058ceecc3a29fe4aaa0bd26000)
1 /* $OpenBSD: commands.c,v 1.12 2003/06/20 16:53:15 deraadt 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 	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"
77 	    "q       - quit\n"
78 	    "h or ?  - help; show this text\n", stdout);
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"
90 		    "i       - toggle the displaying of idle processes\n"
91 		    "I       - same as 'i'\n"
92 		    "k       - kill processes; send a signal to a list of processes\n"
93 		    "n or #  - change number of processes to display\n", stdout);
94 #ifdef ORDER
95 		fputs(
96 		    "o       - specify sort order (size, res, cpu, time)\n",
97 		    stdout);
98 #endif
99 		fputs(
100 		    "r       - renice a process\n"
101 		    "s       - change number of seconds to delay between updates\n"
102 		    "u       - display processes for only one user (+ selects all users)\n"
103 		    "\n\n", stdout);
104 	}
105 }
106 
107 /*
108  *  Utility routines that help with some of the commands.
109  */
110 static char *
111 next_field(char *str)
112 {
113 	if ((str = strchr(str, ' ')) == NULL)
114 		return (NULL);
115 
116 	*str = '\0';
117 	while (*++str == ' ')	/* loop */
118 		;
119 
120 	/* if there is nothing left of the string, return NULL */
121 	/* This fix is dedicated to Greg Earle */
122 	return (*str == '\0' ? NULL : str);
123 }
124 
125 static int
126 scanint(char *str, int *intp)
127 {
128 	int val = 0;
129 	char ch;
130 
131 	/* if there is nothing left of the string, flag it as an error */
132 	/* This fix is dedicated to Greg Earle */
133 	if (*str == '\0')
134 		return (-1);
135 
136 	while ((ch = *str++) != '\0') {
137 		if (isdigit(ch))
138 			val = val * 10 + (ch - '0');
139 		else if (isspace(ch))
140 			break;
141 		else
142 			return (-1);
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 	int             errno;	/* value of errno (that is, the actual error) */
161 	char           *arg;	/* argument that caused the error */
162 };
163 
164 static struct errs errs[ERRMAX];
165 static int      errcnt;
166 static char    *err_toomany = " too many errors occurred";
167 static char    *err_listem =
168 	" Many errors occurred.  Press `e' to display the list of errors.";
169 
170 /* These macros get used to reset and log the errors */
171 #define ERR_RESET   errcnt = 0
172 #define ERROR(p, e) \
173 	if (errcnt >= ERRMAX) { \
174 		return(err_toomany); \
175 	} else { \
176 		errs[errcnt].arg = (p); \
177 		errs[errcnt++].errno = (e); \
178 	}
179 
180 #define STRMAX 80
181 
182 /*
183  *  err_string() - return an appropriate error string.  This is what the
184  *	command will return for displaying.  If no errors were logged, then
185  *	return NULL.  The maximum length of the error string is defined by
186  *	"STRMAX".
187  */
188 static char *
189 err_string(void)
190 {
191 	int cnt = 0, first = Yes, currerr = -1;
192 	static char string[STRMAX];
193 	struct errs *errp;
194 
195 	/* if there are no errors, return NULL */
196 	if (errcnt == 0)
197 		return (NULL);
198 
199 	/* sort the errors */
200 	qsort(errs, errcnt, sizeof(struct errs), err_compar);
201 
202 	/* need a space at the front of the error string */
203 	string[0] = ' ';
204 	string[1] = '\0';
205 
206 	/* loop thru the sorted list, building an error string */
207 	while (cnt < errcnt) {
208 		errp = &(errs[cnt++]);
209 		if (errp->errno != currerr) {
210 			if (currerr != -1) {
211 				if (str_adderr(string, sizeof string, currerr) >
212 				    sizeof string - 2)
213 					return (err_listem);
214 
215 				/* we know there's more */
216 				(void) strlcat(string, "; ", sizeof string);
217 			}
218 			currerr = errp->errno;
219 			first = Yes;
220 		}
221 		if (str_addarg(string, sizeof string, errp->arg, first) >=
222 		    sizeof string)
223 			return (err_listem);
224 
225 		first = No;
226 	}
227 
228 	/* add final message */
229 	if (str_adderr(string, sizeof string, currerr) >= sizeof string)
230 		return (err_listem);
231 
232 	/* return the error string */
233 	return (string);
234 }
235 
236 /*
237  *  str_adderr(str, len, err) - add an explanation of error "err" to
238  *	the string "str".
239  */
240 static size_t
241 str_adderr(char *str, size_t len, int err)
242 {
243 	size_t msglen;
244 	char *msg;
245 
246 	msg = err == 0 ? "Not a number" : strerror(err);
247 
248 	if ((msglen = strlcat(str, ": ", len)) >= len)
249 		return (msglen);
250 
251 	return (strlcat(str, msg, len));
252 }
253 
254 /*
255  *  str_addarg(str, len, arg, first) - add the string argument "arg" to
256  *	the string "str".  This is the first in the group when "first"
257  *	is set (indicating that a comma should NOT be added to the front).
258  */
259 static size_t
260 str_addarg(char *str, size_t len, char *arg, int first)
261 {
262 	size_t msglen;
263 
264 	if (!first) {
265 		if ((msglen = strlcat(str, ", ", len)) >= len)
266 			return (msglen);
267 	}
268 	return (strlcat(str, arg, len));
269 }
270 
271 /*
272  *  err_compar(p1, p2) - comparison routine used by "qsort"
273  *	for sorting errors.
274  */
275 static int
276 err_compar(const void *e1, const void *e2)
277 {
278 	const struct errs *p1 = (struct errs *) e1;
279 	const struct errs *p2 = (struct errs *) e2;
280 	int result;
281 
282 	if ((result = p1->errno - p2->errno) == 0)
283 		return (strcmp(p1->arg, p2->arg));
284 	return (result);
285 }
286 
287 /*
288  *  error_count() - return the number of errors currently logged.
289  */
290 int
291 error_count(void)
292 {
293 	return (errcnt);
294 }
295 
296 /*
297  *  show_errors() - display on stdout the current log of errors.
298  */
299 void
300 show_errors(void)
301 {
302 	struct errs *errp = errs;
303 	int cnt = 0;
304 
305 	printf("%d error%s:\n\n", errcnt, errcnt == 1 ? "" : "s");
306 	while (cnt++ < errcnt) {
307 		printf("%5s: %s\n", errp->arg,
308 		    errp->errno == 0 ? "Not a number" : strerror(errp->errno));
309 		errp++;
310 	}
311 }
312 
313 /*
314  *  kill_procs(str) - send signals to processes, much like the "kill"
315  *		command does; invoked in response to 'k'.
316  */
317 char *
318 kill_procs(char *str)
319 {
320 	int signum = SIGTERM, procnum;
321 	struct sigdesc *sigp;
322 	uid_t uid, puid;
323 	char *nptr;
324 
325 	/* reset error array */
326 	ERR_RESET;
327 
328 	/* remember our uid */
329 	uid = getuid();
330 
331 	/* skip over leading white space */
332 	while (isspace(*str))
333 		str++;
334 
335 	if (str[0] == '-') {
336 		/* explicit signal specified */
337 		if ((nptr = next_field(str)) == NULL)
338 			return (" kill: no processes specified");
339 
340 		if (isdigit(str[1])) {
341 			(void) scanint(str + 1, &signum);
342 			if (signum <= 0 || signum >= NSIG)
343 				return (" invalid signal number");
344 		} else {
345 			/* translate the name into a number */
346 			for (sigp = sigdesc; sigp->name != NULL; sigp++) {
347 				if (strcmp(sigp->name, str + 1) == 0) {
348 					signum = sigp->number;
349 					break;
350 				}
351 			}
352 
353 			/* was it ever found */
354 			if (sigp->name == NULL)
355 				return (" bad signal name");
356 		}
357 		/* put the new pointer in place */
358 		str = nptr;
359 	}
360 	/* loop thru the string, killing processes */
361 	do {
362 		if (scanint(str, &procnum) == -1) {
363 			ERROR(str, 0);
364 		} else {
365 			/* check process owner if we're not root */
366 			puid = proc_owner(procnum);
367 			if (puid == (uid_t)(-1)) {
368 				ERROR(str, ESRCH);
369 			} else if (uid && (uid != puid)) {
370 				ERROR(str, EACCES);
371 			} else if (kill(procnum, signum) == -1) {
372 				ERROR(str, errno);
373 			}
374 		}
375 	} while ((str = next_field(str)) != NULL);
376 
377 	/* return appropriate error string */
378 	return (err_string());
379 }
380 
381 /*
382  *  renice_procs(str) - change the "nice" of processes, much like the
383  *		"renice" command does; invoked in response to 'r'.
384  */
385 char *
386 renice_procs(char *str)
387 {
388 	uid_t uid;
389 	char negate;
390 	int prio, procnum;
391 
392 	ERR_RESET;
393 	uid = getuid();
394 
395 	/* allow for negative priority values */
396 	if ((negate = (*str == '-')) != 0) {
397 		/* move past the minus sign */
398 		str++;
399 	}
400 	/* use procnum as a temporary holding place and get the number */
401 	procnum = scanint(str, &prio);
402 
403 	/* negate if necessary */
404 	if (negate)
405 		prio = -prio;
406 
407 #if defined(PRIO_MIN) && defined(PRIO_MAX)
408 	/* check for validity */
409 	if (procnum == -1 || prio < PRIO_MIN || prio > PRIO_MAX)
410 		return (" bad priority value");
411 #endif
412 
413 	/* move to the first process number */
414 	if ((str = next_field(str)) == NULL)
415 		return (" no processes specified");
416 
417 	/* loop thru the process numbers, renicing each one */
418 	do {
419 		if (scanint(str, &procnum) == -1) {
420 			ERROR(str, 0);
421 		}
422 		/* check process owner if we're not root */
423 		else if (uid && (uid != proc_owner(procnum))) {
424 			ERROR(str, EACCES);
425 		} else if (setpriority(PRIO_PROCESS, procnum, prio) == -1) {
426 			ERROR(str, errno);
427 		}
428 	} while ((str = next_field(str)) != NULL);
429 
430 	/* return appropriate error string */
431 	return (err_string());
432 }
433