xref: /openbsd-src/usr.bin/top/display.c (revision d1df930ffab53da22f3324c32bed7ac5709915e6)
1 /* $OpenBSD: display.c,v 1.55 2018/09/26 17:23:13 cheloha 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 display information on the screen.
33  *  Each section of the screen has two routines:  one for initially writing
34  *  all constant and dynamic text, and one for only updating the text that
35  *  changes.  The prefix "i_" is used on all the "initial" routines and the
36  *  prefix "u_" is used for all the "updating" routines.
37  *
38  *  ASSUMPTIONS:
39  *        None of the "i_" routines use any of the termcap capabilities.
40  *        In this way, those routines can be safely used on terminals that
41  *        have minimal (or nonexistent) terminal capabilities.
42  *
43  *        The routines are called in this order:  *_loadave, i_timeofday,
44  *        *_procstates, *_cpustates, *_memory, *_message, *_header,
45  *        *_process, u_endscreen.
46  */
47 
48 #include <sys/types.h>
49 #include <sys/time.h>
50 #include <sys/sched.h>
51 #include <curses.h>
52 #include <errno.h>
53 #include <stdio.h>
54 #include <ctype.h>
55 #include <err.h>
56 #include <signal.h>
57 #include <stdlib.h>
58 #include <string.h>
59 #include <unistd.h>
60 
61 #include "screen.h"		/* interface to screen package */
62 #include "layout.h"		/* defines for screen position layout */
63 #include "display.h"
64 #include "top.h"
65 #include "boolean.h"
66 #include "machine.h"		/* we should eliminate this!!! */
67 #include "utils.h"
68 
69 #ifdef DEBUG
70 FILE           *debug;
71 #endif
72 
73 static int      display_width = MAX_COLS;
74 
75 static char    *cpustates_tag(int);
76 static int      string_count(char **);
77 static void     summary_format(char *, size_t, int *, char **);
78 static int	readlinedumb(char *, int);
79 
80 #define lineindex(l) ((l)*display_width)
81 
82 /* things initialized by display_init and used throughout */
83 
84 /* buffer of proc information lines for display updating */
85 char           *screenbuf = NULL;
86 
87 static char   **procstate_names;
88 static char   **cpustate_names;
89 static char   **memory_names;
90 
91 static int      num_cpustates;
92 
93 static int     *cpustate_columns;
94 static int      cpustate_total_length;
95 
96 /* display ips */
97 int y_mem;
98 int y_message;
99 int y_header;
100 int y_idlecursor;
101 int y_procs;
102 extern int ncpu;
103 extern int combine_cpus;
104 extern struct process_select ps;
105 
106 int header_status = Yes;
107 
108 static int
109 empty(void)
110 {
111 	return OK;
112 }
113 
114 static int
115 myfputs(const char *s)
116 {
117 	return fputs(s, stdout);
118 }
119 
120 static int (*addstrp)(const char *);
121 static int (*printwp)(const char *, ...);
122 static int (*standoutp)(void);
123 static int (*standendp)(void);
124 
125 int
126 display_resize(void)
127 {
128 	int cpu_lines, display_lines, ncpuonline;
129 
130 	ncpuonline = getncpuonline();
131 	cpu_lines = (combine_cpus ? 1 : ncpuonline);
132 
133 	y_mem = 2 + cpu_lines;
134 	y_header = 4 + cpu_lines;
135 	y_procs = 5 + cpu_lines;
136 
137 	/* calculate the current dimensions */
138 	/* if operating in "dumb" mode, we only need one line */
139 	display_lines = smart_terminal ? screen_length - y_procs : 1;
140 
141 	y_idlecursor = y_message = 3 + cpu_lines;
142 	if (screen_length <= y_message)
143 		y_idlecursor = y_message = screen_length - 1;
144 
145 	/*
146 	 * we don't want more than MAX_COLS columns, since the
147 	 * machine-dependent modules make static allocations based on
148 	 * MAX_COLS and we don't want to run off the end of their buffers
149 	 */
150 	display_width = screen_width;
151 	if (display_width >= MAX_COLS)
152 		display_width = MAX_COLS - 1;
153 
154 	if (display_lines < 0)
155 		display_lines = 0;
156 
157 	/* return number of lines available */
158 	/* for dumb terminals, pretend like we can show any amount */
159 	return (smart_terminal ? display_lines : Largest);
160 }
161 
162 int
163 display_init(struct statics * statics)
164 {
165 	int display_lines, *ip, i;
166 	char **pp;
167 
168 	if (smart_terminal) {
169 		addstrp = addstr;
170 		printwp = printw;
171 		standoutp = standout;
172 		standendp = standend;
173 	} else {
174 		addstrp = myfputs;
175 		printwp = printf;
176 		standoutp = empty;
177 		standendp = empty;
178 	}
179 
180 	/* call resize to do the dirty work */
181 	display_lines = display_resize();
182 
183 	/* only do the rest if we need to */
184 	/* save pointers and allocate space for names */
185 	procstate_names = statics->procstate_names;
186 
187 	cpustate_names = statics->cpustate_names;
188 	num_cpustates = string_count(cpustate_names);
189 
190 	cpustate_columns = calloc(num_cpustates, sizeof(int));
191 	if (cpustate_columns == NULL)
192 		err(1, NULL);
193 
194 	memory_names = statics->memory_names;
195 
196 	/* calculate starting columns where needed */
197 	cpustate_total_length = 0;
198 	pp = cpustate_names;
199 	ip = cpustate_columns;
200 	while (*pp != NULL) {
201 		if ((i = strlen(*pp++)) > 0) {
202 			*ip++ = cpustate_total_length;
203 			cpustate_total_length += i + 8;
204 		}
205 	}
206 
207 	/* return number of lines available */
208 	return (display_lines);
209 }
210 
211 static void
212 format_uptime(char *buf, size_t buflen)
213 {
214 	time_t uptime;
215 	int days, hrs, mins;
216 	struct timespec boottime;
217 
218 	/*
219 	 * Print how long system has been up.
220 	 */
221 	if (clock_gettime(CLOCK_BOOTTIME, &boottime) != -1) {
222 		uptime = boottime.tv_sec;
223 		uptime += 30;
224 		days = uptime / (3600 * 24);
225 		uptime %= (3600 * 24);
226 		hrs = uptime / 3600;
227 		uptime %= 3600;
228 		mins = uptime / 60;
229 		if (days > 0)
230 			snprintf(buf, buflen, "up %d day%s, %2d:%02d",
231 			    days, days > 1 ? "s" : "", hrs, mins);
232 		else
233 			snprintf(buf, buflen, "up %2d:%02d",
234 			    hrs, mins);
235 	}
236 }
237 
238 
239 void
240 i_loadave(pid_t mpid, double *avenrun)
241 {
242 	if (screen_length > 1 || !smart_terminal) {
243 		int i;
244 
245 		move(0, 0);
246 		clrtoeol();
247 
248 		addstrp("load averages");
249 		/* mpid == -1 implies this system doesn't have an _mpid */
250 		if (mpid != -1)
251 			printwp("last pid: %5ld;  ", (long) mpid);
252 
253 		for (i = 0; i < 3; i++)
254 			printwp("%c %5.2f", i == 0 ? ':' : ',', avenrun[i]);
255 	}
256 
257 }
258 
259 /*
260  *  Display the current time.
261  *  "ctime" always returns a string that looks like this:
262  *
263  *	Sun Sep 16 01:03:52 1973
264  *      012345678901234567890123
265  *	          1         2
266  *
267  *  We want indices 11 thru 18 (length 8).
268  */
269 
270 void
271 i_timeofday(time_t * tod)
272 {
273 	static char buf[30];
274 
275 	if (buf[0] == '\0')
276 		gethostname(buf, sizeof(buf));
277 
278 	if (screen_length > 1 || !smart_terminal) {
279 		if (smart_terminal) {
280 			move(0, screen_width - 8 - strlen(buf) - 1);
281 		} else {
282 			if (fputs("    ", stdout) == EOF)
283 				exit(1);
284 		}
285 #ifdef DEBUG
286 		{
287 			char *foo;
288 			foo = ctime(tod);
289 			addstrp(foo);
290 		}
291 #endif
292 		printwp("%s %-8.8s", buf, &(ctime(tod)[11]));
293 		putn();
294 	}
295 }
296 
297 /*
298  *  *_procstates(total, states, threads) - print the process/thread summary line
299  *
300  *  Assumptions:  cursor is at the beginning of the line on entry
301  */
302 void
303 i_procstates(int total, int *states, int threads)
304 {
305 	if (screen_length > 2 || !smart_terminal) {
306 		char procstates_buffer[MAX_COLS];
307 		char uptime[40];
308 
309 		move(1, 0);
310 		clrtoeol();
311 		/* write current number of procs and remember the value */
312 		if (threads == Yes)
313 			printwp("%d threads: ", total);
314 		else
315 			printwp("%d processes: ", total);
316 
317 		/* format and print the process state summary */
318 		summary_format(procstates_buffer, sizeof(procstates_buffer),
319 		    states, procstate_names);
320 
321 		addstrp(procstates_buffer);
322 
323 		format_uptime(uptime, sizeof(uptime));
324 		if (smart_terminal)
325 			move(1, screen_width - strlen(uptime));
326 		else
327 			printwp("  ");
328 		printwp("%s", uptime);
329 		putn();
330 	}
331 }
332 
333 /*
334  *  *_cpustates(states) - print the cpu state percentages
335  *
336  *  Assumptions:  cursor is on the PREVIOUS line
337  */
338 
339 /* cpustates_tag() calculates the correct tag to use to label the line */
340 
341 static char *
342 cpustates_tag(int cpu)
343 {
344 	if (screen_length > 3 || !smart_terminal) {
345 		static char *tag;
346 		static int cpulen, old_width;
347 		int i;
348 
349 		if (cpulen == 0 && ncpu > 1) {
350 			/* compute length of the cpu string */
351 			for (i = ncpu; i > 0; cpulen++, i /= 10)
352 				continue;
353 		}
354 
355 		if (old_width == screen_width) {
356 			if (ncpu > 1) {
357 				/* just store the cpu number in the tag */
358 				i = tag[3 + cpulen];
359 				snprintf(tag + 3, cpulen + 1, "%.*d", cpulen, cpu);
360 				tag[3 + cpulen] = i;
361 			}
362 		} else {
363 			/*
364 			 * use a long tag if it will fit, otherwise use short one.
365 			 */
366 			free(tag);
367 			if (cpustate_total_length + 10 + cpulen >= screen_width)
368 				i = asprintf(&tag, "CPU%.*d: ", cpulen, cpu);
369 			else
370 				i = asprintf(&tag, "CPU%.*d states: ", cpulen, cpu);
371 			if (i == -1)
372 				tag = NULL;
373 			else
374 				old_width = screen_width;
375 		}
376 		return (tag);
377 	} else
378 		return ("\0");
379 }
380 
381 void
382 i_cpustates(int64_t *ostates, int *online)
383 {
384 	int i, first, cpu, ncpuonline;
385 	double value;
386 	int64_t *states;
387 	char **names, *thisname;
388 
389 	ncpuonline = 0;
390 	for (i = 0; i < ncpu; i++) {
391 		if (online[i])
392 			ncpuonline++;
393 	}
394 
395 	if (combine_cpus) {
396 		static double *values;
397 		if (!values) {
398 			values = calloc(num_cpustates, sizeof(*values));
399 			if (!values)
400 				err(1, NULL);
401 		}
402 		memset(values, 0, num_cpustates * sizeof(*values));
403 		for (cpu = 0; cpu < ncpu; cpu++) {
404 			if (!online[cpu])
405 				continue;
406 			names = cpustate_names;
407 			states = ostates + (CPUSTATES * cpu);
408 			i = 0;
409 			while ((thisname = *names++) != NULL) {
410 				if (*thisname != '\0') {
411 					/* retrieve the value and remember it */
412 					values[i++] += *states++;
413 				}
414 			}
415 		}
416 		if (screen_length > 2 || !smart_terminal) {
417 			names = cpustate_names;
418 			i = 0;
419 			first = 0;
420 			move(2, 0);
421 			clrtoeol();
422 			printwp("%-3d CPUs: ", ncpuonline);
423 
424 			while ((thisname = *names++) != NULL) {
425 				if (*thisname != '\0') {
426 					value = values[i++] / ncpuonline;
427 					/* if percentage is >= 1000, print it as 100% */
428 					printwp((value >= 1000 ? "%s%4.0f%% %s" :
429 					    "%s%4.1f%% %s"), first++ == 0 ? "" : ", ",
430 					    value / 10., thisname);
431 				}
432 			}
433 			putn();
434 		}
435 		return;
436 	}
437 	for (cpu = 0; cpu < ncpu; cpu++) {
438 		/* now walk thru the names and print the line */
439 		names = cpustate_names;
440 		first = 0;
441 		states = ostates + (CPUSTATES * cpu);
442 
443 		if ((screen_length > 2 + cpu && 2 + cpu < y_mem) ||
444 		    !smart_terminal) {
445 			move(2 + cpu, 0);
446 			clrtoeol();
447 			addstrp(cpustates_tag(cpu));
448 
449 			while ((thisname = *names++) != NULL) {
450 				if (*thisname != '\0') {
451 					if (!online[cpu]) {
452 						printwp("%s%5s %s",
453 						    first++ == 0 ? "" : ", ",
454 						    "-", thisname);
455 						continue;
456 					}
457 					/* retrieve the value and remember it */
458 					value = *states++;
459 
460 					/* if percentage is >= 1000, print it as 100% */
461 					printwp((value >= 1000 ? "%s%4.0f%% %s" :
462 					    "%s%4.1f%% %s"), first++ == 0 ? "" : ", ",
463 					    value / 10., thisname);
464 				}
465 			}
466 			putn();
467 		}
468 	}
469 }
470 
471 /*
472  *  *_memory(stats) - print "Memory: " followed by the memory summary string
473  */
474 void
475 i_memory(int *stats)
476 {
477 	if (screen_length > y_mem || !smart_terminal) {
478 		char memory_buffer[MAX_COLS];
479 
480 		move(y_mem, 0);
481 		clrtoeol();
482 		addstrp("Memory: ");
483 
484 		/* format and print the memory summary */
485 		summary_format(memory_buffer, sizeof(memory_buffer), stats,
486 		    memory_names);
487 		addstrp(memory_buffer);
488 		putn();
489 	}
490 }
491 
492 /*
493  *  *_message() - print the next pending message line, or erase the one
494  *                that is there.
495  */
496 
497 /*
498  *  i_message is funny because it gets its message asynchronously (with
499  *	respect to screen updates).
500  */
501 
502 static char     next_msg[MAX_COLS + 5];
503 static int      msgon = 0;
504 
505 void
506 i_message(void)
507 {
508 	move(y_message, 0);
509 	if (next_msg[0] != '\0') {
510 		standoutp();
511 		addstrp(next_msg);
512 		standendp();
513 		clrtoeol();
514 		msgon = TRUE;
515 		next_msg[0] = '\0';
516 	} else if (msgon) {
517 		clrtoeol();
518 		msgon = FALSE;
519 	}
520 }
521 
522 /*
523  *  *_header(text) - print the header for the process area
524  */
525 
526 void
527 i_header(char *text)
528 {
529 	if (header_status == Yes && (screen_length > y_header
530               || !smart_terminal)) {
531 		if (!smart_terminal) {
532 			putn();
533 			if (fputs(text, stdout) == EOF)
534 				exit(1);
535 			putn();
536 		} else {
537 			move(y_header, 0);
538 			clrtoeol();
539 			addstrp(text);
540 		}
541 	}
542 }
543 
544 /*
545  *  *_process(line, thisline) - print one process line
546  */
547 
548 void
549 i_process(int line, char *thisline, int hl)
550 {
551 	/* make sure we are on the correct line */
552 	move(y_procs + line, 0);
553 
554 	/* truncate the line to conform to our current screen width */
555 	thisline[display_width] = '\0';
556 
557 	/* write the line out */
558 	if (hl && smart_terminal)
559 		standoutp();
560 	addstrp(thisline);
561 	if (hl && smart_terminal)
562 		standendp();
563 	putn();
564 	clrtoeol();
565 }
566 
567 void
568 u_endscreen(void)
569 {
570 	if (smart_terminal) {
571 		clrtobot();
572 		/* move the cursor to a pleasant place */
573 		move(y_idlecursor, x_idlecursor);
574 	} else {
575 		/*
576 		 * separate this display from the next with some vertical
577 		 * room
578 		 */
579 		if (fputs("\n\n", stdout) == EOF)
580 			exit(1);
581 	}
582 }
583 
584 void
585 display_header(int status)
586 {
587 	header_status = status;
588 }
589 
590 void
591 new_message(int type, const char *msgfmt,...)
592 {
593 	va_list ap;
594 
595 	va_start(ap, msgfmt);
596 	/* first, format the message */
597 	vsnprintf(next_msg, sizeof(next_msg), msgfmt, ap);
598 	va_end(ap);
599 
600 	if (next_msg[0] != '\0') {
601 		/* message there already -- can we clear it? */
602 		/* yes -- write it and clear to end */
603 		if ((type & MT_delayed) == 0) {
604 			move(y_message, 0);
605 			if (type & MT_standout)
606 				standoutp();
607 			addstrp(next_msg);
608 			if (type & MT_standout)
609 				standendp();
610 			clrtoeol();
611 			msgon = TRUE;
612 			next_msg[0] = '\0';
613 			if (smart_terminal)
614 				refresh();
615 		}
616 	}
617 }
618 
619 void
620 clear_message(void)
621 {
622 	move(y_message, 0);
623 	clrtoeol();
624 }
625 
626 
627 static int
628 readlinedumb(char *buffer, int size)
629 {
630 	char *ptr = buffer, ch, cnt = 0, maxcnt = 0;
631 	extern volatile sig_atomic_t leaveflag;
632 	ssize_t len;
633 
634 	/* allow room for null terminator */
635 	size -= 1;
636 
637 	/* read loop */
638 	while ((fflush(stdout), (len = read(STDIN_FILENO, ptr, 1)) > 0)) {
639 
640 		if (len == 0 || leaveflag) {
641 			end_screen();
642 			exit(0);
643 		}
644 
645 		/* newline means we are done */
646 		if ((ch = *ptr) == '\n')
647 			break;
648 
649 		/* handle special editing characters */
650 		if (ch == ch_kill) {
651 			/* return null string */
652 			*buffer = '\0';
653 			putr();
654 			return (-1);
655 		} else if (ch == ch_erase) {
656 			/* erase previous character */
657 			if (cnt <= 0) {
658 				/* none to erase! */
659 				if (putchar('\7') == EOF)
660 					exit(1);
661 			} else {
662 				if (fputs("\b \b", stdout) == EOF)
663 					exit(1);
664 				ptr--;
665 				cnt--;
666 			}
667 		}
668 		/* check for character validity and buffer overflow */
669 		else if (cnt == size || !isprint((unsigned char)ch)) {
670 			/* not legal */
671 			if (putchar('\7') == EOF)
672 				exit(1);
673 		} else {
674 			/* echo it and store it in the buffer */
675 			if (putchar(ch) == EOF)
676 				exit(1);
677 			ptr++;
678 			cnt++;
679 			if (cnt > maxcnt)
680 				maxcnt = cnt;
681 		}
682 	}
683 
684 	/* all done -- null terminate the string */
685 	*ptr = '\0';
686 
687 	/* return either inputted number or string length */
688 	putr();
689 	return (cnt == 0 ? -1 : cnt);
690 }
691 
692 int
693 readline(char *buffer, int size)
694 {
695 	size_t cnt;
696 
697 	/* allow room for null terminator */
698 	size -= 1;
699 
700 	if (smart_terminal) {
701 		int y, x;
702 		getyx(stdscr, y, x);
703 		while (getnstr(buffer, size) == KEY_RESIZE)
704 			move(y, x);
705 	} else
706 		return readlinedumb(buffer, size);
707 
708 	cnt = strlen(buffer);
709 	if (cnt > 0 && buffer[cnt - 1] == '\n')
710 		buffer[cnt - 1] = '\0';
711 	return (cnt == 0 ? -1 : cnt);
712 }
713 
714 /* internal support routines */
715 static int
716 string_count(char **pp)
717 {
718 	int cnt;
719 
720 	cnt = 0;
721 	while (*pp++ != NULL)
722 		cnt++;
723 	return (cnt);
724 }
725 
726 #define	COPYLEFT(to, from)				\
727 	do {						\
728 		len = strlcpy((to), (from), left);	\
729 		if (len >= left)			\
730 			return;				\
731 		p += len;				\
732 		left -= len;				\
733 	} while (0)
734 
735 static void
736 summary_format(char *buf, size_t left, int *numbers, char **names)
737 {
738 	char *p, *thisname;
739 	size_t len;
740 	int num;
741 
742 	/* format each number followed by its string */
743 	p = buf;
744 	while ((thisname = *names++) != NULL) {
745 		/* get the number to format */
746 		num = *numbers++;
747 
748 		if (num >= 0) {
749 			/* is this number in kilobytes? */
750 			if (thisname[0] == 'K') {
751 				/* yes: format it as a memory value */
752 				COPYLEFT(p, format_k(num));
753 
754 				/*
755 				 * skip over the K, since it was included by
756 				 * format_k
757 				 */
758 				COPYLEFT(p, thisname + 1);
759 			} else if (num > 0) {
760 				len = snprintf(p, left, "%d%s", num, thisname);
761 				if (len == (size_t)-1 || len >= left)
762 					return;
763 				p += len;
764 				left -= len;
765 			}
766 		} else {
767 			/*
768 			 * Ignore negative numbers, but display corresponding
769 			 * string.
770 			 */
771 			COPYLEFT(p, thisname);
772 		}
773 	}
774 
775 	/* if the last two characters in the string are ", ", delete them */
776 	p -= 2;
777 	if (p >= buf && p[0] == ',' && p[1] == ' ')
778 		*p = '\0';
779 }
780 
781 /*
782  *  printable(str) - make the string pointed to by "str" into one that is
783  *	printable (i.e.: all ascii), by converting all non-printable
784  *	characters into '?'.  Replacements are done in place and a pointer
785  *	to the original buffer is returned.
786  */
787 char *
788 printable(char *str)
789 {
790 	char *ptr, ch;
791 
792 	ptr = str;
793 	while ((ch = *ptr) != '\0') {
794 		if (!isprint((unsigned char)ch))
795 			*ptr = '?';
796 		ptr++;
797 	}
798 	return (str);
799 }
800 
801 
802 /*
803  *  show_help() - display the help screen; invoked in response to
804  *		either 'h' or '?'.
805  */
806 void
807 show_help(void)
808 {
809 	if (smart_terminal) {
810 		clear();
811 		nl();
812 	}
813 	printwp("These single-character commands are available:\n"
814 	    "\n"
815 	    "^L           - redraw screen\n"
816 	    "<space>      - update screen\n"
817 	    "+            - reset any g, p, or u filters\n"
818 	    "1            - display CPU statistics on a single line\n"
819 	    "C            - toggle the display of command line arguments\n"
820 	    "d count      - show `count' displays, then exit\n"
821 	    "e            - list errors generated by last \"kill\" or \"renice\" command\n"
822 	    "g string     - filter on command name (g+ selects all commands)\n"
823 	    "h | ?        - help; show this text\n"
824 	    "H            - toggle the display of threads\n"
825 	    "I | i        - toggle the display of idle processes\n"
826 	    "k [-sig] pid - send signal `-sig' to process `pid'\n"
827 	    "n|# count    - show `count' processes\n"
828 	    "o field      - specify sort order (size, res, cpu, time, pri, pid, command)\n"
829 	    "P pid        - highlight process `pid' (P+ switches highlighting off)\n"
830 	    "p pid        - display process by `pid' (p+ selects all processes)\n"
831 	    "q            - quit\n"
832 	    "r count pid  - renice process `pid' to nice value `count'\n"
833 	    "S            - toggle the display of system processes\n"
834 	    "s time       - change delay between displays to `time' seconds\n"
835 	    "u [-]user    - show processes for `user' (u+ shows all, u -user hides user)\n"
836 	    "\n");
837 
838 	if (smart_terminal) {
839 		nonl();
840 		refresh();
841 	}
842 }
843 
844 /*
845  *  show_errors() - display on stdout the current log of errors.
846  */
847 void
848 show_errors(void)
849 {
850 	struct errs *errp = errs;
851 	int cnt = 0;
852 
853 	if (smart_terminal) {
854 		clear();
855 		nl();
856 	}
857 	printwp("%d error%s:\n\n", errcnt, errcnt == 1 ? "" : "s");
858 	while (cnt++ < errcnt) {
859 		printwp("%5s: %s\n", errp->arg,
860 		    errp->err == 0 ? "Not a number" : strerror(errp->err));
861 		errp++;
862 	}
863 	printwp("\n");
864 	if (smart_terminal) {
865 		nonl();
866 		refresh();
867 	}
868 }
869 
870 void
871 anykey(void)
872 {
873 	int ch;
874 	ssize_t len;
875 
876 	standoutp();
877 	addstrp("Hit any key to continue: ");
878 	standendp();
879 	if (smart_terminal)
880 		refresh();
881 	else
882 		fflush(stdout);
883 	while (1) {
884 		len = read(STDIN_FILENO, &ch, 1);
885 		if (len == -1 && errno == EINTR)
886 			continue;
887 		if (len == 0)
888 			exit(1);
889 		break;
890 	}
891 }
892