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