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