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