xref: /freebsd/usr.bin/top/top.c (revision 716fd348e01c5f2ba125f878a634a753436c2994)
1 /*-
2  *  Top users/processes display for Unix
3  *
4  *  This program may be freely redistributed,
5  *  but this entire comment MUST remain intact.
6  *
7  *  Copyright (c) 1984, 1989, William LeFebvre, Rice University
8  *  Copyright (c) 1989 - 1994, William LeFebvre, Northwestern University
9  *  Copyright (c) 1994, 1995, William LeFebvre, Argonne National Laboratory
10  *  Copyright (c) 1996, William LeFebvre, Group sys Consulting
11  *
12  * $FreeBSD$
13  */
14 
15 #include <sys/types.h>
16 #include <sys/time.h>
17 #include <sys/cdefs.h>
18 #include <sys/limits.h>
19 #include <sys/resource.h>
20 #include <sys/select.h>
21 #include <sys/signal.h>
22 
23 #include <assert.h>
24 #include <err.h>
25 #include <errno.h>
26 #include <getopt.h>
27 #include <jail.h>
28 #include <locale.h>
29 #include <stdbool.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <signal.h>
33 #include <string.h>
34 #include <unistd.h>
35 
36 #include "commands.h"
37 #include "display.h"		/* interface to display package */
38 #include "screen.h"		/* interface to screen package */
39 #include "top.h"
40 #include "machine.h"
41 #include "utils.h"
42 #include "username.h"
43 
44 /* Size of the stdio buffer given to stdout */
45 #define Buffersize	2048
46 
47 char copyright[] =
48     "Copyright (c) 1984 through 1996, William LeFebvre";
49 
50 typedef void sigret_t;
51 
52 /* The buffer that stdio will use */
53 static char stdoutbuf[Buffersize];
54 
55 static int fmt_flags = 0;
56 int show_args = false;
57 int pcpu_stats = false;
58 
59 /* signal handling routines */
60 static sigret_t leave(int);
61 static sigret_t tstop(int);
62 static sigret_t top_winch(int);
63 
64 static volatile sig_atomic_t leaveflag;
65 static volatile sig_atomic_t tstopflag;
66 static volatile sig_atomic_t winchflag;
67 
68 /* values which need to be accessed by signal handlers */
69 static int max_topn;		/* maximum displayable processes */
70 
71 /* miscellaneous things */
72 struct process_select ps;
73 pid_t mypid;
74 
75 /* pointers to display routines */
76 static void (*d_loadave)(int mpid, double *avenrun) = i_loadave;
77 static void (*d_procstates)(int total, int *brkdn) = i_procstates;
78 static void (*d_cpustates)(int *states) = i_cpustates;
79 static void (*d_memory)(int *stats) = i_memory;
80 static void (*d_arc)(int *stats) = i_arc;
81 static void (*d_carc)(int *stats) = i_carc;
82 static void (*d_swap)(int *stats) = i_swap;
83 static void (*d_message)(void) = i_message;
84 static void (*d_header)(const char *text) = i_header;
85 static void (*d_process)(int line, char *thisline) = i_process;
86 
87 static void reset_display(void);
88 
89 static const struct option longopts[] = {
90     { "cpu-display-mode", no_argument, NULL, 'C' }, /* differs from orignal */
91     /* D reserved */
92     { "thread", no_argument, NULL, 'H' },
93     { "idle-procs", no_argument, NULL, 'I' },
94 	{ "jail", required_argument, NULL, 'J' },
95 	{ "per-cpu", no_argument, NULL, 'P' },
96     { "system-procs", no_argument, NULL, 'S' },
97     { "thread-id", no_argument, NULL, 'T' }, /* differs from orignal */
98     { "user", required_argument, NULL, 'U' },
99     { "all", no_argument, NULL, 'a' },
100     { "batch", no_argument, NULL, 'b' },
101     /* c reserved */
102     { "displays", required_argument, NULL, 'd' },
103     { "interactive", no_argument, NULL, 'i' },
104     { "jail-id", no_argument, NULL, 'j' },
105     { "display-mode", required_argument, NULL, 'm' },
106     /* n is identical to batch */
107     { "sort-order", required_argument, NULL, 'o' },
108     { "pid", required_argument, NULL, 'p' },
109     { "quick", no_argument, NULL, 'q' },
110     { "delay", required_argument, NULL, 's' },
111     { "threads", no_argument, NULL, 't' },
112     { "uids", no_argument, NULL, 'u' },
113     { "version", no_argument, NULL, 'v' },
114 	{ "swap", no_argument, NULL, 'w' },
115 	{ "system-idle-procs", no_argument, NULL, 'z' },
116 	{ NULL, 0, NULL, 0 }
117 };
118 
119 static void
120 reset_uids(void)
121 {
122     for (size_t i = 0; i < TOP_MAX_UIDS; ++i)
123 	ps.uid[i] = -1;
124 }
125 
126 static int
127 add_uid(int uid)
128 {
129     size_t i = 0;
130 
131     /* Add the uid if there's room */
132     for (; i < TOP_MAX_UIDS; ++i)
133     {
134 	if (ps.uid[i] == -1 || ps.uid[i] == uid)
135 	{
136 	    ps.uid[i] = uid;
137 	    break;
138 	}
139     }
140 
141     return (i == TOP_MAX_UIDS);
142 }
143 
144 static void
145 rem_uid(int uid)
146 {
147     size_t i = 0;
148     size_t where = TOP_MAX_UIDS;
149 
150     /* Look for the user to remove - no problem if it's not there */
151     for (; i < TOP_MAX_UIDS; ++i)
152     {
153 	if (ps.uid[i] == -1)
154 	    break;
155 	if (ps.uid[i] == uid)
156 	    where = i;
157     }
158 
159     /* Make sure we don't leave a hole in the middle */
160     if (where != TOP_MAX_UIDS)
161     {
162 	ps.uid[where] = ps.uid[i-1];
163 	ps.uid[i-1] = -1;
164     }
165 }
166 
167 static int
168 handle_user(char *buf, size_t buflen)
169 {
170     int rc = 0;
171     int uid = -1;
172     char *buf2 = buf;
173 
174     new_message(MT_standout, "Username to show (+ for all): ");
175     if (readline(buf, buflen, false) <= 0)
176     {
177 	clear_message();
178 	return (rc);
179     }
180 
181     if (buf[0] == '+' || buf[0] == '-')
182     {
183 	if (buf[1] == '\0')
184 	{
185 	    reset_uids();
186 	    goto end;
187 	}
188 	else
189 	    ++buf2;
190     }
191 
192     if ((uid = userid(buf2)) == -1)
193     {
194 	new_message(MT_standout, " %s: unknown user", buf2);
195 	rc = 1;
196 	goto end;
197     }
198 
199     if (buf2 == buf)
200     {
201 	reset_uids();
202 	ps.uid[0] = uid;
203 	goto end;
204     }
205 
206     if (buf[0] == '+')
207     {
208 	if (add_uid(uid))
209 	{
210 	    new_message(MT_standout, " too many users, reset with '+'");
211 	    rc = 1;
212 	    goto end;
213 	}
214     }
215     else
216 	rem_uid(uid);
217 
218 end:
219     putchar('\r');
220     return (rc);
221 }
222 
223 int
224 main(int argc, const char *argv[])
225 {
226     int i;
227     int active_procs;
228 
229     struct system_info system_info;
230     struct statics statics;
231     void * processes;
232 
233     static char tempbuf1[50];
234     static char tempbuf2[50];
235 	sigset_t old_sigmask, new_sigmask;
236     int topn = Infinity;
237     struct timeval delay = { 2, 0 };
238     int displays = 0;		/* indicates unspecified */
239     int sel_ret = 0;
240     time_t curr_time;
241     char *(*get_userid)(int) = username;
242     const char *uname_field = "USERNAME";
243     const char *header_text;
244     char *env_top;
245     const char **preset_argv;
246     int  preset_argc = 0;
247     const char **av = NULL;
248     int  ac = -1;
249     bool do_unames = true;
250     char interactive = 2;
251     char warnings = 0;
252     char topn_specified = false;
253     char ch;
254     char no_command = 1;
255     struct timeval timeout;
256     char *order_name = NULL;
257     int order_index = 0;
258     fd_set readfds;
259 	char *nptr;
260 
261     /* set the buffer for stdout */
262 #ifdef DEBUG
263     extern FILE *debug;
264     debug = fopen("debug.run", "w");
265     setbuffer(stdout, NULL, 0);
266 #else
267     setbuffer(stdout, stdoutbuf, Buffersize);
268 #endif
269 
270     if (setlocale(LC_ALL, "") == NULL) {
271         fprintf(stderr, "invalid locale.\n");
272 	exit(1);
273     }
274 
275     mypid = getpid();
276 
277     /* get our name */
278     /* initialize some selection options */
279     ps.idle    = true;
280     ps.self    = true;
281     ps.system  = false;
282     reset_uids();
283     ps.thread  = false;
284     ps.wcpu    = 1;
285     ps.jid     = -1;
286     ps.jail    = false;
287     ps.swap    = false;
288     ps.kidle   = true;
289     ps.pid     = -1;
290     ps.command = NULL;
291     ps.thread_id = false;
292 
293     /* get preset options from the environment */
294     if ((env_top = getenv("TOP")) != NULL)
295     {
296 	av = preset_argv = argparse(env_top, &preset_argc);
297 	ac = preset_argc;
298 
299 	/* set the dummy argument to an explanatory message, in case
300 	   getopt encounters a bad argument */
301 	preset_argv[0] = "while processing environment";
302     }
303 
304     /* process options */
305     do {
306 	/* if we're done doing the presets, then process the real arguments */
307 	if (preset_argc == 0)
308 	{
309 	    ac = argc;
310 	    av = argv;
311 
312 	    /* this should keep getopt happy... */
313 	    optind = 1;
314 	}
315 
316 	while ((i = getopt_long(ac, __DECONST(char * const *, av), "CSIHPabijJ:nquvzs:d:U:m:o:p:Ttw", longopts, NULL)) != EOF)
317 	{
318 	    switch(i)
319 	    {
320 	      case 'v':			/* show version number */
321 			  errx(0, "version FreeBSD");
322 			  break;
323 
324 	      case 'u':			/* toggle uid/username display */
325 		do_unames = !do_unames;
326 		break;
327 
328 	      case 'U':			/* display only username's processes */
329 		if ((ps.uid[0] = userid(optarg)) == -1)
330 		{
331 		    errx(1, "%s: unknown user\n", optarg);
332 		}
333 		break;
334 
335 	      case 'S':			/* show system processes */
336 		ps.system = true;
337 		break;
338 
339 	      case 'I':                   /* show idle processes */
340 		ps.idle = !ps.idle;
341 		break;
342 
343 	      case 'i':			/* go interactive regardless */
344 		interactive = 1;
345 		break;
346 
347 	      case 'n':			/* batch, or non-interactive */
348 	      case 'b':
349 		interactive = 0;
350 		break;
351 
352 	      case 'a':
353 		fmt_flags ^= FMT_SHOWARGS;
354 		break;
355 
356 	      case 'd':			/* number of displays to show */
357 		if ((i = atoiwi(optarg)) == Invalid || i == 0)
358 		{
359 		    warnx("warning: display count should be positive -- option ignored");
360 		    warnings++;
361 		}
362 		else
363 		{
364 		    displays = i;
365 		}
366 		break;
367 	      case 'p': {
368 		unsigned long long num;
369 		const char *errstr;
370 
371 		num = strtonum(optarg, 0, INT_MAX, &errstr);
372 		if (errstr != NULL || !find_pid(num)) {
373 			fprintf(stderr, "%s: unknown pid\n", optarg);
374 			exit(1);
375 		}
376 		ps.pid = (pid_t)num;
377 		ps.system = true;
378 		break;
379 	      }
380 
381 	      case 's':
382 	      {
383 		  double delay_d = strtod(optarg, &nptr);
384 		  if (nptr == optarg)
385 		  {
386 		      warnx("warning: invalid delay");
387 		      warnings++;
388 		  }
389 		  else if (delay_d <= 0)
390 		  {
391 		      warnx("warning: seconds delay should be positive -- using default");
392 		      warnings++;
393 		  }
394 		  else
395 		  {
396 		      delay.tv_sec = delay_d;
397 		      delay.tv_usec = (delay_d - delay.tv_sec) * 1e6;
398 		  }
399 		  break;
400 	      }
401 
402 	      case 'q':		/* be quick about it */
403 			errno = 0;
404 			i = setpriority(PRIO_PROCESS, 0, PRIO_MIN);
405 			if (i == -1 && errno != 0) {
406 				warnx("warning: `-q' option failed (%m)");
407 				warnings++;
408 			}
409 		break;
410 
411 	      case 'm':		/* select display mode */
412 		if (strcmp(optarg, "io") == 0) {
413 			displaymode = DISP_IO;
414 		} else if (strcmp(optarg, "cpu") == 0) {
415 			displaymode = DISP_CPU;
416 		} else {
417 			errx(1, "warning: `-m' option can only take args 'io' or 'cpu'");
418 		}
419 		break;
420 
421 	      case 'o':		/* select sort order */
422 		order_name = optarg;
423 		break;
424 
425 	      case 't':
426 		ps.self = !ps.self;
427 		break;
428 
429 	      case 'C':
430 		ps.wcpu = !ps.wcpu;
431 		break;
432 
433 	      case 'H':
434 		ps.thread = !ps.thread;
435 		break;
436 
437 	      case 'T':
438 		ps.thread_id = !ps.thread_id;
439 		break;
440 
441 	      case 'j':
442 		ps.jail = !ps.jail;
443 		break;
444 
445 	      case 'J':			/* display only jail's processes */
446 		if ((ps.jid = jail_getid(optarg)) == -1)
447 		{
448 		    fprintf(stderr, "%s: unknown jail\n", optarg);
449 		    exit(1);
450 		}
451 		ps.jail = 1;
452 		break;
453 
454 	      case 'P':
455 		pcpu_stats = !pcpu_stats;
456 		break;
457 
458 	      case 'w':
459 		ps.swap = 1;
460 		break;
461 
462 	      case 'z':
463 		ps.kidle = !ps.kidle;
464 		break;
465 
466 	      default:
467 		errx(1,
468 "[-abCHIijnPqStuvwz] [-d count] [-J jail] [-m cpu | io] [-o field]\n"
469 "     [-p pid] [-s time] [-U username] [number]");
470 	    }
471 	}
472 
473 	/* get count of top processes to display (if any) */
474 	if (optind < ac)
475 	{
476 	    if ((topn = atoiwi(av[optind])) == Invalid)
477 	    {
478 			warnx("warning: process display count should be non-negative -- using default");
479 			warnings++;
480 	    }
481             else
482 	    {
483 		topn_specified = true;
484 	    }
485 	}
486 
487 	/* tricky:  remember old value of preset_argc & set preset_argc = 0 */
488 	i = preset_argc;
489 	preset_argc = 0;
490 
491     /* repeat only if we really did the preset arguments */
492     } while (i != 0);
493 
494     /* set constants for username/uid display correctly */
495     if (!do_unames)
496     {
497 	uname_field = "   UID  ";
498 	get_userid = itoa7;
499     }
500 
501     /* initialize the kernel memory interface */
502     if (machine_init(&statics) == -1)
503     {
504 	exit(1);
505     }
506 
507     /* determine sorting order index, if necessary */
508     if (order_name != NULL)
509     {
510 	if ((order_index = string_index(order_name, statics.order_names)) == -1)
511 	{
512 	    const char * const *pp;
513 
514 	    warnx("'%s' is not a recognized sorting order.", order_name);
515 	    fprintf(stderr, "\tTry one of these:");
516 	    pp = statics.order_names;
517 	    while (*pp != NULL)
518 	    {
519 		fprintf(stderr, " %s", *pp++);
520 	    }
521 	    fputc('\n', stderr);
522 	    exit(1);
523 	}
524     }
525 
526     /* initialize termcap */
527     init_termcap(interactive);
528 
529     /* get the string to use for the process area header */
530     header_text = format_header(uname_field);
531 
532     /* initialize display interface */
533     if ((max_topn = display_init(&statics)) == -1)
534     {
535 		errx(4, "can't allocate sufficient memory");
536     }
537 
538     /* print warning if user requested more processes than we can display */
539     if (topn > max_topn)
540     {
541 		warnx("warning: this terminal can only display %d processes.", max_topn);
542 		warnings++;
543     }
544 
545     /* adjust for topn == Infinity */
546     if (topn == Infinity)
547     {
548 	/*
549 	 *  For smart terminals, infinity really means everything that can
550 	 *  be displayed, or Largest.
551 	 *  On dumb terminals, infinity means every process in the system!
552 	 *  We only really want to do that if it was explicitly specified.
553 	 *  This is always the case when "Default_TOPN != Infinity".  But if
554 	 *  topn wasn't explicitly specified and we are on a dumb terminal
555 	 *  and the default is Infinity, then (and only then) we use
556 	 *  "Nominal_TOPN" instead.
557 	 */
558 	topn = smart_terminal ? Largest :
559 		    (topn_specified ? Largest : Nominal_TOPN);
560     }
561 
562     /* set header display accordingly */
563     display_header(topn > 0);
564 
565     /* determine interactive state */
566     if (interactive == 2)
567     {
568 	interactive = smart_terminal;
569     }
570 
571     /* if # of displays not specified, fill it in */
572     if (displays == 0)
573     {
574 	displays = smart_terminal ? Infinity : 1;
575     }
576 
577     /* hold interrupt signals while setting up the screen and the handlers */
578 
579 	sigemptyset(&new_sigmask);
580 	sigaddset(&new_sigmask, SIGINT);
581 	sigaddset(&new_sigmask, SIGQUIT);
582 	sigaddset(&new_sigmask, SIGTSTP);
583 	sigprocmask(SIG_BLOCK, &new_sigmask, &old_sigmask);
584     init_screen();
585     signal(SIGINT, leave);
586     signal(SIGQUIT, leave);
587     signal(SIGTSTP, tstop);
588     signal(SIGWINCH, top_winch);
589     sigprocmask(SIG_SETMASK, &old_sigmask, NULL);
590     if (warnings)
591     {
592 	fputs("....", stderr);
593 	fflush(stderr);
594 	sleep(3 * warnings);
595 	fputc('\n', stderr);
596     }
597 
598 restart:
599 
600     /*
601      *  main loop -- repeat while display count is positive or while it
602      *		indicates infinity (by being -1)
603      */
604 
605     while ((displays == -1) || (displays-- > 0))
606     {
607 	int (*compare)(const void * const, const void * const);
608 
609 
610 	/* get the current stats */
611 	get_system_info(&system_info);
612 
613 	compare = compares[order_index];
614 
615 	/* get the current set of processes */
616 	processes =
617 		get_process_info(&system_info, &ps, compare);
618 
619 	/* display the load averages */
620 	(*d_loadave)(system_info.last_pid,
621 		     system_info.load_avg);
622 
623 	/* display the battery info (if any) */
624 	i_battery(statics.nbatteries, system_info.battery);
625 
626 	/* display the current time */
627 	/* this method of getting the time SHOULD be fairly portable */
628 	time(&curr_time);
629 	i_uptime(&system_info.boottime, &curr_time);
630 	i_timeofday(&curr_time);
631 
632 	/* display process state breakdown */
633 	(*d_procstates)(system_info.p_total,
634 			system_info.procstates);
635 	(*d_cpustates)(system_info.cpustates);
636 
637 	/* display memory stats */
638 	(*d_memory)(system_info.memory);
639 	(*d_arc)(system_info.arc);
640 	(*d_carc)(system_info.carc);
641 
642 	/* display swap stats */
643 	(*d_swap)(system_info.swap);
644 
645 	/* handle message area */
646 	(*d_message)();
647 
648 	/* update the header area */
649 	(*d_header)(header_text);
650 
651 	if (topn > 0)
652 	{
653 	    /* determine number of processes to actually display */
654 	    /* this number will be the smallest of:  active processes,
655 	       number user requested, number current screen accommodates */
656 	    active_procs = system_info.p_pactive;
657 	    if (active_procs > topn)
658 	    {
659 		active_procs = topn;
660 	    }
661 	    if (active_procs > max_topn)
662 	    {
663 		active_procs = max_topn;
664 	    }
665 
666 	    /* now show the top "n" processes. */
667 	    for (i = 0; i < active_procs; i++)
668 	    {
669 		(*d_process)(i, format_next_process(processes, get_userid,
670 			     fmt_flags));
671 	    }
672 	}
673 	else
674 	{
675 	    i = 0;
676 	}
677 
678 	/* do end-screen processing */
679 	u_endscreen(i);
680 
681 	/* now, flush the output buffer */
682 	if (fflush(stdout) != 0)
683 	{
684 	    new_message(MT_standout, " Write error on stdout");
685 	    putchar('\r');
686 	    quit(1);
687 	}
688 
689 	/* only do the rest if we have more displays to show */
690 	if (displays)
691 	{
692 	    /* switch out for new display on smart terminals */
693 	    if (smart_terminal)
694 	    {
695 		if (overstrike)
696 		{
697 		    reset_display();
698 		}
699 		else
700 		{
701 		    d_loadave = u_loadave;
702 		    d_procstates = u_procstates;
703 		    d_cpustates = u_cpustates;
704 		    d_memory = u_memory;
705 		    d_arc = u_arc;
706 		    d_carc = u_carc;
707 		    d_swap = u_swap;
708 		    d_message = u_message;
709 		    d_header = u_header;
710 		    d_process = u_process;
711 		}
712 	    }
713 
714 	    no_command = true;
715 	    if (!interactive)
716 	    {
717 		timeout = delay;
718 		select(0, NULL, NULL, NULL, &timeout);
719 		if (leaveflag) {
720 		    end_screen();
721 		    exit(0);
722 		}
723 	    }
724 	    else while (no_command)
725 	    {
726 		/* assume valid command unless told otherwise */
727 		no_command = false;
728 
729 		/* set up arguments for select with timeout */
730 		FD_ZERO(&readfds);
731 		FD_SET(0, &readfds);		/* for standard input */
732 		timeout = delay;
733 
734 		if (leaveflag) {
735 		    end_screen();
736 		    exit(0);
737 		}
738 
739 		if (tstopflag) {
740 		    /* move to the lower left */
741 		    end_screen();
742 		    fflush(stdout);
743 
744 		    /* default the signal handler action */
745 		    signal(SIGTSTP, SIG_DFL);
746 
747 		    /* unblock the signal and send ourselves one */
748 		    sigsetmask(sigblock(0) & ~(1 << (SIGTSTP - 1)));
749 		    kill(0, SIGTSTP);
750 
751 		    /* reset the signal handler */
752 		    signal(SIGTSTP, tstop);
753 
754 		    /* reinit screen */
755 		    reinit_screen();
756 		    reset_display();
757 		    tstopflag = 0;
758 		    goto restart;
759 		}
760 
761 		if (winchflag) {
762 		    /* reascertain the screen dimensions */
763 		    get_screensize();
764 
765 		    /* tell display to resize */
766 		    max_topn = display_resize();
767 
768 		    /* reset the signal handler */
769 		    signal(SIGWINCH, top_winch);
770 
771 		    reset_display();
772 		    winchflag = 0;
773 		    goto restart;
774 		}
775 
776 		/* wait for either input or the end of the delay period */
777 		sel_ret = select(2, &readfds, NULL, NULL, &timeout);
778 		if (sel_ret < 0 && errno != EINTR)
779 		    quit(0);
780 		if (sel_ret > 0)
781 		{
782 		    int newval;
783 		    const char *errmsg;
784 			const struct command *cptr;
785 
786 		    /* something to read -- clear the message area first */
787 		    clear_message();
788 
789 		    /* now read it and convert to command strchr */
790 		    /* (use "change" as a temporary to hold strchr) */
791 		    if (read(0, &ch, 1) != 1)
792 		    {
793 			/* read error: either 0 or -1 */
794 			new_message(MT_standout, " Read error on stdin");
795 			putchar('\r');
796 			quit(1);
797 		    }
798 			if (ch == '\r' || ch == '\n') {
799 				continue;
800 			}
801 			cptr = all_commands;
802 			while (cptr->c != '\0') {
803 				if (cptr->c == ch) {
804 					break;
805 				}
806 				cptr++;
807 			}
808 			if (cptr->c == '\0') {
809 			    new_message(MT_standout, " Command not understood");
810 			    putchar('\r');
811 				no_command = true;
812 			}
813 			if (overstrike && !cptr->available_to_dumb)
814 			{
815 			    new_message(MT_standout,
816 			    " Command cannot be handled by this terminal");
817 			    putchar('\r');
818 				no_command = true;
819 			}
820 			if (!no_command) {
821 			switch(cptr->id)
822 			{
823 			    case CMD_redraw:	/* redraw screen */
824 				reset_display();
825 				break;
826 
827 			    case CMD_update:	/* merely update display */
828 				break;
829 
830 			    case CMD_quit:
831 				quit(0);
832 				break;
833 
834 			    case CMD_help:
835 				reset_display();
836 				top_clear();
837 				show_help();
838 				top_standout("Hit any key to continue: ");
839 				fflush(stdout);
840 				read(0, &ch, 1);
841 				break;
842 
843 			    case CMD_errors:	/* show errors */
844 				if (error_count() == 0)
845 				{
846 				    new_message(MT_standout,
847 					" Currently no errors to report.");
848 				    putchar('\r');
849 				    no_command = true;
850 				}
851 				else
852 				{
853 				    reset_display();
854 				    top_clear();
855 				    show_errors();
856 				    top_standout("Hit any key to continue: ");
857 				    fflush(stdout);
858 				    read(0, &ch, 1);
859 				}
860 				break;
861 
862 			    case CMD_number:
863 				new_message(MT_standout,
864 				    "Number of processes to show: ");
865 				newval = readline(tempbuf1, 8, true);
866 				if (newval > -1)
867 				{
868 				    if (newval > max_topn)
869 				    {
870 					new_message(MT_standout | MT_delayed,
871 					  " This terminal can only display %d processes.",
872 					  max_topn);
873 					putchar('\r');
874 				    }
875 
876 				    if (newval == 0)
877 				    {
878 					/* inhibit the header */
879 					display_header(false);
880 				    }
881 				    else if (newval > topn && topn == 0)
882 				    {
883 					/* redraw the header */
884 					display_header(true);
885 					d_header = i_header;
886 				    }
887 				    topn = newval;
888 				}
889 				break;
890 
891 			    case CMD_delay:	/* new seconds delay */
892 				new_message(MT_standout, "Seconds to delay: ");
893 				if ((i = readline(tempbuf1, 8, false)) > 0)
894 				{
895 				    double delay_d = strtod(tempbuf1, &nptr);
896 				    if (nptr == tempbuf1 || delay_d <= 0)
897 				    {
898 					new_message(MT_standout, " Invalid delay");
899 					putchar('\r');
900 					no_command = true;
901 				    }
902 				    else
903 				    {
904 					delay.tv_sec = delay_d;
905 					delay.tv_usec = (delay_d - delay.tv_sec) * 1e6;
906 					clear_message();
907 				    }
908 				}
909 				break;
910 
911 			    case CMD_grep: /* grep command name */
912 				new_message(MT_standout,
913 				    "Grep command name (+ for all): ");
914 				if (readline(tempbuf1, sizeof(tempbuf1), false) > 0) {
915 					free(ps.command);
916 					if (tempbuf1[0] == '+' && tempbuf1[1] == '\0') {
917 						ps.command = NULL;
918 					} else if ((ps.command = strdup(tempbuf1)) == NULL)
919 						quit(1);
920 				}
921 				clear_message();
922 				break;
923 
924 			    case CMD_displays:	/* change display count */
925 				new_message(MT_standout,
926 					"Displays to show (currently %s): ",
927 					displays == -1 ? "infinite" :
928 							 itoa(displays));
929 				if ((i = readline(tempbuf1, 10, true)) > 0)
930 				{
931 				    displays = i;
932 				}
933 				else if (i == 0)
934 				{
935 				    quit(0);
936 				}
937 				clear_message();
938 				break;
939 
940 			    case CMD_kill:	/* kill program */
941 				new_message(0, "kill ");
942 				if (readline(tempbuf2, sizeof(tempbuf2), false) > 0)
943 				{
944 				    if ((errmsg = kill_procs(tempbuf2)) != NULL)
945 				    {
946 					new_message(MT_standout, "%s", errmsg);
947 					putchar('\r');
948 					no_command = true;
949 				    }
950 				}
951 				else
952 				{
953 				    clear_message();
954 				}
955 				break;
956 
957 			    case CMD_renice:	/* renice program */
958 				new_message(0, "renice ");
959 				if (readline(tempbuf2, sizeof(tempbuf2), false) > 0)
960 				{
961 				    if ((errmsg = renice_procs(tempbuf2)) != NULL)
962 				    {
963 					new_message(MT_standout, "%s", errmsg);
964 					putchar('\r');
965 					no_command = true;
966 				    }
967 				}
968 				else
969 				{
970 				    clear_message();
971 				}
972 				break;
973 
974 			    case CMD_idletog:
975 				ps.idle = !ps.idle;
976 				new_message(MT_standout | MT_delayed,
977 				    " %sisplaying idle processes.",
978 				    ps.idle ? "D" : "Not d");
979 				putchar('\r');
980 				break;
981 
982 			    case CMD_selftog:
983 				ps.self = !ps.self;
984 				new_message(MT_standout | MT_delayed,
985 				    " %sisplaying self.",
986 				    (ps.self) ? "D" : "Not d");
987 				putchar('\r');
988 				break;
989 
990 			    case CMD_user:
991 				if (handle_user(tempbuf2, sizeof(tempbuf2)))
992 				    no_command = true;
993 				break;
994 
995 			    case CMD_thrtog:
996 				ps.thread = !ps.thread;
997 				new_message(MT_standout | MT_delayed,
998 				    " Displaying threads %s",
999 				    ps.thread ? "separately" : "as a count");
1000 				header_text = format_header(uname_field);
1001 				reset_display();
1002 				putchar('\r');
1003 				break;
1004 
1005 			    case CMD_toggletid:
1006 				ps.thread_id = !ps.thread_id;
1007 				new_message(MT_standout | MT_delayed,
1008 				    " Displaying %s",
1009 				    ps.thread_id ? "tid" : "pid");
1010 				header_text = format_header(uname_field);
1011 				reset_display();
1012 				putchar('\r');
1013 				break;
1014 
1015 			    case CMD_wcputog:
1016 				ps.wcpu = !ps.wcpu;
1017 				new_message(MT_standout | MT_delayed,
1018 				    " Displaying %s CPU",
1019 				    ps.wcpu ? "weighted" : "raw");
1020 				header_text = format_header(uname_field);
1021 				reset_display();
1022 				putchar('\r');
1023 				break;
1024 			    case CMD_viewtog:
1025 				displaymode = displaymode == DISP_IO ? DISP_CPU : DISP_IO;
1026 				new_message(MT_standout | MT_delayed,
1027 				    " Displaying %s statistics.",
1028 				    displaymode == DISP_IO ? "IO" : "CPU");
1029 				header_text = format_header(uname_field);
1030 				display_header(true);
1031 				d_header = i_header;
1032 				reset_display();
1033 				break;
1034 			    case CMD_viewsys:
1035 				ps.system = !ps.system;
1036 				new_message(MT_standout | MT_delayed,
1037 				    " %sisplaying system processes.",
1038 				    ps.system ? "D" : "Not d");
1039 				break;
1040 			    case CMD_showargs:
1041 				fmt_flags ^= FMT_SHOWARGS;
1042 				show_args = fmt_flags & FMT_SHOWARGS;
1043 				new_message(MT_standout | MT_delayed,
1044 				    " %sisplaying process arguments.",
1045 				    fmt_flags & FMT_SHOWARGS ? "D" : "Not d");
1046 				break;
1047 			    case CMD_order:
1048 				new_message(MT_standout,
1049 				    "Order to sort: ");
1050 				if (readline(tempbuf2, sizeof(tempbuf2), false) > 0)
1051 				{
1052 				  if ((i = string_index(tempbuf2, statics.order_names)) == -1)
1053 					{
1054 					  new_message(MT_standout,
1055 					      " %s: unrecognized sorting order", tempbuf2);
1056 					  no_command = true;
1057 				    }
1058 				    else
1059 				    {
1060 					order_index = i;
1061 				    }
1062 				    putchar('\r');
1063 				}
1064 				else
1065 				{
1066 				    clear_message();
1067 				}
1068 				break;
1069 			    case CMD_jidtog:
1070 				ps.jail = !ps.jail;
1071 				new_message(MT_standout | MT_delayed,
1072 				    " %sisplaying jail ID.",
1073 				    ps.jail ? "D" : "Not d");
1074 				header_text = format_header(uname_field);
1075 				reset_display();
1076 				putchar('\r');
1077 				break;
1078 
1079 			    case CMD_jail:
1080 				new_message(MT_standout,
1081 				    "Jail to show (+ for all): ");
1082 				if (readline(tempbuf2, sizeof(tempbuf2), false) > 0)
1083 				{
1084 				    if (tempbuf2[0] == '+' &&
1085 					tempbuf2[1] == '\0')
1086 				    {
1087 					ps.jid = -1;
1088 				    }
1089 				    else if ((i = jail_getid(tempbuf2)) == -1)
1090 				    {
1091 					new_message(MT_standout,
1092 					    " %s: unknown jail", tempbuf2);
1093 					no_command = true;
1094 				    }
1095 				    else
1096 				    {
1097 					ps.jid = i;
1098 				    }
1099 				    if (ps.jail == 0) {
1100 					    ps.jail = 1;
1101 					    new_message(MT_standout |
1102 						MT_delayed, " Displaying jail "
1103 						"ID.");
1104 					    header_text =
1105 						format_header(uname_field);
1106 					    reset_display();
1107 				    }
1108 				    putchar('\r');
1109 				}
1110 				else
1111 				{
1112 				    clear_message();
1113 				}
1114 				break;
1115 
1116 			    case CMD_kidletog:
1117 				ps.kidle = !ps.kidle;
1118 				new_message(MT_standout | MT_delayed,
1119 				    " %sisplaying system idle process.",
1120 				    ps.kidle ? "D" : "Not d");
1121 				putchar('\r');
1122 				break;
1123 			    case CMD_pcputog:
1124 				pcpu_stats = !pcpu_stats;
1125 				new_message(MT_standout | MT_delayed,
1126 				    " Displaying %sCPU statistics.",
1127 				    pcpu_stats ? "per-" : "global ");
1128 				toggle_pcpustats();
1129 				max_topn = display_updatecpus(&statics);
1130 				reset_display();
1131 				putchar('\r');
1132 				break;
1133 			    case CMD_swaptog:
1134 				ps.swap = !ps.swap;
1135 				new_message(MT_standout | MT_delayed,
1136 				    " %sisplaying per-process swap usage.",
1137 				    ps.swap ? "D" : "Not d");
1138 				header_text = format_header(uname_field);
1139 				reset_display();
1140 				putchar('\r');
1141 				break;
1142 			    case CMD_pid:
1143 				new_message(MT_standout,
1144 					"Process id to show (+ for all): ");
1145 				if (readline(tempbuf2, sizeof(tempbuf2), false) > 0) {
1146 					if (tempbuf2[0] == '+' &&
1147                    			    tempbuf2[1] == '\0') {
1148 						ps.pid = (pid_t)-1;
1149 					} else {
1150 						unsigned long long num;
1151 						const char *errstr;
1152 
1153 						num = strtonum(tempbuf2, 0, INT_MAX,
1154 							&errstr);
1155 						if (errstr != NULL || !find_pid(num)) {
1156 							new_message(MT_standout,
1157 								" %s: unknown pid",
1158 								tempbuf2);
1159 							no_command = true;
1160 						} else {
1161 							ps.pid = (pid_t)num;
1162 						}
1163 					}
1164 					putchar('\r');
1165 				} else
1166 					clear_message();
1167 				break;
1168 			    case CMD_NONE:
1169 					assert(false && "reached switch without command");
1170 			}
1171 			}
1172 		    }
1173 
1174 		    /* flush out stuff that may have been written */
1175 		    fflush(stdout);
1176 		}
1177 	    }
1178     }
1179 
1180 #ifdef DEBUG
1181     fclose(debug);
1182 #endif
1183     quit(0);
1184 }
1185 
1186 /*
1187  *  reset_display() - reset all the display routine pointers so that entire
1188  *	screen will get redrawn.
1189  */
1190 
1191 static void
1192 reset_display(void)
1193 {
1194     d_loadave    = i_loadave;
1195     d_procstates = i_procstates;
1196     d_cpustates  = i_cpustates;
1197     d_memory     = i_memory;
1198     d_arc        = i_arc;
1199     d_carc       = i_carc;
1200     d_swap       = i_swap;
1201     d_message	 = i_message;
1202     d_header	 = i_header;
1203     d_process	 = i_process;
1204 }
1205 
1206 /*
1207  *  signal handlers
1208  */
1209 
1210 static sigret_t
1211 leave(int i __unused)	/* exit under normal conditions -- INT handler */
1212 {
1213 
1214     leaveflag = 1;
1215 }
1216 
1217 static sigret_t
1218 tstop(int i __unused)	/* SIGTSTP handler */
1219 {
1220 
1221     tstopflag = 1;
1222 }
1223 
1224 static sigret_t
1225 top_winch(int i __unused)		/* SIGWINCH handler */
1226 {
1227 
1228     winchflag = 1;
1229 }
1230 
1231 void __dead2
1232 quit(int status)		/* exit under duress */
1233 {
1234     end_screen();
1235     exit(status);
1236 }
1237