xref: /openbsd-src/usr.bin/top/commands.c (revision 47911bd667ac77dc523b8a13ef40b012dbffa741)
1 /*	$OpenBSD: commands.c,v 1.6 2002/07/15 17:20:36 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 int str_adderr(char *, int, int);
60 static int str_addarg(char *, int, 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 
68 void
69 show_help()
70 
71 {
72     printf("Top version %s, %s\n", version_string(), copyright);
73     fputs("\n\n\
74 A top users display for Unix\n\
75 \n\
76 These single-character commands are available:\n\
77 \n\
78 ^L      - redraw screen\n\
79 q       - quit\n\
80 h or ?  - help; show this text\n", stdout);
81 
82     /* not all commands are availalbe with overstrike terminals */
83     if (overstrike)
84     {
85 	fputs("\n\
86 Other commands are also available, but this terminal is not\n\
87 sophisticated enough to handle those commands gracefully.\n\n", stdout);
88     }
89     else
90     {
91 	fputs("\
92 d       - change number of displays to show\n\
93 e       - list errors generated by last \"kill\" or \"renice\" command\n\
94 i       - toggle the displaying of idle processes\n\
95 I       - same as 'i'\n\
96 k       - kill processes; send a signal to a list of processes\n\
97 n or #  - change number of processes to display\n", stdout);
98 #ifdef ORDER
99 	fputs("\
100 o       - specify sort order (size, res, cpu, time)\n", stdout);
101 #endif
102 	fputs("\
103 r       - renice a process\n\
104 s       - change number of seconds to delay between updates\n\
105 u       - display processes for only one user (+ selects all users)\n\
106 \n\
107 \n", stdout);
108     }
109 }
110 
111 /*
112  *  Utility routines that help with some of the commands.
113  */
114 
115 static char *next_field(str)
116 
117 char *str;
118 
119 {
120     if ((str = strchr(str, ' ')) == NULL)
121     {
122 	return(NULL);
123     }
124     *str = '\0';
125     while (*++str == ' ') /* loop */;
126 
127     /* if there is nothing left of the string, return NULL */
128     /* This fix is dedicated to Greg Earle */
129     return(*str == '\0' ? NULL : str);
130 }
131 
132 static int scanint(str, intp)
133 
134 char *str;
135 int  *intp;
136 
137 {
138     int val = 0;
139     char ch;
140 
141     /* if there is nothing left of the string, flag it as an error */
142     /* This fix is dedicated to Greg Earle */
143     if (*str == '\0')
144     {
145 	return(-1);
146     }
147 
148     while ((ch = *str++) != '\0')
149     {
150 	if (isdigit(ch))
151 	{
152 	    val = val * 10 + (ch - '0');
153 	}
154 	else if (isspace(ch))
155 	{
156 	    break;
157 	}
158 	else
159 	{
160 	    return(-1);
161 	}
162     }
163     *intp = val;
164     return(0);
165 }
166 
167 /*
168  *  Some of the commands make system calls that could generate errors.
169  *  These errors are collected up in an array of structures for later
170  *  contemplation and display.  Such routines return a string containing an
171  *  error message, or NULL if no errors occurred.  The next few routines are
172  *  for manipulating and displaying these errors.  We need an upper limit on
173  *  the number of errors, so we arbitrarily choose 20.
174  */
175 
176 #define ERRMAX 20
177 
178 struct errs		/* structure for a system-call error */
179 {
180     int  errno;		/* value of errno (that is, the actual error) */
181     char *arg;		/* argument that caused the error */
182 };
183 
184 static struct errs errs[ERRMAX];
185 static int errcnt;
186 static char *err_toomany = " too many errors occurred";
187 static char *err_listem =
188 	" Many errors occurred.  Press `e' to display the list of errors.";
189 
190 /* These macros get used to reset and log the errors */
191 #define ERR_RESET   errcnt = 0
192 #define ERROR(p, e) if (errcnt >= ERRMAX) \
193 		    { \
194 			return(err_toomany); \
195 		    } \
196 		    else \
197 		    { \
198 			errs[errcnt].arg = (p); \
199 			errs[errcnt++].errno = (e); \
200 		    }
201 
202 /*
203  *  err_string() - return an appropriate error string.  This is what the
204  *	command will return for displaying.  If no errors were logged, then
205  *	return NULL.  The maximum length of the error string is defined by
206  *	"STRMAX".
207  */
208 
209 #define STRMAX 80
210 
211 static char *err_string()
212 
213 {
214     struct errs *errp;
215     int  cnt = 0;
216     int  first = Yes;
217     int  currerr = -1;
218     int stringlen;		/* characters still available in "string" */
219     static char string[STRMAX];
220 
221     /* if there are no errors, return NULL */
222     if (errcnt == 0)
223     {
224 	return(NULL);
225     }
226 
227     /* sort the errors */
228     qsort((char *)errs, errcnt, sizeof(struct errs), err_compar);
229 
230     /* need a space at the front of the error string */
231     string[0] = ' ';
232     string[1] = '\0';
233     stringlen = STRMAX - 2;
234 
235     /* loop thru the sorted list, building an error string */
236     while (cnt < errcnt)
237     {
238 	errp = &(errs[cnt++]);
239 	if (errp->errno != currerr)
240 	{
241 	    if (currerr != -1)
242 	    {
243 		if ((stringlen = str_adderr(string, stringlen, currerr)) < 2)
244 		{
245 		    return(err_listem);
246 		}
247 		/* we know there's more */
248 		(void) strlcat(string, "; ", sizeof string);
249 	    }
250 	    currerr = errp->errno;
251 	    first = Yes;
252 	}
253 	if ((stringlen = str_addarg(string, stringlen, errp->arg, first)) ==0)
254 	{
255 	    return(err_listem);
256 	}
257 	first = No;
258     }
259 
260     /* add final message */
261     stringlen = str_adderr(string, stringlen, currerr);
262 
263     /* return the error string */
264     return(stringlen == 0 ? err_listem : string);
265 }
266 
267 /*
268  *  str_adderr(str, len, err) - add an explanation of error "err" to
269  *	the string "str".
270  */
271 
272 static int str_adderr(str, len, err)
273 
274 char *str;
275 int len;
276 int err;
277 
278 {
279     char *msg;
280     int  msglen;
281 
282     msg = err == 0 ? "Not a number" : strerror(err);
283     msglen = strlen(msg) + 2;
284     if (len <= msglen)
285     {
286 	return(0);
287     }
288     (void) strcat(str, ": ");
289     (void) strcat(str, msg);
290     return(len - msglen);
291 }
292 
293 /*
294  *  str_addarg(str, len, arg, first) - add the string argument "arg" to
295  *	the string "str".  This is the first in the group when "first"
296  *	is set (indicating that a comma should NOT be added to the front).
297  */
298 
299 static int str_addarg(str, len, arg, first)
300 
301 char *str;
302 int  len;
303 char *arg;
304 int  first;
305 
306 {
307     int arglen;
308 
309     arglen = strlen(arg);
310     if (!first)
311     {
312 	arglen += 2;
313     }
314     if (len <= arglen)
315     {
316 	return(0);
317     }
318     if (!first)
319     {
320 	(void) strcat(str, ", ");
321     }
322     (void) strcat(str, arg);
323     return(len - arglen);
324 }
325 
326 /*
327  *  err_compar(p1, p2) - comparison routine used by "qsort"
328  *	for sorting errors.
329  */
330 
331 static int err_compar(e1, e2)
332 
333 const void *e1, *e2;
334 
335 {
336     const struct errs *p1 = (struct errs *)e1;
337     const struct errs *p2 = (struct errs *)e2;
338     int result;
339 
340     if ((result = p1->errno - p2->errno) == 0)
341     {
342 	return(strcmp(p1->arg, p2->arg));
343     }
344     return(result);
345 }
346 
347 /*
348  *  error_count() - return the number of errors currently logged.
349  */
350 
351 int error_count()
352 
353 {
354     return(errcnt);
355 }
356 
357 /*
358  *  show_errors() - display on stdout the current log of errors.
359  */
360 
361 void show_errors()
362 
363 {
364     int cnt = 0;
365     struct errs *errp = errs;
366 
367     printf("%d error%s:\n\n", errcnt, errcnt == 1 ? "" : "s");
368     while (cnt++ < errcnt)
369     {
370 	printf("%5s: %s\n", errp->arg,
371 	    errp->errno == 0 ? "Not a number" : strerror(errp->errno));
372 	errp++;
373     }
374 }
375 
376 /*
377  *  kill_procs(str) - send signals to processes, much like the "kill"
378  *		command does; invoked in response to 'k'.
379  */
380 
381 char *kill_procs(str)
382 
383 char *str;
384 
385 {
386     char *nptr;
387     int signum = SIGTERM;	/* default */
388     int procnum;
389     struct sigdesc *sigp;
390     int uid;
391 
392     /* reset error array */
393     ERR_RESET;
394 
395     /* remember our uid */
396     uid = getuid();
397 
398     /* skip over leading white space */
399     while (isspace(*str)) str++;
400 
401     if (str[0] == '-')
402     {
403 	/* explicit signal specified */
404 	if ((nptr = next_field(str)) == NULL)
405 	{
406 	    return(" kill: no processes specified");
407 	}
408 
409 	if (isdigit(str[1]))
410 	{
411 	    (void) scanint(str + 1, &signum);
412 	    if (signum <= 0 || signum >= NSIG)
413 	    {
414 		return(" invalid signal number");
415 	    }
416 	}
417 	else
418 	{
419 	    /* translate the name into a number */
420 	    for (sigp = sigdesc; sigp->name != NULL; sigp++)
421 	    {
422 		if (strcmp(sigp->name, str + 1) == 0)
423 		{
424 		    signum = sigp->number;
425 		    break;
426 		}
427 	    }
428 
429 	    /* was it ever found */
430 	    if (sigp->name == NULL)
431 	    {
432 		return(" bad signal name");
433 	    }
434 	}
435 	/* put the new pointer in place */
436 	str = nptr;
437     }
438 
439     /* loop thru the string, killing processes */
440     do
441     {
442 	if (scanint(str, &procnum) == -1)
443 	{
444 	    ERROR(str, 0);
445 	}
446 	else
447 	{
448 	    /* check process owner if we're not root */
449 	    if (uid && (uid != proc_owner(procnum)))
450 	    {
451 		ERROR(str, EACCES);
452 	    }
453 	    /* go in for the kill */
454 	    else if (kill(procnum, signum) == -1)
455 	    {
456 		/* chalk up an error */
457 		ERROR(str, errno);
458 	    }
459 	}
460     } while ((str = next_field(str)) != NULL);
461 
462     /* return appropriate error string */
463     return(err_string());
464 }
465 
466 /*
467  *  renice_procs(str) - change the "nice" of processes, much like the
468  *		"renice" command does; invoked in response to 'r'.
469  */
470 
471 char *renice_procs(str)
472 
473 char *str;
474 
475 {
476     char negate;
477     int prio;
478     int procnum;
479     int uid;
480 
481     ERR_RESET;
482     uid = getuid();
483 
484     /* allow for negative priority values */
485     if ((negate = (*str == '-')) != 0)
486     {
487 	/* move past the minus sign */
488 	str++;
489     }
490 
491     /* use procnum as a temporary holding place and get the number */
492     procnum = scanint(str, &prio);
493 
494     /* negate if necessary */
495     if (negate)
496     {
497 	prio = -prio;
498     }
499 
500 #if defined(PRIO_MIN) && defined(PRIO_MAX)
501     /* check for validity */
502     if (procnum == -1 || prio < PRIO_MIN || prio > PRIO_MAX)
503     {
504 	return(" bad priority value");
505     }
506 #endif
507 
508     /* move to the first process number */
509     if ((str = next_field(str)) == NULL)
510     {
511 	return(" no processes specified");
512     }
513 
514     /* loop thru the process numbers, renicing each one */
515     do
516     {
517 	if (scanint(str, &procnum) == -1)
518 	{
519 	    ERROR(str, 0);
520 	}
521 
522 	/* check process owner if we're not root */
523 	else if (uid && (uid != proc_owner(procnum)))
524 	{
525 	    ERROR(str, EACCES);
526 	}
527 	else if (setpriority(PRIO_PROCESS, procnum, prio) == -1)
528 	{
529 	    ERROR(str, errno);
530 	}
531     } while ((str = next_field(str)) != NULL);
532 
533     /* return appropriate error string */
534     return(err_string());
535 }
536 
537