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