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