xref: /openbsd-src/usr.bin/top/commands.c (revision a28daedfc357b214be5c701aa8ba8adb29a7f1c2)
1 /* $OpenBSD: commands.c,v 1.28 2007/05/29 00:56:56 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 "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  *  Utility routines that help with some of the commands.
64  */
65 static char *
66 next_field(char *str)
67 {
68 	if ((str = strchr(str, ' ')) == NULL)
69 		return (NULL);
70 
71 	*str = '\0';
72 	while (*++str == ' ')	/* loop */
73 		;
74 
75 	/* if there is nothing left of the string, return NULL */
76 	/* This fix is dedicated to Greg Earle */
77 	return (*str == '\0' ? NULL : str);
78 }
79 
80 static int
81 scanint(char *str, int *intp)
82 {
83 	int val = 0;
84 	char ch;
85 
86 	/* if there is nothing left of the string, flag it as an error */
87 	/* This fix is dedicated to Greg Earle */
88 	if (*str == '\0')
89 		return (-1);
90 
91 	while ((ch = *str++) != '\0') {
92 		if (isdigit(ch))
93 			val = val * 10 + (ch - '0');
94 		else if (isspace(ch))
95 			break;
96 		else
97 			return (-1);
98 	}
99 	*intp = val;
100 	return (0);
101 }
102 
103 /*
104  *  Some of the commands make system calls that could generate errors.
105  *  These errors are collected up in an array of structures for later
106  *  contemplation and display.  Such routines return a string containing an
107  *  error message, or NULL if no errors occurred.  The next few routines are
108  *  for manipulating and displaying these errors.  We need an upper limit on
109  *  the number of errors, so we arbitrarily choose 20.
110  */
111 
112 #define ERRMAX 20
113 
114 struct errs	errs[ERRMAX];
115 int		errcnt;
116 static char    *err_toomany = " too many errors occurred";
117 static char    *err_listem =
118 	" Many errors occurred.  Press `e' to display the list of errors.";
119 
120 /* These macros get used to reset and log the errors */
121 #define ERR_RESET   errcnt = 0
122 #define ERROR(p, e) \
123 	if (errcnt >= ERRMAX) { \
124 		return(err_toomany); \
125 	} else { \
126 		errs[errcnt].arg = (p); \
127 		errs[errcnt++].err = (e); \
128 	}
129 
130 #define STRMAX 80
131 
132 /*
133  *  err_string() - return an appropriate error string.  This is what the
134  *	command will return for displaying.  If no errors were logged, then
135  *	return NULL.  The maximum length of the error string is defined by
136  *	"STRMAX".
137  */
138 static char *
139 err_string(void)
140 {
141 	int cnt = 0, first = Yes, currerr = -1;
142 	static char string[STRMAX];
143 	struct errs *errp;
144 
145 	/* if there are no errors, return NULL */
146 	if (errcnt == 0)
147 		return (NULL);
148 
149 	/* sort the errors */
150 	qsort(errs, errcnt, sizeof(struct errs), err_compar);
151 
152 	/* need a space at the front of the error string */
153 	string[0] = ' ';
154 	string[1] = '\0';
155 
156 	/* loop thru the sorted list, building an error string */
157 	while (cnt < errcnt) {
158 		errp = &(errs[cnt++]);
159 		if (errp->err != currerr) {
160 			if (currerr != -1) {
161 				if (str_adderr(string, sizeof string, currerr) >
162 				    sizeof string - 2)
163 					return (err_listem);
164 
165 				/* we know there's more */
166 				(void) strlcat(string, "; ", sizeof string);
167 			}
168 			currerr = errp->err;
169 			first = Yes;
170 		}
171 		if (str_addarg(string, sizeof string, errp->arg, first) >=
172 		    sizeof string)
173 			return (err_listem);
174 
175 		first = No;
176 	}
177 
178 	/* add final message */
179 	if (str_adderr(string, sizeof string, currerr) >= sizeof string)
180 		return (err_listem);
181 
182 	/* return the error string */
183 	return (string);
184 }
185 
186 /*
187  *  str_adderr(str, len, err) - add an explanation of error "err" to
188  *	the string "str".
189  */
190 static size_t
191 str_adderr(char *str, size_t len, int err)
192 {
193 	size_t msglen;
194 	char *msg;
195 
196 	msg = err == 0 ? "Not a number" : strerror(err);
197 
198 	if ((msglen = strlcat(str, ": ", len)) >= len)
199 		return (msglen);
200 
201 	return (strlcat(str, msg, len));
202 }
203 
204 /*
205  *  str_addarg(str, len, arg, first) - add the string argument "arg" to
206  *	the string "str".  This is the first in the group when "first"
207  *	is set (indicating that a comma should NOT be added to the front).
208  */
209 static size_t
210 str_addarg(char *str, size_t len, char *arg, int first)
211 {
212 	size_t msglen;
213 
214 	if (!first) {
215 		if ((msglen = strlcat(str, ", ", len)) >= len)
216 			return (msglen);
217 	}
218 	return (strlcat(str, arg, len));
219 }
220 
221 /*
222  *  err_compar(p1, p2) - comparison routine used by "qsort"
223  *	for sorting errors.
224  */
225 static int
226 err_compar(const void *e1, const void *e2)
227 {
228 	const struct errs *p1 = (const struct errs *) e1;
229 	const struct errs *p2 = (const struct errs *) e2;
230 	int result;
231 
232 	if ((result = p1->err - p2->err) == 0)
233 		return (strcmp(p1->arg, p2->arg));
234 	return (result);
235 }
236 
237 /*
238  *  error_count() - return the number of errors currently logged.
239  */
240 int
241 error_count(void)
242 {
243 	return (errcnt);
244 }
245 
246 /*
247  *  kill_procs(str) - send signals to processes, much like the "kill"
248  *		command does; invoked in response to 'k'.
249  */
250 char *
251 kill_procs(char *str)
252 {
253 	int signum = SIGTERM, procnum;
254 	uid_t uid, puid;
255 	char *nptr;
256 
257 	/* reset error array */
258 	ERR_RESET;
259 
260 	/* remember our uid */
261 	uid = getuid();
262 
263 	/* skip over leading white space */
264 	while (isspace(*str))
265 		str++;
266 
267 	if (str[0] == '-') {
268 		/* explicit signal specified */
269 		if ((nptr = next_field(str)) == NULL)
270 			return (" kill: no processes specified");
271 
272 		if (isdigit(str[1])) {
273 			(void) scanint(str + 1, &signum);
274 			if (signum <= 0 || signum >= NSIG)
275 				return (" invalid signal number");
276 		} else {
277 			/* translate the name into a number */
278 			for (signum = 0; signum < NSIG; signum++) {
279 				if (strcasecmp(sys_signame[signum], str + 1) == 0)
280 					break;
281 			}
282 
283 			/* was it ever found */
284 			if (signum == NSIG)
285 				return (" bad signal name");
286 		}
287 		/* put the new pointer in place */
288 		str = nptr;
289 	}
290 	/* loop thru the string, killing processes */
291 	do {
292 		if (scanint(str, &procnum) == -1) {
293 			ERROR(str, 0);
294 		} else {
295 			/* check process owner if we're not root */
296 			puid = proc_owner(procnum);
297 			if (puid == (uid_t)(-1)) {
298 				ERROR(str, ESRCH);
299 			} else if (uid && (uid != puid)) {
300 				ERROR(str, EACCES);
301 			} else if (kill(procnum, signum) == -1) {
302 				ERROR(str, errno);
303 			}
304 		}
305 	} while ((str = next_field(str)) != NULL);
306 
307 	/* return appropriate error string */
308 	return (err_string());
309 }
310 
311 /*
312  *  renice_procs(str) - change the "nice" of processes, much like the
313  *		"renice" command does; invoked in response to 'r'.
314  */
315 char *
316 renice_procs(char *str)
317 {
318 	uid_t uid;
319 	char negate;
320 	int prio, procnum;
321 
322 	ERR_RESET;
323 	uid = getuid();
324 
325 	/* allow for negative priority values */
326 	if ((negate = (*str == '-')) != 0) {
327 		/* move past the minus sign */
328 		str++;
329 	}
330 	/* use procnum as a temporary holding place and get the number */
331 	procnum = scanint(str, &prio);
332 
333 	/* negate if necessary */
334 	if (negate)
335 		prio = -prio;
336 
337 #if defined(PRIO_MIN) && defined(PRIO_MAX)
338 	/* check for validity */
339 	if (procnum == -1 || prio < PRIO_MIN || prio > PRIO_MAX)
340 		return (" bad priority value");
341 #endif
342 
343 	/* move to the first process number */
344 	if ((str = next_field(str)) == NULL)
345 		return (" no processes specified");
346 
347 	/* loop thru the process numbers, renicing each one */
348 	do {
349 		if (scanint(str, &procnum) == -1) {
350 			ERROR(str, 0);
351 		}
352 		/* check process owner if we're not root */
353 		else if (uid && (uid != proc_owner(procnum))) {
354 			ERROR(str, EACCES);
355 		} else if (setpriority(PRIO_PROCESS, procnum, prio) == -1) {
356 			ERROR(str, errno);
357 		}
358 	} while ((str = next_field(str)) != NULL);
359 
360 	/* return appropriate error string */
361 	return (err_string());
362 }
363