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