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