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