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