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