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