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