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