xref: /openbsd-src/usr.bin/top/display.c (revision 8500990981f885cbe5e6a4958549cacc238b5ae6)
1 /* $OpenBSD: display.c,v 1.17 2003/11/01 20:20:57 deraadt 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 nonexistant) 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 <stdio.h>
50 #include <ctype.h>
51 #include <stdlib.h>
52 #include <string.h>
53 #include <signal.h>
54 #include <term.h>
55 #include <time.h>
56 #include <unistd.h>
57 #include <stdarg.h>
58 
59 #include "screen.h"		/* interface to screen package */
60 #include "layout.h"		/* defines for screen position layout */
61 #include "display.h"
62 #include "top.h"
63 #include "top.local.h"
64 #include "boolean.h"
65 #include "machine.h"		/* we should eliminate this!!! */
66 #include "utils.h"
67 
68 #ifdef DEBUG
69 FILE           *debug;
70 #endif
71 
72 static pid_t    lmpid = 0;
73 static int      last_hi = 0;	/* used in u_process and u_endscreen */
74 static int      lastline = 0;
75 static int      display_width = MAX_COLS;
76 
77 static char    *cpustates_tag(void);
78 static int      string_count(char **);
79 static void     summary_format(char *, size_t, int *, char **);
80 static void     line_update(char *, char *, int, int);
81 
82 #define lineindex(l) ((l)*display_width)
83 
84 /* things initialized by display_init and used thruout */
85 
86 /* buffer of proc information lines for display updating */
87 char           *screenbuf = NULL;
88 
89 static char   **procstate_names;
90 static char   **cpustate_names;
91 static char   **memory_names;
92 
93 static int      num_procstates;
94 static int      num_cpustates;
95 
96 static int     *lprocstates;
97 static int     *lcpustates;
98 
99 static int     *cpustate_columns;
100 static int      cpustate_total_length;
101 
102 static enum {
103 	OFF, ON, ERASE
104 } header_status = ON;
105 
106 int
107 display_resize(void)
108 {
109 	int display_lines;
110 
111 	/* first, deallocate any previous buffer that may have been there */
112 	if (screenbuf != NULL)
113 		free(screenbuf);
114 
115 	/* calculate the current dimensions */
116 	/* if operating in "dumb" mode, we only need one line */
117 	display_lines = smart_terminal ? screen_length - Header_lines : 1;
118 
119 	/*
120 	 * we don't want more than MAX_COLS columns, since the
121 	 * machine-dependent modules make static allocations based on
122 	 * MAX_COLS and we don't want to run off the end of their buffers
123 	 */
124 	display_width = screen_width;
125 	if (display_width >= MAX_COLS)
126 		display_width = MAX_COLS - 1;
127 
128 	/* now, allocate space for the screen buffer */
129 	screenbuf = (char *) malloc(display_lines * display_width);
130 	if (screenbuf == (char *) NULL)
131 		return (-1);
132 
133 	/* return number of lines available */
134 	/* for dumb terminals, pretend like we can show any amount */
135 	return (smart_terminal ? display_lines : Largest);
136 }
137 
138 int
139 display_init(struct statics * statics)
140 {
141 	int display_lines, *ip, i;
142 	char **pp;
143 
144 	/* call resize to do the dirty work */
145 	display_lines = display_resize();
146 
147 	/* only do the rest if we need to */
148 	if (display_lines > -1) {
149 		/* save pointers and allocate space for names */
150 		procstate_names = statics->procstate_names;
151 		num_procstates = string_count(procstate_names);
152 		lprocstates = (int *) malloc(num_procstates * sizeof(int));
153 
154 		cpustate_names = statics->cpustate_names;
155 		num_cpustates = string_count(cpustate_names);
156 		lcpustates = (int *) malloc(num_cpustates * sizeof(int));
157 		cpustate_columns = (int *) malloc(num_cpustates * sizeof(int));
158 
159 		memory_names = statics->memory_names;
160 
161 		/* calculate starting columns where needed */
162 		cpustate_total_length = 0;
163 		pp = cpustate_names;
164 		ip = cpustate_columns;
165 		while (*pp != NULL) {
166 			if ((i = strlen(*pp++)) > 0) {
167 				*ip++ = cpustate_total_length;
168 				cpustate_total_length += i + 8;
169 			}
170 		}
171 	}
172 	/* return number of lines available */
173 	return (display_lines);
174 }
175 
176 void
177 i_loadave(pid_t mpid, double *avenrun)
178 {
179 	int i;
180 
181 	/* i_loadave also clears the screen, since it is first */
182 	clear();
183 
184 	/* mpid == -1 implies this system doesn't have an _mpid */
185 	if (mpid != -1)
186 		printf("last pid: %5ld;  ", (long) mpid);
187 
188 	printf("load averages");
189 
190 	for (i = 0; i < 3; i++)
191 		printf("%c %5.2f", i == 0 ? ':' : ',', avenrun[i]);
192 
193 	lmpid = mpid;
194 }
195 
196 void
197 u_loadave(pid_t mpid, double *avenrun)
198 {
199 	int i;
200 
201 	if (mpid != -1) {
202 		/* change screen only when value has really changed */
203 		if (mpid != lmpid) {
204 			Move_to(x_lastpid, y_lastpid);
205 			printf("%5ld", (long) mpid);
206 			lmpid = mpid;
207 		}
208 		/* i remembers x coordinate to move to */
209 		i = x_loadave;
210 	} else
211 		i = x_loadave_nompid;
212 
213 	/* move into position for load averages */
214 	Move_to(i, y_loadave);
215 
216 	/* display new load averages */
217 	/* we should optimize this and only display changes */
218 	for (i = 0; i < 3; i++)
219 		printf("%s%5.2f", i == 0 ? "" : ", ", avenrun[i]);
220 }
221 
222 /*
223  *  Display the current time.
224  *  "ctime" always returns a string that looks like this:
225  *
226  *	Sun Sep 16 01:03:52 1973
227  *      012345678901234567890123
228  *	          1         2
229  *
230  *  We want indices 11 thru 18 (length 8).
231  */
232 
233 void
234 i_timeofday(time_t * tod)
235 {
236 
237 	if (smart_terminal) {
238 		Move_to(screen_width - 8, 0);
239 	} else {
240 		if (fputs("    ", stdout) == EOF)
241 			exit(1);
242 	}
243 #ifdef DEBUG
244 	{
245 		char *foo;
246 		foo = ctime(tod);
247 		if (fputs(foo, stdout) == EOF)
248 			exit(1);
249 	}
250 #endif
251 	printf("%-8.8s\n", &(ctime(tod)[11]));
252 	lastline = 1;
253 }
254 
255 static int      ltotal = 0;
256 static char     procstates_buffer[128];
257 
258 /*
259  *  *_procstates(total, brkdn, names) - print the process summary line
260  *
261  *  Assumptions:  cursor is at the beginning of the line on entry
262  *		  lastline is valid
263  */
264 void
265 i_procstates(int total, int *brkdn)
266 {
267 	int i;
268 
269 	/* write current number of processes and remember the value */
270 	printf("%d processes:", total);
271 	ltotal = total;
272 
273 	/* put out enough spaces to get to column 15 */
274 	i = digits(total);
275 	while (i++ < 4) {
276 		if (putchar(' ') == EOF)
277 			exit(1);
278 	}
279 
280 	/* format and print the process state summary */
281 	summary_format(procstates_buffer, sizeof(procstates_buffer), brkdn,
282 	    procstate_names);
283 	if (fputs(procstates_buffer, stdout) == EOF)
284 		exit(1);
285 
286 	/* save the numbers for next time */
287 	memcpy(lprocstates, brkdn, num_procstates * sizeof(int));
288 }
289 
290 void
291 u_procstates(int total, int *brkdn)
292 {
293 	static char new[128];
294 	int i;
295 
296 	/* update number of processes only if it has changed */
297 	if (ltotal != total) {
298 		/* move and overwrite */
299 #if (x_procstate == 0)
300 		Move_to(x_procstate, y_procstate);
301 #else
302 		/* cursor is already there...no motion needed */
303 		/* assert(lastline == 1); */
304 #endif
305 		printf("%d", total);
306 
307 		/* if number of digits differs, rewrite the label */
308 		if (digits(total) != digits(ltotal)) {
309 			if (fputs(" processes:", stdout) == EOF)
310 				exit(1);
311 			/* put out enough spaces to get to column 15 */
312 			i = digits(total);
313 			while (i++ < 4) {
314 				if (putchar(' ') == EOF)
315 					exit(1);
316 			}
317 			/* cursor may end up right where we want it!!! */
318 		}
319 		/* save new total */
320 		ltotal = total;
321 	}
322 	/* see if any of the state numbers has changed */
323 	if (memcmp(lprocstates, brkdn, num_procstates * sizeof(int)) != 0) {
324 		/* format and update the line */
325 		summary_format(new, sizeof(new), brkdn, procstate_names);
326 		line_update(procstates_buffer, new, x_brkdn, y_brkdn);
327 		memcpy(lprocstates, brkdn, num_procstates * sizeof(int));
328 	}
329 }
330 
331 /*
332  *  *_cpustates(states, names) - print the cpu state percentages
333  *
334  *  Assumptions:  cursor is on the PREVIOUS line
335  */
336 
337 static int      cpustates_column;
338 
339 /* cpustates_tag() calculates the correct tag to use to label the line */
340 
341 static char *
342 cpustates_tag(void)
343 {
344 	static char *short_tag = "CPU: ";
345 	static char *long_tag = "CPU states: ";
346 	char *use;
347 
348 	/*
349 	 * if length + strlen(long_tag) >= screen_width, then we have to use
350 	 * the shorter tag (we subtract 2 to account for ": ")
351 	 */
352 	if (cpustate_total_length + (int) strlen(long_tag) - 2 >= screen_width)
353 		use = short_tag;
354 	else
355 		use = long_tag;
356 
357 	/* set cpustates_column accordingly then return result */
358 	cpustates_column = strlen(use);
359 	return (use);
360 }
361 
362 void
363 i_cpustates(int *states)
364 {
365 	int i = 0, value;
366 	char **names = cpustate_names, *thisname;
367 
368 	/* print tag and bump lastline */
369 	printf("\n%s", cpustates_tag());
370 	lastline++;
371 
372 	/* now walk thru the names and print the line */
373 	while ((thisname = *names++) != NULL) {
374 		if (*thisname != '\0') {
375 			/* retrieve the value and remember it */
376 			value = *states++;
377 
378 			/* if percentage is >= 1000, print it as 100% */
379 			printf((value >= 1000 ? "%s%4.0f%% %s" : "%s%4.1f%% %s"),
380 			    i++ == 0 ? "" : ", ",
381 			    ((float) value) / 10.,
382 			    thisname);
383 		}
384 	}
385 
386 	/* copy over values into "last" array */
387 	memcpy(lcpustates, states, num_cpustates * sizeof(int));
388 }
389 
390 void
391 u_cpustates(int *states)
392 {
393 	char **names = cpustate_names, *thisname;
394 	int value, *lp, *colp;
395 
396 	Move_to(cpustates_column, y_cpustates);
397 	lastline = y_cpustates;
398 	lp = lcpustates;
399 	colp = cpustate_columns;
400 
401 	/* we could be much more optimal about this */
402 	while ((thisname = *names++) != NULL) {
403 		if (*thisname != '\0') {
404 			/* did the value change since last time? */
405 			if (*lp != *states) {
406 				/* yes, move and change */
407 				Move_to(cpustates_column + *colp, y_cpustates);
408 				lastline = y_cpustates;
409 
410 				/* retrieve value and remember it */
411 				value = *states;
412 
413 				/* if percentage is >= 1000, print it as 100% */
414 				printf((value >= 1000 ? "%4.0f" : "%4.1f"),
415 				    ((double) value) / 10.);
416 
417 				/* remember it for next time */
418 				*lp = *states;
419 			}
420 		}
421 		/* increment and move on */
422 		lp++;
423 		states++;
424 		colp++;
425 	}
426 }
427 
428 void
429 z_cpustates(void)
430 {
431 	char **names = cpustate_names, *thisname;
432 	int i = 0, *lp;
433 
434 	/* show tag and bump lastline */
435 	printf("\n%s", cpustates_tag());
436 	lastline++;
437 
438 	while ((thisname = *names++) != NULL) {
439 		if (*thisname != '\0')
440 			printf("%s    %% %s", i++ == 0 ? "" : ", ", thisname);
441 	}
442 
443 	/* fill the "last" array with all -1s, to insure correct updating */
444 	lp = lcpustates;
445 	i = num_cpustates;
446 	while (--i >= 0)
447 		*lp++ = -1;
448 }
449 
450 static char     memory_buffer[MAX_COLS];
451 
452 /*
453  *  *_memory(stats) - print "Memory: " followed by the memory summary string
454  *
455  *  Assumptions:  cursor is on "lastline"
456  *                for i_memory ONLY: cursor is on the previous line
457  */
458 void
459 i_memory(int *stats)
460 {
461 	if (fputs("\nMemory: ", stdout) == EOF)
462 		exit(1);
463 	lastline++;
464 
465 	/* format and print the memory summary */
466 	summary_format(memory_buffer, sizeof(memory_buffer), stats,
467 	    memory_names);
468 	if (fputs(memory_buffer, stdout) == EOF)
469 		exit(1);
470 }
471 
472 void
473 u_memory(int *stats)
474 {
475 	static char new[MAX_COLS];
476 
477 	/* format the new line */
478 	summary_format(new, sizeof(new), stats, memory_names);
479 	line_update(memory_buffer, new, x_mem, y_mem);
480 }
481 
482 /*
483  *  *_message() - print the next pending message line, or erase the one
484  *                that is there.
485  *
486  *  Note that u_message is (currently) the same as i_message.
487  *
488  *  Assumptions:  lastline is consistent
489  */
490 
491 /*
492  *  i_message is funny because it gets its message asynchronously (with
493  *	respect to screen updates).
494  */
495 
496 static char     next_msg[MAX_COLS + 5];
497 static int      msglen = 0;
498 /*
499  * Invariant: msglen is always the length of the message currently displayed
500  * on the screen (even when next_msg doesn't contain that message).
501  */
502 
503 void
504 i_message(void)
505 {
506 	while (lastline < y_message) {
507 		if (fputc('\n', stdout) == EOF)
508 			exit(1);
509 		lastline++;
510 	}
511 	if (next_msg[0] != '\0') {
512 		standout(next_msg);
513 		msglen = strlen(next_msg);
514 		next_msg[0] = '\0';
515 	} else if (msglen > 0) {
516 		(void) clear_eol(msglen);
517 		msglen = 0;
518 	}
519 }
520 
521 void
522 u_message(void)
523 {
524 	i_message();
525 }
526 
527 static int      header_length;
528 
529 /*
530  *  *_header(text) - print the header for the process area
531  *
532  *  Assumptions:  cursor is on the previous line and lastline is consistent
533  */
534 
535 void
536 i_header(char *text)
537 {
538 	header_length = strlen(text);
539 	if (header_status == ON) {
540 		if (putchar('\n') == EOF)
541 			exit(1);
542 		if (fputs(text, stdout) == EOF)
543 			exit(1);
544 		lastline++;
545 	} else if (header_status == ERASE) {
546 		header_status = OFF;
547 	}
548 }
549 
550 /* ARGSUSED */
551 void
552 u_header(char *text)
553 {
554 	if (header_status == ERASE) {
555 		if (putchar('\n') == EOF)
556 			exit(1);
557 		lastline++;
558 		clear_eol(header_length);
559 		header_status = OFF;
560 	}
561 }
562 
563 /*
564  *  *_process(line, thisline) - print one process line
565  *
566  *  Assumptions:  lastline is consistent
567  */
568 
569 void
570 i_process(int line, char *thisline)
571 {
572 	char *base;
573 	size_t len;
574 
575 	/* make sure we are on the correct line */
576 	while (lastline < y_procs + line) {
577 		if (putchar('\n') == EOF)
578 			exit(1);
579 		lastline++;
580 	}
581 
582 	/* truncate the line to conform to our current screen width */
583 	thisline[display_width] = '\0';
584 
585 	/* write the line out */
586 	if (fputs(thisline, stdout) == EOF)
587 		exit(1);
588 
589 	/* copy it in to our buffer */
590 	base = smart_terminal ? screenbuf + lineindex(line) : screenbuf;
591 	len = strlcpy(base, thisline, display_width);
592 	if (len < (size_t)display_width) {
593 		/* zero fill the rest of it */
594 		memset(base + len, 0, display_width - len);
595 	}
596 }
597 
598 void
599 u_process(int linenum, char *linebuf)
600 {
601 	int screen_line = linenum + Header_lines;
602 	char *bufferline;
603 	size_t len;
604 
605 	/* remember a pointer to the current line in the screen buffer */
606 	bufferline = &screenbuf[lineindex(linenum)];
607 
608 	/* truncate the line to conform to our current screen width */
609 	linebuf[display_width] = '\0';
610 
611 	/* is line higher than we went on the last display? */
612 	if (linenum >= last_hi) {
613 		/* yes, just ignore screenbuf and write it out directly */
614 		/* get positioned on the correct line */
615 		if (screen_line - lastline == 1) {
616 			if (putchar('\n') == EOF)
617 				exit(1);
618 			lastline++;
619 		} else {
620 			Move_to(0, screen_line);
621 			lastline = screen_line;
622 		}
623 
624 		/* now write the line */
625 		if (fputs(linebuf, stdout) == EOF)
626 			exit(1);
627 
628 		/* copy it in to the buffer */
629 		len = strlcpy(bufferline, linebuf, display_width);
630 		if (len < (size_t)display_width) {
631 			/* zero fill the rest of it */
632 			memset(bufferline + len, 0, display_width - len);
633 		}
634 	} else {
635 		line_update(bufferline, linebuf, 0, linenum + Header_lines);
636 	}
637 }
638 
639 void
640 u_endscreen(int hi)
641 {
642 	int screen_line = hi + Header_lines, i;
643 
644 	if (smart_terminal) {
645 		if (hi < last_hi) {
646 			/* need to blank the remainder of the screen */
647 			/*
648 			 * but only if there is any screen left below this
649 			 * line
650 			 */
651 			if (lastline + 1 < screen_length) {
652 				/*
653 				 * efficiently move to the end of currently
654 				 * displayed info
655 				 */
656 				if (screen_line - lastline < 5) {
657 					while (lastline < screen_line) {
658 						if (putchar('\n') == EOF)
659 							exit(1);
660 						lastline++;
661 					}
662 				} else {
663 					Move_to(0, screen_line);
664 					lastline = screen_line;
665 				}
666 
667 				if (clear_to_end) {
668 					/* we can do this the easy way */
669 					putcap(clear_to_end);
670 				} else {
671 					/* use clear_eol on each line */
672 					i = hi;
673 					while ((void) clear_eol(strlen(&screenbuf[lineindex(i++)])), i < last_hi) {
674 						if (putchar('\n') == EOF)
675 							exit(1);
676 					}
677 				}
678 			}
679 		}
680 		last_hi = hi;
681 
682 		/* move the cursor to a pleasant place */
683 		Move_to(x_idlecursor, y_idlecursor);
684 		lastline = y_idlecursor;
685 	} else {
686 		/*
687 		 * separate this display from the next with some vertical
688 		 * room
689 		 */
690 		if (fputs("\n\n", stdout) == EOF)
691 			exit(1);
692 	}
693 }
694 
695 void
696 display_header(int t)
697 {
698 	if (t) {
699 		header_status = ON;
700 	} else if (header_status == ON) {
701 		header_status = ERASE;
702 	}
703 }
704 
705 void
706 new_message(int type, const char *msgfmt,...)
707 {
708 	va_list ap;
709 	int i;
710 
711 	va_start(ap, msgfmt);
712 	/* first, format the message */
713 	vsnprintf(next_msg, sizeof(next_msg), msgfmt, ap);
714 	va_end(ap);
715 
716 	if (msglen > 0) {
717 		/* message there already -- can we clear it? */
718 		if (!overstrike) {
719 			/* yes -- write it and clear to end */
720 			i = strlen(next_msg);
721 			if ((type & MT_delayed) == 0) {
722 				if (type & MT_standout)
723 					standout(next_msg);
724 				else {
725 					if (fputs(next_msg, stdout) == EOF)
726 						exit(1);
727 				}
728 				(void) clear_eol(msglen - i);
729 				msglen = i;
730 				next_msg[0] = '\0';
731 			}
732 		}
733 	} else {
734 		if ((type & MT_delayed) == 0) {
735 			if (type & MT_standout)
736 				standout(next_msg);
737 			else {
738 				if (fputs(next_msg, stdout) == EOF)
739 					exit(1);
740 			}
741 			msglen = strlen(next_msg);
742 			next_msg[0] = '\0';
743 		}
744 	}
745 }
746 
747 void
748 clear_message(void)
749 {
750 	if (clear_eol(msglen) == 1) {
751 		if (putchar('\r') == EOF)
752 			exit(1);
753 	}
754 }
755 
756 int
757 readline(char *buffer, int size, int numeric)
758 {
759 	char *ptr = buffer, ch, cnt = 0, maxcnt = 0;
760 	extern volatile sig_atomic_t leaveflag;
761 	ssize_t len;
762 
763 	/* allow room for null terminator */
764 	size -= 1;
765 
766 	/* read loop */
767 	while ((fflush(stdout), (len = read(0, ptr, 1)) > 0)) {
768 
769 		if (len == 0 || leaveflag) {
770 			end_screen();
771 			exit(0);
772 		}
773 
774 		/* newline means we are done */
775 		if ((ch = *ptr) == '\n')
776 			break;
777 
778 		/* handle special editing characters */
779 		if (ch == ch_kill) {
780 			/* kill line -- account for overstriking */
781 			if (overstrike)
782 				msglen += maxcnt;
783 
784 			/* return null string */
785 			*buffer = '\0';
786 			if (putchar('\r') == EOF)
787 				exit(1);
788 			return (-1);
789 		} else if (ch == ch_erase) {
790 			/* erase previous character */
791 			if (cnt <= 0) {
792 				/* none to erase! */
793 				if (putchar('\7') == EOF)
794 					exit(1);
795 			} else {
796 				if (fputs("\b \b", stdout) == EOF)
797 					exit(1);
798 				ptr--;
799 				cnt--;
800 			}
801 		}
802 		/* check for character validity and buffer overflow */
803 		else if (cnt == size || (numeric && !isdigit(ch)) ||
804 		    !isprint(ch)) {
805 			/* not legal */
806 			if (putchar('\7') == EOF)
807 				exit(1);
808 		} else {
809 			/* echo it and store it in the buffer */
810 			if (putchar(ch) == EOF)
811 				exit(1);
812 			ptr++;
813 			cnt++;
814 			if (cnt > maxcnt)
815 				maxcnt = cnt;
816 		}
817 	}
818 
819 	/* all done -- null terminate the string */
820 	*ptr = '\0';
821 
822 	/* account for the extra characters in the message area */
823 	/* (if terminal overstrikes, remember the furthest they went) */
824 	msglen += overstrike ? maxcnt : cnt;
825 
826 	/* return either inputted number or string length */
827 	if (putchar('\r') == EOF)
828 		exit(1);
829 	return (cnt == 0 ? -1 : numeric ? atoi(buffer) : cnt);
830 }
831 
832 /* internal support routines */
833 static int
834 string_count(char **pp)
835 {
836 	int cnt;
837 
838 	cnt = 0;
839 	while (*pp++ != NULL)
840 		cnt++;
841 	return (cnt);
842 }
843 
844 #define	COPYLEFT(to, from)				\
845 	do {						\
846 		len = strlcpy((to), (from), left);	\
847 		if (len >= left)			\
848 			return;				\
849 		p += len;				\
850 		left -= len;				\
851 	} while (0)
852 
853 static void
854 summary_format(char *buf, size_t left, int *numbers, char **names)
855 {
856 	char *p, *thisname;
857 	size_t len;
858 	int num;
859 
860 	/* format each number followed by its string */
861 	p = buf;
862 	while ((thisname = *names++) != NULL) {
863 		/* get the number to format */
864 		num = *numbers++;
865 
866 		if (num >= 0) {
867 			/* is this number in kilobytes? */
868 			if (thisname[0] == 'K') {
869 				/* yes: format it as a memory value */
870 				COPYLEFT(p, format_k(num));
871 
872 				/*
873 				 * skip over the K, since it was included by
874 				 * format_k
875 				 */
876 				COPYLEFT(p, thisname + 1);
877 			} else if (num > 0) {
878 				len = snprintf(p, left, "%d%s", num, thisname);
879 				if (len == (size_t)-1 || len >= left)
880 					return;
881 				p += len;
882 				left -= len;
883 			}
884 		} else {
885 			/*
886 			 * Ignore negative numbers, but display corresponding
887 			 * string.
888 			 */
889 			COPYLEFT(p, thisname);
890 		}
891 	}
892 
893 	/* if the last two characters in the string are ", ", delete them */
894 	p -= 2;
895 	if (p >= buf && p[0] == ',' && p[1] == ' ')
896 		*p = '\0';
897 }
898 
899 static void
900 line_update(char *old, char *new, int start, int line)
901 {
902 	int ch, diff, newcol = start + 1, lastcol = start;
903 	char cursor_on_line = No, *current;
904 
905 	/* compare the two strings and only rewrite what has changed */
906 	current = old;
907 #ifdef DEBUG
908 	fprintf(debug, "line_update, starting at %d\n", start);
909 	fputs(old, debug);
910 	fputc('\n', debug);
911 	fputs(new, debug);
912 	fputs("\n-\n", debug);
913 #endif
914 
915 	/* start things off on the right foot		    */
916 	/* this is to make sure the invariants get set up right */
917 	if ((ch = *new++) != *old) {
918 		if (line - lastline == 1 && start == 0) {
919 			if (putchar('\n') == EOF)
920 				exit(1);
921 		} else
922 			Move_to(start, line);
923 
924 		cursor_on_line = Yes;
925 		if (putchar(ch) == EOF)
926 			exit(1);
927 		*old = ch;
928 		lastcol = 1;
929 	}
930 	old++;
931 
932 	/*
933 	 *  main loop -- check each character.  If the old and new aren't the
934 	 *	same, then update the display.  When the distance from the
935 	 *	current cursor position to the new change is small enough,
936 	 *	the characters that belong there are written to move the
937 	 *	cursor over.
938 	 *
939 	 *	Invariants:
940 	 *	    lastcol is the column where the cursor currently is sitting
941 	 *		(always one beyond the end of the last mismatch).
942 	 */
943 	do {
944 		if ((ch = *new++) != *old) {
945 			/* new character is different from old	  */
946 			/* make sure the cursor is on top of this character */
947 			diff = newcol - lastcol;
948 			if (diff > 0) {
949 				/*
950 				 * some motion is required--figure out which
951 				 * is shorter
952 				 */
953 				if (diff < 6 && cursor_on_line) {
954 					/*
955 					 * overwrite old stuff--get it out of
956 					 * the old buffer
957 					 */
958 					printf("%.*s", diff, &current[lastcol - start]);
959 				} else {
960 					/* use cursor addressing */
961 					Move_to(newcol, line);
962 					cursor_on_line = Yes;
963 				}
964 				/* remember where the cursor is */
965 				lastcol = newcol + 1;
966 			} else {
967 				/* already there, update position */
968 				lastcol++;
969 			}
970 
971 			/* write what we need to */
972 			if (ch == '\0') {
973 				/*
974 				 * at the end--terminate with a
975 				 * clear-to-end-of-line
976 				 */
977 				(void) clear_eol(strlen(old));
978 			} else {
979 				/* write the new character */
980 				if (putchar(ch) == EOF)
981 					exit(1);
982 			}
983 			/* put the new character in the screen buffer */
984 			*old = ch;
985 		}
986 		/* update working column and screen buffer pointer */
987 		newcol++;
988 		old++;
989 	} while (ch != '\0');
990 
991 	/* zero out the rest of the line buffer -- MUST BE DONE! */
992 	diff = display_width - newcol;
993 	if (diff > 0)
994 		memset(old, 0, diff);
995 
996 	/* remember where the current line is */
997 	if (cursor_on_line)
998 		lastline = line;
999 }
1000 
1001 /*
1002  *  printable(str) - make the string pointed to by "str" into one that is
1003  *	printable (i.e.: all ascii), by converting all non-printable
1004  *	characters into '?'.  Replacements are done in place and a pointer
1005  *	to the original buffer is returned.
1006  */
1007 char *
1008 printable(char *str)
1009 {
1010 	char *ptr, ch;
1011 
1012 	ptr = str;
1013 	while ((ch = *ptr) != '\0') {
1014 		if (!isprint(ch))
1015 			*ptr = '?';
1016 		ptr++;
1017 	}
1018 	return (str);
1019 }
1020