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