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