xref: /freebsd/usr.bin/top/display.c (revision 6e0632db)
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 processes:", total);
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 #else
383 	/* cursor is already there...no motion needed */
384 	/* assert(lastline == 1); */
385 #endif
386 	printf("%d", total);
387 
388 	/* if number of digits differs, rewrite the label */
389 	if (digits(total) != digits(ltotal))
390 	{
391 	    fputs(" processes:", stdout);
392 	    /* put out enough spaces to get to column 15 */
393 	    i = digits(total);
394 	    while (i++ < 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 cpu;
423 
424 for (cpu = 0; cpu < num_cpus; cpu++) {
425     names = cpustate_names;
426 
427     /* print tag and bump lastline */
428     if (num_cpus == 1)
429 	printf("\nCPU: ");
430     else {
431 	value = printf("\nCPU %d: ", cpu);
432 	while (value++ <= cpustates_column)
433 		printf(" ");
434     }
435     lastline++;
436 
437     /* now walk thru the names and print the line */
438     while ((thisname = *names++) != NULL)
439     {
440 	if (*thisname != '\0')
441 	{
442 	    /* retrieve the value and remember it */
443 	    value = *states++;
444 
445 	    /* if percentage is >= 1000, print it as 100% */
446 	    printf((value >= 1000 ? "%s%4.0f%% %s" : "%s%4.1f%% %s"),
447 		   (i++ % num_cpustates) == 0 ? "" : ", ",
448 		   ((float)value)/10.,
449 		   thisname);
450 	}
451     }
452 }
453 
454     /* copy over values into "last" array */
455     memcpy(lcpustates, states, num_cpustates * sizeof(int) * num_cpus);
456 }
457 
458 void
459 u_cpustates(int *states)
460 {
461     int value;
462     const char * const *names;
463     const char *thisname;
464     int *lp;
465     int *colp;
466     int cpu;
467 
468 for (cpu = 0; cpu < num_cpus; cpu++) {
469     names = cpustate_names;
470 
471     Move_to(cpustates_column, y_cpustates + cpu);
472     lastline = y_cpustates + cpu;
473     lp = lcpustates + (cpu * num_cpustates);
474     colp = cpustate_columns;
475 
476     /* we could be much more optimal about this */
477     while ((thisname = *names++) != NULL)
478     {
479 	if (*thisname != '\0')
480 	{
481 	    /* did the value change since last time? */
482 	    if (*lp != *states)
483 	    {
484 		/* yes, move and change */
485 		Move_to(cpustates_column + *colp, y_cpustates + cpu);
486 		lastline = y_cpustates + cpu;
487 
488 		/* retrieve value and remember it */
489 		value = *states;
490 
491 		/* if percentage is >= 1000, print it as 100% */
492 		printf((value >= 1000 ? "%4.0f" : "%4.1f"),
493 		       ((double)value)/10.);
494 
495 		/* remember it for next time */
496 		*lp = value;
497 	    }
498 	}
499 
500 	/* increment and move on */
501 	lp++;
502 	states++;
503 	colp++;
504     }
505 }
506 }
507 
508 void
509 z_cpustates(void)
510 {
511     int i = 0;
512     const char **names;
513     char *thisname;
514     int cpu, value;
515 
516     for (cpu = 0; cpu < num_cpus; cpu++) {
517 	    names = cpustate_names;
518 
519 	    /* show tag and bump lastline */
520 	    if (num_cpus == 1)
521 		    printf("\nCPU: ");
522 	    else {
523 		    value = printf("\nCPU %d: ", cpu);
524 		    while (value++ <= cpustates_column)
525 			    printf(" ");
526 	    }
527 	    lastline++;
528 
529 	    while ((thisname = *names++) != NULL)
530 	    {
531 		    if (*thisname != '\0')
532 		    {
533 			    printf("%s    %% %s", (i++ % num_cpustates) == 0 ? "" : ", ", thisname);
534 		    }
535 	    }
536     }
537 
538     /* fill the "last" array with all -1s, to insure correct updating */
539 	for (i = 0; i < num_cpustates * num_cpus; ++i) {
540 		lcpustates[i] = -1;
541     }
542 }
543 
544 /*
545  *  *_memory(stats) - print "Memory: " followed by the memory summary string
546  *
547  *  Assumptions:  cursor is on "lastline"
548  *                for i_memory ONLY: cursor is on the previous line
549  */
550 
551 static char memory_buffer[MAX_COLS];
552 
553 void
554 i_memory(int *stats)
555 {
556     fputs("\nMem: ", stdout);
557     lastline++;
558 
559     /* format and print the memory summary */
560     summary_format(memory_buffer, stats, memory_names);
561     fputs(memory_buffer, stdout);
562 }
563 
564 void
565 u_memory(int *stats)
566 {
567     static char new[MAX_COLS];
568 
569     /* format the new line */
570     summary_format(new, stats, memory_names);
571     line_update(memory_buffer, new, x_mem, y_mem);
572 }
573 
574 /*
575  *  *_arc(stats) - print "ARC: " followed by the ARC summary string
576  *
577  *  Assumptions:  cursor is on "lastline"
578  *                for i_arc ONLY: cursor is on the previous line
579  */
580 static char arc_buffer[MAX_COLS];
581 
582 void
583 i_arc(int *stats)
584 {
585     if (arc_names == NULL)
586 	return;
587 
588     fputs("\nARC: ", stdout);
589     lastline++;
590 
591     /* format and print the memory summary */
592     summary_format(arc_buffer, stats, arc_names);
593     fputs(arc_buffer, stdout);
594 }
595 
596 void
597 u_arc(int *stats)
598 {
599     static char new[MAX_COLS];
600 
601     if (arc_names == NULL)
602 	return;
603 
604     /* format the new line */
605     summary_format(new, stats, arc_names);
606     line_update(arc_buffer, new, x_arc, y_arc);
607 }
608 
609 
610 /*
611  *  *_carc(stats) - print "Compressed ARC: " followed by the summary string
612  *
613  *  Assumptions:  cursor is on "lastline"
614  *                for i_carc ONLY: cursor is on the previous line
615  */
616 static char carc_buffer[MAX_COLS];
617 
618 void
619 i_carc(int *stats)
620 {
621     if (carc_names == NULL)
622 	return;
623 
624     fputs("\n     ", stdout);
625     lastline++;
626 
627     /* format and print the memory summary */
628     summary_format(carc_buffer, stats, carc_names);
629     fputs(carc_buffer, stdout);
630 }
631 
632 void
633 u_carc(int *stats)
634 {
635     static char new[MAX_COLS];
636 
637     if (carc_names == NULL)
638 	return;
639 
640     /* format the new line */
641     summary_format(new, stats, carc_names);
642     line_update(carc_buffer, new, x_carc, y_carc);
643 }
644 
645 /*
646  *  *_swap(stats) - print "Swap: " followed by the swap summary string
647  *
648  *  Assumptions:  cursor is on "lastline"
649  *                for i_swap ONLY: cursor is on the previous line
650  */
651 
652 static char swap_buffer[MAX_COLS];
653 
654 void
655 i_swap(int *stats)
656 {
657     fputs("\nSwap: ", stdout);
658     lastline++;
659 
660     /* format and print the swap summary */
661     summary_format(swap_buffer, stats, swap_names);
662     fputs(swap_buffer, stdout);
663 }
664 
665 void
666 u_swap(int *stats)
667 {
668     static char new[MAX_COLS];
669 
670     /* format the new line */
671     summary_format(new, stats, swap_names);
672     line_update(swap_buffer, new, x_swap, y_swap);
673 }
674 
675 /*
676  *  *_message() - print the next pending message line, or erase the one
677  *                that is there.
678  *
679  *  Note that u_message is (currently) the same as i_message.
680  *
681  *  Assumptions:  lastline is consistent
682  */
683 
684 /*
685  *  i_message is funny because it gets its message asynchronously (with
686  *	respect to screen updates).
687  */
688 
689 static char next_msg[MAX_COLS + 5];
690 static int msglen = 0;
691 /* Invariant: msglen is always the length of the message currently displayed
692    on the screen (even when next_msg doesn't contain that message). */
693 
694 void
695 i_message(void)
696 {
697 
698     while (lastline < y_message)
699     {
700 	fputc('\n', stdout);
701 	lastline++;
702     }
703     if (next_msg[0] != '\0')
704     {
705 	top_standout(next_msg);
706 	msglen = strlen(next_msg);
707 	next_msg[0] = '\0';
708     }
709     else if (msglen > 0)
710     {
711 	(void) clear_eol(msglen);
712 	msglen = 0;
713     }
714 }
715 
716 void
717 u_message(void)
718 {
719     i_message();
720 }
721 
722 static int header_length;
723 
724 /*
725  * Trim a header string to the current display width and return a newly
726  * allocated area with the trimmed header.
727  */
728 
729 const char *
730 trim_header(const char *text)
731 {
732 	char *s;
733 	int width;
734 
735 	s = NULL;
736 	width = display_width;
737 	header_length = strlen(text);
738 	if (header_length >= width) {
739 		s = strndup(text, width);
740 		if (s == NULL)
741 			return (NULL);
742 	}
743 	return (s);
744 }
745 
746 /*
747  *  *_header(text) - print the header for the process area
748  *
749  *  Assumptions:  cursor is on the previous line and lastline is consistent
750  */
751 
752 void
753 i_header(const char *text)
754 {
755     char *s;
756 
757     s = trim_header(text);
758     if (s != NULL)
759 	text = s;
760 
761     if (header_status == ON)
762     {
763 	putchar('\n');
764 	fputs(text, stdout);
765 	lastline++;
766     }
767     else if (header_status == ERASE)
768     {
769 	header_status = OFF;
770     }
771     free(s);
772 }
773 
774 void
775 u_header(const char *text __unused)
776 {
777 
778     if (header_status == ERASE)
779     {
780 	putchar('\n');
781 	lastline++;
782 	clear_eol(header_length);
783 	header_status = OFF;
784     }
785 }
786 
787 /*
788  *  *_process(line, thisline) - print one process line
789  *
790  *  Assumptions:  lastline is consistent
791  */
792 
793 void
794 i_process(int line, char *thisline)
795 {
796     char *p;
797     char *base;
798 
799     /* make sure we are on the correct line */
800     while (lastline < y_procs + line)
801     {
802 	putchar('\n');
803 	lastline++;
804     }
805 
806     /* truncate the line to conform to our current screen width */
807     thisline[display_width] = '\0';
808 
809     /* write the line out */
810     fputs(thisline, stdout);
811 
812     /* copy it in to our buffer */
813     base = smart_terminal ? screenbuf + lineindex(line) : screenbuf;
814     p = stpcpy(base, thisline);
815 
816     /* zero fill the rest of it */
817     memset(p, 0, display_width - (p - base));
818 }
819 
820 void
821 u_process(int line, char *newline)
822 {
823     char *optr;
824     int screen_line = line + Header_lines;
825     char *bufferline;
826 
827     /* remember a pointer to the current line in the screen buffer */
828     bufferline = &screenbuf[lineindex(line)];
829 
830     /* truncate the line to conform to our current screen width */
831     newline[display_width] = '\0';
832 
833     /* is line higher than we went on the last display? */
834     if (line >= last_hi)
835     {
836 	/* yes, just ignore screenbuf and write it out directly */
837 	/* get positioned on the correct line */
838 	if (screen_line - lastline == 1)
839 	{
840 	    putchar('\n');
841 	    lastline++;
842 	}
843 	else
844 	{
845 	    Move_to(0, screen_line);
846 	    lastline = screen_line;
847 	}
848 
849 	/* now write the line */
850 	fputs(newline, stdout);
851 
852 	/* copy it in to the buffer */
853 	optr = stpcpy(bufferline, newline);
854 
855 	/* zero fill the rest of it */
856 	memset(optr, 0, display_width - (optr - bufferline));
857     }
858     else
859     {
860 	line_update(bufferline, newline, 0, line + Header_lines);
861     }
862 }
863 
864 void
865 u_endscreen(int hi)
866 {
867     int screen_line = hi + Header_lines;
868     int i;
869 
870     if (smart_terminal)
871     {
872 	if (hi < last_hi)
873 	{
874 	    /* need to blank the remainder of the screen */
875 	    /* but only if there is any screen left below this line */
876 	    if (lastline + 1 < screen_length)
877 	    {
878 		/* efficiently move to the end of currently displayed info */
879 		if (screen_line - lastline < 5)
880 		{
881 		    while (lastline < screen_line)
882 		    {
883 			putchar('\n');
884 			lastline++;
885 		    }
886 		}
887 		else
888 		{
889 		    Move_to(0, screen_line);
890 		    lastline = screen_line;
891 		}
892 
893 		if (clear_to_end)
894 		{
895 		    /* we can do this the easy way */
896 		    putcap(clear_to_end);
897 		}
898 		else
899 		{
900 		    /* use clear_eol on each line */
901 		    i = hi;
902 		    while ((void) clear_eol(strlen(&screenbuf[lineindex(i++)])), i < last_hi)
903 		    {
904 			putchar('\n');
905 		    }
906 		}
907 	    }
908 	}
909 	last_hi = hi;
910 
911 	/* move the cursor to a pleasant place */
912 	Move_to(x_idlecursor, y_idlecursor);
913 	lastline = y_idlecursor;
914     }
915     else
916     {
917 	/* separate this display from the next with some vertical room */
918 	fputs("\n\n", stdout);
919     }
920 }
921 
922 void
923 display_header(int t)
924 {
925 
926     if (t)
927     {
928 	header_status = ON;
929     }
930     else if (header_status == ON)
931     {
932 	header_status = ERASE;
933     }
934 }
935 
936 void
937 new_message(int type, const char *msgfmt, ...)
938 {
939     va_list args;
940     size_t i;
941 
942     va_start(args, msgfmt);
943 
944     /* first, format the message */
945     vsnprintf(next_msg, sizeof(next_msg), msgfmt, args);
946 
947     va_end(args);
948 
949     if (msglen > 0)
950     {
951 	/* message there already -- can we clear it? */
952 	if (!overstrike)
953 	{
954 	    /* yes -- write it and clear to end */
955 	    i = strlen(next_msg);
956 	    if ((type & MT_delayed) == 0)
957 	    {
958 		type & MT_standout ? top_standout(next_msg) :
959 		                     fputs(next_msg, stdout);
960 		(void) clear_eol(msglen - i);
961 		msglen = i;
962 		next_msg[0] = '\0';
963 	    }
964 	}
965     }
966     else
967     {
968 	if ((type & MT_delayed) == 0)
969 	{
970 	    type & MT_standout ? top_standout(next_msg) : fputs(next_msg, stdout);
971 	    msglen = strlen(next_msg);
972 	    next_msg[0] = '\0';
973 	}
974     }
975 }
976 
977 void
978 clear_message(void)
979 {
980     if (clear_eol(msglen) == 1)
981     {
982 	putchar('\r');
983     }
984 }
985 
986 int
987 readline(char *buffer, int size, int numeric)
988 {
989     char *ptr = buffer;
990     char ch;
991     char cnt = 0;
992     char maxcnt = 0;
993 
994     /* allow room for null terminator */
995     size -= 1;
996 
997     /* read loop */
998     while ((fflush(stdout), read(0, ptr, 1) > 0))
999     {
1000 	/* newline means we are done */
1001 	if ((ch = *ptr) == '\n' || ch == '\r')
1002 	{
1003 	    break;
1004 	}
1005 
1006 	/* handle special editing characters */
1007 	if (ch == ch_kill)
1008 	{
1009 	    /* kill line -- account for overstriking */
1010 	    if (overstrike)
1011 	    {
1012 		msglen += maxcnt;
1013 	    }
1014 
1015 	    /* return null string */
1016 	    *buffer = '\0';
1017 	    putchar('\r');
1018 	    return(-1);
1019 	}
1020 	else if (ch == ch_erase)
1021 	{
1022 	    /* erase previous character */
1023 	    if (cnt <= 0)
1024 	    {
1025 		/* none to erase! */
1026 		putchar('\7');
1027 	    }
1028 	    else
1029 	    {
1030 		fputs("\b \b", stdout);
1031 		ptr--;
1032 		cnt--;
1033 	    }
1034 	}
1035 	/* check for character validity and buffer overflow */
1036 	else if (cnt == size || (numeric && !isdigit(ch)) ||
1037 		!isprint(ch))
1038 	{
1039 	    /* not legal */
1040 	    putchar('\7');
1041 	}
1042 	else
1043 	{
1044 	    /* echo it and store it in the buffer */
1045 	    putchar(ch);
1046 	    ptr++;
1047 	    cnt++;
1048 	    if (cnt > maxcnt)
1049 	    {
1050 		maxcnt = cnt;
1051 	    }
1052 	}
1053     }
1054 
1055     /* all done -- null terminate the string */
1056     *ptr = '\0';
1057 
1058     /* account for the extra characters in the message area */
1059     /* (if terminal overstrikes, remember the furthest they went) */
1060     msglen += overstrike ? maxcnt : cnt;
1061 
1062     /* return either inputted number or string length */
1063     putchar('\r');
1064     return(cnt == 0 ? -1 : numeric ? atoi(buffer) : cnt);
1065 }
1066 
1067 /* internal support routines */
1068 
1069 static void summary_format(char *str, int *numbers, const char * const *names)
1070 {
1071     char *p;
1072     int num;
1073     const char *thisname;
1074     char rbuf[6];
1075 
1076     /* format each number followed by its string */
1077     p = str;
1078     while ((thisname = *names++) != NULL)
1079     {
1080 	/* get the number to format */
1081 	num = *numbers++;
1082 
1083 	/* display only non-zero numbers */
1084 	if (num > 0)
1085 	{
1086 	    /* is this number in kilobytes? */
1087 	    if (thisname[0] == 'K')
1088 	    {
1089 		/* yes: format it as a memory value */
1090 		p = stpcpy(p, format_k(num));
1091 
1092 		/* skip over the K, since it was included by format_k */
1093 		p = stpcpy(p, thisname+1);
1094 	    }
1095 	    /* is this number a ratio? */
1096 	    else if (thisname[0] == ':')
1097 	    {
1098 		(void) snprintf(rbuf, sizeof(rbuf), "%.2f",
1099 		    (float)*(numbers - 2) / (float)num);
1100 		p = stpcpy(p, rbuf);
1101 		p = stpcpy(p, thisname);
1102 	    }
1103 	    else
1104 	    {
1105 		p = stpcpy(p, itoa(num));
1106 		p = stpcpy(p, thisname);
1107 	    }
1108 	}
1109 
1110 	/* ignore negative numbers, but display corresponding string */
1111 	else if (num < 0)
1112 	{
1113 	    p = stpcpy(p, thisname);
1114 	}
1115     }
1116 
1117     /* if the last two characters in the string are ", ", delete them */
1118     p -= 2;
1119     if (p >= str && p[0] == ',' && p[1] == ' ')
1120     {
1121 	*p = '\0';
1122     }
1123 }
1124 
1125 static void
1126 line_update(char *old, char *new, int start, int line)
1127 {
1128     int ch;
1129     int diff;
1130     int newcol = start + 1;
1131     int lastcol = start;
1132     char cursor_on_line = false;
1133     char *current;
1134 
1135     /* compare the two strings and only rewrite what has changed */
1136     current = old;
1137 #ifdef DEBUG
1138     fprintf(debug, "line_update, starting at %d\n", start);
1139     fputs(old, debug);
1140     fputc('\n', debug);
1141     fputs(new, debug);
1142     fputs("\n-\n", debug);
1143 #endif
1144 
1145     /* start things off on the right foot		    */
1146     /* this is to make sure the invariants get set up right */
1147     if ((ch = *new++) != *old)
1148     {
1149 	if (line - lastline == 1 && start == 0)
1150 	{
1151 	    putchar('\n');
1152 	}
1153 	else
1154 	{
1155 	    Move_to(start, line);
1156 	}
1157 	cursor_on_line = true;
1158 	putchar(ch);
1159 	*old = ch;
1160 	lastcol = 1;
1161     }
1162     old++;
1163 
1164     /*
1165      *  main loop -- check each character.  If the old and new aren't the
1166      *	same, then update the display.  When the distance from the
1167      *	current cursor position to the new change is small enough,
1168      *	the characters that belong there are written to move the
1169      *	cursor over.
1170      *
1171      *	Invariants:
1172      *	    lastcol is the column where the cursor currently is sitting
1173      *		(always one beyond the end of the last mismatch).
1174      */
1175     do		/* yes, a do...while */
1176     {
1177 	if ((ch = *new++) != *old)
1178 	{
1179 	    /* new character is different from old	  */
1180 	    /* make sure the cursor is on top of this character */
1181 	    diff = newcol - lastcol;
1182 	    if (diff > 0)
1183 	    {
1184 		/* some motion is required--figure out which is shorter */
1185 		if (diff < 6 && cursor_on_line)
1186 		{
1187 		    /* overwrite old stuff--get it out of the old buffer */
1188 		    printf("%.*s", diff, &current[lastcol-start]);
1189 		}
1190 		else
1191 		{
1192 		    /* use cursor addressing */
1193 		    Move_to(newcol, line);
1194 		    cursor_on_line = true;
1195 		}
1196 		/* remember where the cursor is */
1197 		lastcol = newcol + 1;
1198 	    }
1199 	    else
1200 	    {
1201 		/* already there, update position */
1202 		lastcol++;
1203 	    }
1204 
1205 	    /* write what we need to */
1206 	    if (ch == '\0')
1207 	    {
1208 		/* at the end--terminate with a clear-to-end-of-line */
1209 		(void) clear_eol(strlen(old));
1210 	    }
1211 	    else
1212 	    {
1213 		/* write the new character */
1214 		putchar(ch);
1215 	    }
1216 	    /* put the new character in the screen buffer */
1217 	    *old = ch;
1218 	}
1219 
1220 	/* update working column and screen buffer pointer */
1221 	newcol++;
1222 	old++;
1223 
1224     } while (ch != '\0');
1225 
1226     /* zero out the rest of the line buffer -- MUST BE DONE! */
1227     diff = display_width - newcol;
1228     if (diff > 0)
1229     {
1230 	memset(old, 0, diff);
1231     }
1232 
1233     /* remember where the current line is */
1234     if (cursor_on_line)
1235     {
1236 	lastline = line;
1237     }
1238 }
1239 
1240 /*
1241  *  printable(str) - make the string pointed to by "str" into one that is
1242  *	printable (i.e.: all ascii), by converting all non-printable
1243  *	characters into '?'.  Replacements are done in place and a pointer
1244  *	to the original buffer is returned.
1245  */
1246 
1247 char *
1248 printable(char str[])
1249 {
1250     char *ptr;
1251     char ch;
1252 
1253     ptr = str;
1254     while ((ch = *ptr) != '\0')
1255     {
1256 	if (!isprint(ch))
1257 	{
1258 	    *ptr = '?';
1259 	}
1260 	ptr++;
1261     }
1262     return(str);
1263 }
1264 
1265 void
1266 i_uptime(struct timeval *bt, time_t *tod)
1267 {
1268     time_t uptime;
1269     int days, hrs, mins, secs;
1270 
1271     if (bt->tv_sec != -1) {
1272 	uptime = *tod - bt->tv_sec;
1273 	days = uptime / 86400;
1274 	uptime %= 86400;
1275 	hrs = uptime / 3600;
1276 	uptime %= 3600;
1277 	mins = uptime / 60;
1278 	secs = uptime % 60;
1279 
1280 	/*
1281 	 *  Display the uptime.
1282 	 */
1283 
1284 	if (smart_terminal)
1285 	{
1286 	    Move_to((screen_width - 24) - (days > 9 ? 1 : 0), 0);
1287 	}
1288 	else
1289 	{
1290 	    fputs(" ", stdout);
1291 	}
1292 	printf(" up %d+%02d:%02d:%02d", days, hrs, mins, secs);
1293     }
1294 }
1295