xref: /freebsd/usr.bin/top/display.c (revision 5e71fdc20dd45d9202261dd82cb68b16197beba6)
1 /*
2  *  Top users/processes display for Unix
3  *  Version 3
4  *
5  *  This program may be freely redistributed,
6  *  but this entire comment MUST remain intact.
7  *
8  *  Copyright (c) 1984, 1989, William LeFebvre, Rice University
9  *  Copyright (c) 1989, 1990, 1992, William LeFebvre, Northwestern University
10  *
11  * $FreeBSD$
12  */
13 
14 /*
15  *  This file contains the routines that display information on the screen.
16  *  Each section of the screen has two routines:  one for initially writing
17  *  all constant and dynamic text, and one for only updating the text that
18  *  changes.  The prefix "i_" is used on all the "initial" routines and the
19  *  prefix "u_" is used for all the "updating" routines.
20  *
21  *  ASSUMPTIONS:
22  *        None of the "i_" routines use any of the termcap capabilities.
23  *        In this way, those routines can be safely used on terminals that
24  *        have minimal (or nonexistant) terminal capabilities.
25  *
26  *        The routines are called in this order:  *_loadave, i_timeofday,
27  *        *_procstates, *_cpustates, *_memory, *_message, *_header,
28  *        *_process, u_endscreen.
29  */
30 
31 #include <sys/cdefs.h>
32 #include <sys/resource.h>
33 #include <sys/time.h>
34 
35 #include <assert.h>
36 #include <ctype.h>
37 #include <stdarg.h>
38 #include <stdbool.h>
39 #include <stdlib.h>
40 #include <stdio.h>
41 #include <string.h>
42 #include <termcap.h>
43 #include <time.h>
44 #include <unistd.h>
45 
46 #include "screen.h"		/* interface to screen package */
47 #include "layout.h"		/* defines for screen position layout */
48 #include "display.h"
49 #include "top.h"
50 #include "machine.h"		/* we should eliminate this!!! */
51 #include "utils.h"
52 
53 #ifdef DEBUG
54 FILE *debug;
55 #endif
56 
57 static int lmpid = 0;
58 static int last_hi = 0;		/* used in u_process and u_endscreen */
59 static int lastline = 0;
60 
61 #define lineindex(l) ((l)*screen_width)
62 
63 
64 /* things initialized by display_init and used thruout */
65 
66 /* buffer of proc information lines for display updating */
67 static char *screenbuf = NULL;
68 
69 static const char * const *procstate_names;
70 static const char * const *cpustate_names;
71 static const char * const *memory_names;
72 static const char * const *arc_names;
73 static const char * const *carc_names;
74 static const char * const *swap_names;
75 
76 static int num_procstates;
77 static int num_cpustates;
78 static int num_memory;
79 static int num_swap;
80 
81 static int *lprocstates;
82 static int *lcpustates;
83 static int *lmemory;
84 static int *lswap;
85 
86 static int num_cpus;
87 static int *cpustate_columns;
88 static int cpustate_total_length;
89 static int cpustates_column;
90 
91 static enum { OFF, ON, ERASE } header_status = ON;
92 
93 static void summary_format(char *, int *, const char * const *);
94 static void line_update(char *, char *, int, int);
95 
96 static int setup_buffer_bufsiz = 0;
97 static char * setup_buffer(char *, int);
98 
99 int  x_lastpid =	10;
100 int  y_lastpid =	0;
101 int  x_loadave =	33;
102 int  x_loadave_nompid =	15;
103 int  y_loadave =	0;
104 int  x_procstate =	0;
105 int  y_procstate =	1;
106 int  x_brkdn =		15;
107 int  y_brkdn =		1;
108 int  x_mem =		5;
109 int  y_mem =		3;
110 int  x_arc =		5;
111 int  y_arc =		4;
112 int  x_carc =		5;
113 int  y_carc =		5;
114 int  x_swap =		6;
115 int  y_swap =		4;
116 int  y_message =	5;
117 int  x_header =		0;
118 int  y_header =		6;
119 int  x_idlecursor =	0;
120 int  y_idlecursor =	5;
121 int  y_procs =		7;
122 
123 int  y_cpustates =	2;
124 int  Header_lines =	7;
125 
126 int
127 display_resize(void)
128 {
129     int lines;
130 
131     /* first, deallocate any previous buffer that may have been there */
132     if (screenbuf != NULL)
133     {
134 	free(screenbuf);
135     }
136 
137     /* calculate the current dimensions */
138     /* if operating in "dumb" mode, we only need one line */
139     lines = smart_terminal ? screen_length - Header_lines : 1;
140 
141     if (lines < 0)
142 	lines = 0;
143 
144     /* now, allocate space for the screen buffer */
145     screenbuf = calloc(lines, screen_width);
146     if (screenbuf == NULL)
147     {
148 	/* oops! */
149 	return(-1);
150     }
151 
152     /* return number of lines available */
153     /* for dumb terminals, pretend like we can show any amount */
154     return(smart_terminal ? lines : Largest);
155 }
156 
157 int
158 display_updatecpus(struct statics *statics)
159 {
160     int lines;
161     int i;
162 
163     /* call resize to do the dirty work */
164     lines = display_resize();
165     if (pcpu_stats)
166 		num_cpus = statics->ncpus;
167     else
168 		num_cpus = 1;
169     cpustates_column = 5;	/* CPU: */
170     if (num_cpus > 1) {
171 		cpustates_column += 1 + digits(num_cpus); /* CPU #: */
172 	}
173 
174     /* fill the "last" array with all -1s, to insure correct updating */
175 	for (i = 0; i < num_cpustates * num_cpus; ++i) {
176 		lcpustates[i] = -1;
177     }
178 
179     return(lines);
180 }
181 
182 int
183 display_init(struct statics * statics)
184 {
185     int lines;
186     char **pp;
187     int *ip;
188     int i;
189 
190     lines = display_updatecpus(statics);
191 
192     /* only do the rest if we need to */
193     if (lines > -1)
194     {
195 	/* save pointers and allocate space for names */
196 	procstate_names = statics->procstate_names;
197 	num_procstates = 8;
198 	assert(num_procstates > 0);
199 	lprocstates = calloc(num_procstates, sizeof(int));
200 
201 	cpustate_names = statics->cpustate_names;
202 
203 	swap_names = statics->swap_names;
204 	num_swap = 7;
205 	assert(num_swap > 0);
206 	lswap = calloc(num_swap, sizeof(int));
207 	num_cpustates = CPUSTATES;
208 	assert(num_cpustates > 0);
209 	lcpustates = calloc(num_cpustates * sizeof(int), statics->ncpus);
210 	cpustate_columns = calloc(num_cpustates, sizeof(int));
211 
212 	memory_names = statics->memory_names;
213 	num_memory = 7;
214 	assert(num_memory > 0);
215 	lmemory = calloc(num_memory, sizeof(int));
216 
217 	arc_names = statics->arc_names;
218 	carc_names = statics->carc_names;
219 
220 	/* calculate starting columns where needed */
221 	cpustate_total_length = 0;
222 	pp = cpustate_names;
223 	ip = cpustate_columns;
224 	while (*pp != NULL)
225 	{
226 	    *ip++ = cpustate_total_length;
227 	    if ((i = strlen(*pp++)) > 0)
228 	    {
229 		cpustate_total_length += i + 8;
230 	    }
231 	}
232     }
233 
234     /* return number of lines available */
235     return(lines);
236 }
237 
238 void
239 i_loadave(int mpid, double avenrun[])
240 {
241     int i;
242 
243     /* i_loadave also clears the screen, since it is first */
244     top_clear();
245 
246     /* mpid == -1 implies this system doesn't have an _mpid */
247     if (mpid != -1)
248     {
249 	printf("last pid: %5d;  ", mpid);
250     }
251 
252     printf("load averages");
253 
254     for (i = 0; i < 3; i++)
255     {
256 	printf("%c %5.2f",
257 	    i == 0 ? ':' : ',',
258 	    avenrun[i]);
259     }
260     lmpid = mpid;
261 }
262 
263 void
264 u_loadave(int mpid, double *avenrun)
265 {
266     int i;
267 
268     if (mpid != -1)
269     {
270 	/* change screen only when value has really changed */
271 	if (mpid != lmpid)
272 	{
273 	    Move_to(x_lastpid, y_lastpid);
274 	    printf("%5d", mpid);
275 	    lmpid = mpid;
276 	}
277 
278 	/* i remembers x coordinate to move to */
279 	i = x_loadave;
280     }
281     else
282     {
283 	i = x_loadave_nompid;
284     }
285 
286     /* move into position for load averages */
287     Move_to(i, y_loadave);
288 
289     /* display new load averages */
290     /* we should optimize this and only display changes */
291     for (i = 0; i < 3; i++)
292     {
293 	printf("%s%5.2f",
294 	    i == 0 ? "" : ", ",
295 	    avenrun[i]);
296     }
297 }
298 
299 void
300 i_timeofday(time_t *tod)
301 {
302     /*
303      *  Display the current time.
304      *  "ctime" always returns a string that looks like this:
305      *
306      *	Sun Sep 16 01:03:52 1973
307      *      012345678901234567890123
308      *	          1         2
309      *
310      *  We want indices 11 thru 18 (length 8).
311      */
312 
313     if (smart_terminal)
314     {
315 	Move_to(screen_width - 8, 0);
316     }
317     else
318     {
319 	fputs("    ", stdout);
320     }
321 #ifdef DEBUG
322     {
323 	char *foo;
324 	foo = ctime(tod);
325 	fputs(foo, stdout);
326     }
327 #endif
328     printf("%-8.8s\n", &(ctime(tod)[11]));
329     lastline = 1;
330 }
331 
332 static int ltotal = 0;
333 static char *procstates_buffer = NULL;
334 
335 /*
336  *  *_procstates(total, brkdn, names) - print the process summary line
337  *
338  *  Assumptions:  cursor is at the beginning of the line on entry
339  *		  lastline is valid
340  */
341 
342 void
343 i_procstates(int total, int *brkdn)
344 {
345     int i;
346 
347     procstates_buffer = setup_buffer(procstates_buffer, 0);
348 
349     /* write current number of processes and remember the value */
350     printf("%d %s:", total, (ps.thread) ? "threads" :"processes");
351     ltotal = total;
352 
353     /* put out enough spaces to get to column 15 */
354     i = digits(total);
355     while (i++ < 4)
356     {
357 	putchar(' ');
358     }
359 
360     /* format and print the process state summary */
361     summary_format(procstates_buffer, brkdn, procstate_names);
362     fputs(procstates_buffer, stdout);
363 
364     /* save the numbers for next time */
365     memcpy(lprocstates, brkdn, num_procstates * sizeof(int));
366 }
367 
368 void
369 u_procstates(int total, int *brkdn)
370 {
371     static char *new = NULL;
372     int i;
373 
374     new = setup_buffer(new, 0);
375 
376     /* update number of processes only if it has changed */
377     if (ltotal != total)
378     {
379 	/* move and overwrite */
380 if (x_procstate == 0) {
381 	Move_to(x_procstate, y_procstate);
382 }
383 else {
384 	/* cursor is already there...no motion needed */
385 	assert(lastline == 1);
386 }
387 	printf("%d", total);
388 
389 	/* if number of digits differs, rewrite the label */
390 	if (digits(total) != digits(ltotal))
391 	{
392 	    fputs(" processes:", stdout);
393 	    /* put out enough spaces to get to column 15 */
394 	    i = digits(total);
395 	    while (i++ < 4)
396 	    {
397 		putchar(' ');
398 	    }
399 	    /* cursor may end up right where we want it!!! */
400 	}
401 
402 	/* save new total */
403 	ltotal = total;
404     }
405 
406     /* see if any of the state numbers has changed */
407     if (memcmp(lprocstates, brkdn, num_procstates * sizeof(int)) != 0)
408     {
409 	/* format and update the line */
410 	summary_format(new, brkdn, procstate_names);
411 	line_update(procstates_buffer, new, x_brkdn, y_brkdn);
412 	memcpy(lprocstates, brkdn, num_procstates * sizeof(int));
413     }
414 }
415 
416 void
417 i_cpustates(int *states)
418 {
419     int i = 0;
420     int value;
421     const char * const *names;
422     const char *thisname;
423     int cpu;
424 
425 for (cpu = 0; cpu < num_cpus; cpu++) {
426     names = cpustate_names;
427 
428     /* print tag and bump lastline */
429     if (num_cpus == 1)
430 	printf("\nCPU: ");
431     else {
432 	value = printf("\nCPU %d: ", cpu);
433 	while (value++ <= cpustates_column)
434 		printf(" ");
435     }
436     lastline++;
437 
438     /* now walk thru the names and print the line */
439     while ((thisname = *names++) != NULL)
440     {
441 	if (*thisname != '\0')
442 	{
443 	    /* retrieve the value and remember it */
444 	    value = *states++;
445 
446 	    /* if percentage is >= 1000, print it as 100% */
447 	    printf((value >= 1000 ? "%s%4.0f%% %s" : "%s%4.1f%% %s"),
448 		   (i++ % num_cpustates) == 0 ? "" : ", ",
449 		   ((float)value)/10.,
450 		   thisname);
451 	}
452     }
453 }
454 
455     /* copy over values into "last" array */
456     memcpy(lcpustates, states, num_cpustates * sizeof(int) * num_cpus);
457 }
458 
459 void
460 u_cpustates(int *states)
461 {
462     int value;
463     const char * const *names;
464     const char *thisname;
465     int *lp;
466     int *colp;
467     int cpu;
468 
469 for (cpu = 0; cpu < num_cpus; cpu++) {
470     names = cpustate_names;
471 
472     Move_to(cpustates_column, y_cpustates + cpu);
473     lastline = y_cpustates + cpu;
474     lp = lcpustates + (cpu * num_cpustates);
475     colp = cpustate_columns;
476 
477     /* we could be much more optimal about this */
478     while ((thisname = *names++) != NULL)
479     {
480 	if (*thisname != '\0')
481 	{
482 	    /* did the value change since last time? */
483 	    if (*lp != *states)
484 	    {
485 		/* yes, move and change */
486 		Move_to(cpustates_column + *colp, y_cpustates + cpu);
487 		lastline = y_cpustates + cpu;
488 
489 		/* retrieve value and remember it */
490 		value = *states;
491 
492 		/* if percentage is >= 1000, print it as 100% */
493 		printf((value >= 1000 ? "%4.0f" : "%4.1f"),
494 		       ((double)value)/10.);
495 
496 		/* remember it for next time */
497 		*lp = value;
498 	    }
499 	}
500 
501 	/* increment and move on */
502 	lp++;
503 	states++;
504 	colp++;
505     }
506 }
507 }
508 
509 void
510 z_cpustates(void)
511 {
512     int i = 0;
513     const char **names;
514     char *thisname;
515     int cpu, value;
516 
517     for (cpu = 0; cpu < num_cpus; cpu++) {
518 	    names = cpustate_names;
519 
520 	    /* show tag and bump lastline */
521 	    if (num_cpus == 1)
522 		    printf("\nCPU: ");
523 	    else {
524 		    value = printf("\nCPU %d: ", cpu);
525 		    while (value++ <= cpustates_column)
526 			    printf(" ");
527 	    }
528 	    lastline++;
529 
530 	    while ((thisname = *names++) != NULL)
531 	    {
532 		    if (*thisname != '\0')
533 		    {
534 			    printf("%s    %% %s", (i++ % num_cpustates) == 0 ? "" : ", ", thisname);
535 		    }
536 	    }
537     }
538 
539     /* fill the "last" array with all -1s, to insure correct updating */
540 	for (i = 0; i < num_cpustates * num_cpus; ++i) {
541 		lcpustates[i] = -1;
542     }
543 }
544 
545 /*
546  *  *_memory(stats) - print "Memory: " followed by the memory summary string
547  *
548  *  Assumptions:  cursor is on "lastline"
549  *                for i_memory ONLY: cursor is on the previous line
550  */
551 
552 static char *memory_buffer = NULL;
553 
554 void
555 i_memory(int *stats)
556 {
557     memory_buffer = setup_buffer(memory_buffer, 0);
558 
559     fputs("\nMem: ", stdout);
560     lastline++;
561 
562     /* format and print the memory summary */
563     summary_format(memory_buffer, stats, memory_names);
564     fputs(memory_buffer, stdout);
565 }
566 
567 void
568 u_memory(int *stats)
569 {
570     static char *new = NULL;
571 
572     new = setup_buffer(new, 0);
573 
574     /* format the new line */
575     summary_format(new, stats, memory_names);
576     line_update(memory_buffer, new, x_mem, y_mem);
577 }
578 
579 /*
580  *  *_arc(stats) - print "ARC: " followed by the ARC summary string
581  *
582  *  Assumptions:  cursor is on "lastline"
583  *                for i_arc ONLY: cursor is on the previous line
584  */
585 static char *arc_buffer = NULL;
586 
587 void
588 i_arc(int *stats)
589 {
590     arc_buffer = setup_buffer(arc_buffer, 0);
591 
592     if (arc_names == NULL)
593 	return;
594 
595     fputs("\nARC: ", stdout);
596     lastline++;
597 
598     /* format and print the memory summary */
599     summary_format(arc_buffer, stats, arc_names);
600     fputs(arc_buffer, stdout);
601 }
602 
603 void
604 u_arc(int *stats)
605 {
606     static char *new = NULL;
607 
608     new = setup_buffer(new, 0);
609 
610     if (arc_names == NULL)
611 	return;
612 
613     /* format the new line */
614     summary_format(new, stats, arc_names);
615     line_update(arc_buffer, new, x_arc, y_arc);
616 }
617 
618 
619 /*
620  *  *_carc(stats) - print "Compressed ARC: " followed by the summary string
621  *
622  *  Assumptions:  cursor is on "lastline"
623  *                for i_carc ONLY: cursor is on the previous line
624  */
625 static char *carc_buffer = NULL;
626 
627 void
628 i_carc(int *stats)
629 {
630     carc_buffer = setup_buffer(carc_buffer, 0);
631 
632     if (carc_names == NULL)
633 	return;
634 
635     fputs("\n     ", stdout);
636     lastline++;
637 
638     /* format and print the memory summary */
639     summary_format(carc_buffer, stats, carc_names);
640     fputs(carc_buffer, stdout);
641 }
642 
643 void
644 u_carc(int *stats)
645 {
646     static char *new = NULL;
647 
648     new = setup_buffer(new, 0);
649 
650     if (carc_names == NULL)
651 	return;
652 
653     /* format the new line */
654     summary_format(new, stats, carc_names);
655     line_update(carc_buffer, new, x_carc, y_carc);
656 }
657 
658 /*
659  *  *_swap(stats) - print "Swap: " followed by the swap summary string
660  *
661  *  Assumptions:  cursor is on "lastline"
662  *                for i_swap ONLY: cursor is on the previous line
663  */
664 
665 static char *swap_buffer = NULL;
666 
667 void
668 i_swap(int *stats)
669 {
670     swap_buffer = setup_buffer(swap_buffer, 0);
671 
672     fputs("\nSwap: ", stdout);
673     lastline++;
674 
675     /* format and print the swap summary */
676     summary_format(swap_buffer, stats, swap_names);
677     fputs(swap_buffer, stdout);
678 }
679 
680 void
681 u_swap(int *stats)
682 {
683     static char *new = NULL;
684 
685     new = setup_buffer(new, 0);
686 
687     /* format the new line */
688     summary_format(new, stats, swap_names);
689     line_update(swap_buffer, new, x_swap, y_swap);
690 }
691 
692 /*
693  *  *_message() - print the next pending message line, or erase the one
694  *                that is there.
695  *
696  *  Note that u_message is (currently) the same as i_message.
697  *
698  *  Assumptions:  lastline is consistent
699  */
700 
701 /*
702  *  i_message is funny because it gets its message asynchronously (with
703  *	respect to screen updates).
704  */
705 
706 static char *next_msg = NULL;
707 static int msglen = 0;
708 /* Invariant: msglen is always the length of the message currently displayed
709    on the screen (even when next_msg doesn't contain that message). */
710 
711 void
712 i_message(void)
713 {
714     next_msg = setup_buffer(next_msg, 5);
715 
716     while (lastline < y_message)
717     {
718 	fputc('\n', stdout);
719 	lastline++;
720     }
721     if (next_msg[0] != '\0')
722     {
723 	top_standout(next_msg);
724 	msglen = strlen(next_msg);
725 	next_msg[0] = '\0';
726     }
727     else if (msglen > 0)
728     {
729 	(void) clear_eol(msglen);
730 	msglen = 0;
731     }
732 }
733 
734 void
735 u_message(void)
736 {
737     i_message();
738 }
739 
740 static int header_length;
741 
742 /*
743  * Trim a header string to the current display width and return a newly
744  * allocated area with the trimmed header.
745  */
746 
747 const char *
748 trim_header(const char *text)
749 {
750 	char *s;
751 	int width;
752 
753 	s = NULL;
754 	width = screen_width;
755 	header_length = strlen(text);
756 	if (header_length >= width) {
757 		s = strndup(text, width);
758 		if (s == NULL)
759 			return (NULL);
760 	}
761 	return (s);
762 }
763 
764 /*
765  *  *_header(text) - print the header for the process area
766  *
767  *  Assumptions:  cursor is on the previous line and lastline is consistent
768  */
769 
770 void
771 i_header(const char *text)
772 {
773     char *s;
774 
775     s = trim_header(text);
776     if (s != NULL)
777 	text = s;
778 
779     if (header_status == ON)
780     {
781 	putchar('\n');
782 	fputs(text, stdout);
783 	lastline++;
784     }
785     else if (header_status == ERASE)
786     {
787 	header_status = OFF;
788     }
789     free(s);
790 }
791 
792 void
793 u_header(const char *text __unused)
794 {
795 
796     if (header_status == ERASE)
797     {
798 	putchar('\n');
799 	lastline++;
800 	clear_eol(header_length);
801 	header_status = OFF;
802     }
803 }
804 
805 /*
806  *  *_process(line, thisline) - print one process line
807  *
808  *  Assumptions:  lastline is consistent
809  */
810 
811 void
812 i_process(int line, char *thisline)
813 {
814     char *p;
815     char *base;
816 
817     /* make sure we are on the correct line */
818     while (lastline < y_procs + line)
819     {
820 	putchar('\n');
821 	lastline++;
822     }
823 
824     /* truncate the line to conform to our current screen width */
825     thisline[screen_width] = '\0';
826 
827     /* write the line out */
828     fputs(thisline, stdout);
829 
830     /* copy it in to our buffer */
831     base = smart_terminal ? screenbuf + lineindex(line) : screenbuf;
832     p = stpcpy(base, thisline);
833 
834     /* zero fill the rest of it */
835     memset(p, 0, screen_width - (p - base));
836 }
837 
838 void
839 u_process(int line, char *newline)
840 {
841     char *optr;
842     int screen_line = line + Header_lines;
843     char *bufferline;
844 
845     /* remember a pointer to the current line in the screen buffer */
846     bufferline = &screenbuf[lineindex(line)];
847 
848     /* truncate the line to conform to our current screen width */
849     newline[screen_width] = '\0';
850 
851     /* is line higher than we went on the last display? */
852     if (line >= last_hi)
853     {
854 	/* yes, just ignore screenbuf and write it out directly */
855 	/* get positioned on the correct line */
856 	if (screen_line - lastline == 1)
857 	{
858 	    putchar('\n');
859 	    lastline++;
860 	}
861 	else
862 	{
863 	    Move_to(0, screen_line);
864 	    lastline = screen_line;
865 	}
866 
867 	/* now write the line */
868 	fputs(newline, stdout);
869 
870 	/* copy it in to the buffer */
871 	optr = stpcpy(bufferline, newline);
872 
873 	/* zero fill the rest of it */
874 	memset(optr, 0, screen_width - (optr - bufferline));
875     }
876     else
877     {
878 	line_update(bufferline, newline, 0, line + Header_lines);
879     }
880 }
881 
882 void
883 u_endscreen(int hi)
884 {
885     int screen_line = hi + Header_lines;
886     int i;
887 
888     if (smart_terminal)
889     {
890 	if (hi < last_hi)
891 	{
892 	    /* need to blank the remainder of the screen */
893 	    /* but only if there is any screen left below this line */
894 	    if (lastline + 1 < screen_length)
895 	    {
896 		/* efficiently move to the end of currently displayed info */
897 		if (screen_line - lastline < 5)
898 		{
899 		    while (lastline < screen_line)
900 		    {
901 			putchar('\n');
902 			lastline++;
903 		    }
904 		}
905 		else
906 		{
907 		    Move_to(0, screen_line);
908 		    lastline = screen_line;
909 		}
910 
911 		if (clear_to_end)
912 		{
913 		    /* we can do this the easy way */
914 		    putcap(clear_to_end);
915 		}
916 		else
917 		{
918 		    /* use clear_eol on each line */
919 		    i = hi;
920 		    while ((void) clear_eol(strlen(&screenbuf[lineindex(i++)])), i < last_hi)
921 		    {
922 			putchar('\n');
923 		    }
924 		}
925 	    }
926 	}
927 	last_hi = hi;
928 
929 	/* move the cursor to a pleasant place */
930 	Move_to(x_idlecursor, y_idlecursor);
931 	lastline = y_idlecursor;
932     }
933     else
934     {
935 	/* separate this display from the next with some vertical room */
936 	fputs("\n\n", stdout);
937     }
938 }
939 
940 void
941 display_header(int t)
942 {
943 
944     if (t)
945     {
946 	header_status = ON;
947     }
948     else if (header_status == ON)
949     {
950 	header_status = ERASE;
951     }
952 }
953 
954 void
955 new_message(int type, const char *msgfmt, ...)
956 {
957     va_list args;
958     size_t i;
959 
960     va_start(args, msgfmt);
961 
962     /* first, format the message */
963     vsnprintf(next_msg, strlen(next_msg), msgfmt, args);
964 
965     va_end(args);
966 
967     if (msglen > 0)
968     {
969 	/* message there already -- can we clear it? */
970 	if (!overstrike)
971 	{
972 	    /* yes -- write it and clear to end */
973 	    i = strlen(next_msg);
974 	    if ((type & MT_delayed) == 0)
975 	    {
976 			if (type & MT_standout) {
977 				top_standout(next_msg);
978 			} else {
979 				fputs(next_msg, stdout);
980 			}
981 			clear_eol(msglen - i);
982 			msglen = i;
983 			next_msg[0] = '\0';
984 	    }
985 	}
986     }
987     else
988     {
989 	if ((type & MT_delayed) == 0)
990 	{
991 		if (type & MT_standout) {
992 			top_standout(next_msg);
993 		} else {
994 			fputs(next_msg, stdout);
995 		}
996 	    msglen = strlen(next_msg);
997 	    next_msg[0] = '\0';
998 	}
999     }
1000 }
1001 
1002 void
1003 clear_message(void)
1004 {
1005     if (clear_eol(msglen) == 1)
1006     {
1007 	putchar('\r');
1008     }
1009 }
1010 
1011 int
1012 readline(char *buffer, int size, int numeric)
1013 {
1014     char *ptr = buffer;
1015     char ch;
1016     char cnt = 0;
1017     char maxcnt = 0;
1018 
1019     /* allow room for null terminator */
1020     size -= 1;
1021 
1022     /* read loop */
1023     while ((fflush(stdout), read(0, ptr, 1) > 0))
1024     {
1025 	/* newline means we are done */
1026 	if ((ch = *ptr) == '\n' || ch == '\r')
1027 	{
1028 	    break;
1029 	}
1030 
1031 	/* handle special editing characters */
1032 	if (ch == ch_kill)
1033 	{
1034 	    /* kill line -- account for overstriking */
1035 	    if (overstrike)
1036 	    {
1037 		msglen += maxcnt;
1038 	    }
1039 
1040 	    /* return null string */
1041 	    *buffer = '\0';
1042 	    putchar('\r');
1043 	    return(-1);
1044 	}
1045 	else if (ch == ch_erase)
1046 	{
1047 	    /* erase previous character */
1048 	    if (cnt <= 0)
1049 	    {
1050 		/* none to erase! */
1051 		putchar('\7');
1052 	    }
1053 	    else
1054 	    {
1055 		fputs("\b \b", stdout);
1056 		ptr--;
1057 		cnt--;
1058 	    }
1059 	}
1060 	/* check for character validity and buffer overflow */
1061 	else if (cnt == size || (numeric && !isdigit(ch)) ||
1062 		!isprint(ch))
1063 	{
1064 	    /* not legal */
1065 	    putchar('\7');
1066 	}
1067 	else
1068 	{
1069 	    /* echo it and store it in the buffer */
1070 	    putchar(ch);
1071 	    ptr++;
1072 	    cnt++;
1073 	    if (cnt > maxcnt)
1074 	    {
1075 		maxcnt = cnt;
1076 	    }
1077 	}
1078     }
1079 
1080     /* all done -- null terminate the string */
1081     *ptr = '\0';
1082 
1083     /* account for the extra characters in the message area */
1084     /* (if terminal overstrikes, remember the furthest they went) */
1085     msglen += overstrike ? maxcnt : cnt;
1086 
1087     /* return either inputted number or string length */
1088     putchar('\r');
1089     return(cnt == 0 ? -1 : numeric ? atoi(buffer) : cnt);
1090 }
1091 
1092 /* internal support routines */
1093 
1094 static void
1095 summary_format(char *str, int *numbers, const char * const *names)
1096 {
1097     char *p;
1098     int num;
1099     const char *thisname;
1100     char rbuf[6];
1101 
1102     /* format each number followed by its string */
1103     p = str;
1104     while ((thisname = *names++) != NULL)
1105     {
1106 	/* get the number to format */
1107 	num = *numbers++;
1108 
1109 	/* display only non-zero numbers */
1110 	if (num > 0)
1111 	{
1112 	    /* is this number in kilobytes? */
1113 	    if (thisname[0] == 'K')
1114 	    {
1115 		/* yes: format it as a memory value */
1116 		p = stpcpy(p, format_k(num));
1117 
1118 		/* skip over the K, since it was included by format_k */
1119 		p = stpcpy(p, thisname+1);
1120 	    }
1121 	    /* is this number a ratio? */
1122 	    else if (thisname[0] == ':')
1123 	    {
1124 		(void) snprintf(rbuf, sizeof(rbuf), "%.2f",
1125 		    (float)*(numbers - 2) / (float)num);
1126 		p = stpcpy(p, rbuf);
1127 		p = stpcpy(p, thisname);
1128 	    }
1129 	    else
1130 	    {
1131 		p = stpcpy(p, itoa(num));
1132 		p = stpcpy(p, thisname);
1133 	    }
1134 	}
1135 
1136 	/* ignore negative numbers, but display corresponding string */
1137 	else if (num < 0)
1138 	{
1139 	    p = stpcpy(p, thisname);
1140 	}
1141     }
1142 
1143     /* if the last two characters in the string are ", ", delete them */
1144     p -= 2;
1145     if (p >= str && p[0] == ',' && p[1] == ' ')
1146     {
1147 	*p = '\0';
1148     }
1149 }
1150 
1151 static void
1152 line_update(char *old, char *new, int start, int line)
1153 {
1154     int ch;
1155     int diff;
1156     int newcol = start + 1;
1157     int lastcol = start;
1158     char cursor_on_line = false;
1159     char *current;
1160 
1161     /* compare the two strings and only rewrite what has changed */
1162     current = old;
1163 #ifdef DEBUG
1164     fprintf(debug, "line_update, starting at %d\n", start);
1165     fputs(old, debug);
1166     fputc('\n', debug);
1167     fputs(new, debug);
1168     fputs("\n-\n", debug);
1169 #endif
1170 
1171     /* start things off on the right foot		    */
1172     /* this is to make sure the invariants get set up right */
1173     if ((ch = *new++) != *old)
1174     {
1175 	if (line - lastline == 1 && start == 0)
1176 	{
1177 	    putchar('\n');
1178 	}
1179 	else
1180 	{
1181 	    Move_to(start, line);
1182 	}
1183 	cursor_on_line = true;
1184 	putchar(ch);
1185 	*old = ch;
1186 	lastcol = 1;
1187     }
1188     old++;
1189 
1190     /*
1191      *  main loop -- check each character.  If the old and new aren't the
1192      *	same, then update the display.  When the distance from the
1193      *	current cursor position to the new change is small enough,
1194      *	the characters that belong there are written to move the
1195      *	cursor over.
1196      *
1197      *	Invariants:
1198      *	    lastcol is the column where the cursor currently is sitting
1199      *		(always one beyond the end of the last mismatch).
1200      */
1201     do		/* yes, a do...while */
1202     {
1203 	if ((ch = *new++) != *old)
1204 	{
1205 	    /* new character is different from old	  */
1206 	    /* make sure the cursor is on top of this character */
1207 	    diff = newcol - lastcol;
1208 	    if (diff > 0)
1209 	    {
1210 		/* some motion is required--figure out which is shorter */
1211 		if (diff < 6 && cursor_on_line)
1212 		{
1213 		    /* overwrite old stuff--get it out of the old buffer */
1214 		    printf("%.*s", diff, &current[lastcol-start]);
1215 		}
1216 		else
1217 		{
1218 		    /* use cursor addressing */
1219 		    Move_to(newcol, line);
1220 		    cursor_on_line = true;
1221 		}
1222 		/* remember where the cursor is */
1223 		lastcol = newcol + 1;
1224 	    }
1225 	    else
1226 	    {
1227 		/* already there, update position */
1228 		lastcol++;
1229 	    }
1230 
1231 	    /* write what we need to */
1232 	    if (ch == '\0')
1233 	    {
1234 		/* at the end--terminate with a clear-to-end-of-line */
1235 		(void) clear_eol(strlen(old));
1236 	    }
1237 	    else
1238 	    {
1239 		/* write the new character */
1240 		putchar(ch);
1241 	    }
1242 	    /* put the new character in the screen buffer */
1243 	    *old = ch;
1244 	}
1245 
1246 	/* update working column and screen buffer pointer */
1247 	newcol++;
1248 	old++;
1249 
1250     } while (ch != '\0');
1251 
1252     /* zero out the rest of the line buffer -- MUST BE DONE! */
1253     diff = screen_width - newcol;
1254     if (diff > 0)
1255     {
1256 	memset(old, 0, diff);
1257     }
1258 
1259     /* remember where the current line is */
1260     if (cursor_on_line)
1261     {
1262 	lastline = line;
1263     }
1264 }
1265 
1266 /*
1267  *  printable(str) - make the string pointed to by "str" into one that is
1268  *	printable (i.e.: all ascii), by converting all non-printable
1269  *	characters into '?'.  Replacements are done in place and a pointer
1270  *	to the original buffer is returned.
1271  */
1272 
1273 char *
1274 printable(char str[])
1275 {
1276 	char *ptr;
1277 	char ch;
1278 
1279 	ptr = str;
1280 	if (utf8flag) {
1281 		while ((ch = *ptr) != '\0') {
1282 			if (0x00 == (0x80 & ch)) {
1283 				if (!isprint(ch)) {
1284 					*ptr = '?';
1285 				}
1286 				++ptr;
1287 			} else if (0xC0 == (0xE0 & ch)) {
1288 				++ptr;
1289 				if ('\0' != *ptr) ++ptr;
1290 			} else if (0xE0 == (0xF0 & ch)) {
1291 				++ptr;
1292 				if ('\0' != *ptr) ++ptr;
1293 				if ('\0' != *ptr) ++ptr;
1294 			} else if (0xF0 == (0xF8 & ch)) {
1295 				++ptr;
1296 				if ('\0' != *ptr) ++ptr;
1297 				if ('\0' != *ptr) ++ptr;
1298 				if ('\0' != *ptr) ++ptr;
1299 			} else {
1300 				*ptr = '?';
1301 				++ptr;
1302 			}
1303 		}
1304 	} else {
1305 		while ((ch = *ptr) != '\0') {
1306 			if (!isprint(ch)) {
1307 				*ptr = '?';
1308 			}
1309 			ptr++;
1310 		}
1311 	}
1312 	return(str);
1313 }
1314 
1315 void
1316 i_uptime(struct timeval *bt, time_t *tod)
1317 {
1318     time_t uptime;
1319     int days, hrs, mins, secs;
1320 
1321     if (bt->tv_sec != -1) {
1322 	uptime = *tod - bt->tv_sec;
1323 	days = uptime / 86400;
1324 	uptime %= 86400;
1325 	hrs = uptime / 3600;
1326 	uptime %= 3600;
1327 	mins = uptime / 60;
1328 	secs = uptime % 60;
1329 
1330 	/*
1331 	 *  Display the uptime.
1332 	 */
1333 
1334 	if (smart_terminal)
1335 	{
1336 	    Move_to((screen_width - 24) - (days > 9 ? 1 : 0), 0);
1337 	}
1338 	else
1339 	{
1340 	    fputs(" ", stdout);
1341 	}
1342 	printf(" up %d+%02d:%02d:%02d", days, hrs, mins, secs);
1343     }
1344 }
1345 
1346 static char *
1347 setup_buffer(char *buffer, int addlen)
1348 {
1349 	char *b = NULL;
1350 
1351 	if (NULL == buffer) {
1352 		setup_buffer_bufsiz = screen_width;
1353 		b = calloc(setup_buffer_bufsiz + addlen, sizeof(char));
1354 	} else {
1355 		if (screen_width > setup_buffer_bufsiz) {
1356 			setup_buffer_bufsiz = screen_width;
1357 			free(buffer);
1358 			b = calloc(setup_buffer_bufsiz + addlen,
1359 					sizeof(char));
1360 		} else {
1361 			b = buffer;
1362 		}
1363 	}
1364 
1365 	if (NULL == b) {
1366 		fprintf(stderr, "%s: can't allocate sufficient memory\n",
1367 				myname);
1368 		exit(4);
1369 	}
1370 
1371 	return b;
1372 }
1373