1 /* $OpenBSD: display.c,v 1.22 2005/07/01 19:33:35 jaredy Exp $ */
2
3 /*
4 * Top users/processes display for Unix
5 * Version 3
6 *
7 * Copyright (c) 1984, 1989, William LeFebvre, Rice University
8 * Copyright (c) 1989, 1990, 1992, William LeFebvre, Northwestern University
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
20 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22 * IN NO EVENT SHALL THE AUTHOR OR HIS EMPLOYER BE LIABLE FOR ANY DIRECT, INDIRECT,
23 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
24 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31 /*
32 * This file contains the routines that display information on the screen.
33 * Each section of the screen has two routines: one for initially writing
34 * all constant and dynamic text, and one for only updating the text that
35 * changes. The prefix "i_" is used on all the "initial" routines and the
36 * prefix "u_" is used for all the "updating" routines.
37 *
38 * ASSUMPTIONS:
39 * None of the "i_" routines use any of the termcap capabilities.
40 * In this way, those routines can be safely used on terminals that
41 * have minimal (or nonexistent) terminal capabilities.
42 *
43 * The routines are called in this order: *_loadave, i_timeofday,
44 * *_procstates, *_cpustates, *_memory, *_message, *_header,
45 * *_process, u_endscreen.
46 */
47
48 #include <sys/types.h>
49 #include <sys/time.h>
50 #include <sys/dkstat.h>
51 #include <sys/sched.h>
52 #include <stdio.h>
53 #include <ctype.h>
54 #include <err.h>
55 #include <stdlib.h>
56 #include <string.h>
57 #include <signal.h>
58 #include <term.h>
59 #include <time.h>
60 #include <unistd.h>
61 #include <stdarg.h>
62
63 #include "screen.h" /* interface to screen package */
64 #include "layout.h" /* defines for screen position layout */
65 #include "display.h"
66 #include "top.h"
67 #include "top.local.h"
68 #include "boolean.h"
69 #include "machine.h" /* we should eliminate this!!! */
70 #include "utils.h"
71
72 #ifdef DEBUG
73 FILE *debug;
74 #endif
75
76 static pid_t lmpid = 0;
77 static int last_hi = 0; /* used in u_process and u_endscreen */
78 static int lastline = 0;
79 static int display_width = MAX_COLS;
80
81 static char *cpustates_tag(int);
82 static int string_count(char **);
83 static void summary_format(char *, size_t, int *, char **);
84 static void line_update(char *, char *, int, int);
85
86 #define lineindex(l) ((l)*display_width)
87
88 /* things initialized by display_init and used throughout */
89
90 /* buffer of proc information lines for display updating */
91 char *screenbuf = NULL;
92
93 static char **procstate_names;
94 static char **cpustate_names;
95 static char **memory_names;
96
97 static int num_procstates;
98 static int num_cpustates;
99
100 static int *lprocstates;
101 static int64_t **lcpustates;
102
103 static int *cpustate_columns;
104 static int cpustate_total_length;
105
106 /* display ips */
107 int y_mem;
108 int y_message;
109 int y_header;
110 int y_idlecursor;
111 int y_procs;
112 extern int ncpu;
113 int Header_lines;
114
115 static enum {
116 OFF, ON, ERASE
117 } header_status = ON;
118
119 int
display_resize(void)120 display_resize(void)
121 {
122 int display_lines;
123
124 /* first, deallocate any previous buffer that may have been there */
125 if (screenbuf != NULL)
126 free(screenbuf);
127
128 /* calculate the current dimensions */
129 /* if operating in "dumb" mode, we only need one line */
130 display_lines = smart_terminal ? screen_length - Header_lines : 1;
131
132 /*
133 * we don't want more than MAX_COLS columns, since the
134 * machine-dependent modules make static allocations based on
135 * MAX_COLS and we don't want to run off the end of their buffers
136 */
137 display_width = screen_width;
138 if (display_width >= MAX_COLS)
139 display_width = MAX_COLS - 1;
140
141 /* now, allocate space for the screen buffer */
142 screenbuf = malloc(display_lines * display_width + 1);
143 if (screenbuf == NULL)
144 return (-1);
145
146 /* return number of lines available */
147 /* for dumb terminals, pretend like we can show any amount */
148 return (smart_terminal ? display_lines : Largest);
149 }
150
151 int
display_init(struct statics * statics)152 display_init(struct statics * statics)
153 {
154 int display_lines, *ip, i, cpu;
155 char **pp;
156
157 y_mem = 2 + ncpu;
158 y_message = 3 + ncpu;
159 y_header = 4 + ncpu;
160 y_idlecursor = 3 + ncpu;
161 y_procs = 5 + ncpu;
162 Header_lines = 5 + ncpu;
163
164 /* call resize to do the dirty work */
165 display_lines = display_resize();
166
167 /* only do the rest if we need to */
168 if (display_lines > -1) {
169 /* save pointers and allocate space for names */
170 procstate_names = statics->procstate_names;
171 num_procstates = string_count(procstate_names);
172 lprocstates = malloc(num_procstates * sizeof(int));
173 if (lprocstates == NULL)
174 err(1, NULL);
175
176 cpustate_names = statics->cpustate_names;
177 num_cpustates = string_count(cpustate_names);
178 lcpustates = malloc(ncpu * sizeof(int64_t *));
179 if (lcpustates == NULL)
180 err(1, NULL);
181 for (cpu = 0; cpu < ncpu; cpu++) {
182 lcpustates[cpu] = malloc(num_cpustates * sizeof(int64_t));
183 if (lcpustates[cpu] == NULL)
184 err(1, NULL);
185 }
186
187 cpustate_columns = malloc(num_cpustates * sizeof(int));
188 if (cpustate_columns == NULL)
189 err(1, NULL);
190
191 memory_names = statics->memory_names;
192
193 /* calculate starting columns where needed */
194 cpustate_total_length = 0;
195 pp = cpustate_names;
196 ip = cpustate_columns;
197 while (*pp != NULL) {
198 if ((i = strlen(*pp++)) > 0) {
199 *ip++ = cpustate_total_length;
200 cpustate_total_length += i + 8;
201 }
202 }
203 }
204 /* return number of lines available */
205 return (display_lines);
206 }
207
208 void
i_loadave(pid_t mpid,double * avenrun)209 i_loadave(pid_t mpid, double *avenrun)
210 {
211 int i;
212
213 /* i_loadave also clears the screen, since it is first */
214 clear();
215
216 /* mpid == -1 implies this system doesn't have an _mpid */
217 if (mpid != -1)
218 printf("last pid: %5ld; ", (long) mpid);
219
220 printf("load averages");
221
222 for (i = 0; i < 3; i++)
223 printf("%c %5.2f", i == 0 ? ':' : ',', avenrun[i]);
224
225 lmpid = mpid;
226 }
227
228 void
u_loadave(pid_t mpid,double * avenrun)229 u_loadave(pid_t mpid, double *avenrun)
230 {
231 int i;
232
233 if (mpid != -1) {
234 /* change screen only when value has really changed */
235 if (mpid != lmpid) {
236 Move_to(x_lastpid, y_lastpid);
237 printf("%5ld", (long) mpid);
238 lmpid = mpid;
239 }
240 /* i remembers x coordinate to move to */
241 i = x_loadave;
242 } else
243 i = x_loadave_nompid;
244
245 /* move into position for load averages */
246 Move_to(i, y_loadave);
247
248 /* display new load averages */
249 /* we should optimize this and only display changes */
250 for (i = 0; i < 3; i++)
251 printf("%s%5.2f", i == 0 ? "" : ", ", avenrun[i]);
252 }
253
254 /*
255 * Display the current time.
256 * "ctime" always returns a string that looks like this:
257 *
258 * Sun Sep 16 01:03:52 1973
259 * 012345678901234567890123
260 * 1 2
261 *
262 * We want indices 11 thru 18 (length 8).
263 */
264
265 void
i_timeofday(time_t * tod)266 i_timeofday(time_t * tod)
267 {
268
269 if (smart_terminal) {
270 Move_to(screen_width - 8, 0);
271 } else {
272 if (fputs(" ", stdout) == EOF)
273 exit(1);
274 }
275 #ifdef DEBUG
276 {
277 char *foo;
278 foo = ctime(tod);
279 if (fputs(foo, stdout) == EOF)
280 exit(1);
281 }
282 #endif
283 printf("%-8.8s\n", &(ctime(tod)[11]));
284 lastline = 1;
285 }
286
287 static int ltotal = 0;
288 static char procstates_buffer[MAX_COLS];
289
290 /*
291 * *_procstates(total, brkdn, names) - print the process summary line
292 *
293 * Assumptions: cursor is at the beginning of the line on entry
294 * lastline is valid
295 */
296 void
i_procstates(int total,int * brkdn)297 i_procstates(int total, int *brkdn)
298 {
299 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 if (putchar(' ') == EOF)
309 exit(1);
310 }
311
312 /* format and print the process state summary */
313 summary_format(procstates_buffer, sizeof(procstates_buffer), brkdn,
314 procstate_names);
315 if (fputs(procstates_buffer, stdout) == EOF)
316 exit(1);
317
318 /* save the numbers for next time */
319 memcpy(lprocstates, brkdn, num_procstates * sizeof(int));
320 }
321
322 void
u_procstates(int total,int * brkdn)323 u_procstates(int total, int *brkdn)
324 {
325 static char new[MAX_COLS];
326 int i;
327
328 /* update number of processes only if it has changed */
329 if (ltotal != total) {
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 if (fputs(" processes:", stdout) == EOF)
342 exit(1);
343 /* put out enough spaces to get to column 15 */
344 i = digits(total);
345 while (i++ < 4) {
346 if (putchar(' ') == EOF)
347 exit(1);
348 }
349 /* cursor may end up right where we want it!!! */
350 }
351 /* save new total */
352 ltotal = total;
353 }
354 /* see if any of the state numbers has changed */
355 if (memcmp(lprocstates, brkdn, num_procstates * sizeof(int)) != 0) {
356 /* format and update the line */
357 summary_format(new, sizeof(new), brkdn, procstate_names);
358 line_update(procstates_buffer, new, x_brkdn, y_brkdn);
359 memcpy(lprocstates, brkdn, num_procstates * sizeof(int));
360 }
361 }
362
363 /*
364 * *_cpustates(states, names) - print the cpu state percentages
365 *
366 * Assumptions: cursor is on the PREVIOUS line
367 */
368
369 static int cpustates_column;
370
371 /* cpustates_tag() calculates the correct tag to use to label the line */
372
373 static char *
cpustates_tag(int cpu)374 cpustates_tag(int cpu)
375 {
376 static char *tag;
377 static int cpulen, old_width;
378 int i;
379
380 if (cpulen == 0 && ncpu > 1) {
381 /* compute length of the cpu string */
382 for (i = ncpu; i > 0; cpulen++, i /= 10)
383 continue;
384 }
385
386 if (old_width == screen_width) {
387 if (ncpu > 1) {
388 /* just store the cpu number in the tag */
389 i = tag[3 + cpulen];
390 snprintf(tag + 3, cpulen + 1, "%.*d", cpulen, cpu);
391 tag[3 + cpulen] = i;
392 }
393 } else {
394 /*
395 * use a long tag if it will fit, otherwise use short one.
396 */
397 free(tag);
398 if (cpustate_total_length + 10 + cpulen >= screen_width)
399 i = asprintf(&tag, "CPU%.*d: ", cpulen, cpu);
400 else
401 i = asprintf(&tag, "CPU%.*d states: ", cpulen, cpu);
402 if (i == -1)
403 tag = NULL;
404 else {
405 cpustates_column = strlen(tag);
406 old_width = screen_width;
407 }
408 }
409 return (tag);
410 }
411
412 void
i_cpustates(int64_t * ostates)413 i_cpustates(int64_t *ostates)
414 {
415 int i, cpu, value;
416 int64_t *states;
417 char **names = cpustate_names, *thisname;
418
419 for (cpu = 0; cpu < ncpu; cpu++) {
420 /* print tag and bump lastline */
421 printf("\n%s", cpustates_tag(cpu));
422 lastline++;
423
424 /* now walk thru the names and print the line */
425 names = cpustate_names;
426 i = 0;
427 states = ostates + (CPUSTATES * cpu);
428 while ((thisname = *names++) != NULL) {
429 if (*thisname != '\0') {
430 /* retrieve the value and remember it */
431 value = *states++;
432
433 /* if percentage is >= 1000, print it as 100% */
434 printf((value >= 1000 ? "%s%4.0f%% %s" :
435 "%s%4.1f%% %s"), i++ == 0 ? "" : ", ",
436 ((float) value) / 10., thisname);
437 }
438 }
439
440 /* copy over values into "last" array */
441 memcpy(lcpustates[cpu], ostates, num_cpustates * sizeof(int64_t));
442 }
443 }
444
445 void
u_cpustates(int64_t * ostates)446 u_cpustates(int64_t *ostates)
447 {
448 char **names, *thisname;
449 int cpu, value, *colp;
450 int64_t *lp, *states;
451
452 for (cpu = 0; cpu < ncpu; cpu++) {
453 lastline = y_cpustates + cpu;
454 states = ostates + (CPUSTATES * cpu);
455 Move_to(cpustates_column, lastline);
456 lp = lcpustates[cpu];
457 colp = cpustate_columns;
458
459 /* we could be much more optimal about this */
460 names = cpustate_names;
461 while ((thisname = *names++) != NULL) {
462 if (*thisname != '\0') {
463 /* did the value change since last time? */
464 if (*lp != *states) {
465 /* yes, move and change */
466 lastline = y_cpustates + cpu;
467 Move_to(cpustates_column + *colp,
468 lastline);
469
470 /* retrieve value and remember it */
471 value = *states;
472
473 /* if percentage is >= 1000,
474 * print it as 100%
475 */
476 printf((value >= 1000 ? "%4.0f" :
477 "%4.1f"), ((double) value) / 10.);
478
479 /* remember it for next time */
480 *lp = *states;
481 }
482 }
483 /* increment and move on */
484 lp++;
485 states++;
486 colp++;
487 }
488 }
489 }
490
491 void
z_cpustates(void)492 z_cpustates(void)
493 {
494 char **names, *thisname;
495 int cpu, i;
496 int64_t *lp;
497
498 for (cpu = 0; cpu < ncpu; cpu++) {
499 /* show tag and bump lastline */
500 printf("\n%s", cpustates_tag(cpu));
501 lastline++;
502
503 names = cpustate_names;
504 i = 0;
505 while ((thisname = *names++) != NULL) {
506 if (*thisname != '\0')
507 printf("%s %% %s", i++ == 0 ? "" : ", ",
508 thisname);
509 }
510
511 /* fill the "last" array with all -1s, to ensure correct updating */
512 lp = lcpustates[cpu];
513 i = num_cpustates;
514 while (--i >= 0)
515 *lp++ = -1;
516 }
517 }
518
519 static char memory_buffer[MAX_COLS];
520
521 /*
522 * *_memory(stats) - print "Memory: " followed by the memory summary string
523 *
524 * Assumptions: cursor is on "lastline"
525 * for i_memory ONLY: cursor is on the previous line
526 */
527 void
i_memory(int * stats)528 i_memory(int *stats)
529 {
530 if (fputs("\nMemory: ", stdout) == EOF)
531 exit(1);
532 lastline++;
533
534 /* format and print the memory summary */
535 summary_format(memory_buffer, sizeof(memory_buffer), stats,
536 memory_names);
537 if (fputs(memory_buffer, stdout) == EOF)
538 exit(1);
539 }
540
541 void
u_memory(int * stats)542 u_memory(int *stats)
543 {
544 static char new[MAX_COLS];
545
546 /* format the new line */
547 summary_format(new, sizeof(new), stats, memory_names);
548 line_update(memory_buffer, new, x_mem, y_mem);
549 }
550
551 /*
552 * *_message() - print the next pending message line, or erase the one
553 * that is there.
554 *
555 * Note that u_message is (currently) the same as i_message.
556 *
557 * Assumptions: lastline is consistent
558 */
559
560 /*
561 * i_message is funny because it gets its message asynchronously (with
562 * respect to screen updates).
563 */
564
565 static char next_msg[MAX_COLS + 5];
566 static int msglen = 0;
567 /*
568 * Invariant: msglen is always the length of the message currently displayed
569 * on the screen (even when next_msg doesn't contain that message).
570 */
571
572 void
i_message(void)573 i_message(void)
574 {
575 while (lastline < y_message) {
576 if (fputc('\n', stdout) == EOF)
577 exit(1);
578 lastline++;
579 }
580 if (next_msg[0] != '\0') {
581 standout(next_msg);
582 msglen = strlen(next_msg);
583 next_msg[0] = '\0';
584 } else if (msglen > 0) {
585 (void) clear_eol(msglen);
586 msglen = 0;
587 }
588 }
589
590 void
u_message(void)591 u_message(void)
592 {
593 i_message();
594 }
595
596 static int header_length;
597
598 /*
599 * *_header(text) - print the header for the process area
600 *
601 * Assumptions: cursor is on the previous line and lastline is consistent
602 */
603
604 void
i_header(char * text)605 i_header(char *text)
606 {
607 header_length = strlen(text);
608 if (header_status == ON) {
609 if (putchar('\n') == EOF)
610 exit(1);
611 if (fputs(text, stdout) == EOF)
612 exit(1);
613 lastline++;
614 } else if (header_status == ERASE) {
615 header_status = OFF;
616 }
617 }
618
619 /* ARGSUSED */
620 void
u_header(char * text)621 u_header(char *text)
622 {
623 if (header_status == ERASE) {
624 if (putchar('\n') == EOF)
625 exit(1);
626 lastline++;
627 clear_eol(header_length);
628 header_status = OFF;
629 }
630 }
631
632 /*
633 * *_process(line, thisline) - print one process line
634 *
635 * Assumptions: lastline is consistent
636 */
637
638 void
i_process(int line,char * thisline)639 i_process(int line, char *thisline)
640 {
641 char *base;
642 size_t len;
643
644 /* make sure we are on the correct line */
645 while (lastline < y_procs + line) {
646 if (putchar('\n') == EOF)
647 exit(1);
648 lastline++;
649 }
650
651 /* truncate the line to conform to our current screen width */
652 thisline[display_width] = '\0';
653
654 /* write the line out */
655 if (fputs(thisline, stdout) == EOF)
656 exit(1);
657
658 /* copy it in to our buffer */
659 base = smart_terminal ? screenbuf + lineindex(line) : screenbuf;
660 len = strlcpy(base, thisline, display_width);
661 if (len < (size_t)display_width) {
662 /* zero fill the rest of it */
663 memset(base + len, 0, display_width - len);
664 }
665 }
666
667 void
u_process(int linenum,char * linebuf)668 u_process(int linenum, char *linebuf)
669 {
670 int screen_line = linenum + Header_lines;
671 char *bufferline;
672 size_t len;
673
674 /* remember a pointer to the current line in the screen buffer */
675 bufferline = &screenbuf[lineindex(linenum)];
676
677 /* truncate the line to conform to our current screen width */
678 linebuf[display_width] = '\0';
679 bufferline[display_width] = '\0';
680
681 /* is line higher than we went on the last display? */
682 if (linenum >= last_hi) {
683 /* yes, just ignore screenbuf and write it out directly */
684 /* get positioned on the correct line */
685 if (screen_line - lastline == 1) {
686 if (putchar('\n') == EOF)
687 exit(1);
688 lastline++;
689 } else {
690 Move_to(0, screen_line);
691 lastline = screen_line;
692 }
693
694 /* now write the line */
695 if (fputs(linebuf, stdout) == EOF)
696 exit(1);
697
698 /* copy it in to the buffer */
699 len = strlcpy(bufferline, linebuf, display_width);
700 if (len < (size_t)display_width) {
701 /* zero fill the rest of it */
702 memset(bufferline + len, 0, display_width - len);
703 }
704 } else {
705 line_update(bufferline, linebuf, 0, linenum + Header_lines);
706 }
707 }
708
709 void
u_endscreen(int hi)710 u_endscreen(int hi)
711 {
712 int screen_line = hi + Header_lines, i;
713
714 if (smart_terminal) {
715 if (hi < last_hi) {
716 /* need to blank the remainder of the screen */
717 /*
718 * but only if there is any screen left below this
719 * line
720 */
721 if (lastline + 1 < screen_length) {
722 /*
723 * efficiently move to the end of currently
724 * displayed info
725 */
726 if (screen_line - lastline < 5) {
727 while (lastline < screen_line) {
728 if (putchar('\n') == EOF)
729 exit(1);
730 lastline++;
731 }
732 } else {
733 Move_to(0, screen_line);
734 lastline = screen_line;
735 }
736
737 if (clear_to_end) {
738 /* we can do this the easy way */
739 putcap(clear_to_end);
740 } else {
741 /* use clear_eol on each line */
742 i = hi;
743 while ((void) clear_eol(strlen(&screenbuf[lineindex(i++)])), i < last_hi) {
744 if (putchar('\n') == EOF)
745 exit(1);
746 }
747 }
748 }
749 }
750 last_hi = hi;
751
752 /* move the cursor to a pleasant place */
753 Move_to(x_idlecursor, y_idlecursor);
754 lastline = y_idlecursor;
755 } else {
756 /*
757 * separate this display from the next with some vertical
758 * room
759 */
760 if (fputs("\n\n", stdout) == EOF)
761 exit(1);
762 }
763 }
764
765 void
display_header(int t)766 display_header(int t)
767 {
768 if (t) {
769 header_status = ON;
770 } else if (header_status == ON) {
771 header_status = ERASE;
772 }
773 }
774
775 void
new_message(int type,const char * msgfmt,...)776 new_message(int type, const char *msgfmt,...)
777 {
778 va_list ap;
779 int i;
780
781 va_start(ap, msgfmt);
782 /* first, format the message */
783 vsnprintf(next_msg, sizeof(next_msg), msgfmt, ap);
784 va_end(ap);
785
786 if (msglen > 0) {
787 /* message there already -- can we clear it? */
788 if (!overstrike) {
789 /* yes -- write it and clear to end */
790 i = strlen(next_msg);
791 if ((type & MT_delayed) == 0) {
792 if (type & MT_standout)
793 standout(next_msg);
794 else {
795 if (fputs(next_msg, stdout) == EOF)
796 exit(1);
797 }
798 (void) clear_eol(msglen - i);
799 msglen = i;
800 next_msg[0] = '\0';
801 }
802 }
803 } else {
804 if ((type & MT_delayed) == 0) {
805 if (type & MT_standout)
806 standout(next_msg);
807 else {
808 if (fputs(next_msg, stdout) == EOF)
809 exit(1);
810 }
811 msglen = strlen(next_msg);
812 next_msg[0] = '\0';
813 }
814 }
815 }
816
817 void
clear_message(void)818 clear_message(void)
819 {
820 if (clear_eol(msglen) == 1) {
821 if (putchar('\r') == EOF)
822 exit(1);
823 }
824 }
825
826 int
readline(char * buffer,int size,int numeric)827 readline(char *buffer, int size, int numeric)
828 {
829 char *ptr = buffer, ch, cnt = 0, maxcnt = 0;
830 extern volatile sig_atomic_t leaveflag;
831 ssize_t len;
832
833 /* allow room for null terminator */
834 size -= 1;
835
836 /* read loop */
837 while ((fflush(stdout), (len = read(STDIN_FILENO, ptr, 1)) > 0)) {
838
839 if (len == 0 || leaveflag) {
840 end_screen();
841 exit(0);
842 }
843
844 /* newline means we are done */
845 if ((ch = *ptr) == '\n')
846 break;
847
848 /* handle special editing characters */
849 if (ch == ch_kill) {
850 /* kill line -- account for overstriking */
851 if (overstrike)
852 msglen += maxcnt;
853
854 /* return null string */
855 *buffer = '\0';
856 if (putchar('\r') == EOF)
857 exit(1);
858 return (-1);
859 } else if (ch == ch_erase) {
860 /* erase previous character */
861 if (cnt <= 0) {
862 /* none to erase! */
863 if (putchar('\7') == EOF)
864 exit(1);
865 } else {
866 if (fputs("\b \b", stdout) == EOF)
867 exit(1);
868 ptr--;
869 cnt--;
870 }
871 }
872 /* check for character validity and buffer overflow */
873 else if (cnt == size || (numeric && !isdigit(ch)) ||
874 !isprint(ch)) {
875 /* not legal */
876 if (putchar('\7') == EOF)
877 exit(1);
878 } else {
879 /* echo it and store it in the buffer */
880 if (putchar(ch) == EOF)
881 exit(1);
882 ptr++;
883 cnt++;
884 if (cnt > maxcnt)
885 maxcnt = cnt;
886 }
887 }
888
889 /* all done -- null terminate the string */
890 *ptr = '\0';
891
892 /* account for the extra characters in the message area */
893 /* (if terminal overstrikes, remember the furthest they went) */
894 msglen += overstrike ? maxcnt : cnt;
895
896 /* return either inputted number or string length */
897 if (putchar('\r') == EOF)
898 exit(1);
899 return (cnt == 0 ? -1 : numeric ? atoi(buffer) : cnt);
900 }
901
902 /* internal support routines */
903 static int
string_count(char ** pp)904 string_count(char **pp)
905 {
906 int cnt;
907
908 cnt = 0;
909 while (*pp++ != NULL)
910 cnt++;
911 return (cnt);
912 }
913
914 #define COPYLEFT(to, from) \
915 do { \
916 len = strlcpy((to), (from), left); \
917 if (len >= left) \
918 return; \
919 p += len; \
920 left -= len; \
921 } while (0)
922
923 static void
summary_format(char * buf,size_t left,int * numbers,char ** names)924 summary_format(char *buf, size_t left, int *numbers, char **names)
925 {
926 char *p, *thisname;
927 size_t len;
928 int num;
929
930 /* format each number followed by its string */
931 p = buf;
932 while ((thisname = *names++) != NULL) {
933 /* get the number to format */
934 num = *numbers++;
935
936 if (num >= 0) {
937 /* is this number in kilobytes? */
938 if (thisname[0] == 'K') {
939 /* yes: format it as a memory value */
940 COPYLEFT(p, format_k(num));
941
942 /*
943 * skip over the K, since it was included by
944 * format_k
945 */
946 COPYLEFT(p, thisname + 1);
947 } else if (num > 0) {
948 len = snprintf(p, left, "%d%s", num, thisname);
949 if (len == (size_t)-1 || len >= left)
950 return;
951 p += len;
952 left -= len;
953 }
954 } else {
955 /*
956 * Ignore negative numbers, but display corresponding
957 * string.
958 */
959 COPYLEFT(p, thisname);
960 }
961 }
962
963 /* if the last two characters in the string are ", ", delete them */
964 p -= 2;
965 if (p >= buf && p[0] == ',' && p[1] == ' ')
966 *p = '\0';
967 }
968
969 static void
line_update(char * old,char * new,int start,int line)970 line_update(char *old, char *new, int start, int line)
971 {
972 int ch, diff, newcol = start + 1, lastcol = start;
973 char cursor_on_line = No, *current;
974
975 /* compare the two strings and only rewrite what has changed */
976 current = old;
977 #ifdef DEBUG
978 fprintf(debug, "line_update, starting at %d\n", start);
979 fputs(old, debug);
980 fputc('\n', debug);
981 fputs(new, debug);
982 fputs("\n-\n", debug);
983 #endif
984
985 /* start things off on the right foot */
986 /* this is to make sure the invariants get set up right */
987 if ((ch = *new++) != *old) {
988 if (line - lastline == 1 && start == 0) {
989 if (putchar('\n') == EOF)
990 exit(1);
991 } else
992 Move_to(start, line);
993
994 cursor_on_line = Yes;
995 if (putchar(ch) == EOF)
996 exit(1);
997 *old = ch;
998 lastcol = 1;
999 }
1000 old++;
1001
1002 /*
1003 * main loop -- check each character. If the old and new aren't the
1004 * same, then update the display. When the distance from the
1005 * current cursor position to the new change is small enough,
1006 * the characters that belong there are written to move the
1007 * cursor over.
1008 *
1009 * Invariants:
1010 * lastcol is the column where the cursor currently is sitting
1011 * (always one beyond the end of the last mismatch).
1012 */
1013 do {
1014 if ((ch = *new++) != *old) {
1015 /* new character is different from old */
1016 /* make sure the cursor is on top of this character */
1017 diff = newcol - lastcol;
1018 if (diff > 0) {
1019 /*
1020 * some motion is required--figure out which
1021 * is shorter
1022 */
1023 if (diff < 6 && cursor_on_line) {
1024 /*
1025 * overwrite old stuff--get it out of
1026 * the old buffer
1027 */
1028 printf("%.*s", diff, ¤t[lastcol - start]);
1029 } else {
1030 /* use cursor addressing */
1031 Move_to(newcol, line);
1032 cursor_on_line = Yes;
1033 }
1034 /* remember where the cursor is */
1035 lastcol = newcol + 1;
1036 } else {
1037 /* already there, update position */
1038 lastcol++;
1039 }
1040
1041 /* write what we need to */
1042 if (ch == '\0') {
1043 /*
1044 * at the end--terminate with a
1045 * clear-to-end-of-line
1046 */
1047 (void) clear_eol(strlen(old));
1048 } else {
1049 /* write the new character */
1050 if (putchar(ch) == EOF)
1051 exit(1);
1052 }
1053 /* put the new character in the screen buffer */
1054 *old = ch;
1055 }
1056 /* update working column and screen buffer pointer */
1057 newcol++;
1058 old++;
1059 } while (ch != '\0');
1060
1061 /* zero out the rest of the line buffer -- MUST BE DONE! */
1062 diff = display_width - newcol;
1063 if (diff > 0)
1064 memset(old, 0, diff);
1065
1066 /* remember where the current line is */
1067 if (cursor_on_line)
1068 lastline = line;
1069 }
1070
1071 /*
1072 * printable(str) - make the string pointed to by "str" into one that is
1073 * printable (i.e.: all ascii), by converting all non-printable
1074 * characters into '?'. Replacements are done in place and a pointer
1075 * to the original buffer is returned.
1076 */
1077 char *
printable(char * str)1078 printable(char *str)
1079 {
1080 char *ptr, ch;
1081
1082 ptr = str;
1083 while ((ch = *ptr) != '\0') {
1084 if (!isprint(ch))
1085 *ptr = '?';
1086 ptr++;
1087 }
1088 return (str);
1089 }
1090