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