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