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