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