1 /* $OpenBSD: display.c,v 1.40 2010/04/23 09:26:13 otto 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/sched.h> 50 #include <curses.h> 51 #include <errno.h> 52 #include <stdio.h> 53 #include <ctype.h> 54 #include <err.h> 55 #include <signal.h> 56 #include <stdlib.h> 57 #include <string.h> 58 #include <unistd.h> 59 60 #include "screen.h" /* interface to screen package */ 61 #include "layout.h" /* defines for screen position layout */ 62 #include "display.h" 63 #include "top.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 int display_width = MAX_COLS; 73 74 static char *cpustates_tag(int); 75 static int string_count(char **); 76 static void summary_format(char *, size_t, int *, char **); 77 static int readlinedumb(char *, int); 78 79 #define lineindex(l) ((l)*display_width) 80 81 /* things initialized by display_init and used throughout */ 82 83 /* buffer of proc information lines for display updating */ 84 char *screenbuf = NULL; 85 86 static char **procstate_names; 87 static char **cpustate_names; 88 static char **memory_names; 89 90 static int num_cpustates; 91 92 static int *cpustate_columns; 93 static int cpustate_total_length; 94 95 /* display ips */ 96 int y_mem; 97 int y_message; 98 int y_header; 99 int y_idlecursor; 100 int y_procs; 101 extern int ncpu; 102 extern int combine_cpus; 103 104 int header_status = Yes; 105 106 static int 107 empty(void) 108 { 109 return OK; 110 } 111 112 static int 113 myfputs(const char *s) 114 { 115 return fputs(s, stdout); 116 } 117 118 static int (*addstrp)(const char *); 119 static int (*printwp)(const char *, ...); 120 static int (*standoutp)(void); 121 static int (*standendp)(void); 122 123 int 124 display_resize(void) 125 { 126 int display_lines; 127 int cpu_lines = (combine_cpus ? 1 : ncpu); 128 129 y_mem = 2 + cpu_lines; 130 y_header = 4 + cpu_lines; 131 y_procs = 5 + cpu_lines; 132 133 /* calculate the current dimensions */ 134 /* if operating in "dumb" mode, we only need one line */ 135 display_lines = smart_terminal ? screen_length - y_procs : 1; 136 137 y_idlecursor = y_message = 3 + (combine_cpus ? 1 : ncpu); 138 if (screen_length <= y_message) 139 y_idlecursor = y_message = screen_length - 1; 140 141 /* 142 * we don't want more than MAX_COLS columns, since the 143 * machine-dependent modules make static allocations based on 144 * MAX_COLS and we don't want to run off the end of their buffers 145 */ 146 display_width = screen_width; 147 if (display_width >= MAX_COLS) 148 display_width = MAX_COLS - 1; 149 150 /* return number of lines available */ 151 /* for dumb terminals, pretend like we can show any amount */ 152 return (smart_terminal ? display_lines : Largest); 153 } 154 155 int 156 display_init(struct statics * statics) 157 { 158 int display_lines, *ip, i; 159 char **pp; 160 161 if (smart_terminal) { 162 addstrp = addstr; 163 printwp = (int(*)(const char *, ...))printw; 164 standoutp = standout; 165 standendp = standend; 166 } else { 167 addstrp = myfputs; 168 printwp = printf; 169 standoutp = empty; 170 standendp = empty; 171 } 172 173 /* call resize to do the dirty work */ 174 display_lines = display_resize(); 175 176 /* only do the rest if we need to */ 177 /* save pointers and allocate space for names */ 178 procstate_names = statics->procstate_names; 179 180 cpustate_names = statics->cpustate_names; 181 num_cpustates = string_count(cpustate_names); 182 183 cpustate_columns = calloc(num_cpustates, sizeof(int)); 184 if (cpustate_columns == NULL) 185 err(1, NULL); 186 187 memory_names = statics->memory_names; 188 189 /* calculate starting columns where needed */ 190 cpustate_total_length = 0; 191 pp = cpustate_names; 192 ip = cpustate_columns; 193 while (*pp != NULL) { 194 if ((i = strlen(*pp++)) > 0) { 195 *ip++ = cpustate_total_length; 196 cpustate_total_length += i + 8; 197 } 198 } 199 200 if (display_lines < 0) 201 display_lines = 0; 202 203 /* return number of lines available */ 204 return (display_lines); 205 } 206 207 void 208 i_loadave(pid_t mpid, double *avenrun) 209 { 210 if (screen_length > 1 || !smart_terminal) { 211 int i; 212 213 move(0, 0); 214 clrtoeol(); 215 216 addstrp("load averages"); 217 /* mpid == -1 implies this system doesn't have an _mpid */ 218 if (mpid != -1) 219 printwp("last pid: %5ld; ", (long) mpid); 220 221 for (i = 0; i < 3; i++) 222 printwp("%c %5.2f", i == 0 ? ':' : ',', avenrun[i]); 223 } 224 } 225 226 /* 227 * Display the current time. 228 * "ctime" always returns a string that looks like this: 229 * 230 * Sun Sep 16 01:03:52 1973 231 * 012345678901234567890123 232 * 1 2 233 * 234 * We want indices 11 thru 18 (length 8). 235 */ 236 237 void 238 i_timeofday(time_t * tod) 239 { 240 static char buf[30]; 241 242 if (buf[0] == '\0') 243 gethostname(buf, sizeof(buf)); 244 245 if (screen_length > 1 || !smart_terminal) { 246 if (smart_terminal) { 247 move(0, screen_width - 8 - strlen(buf) - 1); 248 } else { 249 if (fputs(" ", stdout) == EOF) 250 exit(1); 251 } 252 #ifdef DEBUG 253 { 254 char *foo; 255 foo = ctime(tod); 256 addstrp(foo); 257 } 258 #endif 259 printwp("%s %-8.8s", buf, &(ctime(tod)[11])); 260 putn(); 261 } 262 } 263 264 /* 265 * *_procstates(total, brkdn, names) - print the process summary line 266 * 267 * Assumptions: cursor is at the beginning of the line on entry 268 */ 269 void 270 i_procstates(int total, int *brkdn) 271 { 272 if (screen_length > 2 || !smart_terminal) { 273 int i; 274 char procstates_buffer[MAX_COLS]; 275 276 move(1, 0); 277 clrtoeol(); 278 /* write current number of processes and remember the value */ 279 printwp("%d processes:", total); 280 281 if (smart_terminal) 282 move(1, 15); 283 else { 284 /* put out enough spaces to get to column 15 */ 285 i = digits(total); 286 while (i++ < 4) { 287 if (putchar(' ') == EOF) 288 exit(1); 289 } 290 } 291 292 /* format and print the process state summary */ 293 summary_format(procstates_buffer, sizeof(procstates_buffer), brkdn, 294 procstate_names); 295 296 addstrp(procstates_buffer); 297 putn(); 298 } 299 } 300 301 /* 302 * *_cpustates(states, names) - print the cpu state percentages 303 * 304 * Assumptions: cursor is on the PREVIOUS line 305 */ 306 307 /* cpustates_tag() calculates the correct tag to use to label the line */ 308 309 static char * 310 cpustates_tag(int cpu) 311 { 312 if (screen_length > 3 || !smart_terminal) { 313 static char *tag; 314 static int cpulen, old_width; 315 int i; 316 317 if (cpulen == 0 && ncpu > 1) { 318 /* compute length of the cpu string */ 319 for (i = ncpu; i > 0; cpulen++, i /= 10) 320 continue; 321 } 322 323 if (old_width == screen_width) { 324 if (ncpu > 1) { 325 /* just store the cpu number in the tag */ 326 i = tag[3 + cpulen]; 327 snprintf(tag + 3, cpulen + 1, "%.*d", cpulen, cpu); 328 tag[3 + cpulen] = i; 329 } 330 } else { 331 /* 332 * use a long tag if it will fit, otherwise use short one. 333 */ 334 free(tag); 335 if (cpustate_total_length + 10 + cpulen >= screen_width) 336 i = asprintf(&tag, "CPU%.*d: ", cpulen, cpu); 337 else 338 i = asprintf(&tag, "CPU%.*d states: ", cpulen, cpu); 339 if (i == -1) 340 tag = NULL; 341 else 342 old_width = screen_width; 343 } 344 return (tag); 345 } else 346 return ("\0"); 347 } 348 349 void 350 i_cpustates(int64_t *ostates) 351 { 352 int i, first, cpu; 353 double value; 354 int64_t *states; 355 char **names, *thisname; 356 357 if (combine_cpus) { 358 static double *values; 359 if (!values) { 360 values = calloc(num_cpustates, sizeof(*values)); 361 if (!values) 362 err(1, NULL); 363 } 364 memset(values, 0, num_cpustates * sizeof(*values)); 365 for (cpu = 0; cpu < ncpu; cpu++) { 366 names = cpustate_names; 367 states = ostates + (CPUSTATES * cpu); 368 i = 0; 369 while ((thisname = *names++) != NULL) { 370 if (*thisname != '\0') { 371 /* retrieve the value and remember it */ 372 values[i++] += *states++; 373 } 374 } 375 } 376 if (screen_length > 2 || !smart_terminal) { 377 names = cpustate_names; 378 i = 0; 379 first = 0; 380 move(2, 0); 381 clrtoeol(); 382 addstrp("All CPUs: "); 383 384 while ((thisname = *names++) != NULL) { 385 if (*thisname != '\0') { 386 value = values[i++] / ncpu; 387 /* if percentage is >= 1000, print it as 100% */ 388 printwp((value >= 1000 ? "%s%4.0f%% %s" : 389 "%s%4.1f%% %s"), first++ == 0 ? "" : ", ", 390 value / 10., thisname); 391 } 392 } 393 putn(); 394 } 395 return; 396 } 397 for (cpu = 0; cpu < ncpu; cpu++) { 398 /* now walk thru the names and print the line */ 399 names = cpustate_names; 400 first = 0; 401 states = ostates + (CPUSTATES * cpu); 402 403 if (screen_length > 2 + cpu || !smart_terminal) { 404 move(2 + cpu, 0); 405 clrtoeol(); 406 addstrp(cpustates_tag(cpu)); 407 408 while ((thisname = *names++) != NULL) { 409 if (*thisname != '\0') { 410 /* retrieve the value and remember it */ 411 value = *states++; 412 413 /* if percentage is >= 1000, print it as 100% */ 414 printwp((value >= 1000 ? "%s%4.0f%% %s" : 415 "%s%4.1f%% %s"), first++ == 0 ? "" : ", ", 416 value / 10., thisname); 417 } 418 } 419 putn(); 420 } 421 } 422 } 423 424 /* 425 * *_memory(stats) - print "Memory: " followed by the memory summary string 426 */ 427 void 428 i_memory(int *stats) 429 { 430 if (screen_length > y_mem || !smart_terminal) { 431 char memory_buffer[MAX_COLS]; 432 433 move(y_mem, 0); 434 clrtoeol(); 435 addstrp("Memory: "); 436 437 /* format and print the memory summary */ 438 summary_format(memory_buffer, sizeof(memory_buffer), stats, 439 memory_names); 440 addstrp(memory_buffer); 441 putn(); 442 } 443 } 444 445 /* 446 * *_message() - print the next pending message line, or erase the one 447 * that is there. 448 */ 449 450 /* 451 * i_message is funny because it gets its message asynchronously (with 452 * respect to screen updates). 453 */ 454 455 static char next_msg[MAX_COLS + 5]; 456 static int msgon = 0; 457 458 void 459 i_message(void) 460 { 461 move(y_message, 0); 462 if (next_msg[0] != '\0') { 463 standoutp(); 464 addstrp(next_msg); 465 standendp(); 466 clrtoeol(); 467 msgon = TRUE; 468 next_msg[0] = '\0'; 469 } else if (msgon) { 470 clrtoeol(); 471 msgon = FALSE; 472 } 473 } 474 475 /* 476 * *_header(text) - print the header for the process area 477 */ 478 479 void 480 i_header(char *text) 481 { 482 if (header_status == Yes && (screen_length > y_header 483 || !smart_terminal)) { 484 if (!smart_terminal) { 485 putn(); 486 if (fputs(text, stdout) == EOF) 487 exit(1); 488 putn(); 489 } else { 490 move(y_header, 0); 491 clrtoeol(); 492 addstrp(text); 493 } 494 } 495 } 496 497 /* 498 * *_process(line, thisline) - print one process line 499 */ 500 501 void 502 i_process(int line, char *thisline, int hl) 503 { 504 /* make sure we are on the correct line */ 505 move(y_procs + line, 0); 506 507 /* truncate the line to conform to our current screen width */ 508 thisline[display_width] = '\0'; 509 510 /* write the line out */ 511 if (hl && smart_terminal) 512 standoutp(); 513 addstrp(thisline); 514 if (hl && smart_terminal) 515 standendp(); 516 putn(); 517 clrtoeol(); 518 } 519 520 void 521 u_endscreen(void) 522 { 523 if (smart_terminal) { 524 clrtobot(); 525 /* move the cursor to a pleasant place */ 526 move(y_idlecursor, x_idlecursor); 527 } else { 528 /* 529 * separate this display from the next with some vertical 530 * room 531 */ 532 if (fputs("\n\n", stdout) == EOF) 533 exit(1); 534 } 535 } 536 537 void 538 display_header(int status) 539 { 540 header_status = status; 541 } 542 543 void 544 new_message(int type, const char *msgfmt,...) 545 { 546 va_list ap; 547 548 va_start(ap, msgfmt); 549 /* first, format the message */ 550 vsnprintf(next_msg, sizeof(next_msg), msgfmt, ap); 551 va_end(ap); 552 553 if (next_msg[0] != '\0') { 554 /* message there already -- can we clear it? */ 555 /* yes -- write it and clear to end */ 556 if ((type & MT_delayed) == 0) { 557 move(y_message, 0); 558 if (type & MT_standout) 559 standoutp(); 560 addstrp(next_msg); 561 if (type & MT_standout) 562 standendp(); 563 clrtoeol(); 564 msgon = TRUE; 565 next_msg[0] = '\0'; 566 if (smart_terminal) 567 refresh(); 568 } 569 } 570 } 571 572 void 573 clear_message(void) 574 { 575 move(y_message, 0); 576 clrtoeol(); 577 } 578 579 580 static int 581 readlinedumb(char *buffer, int size) 582 { 583 char *ptr = buffer, ch, cnt = 0, maxcnt = 0; 584 extern volatile sig_atomic_t leaveflag; 585 ssize_t len; 586 587 /* allow room for null terminator */ 588 size -= 1; 589 590 /* read loop */ 591 while ((fflush(stdout), (len = read(STDIN_FILENO, ptr, 1)) > 0)) { 592 593 if (len == 0 || leaveflag) { 594 end_screen(); 595 exit(0); 596 } 597 598 /* newline means we are done */ 599 if ((ch = *ptr) == '\n') 600 break; 601 602 /* handle special editing characters */ 603 if (ch == ch_kill) { 604 /* return null string */ 605 *buffer = '\0'; 606 putr(); 607 return (-1); 608 } else if (ch == ch_erase) { 609 /* erase previous character */ 610 if (cnt <= 0) { 611 /* none to erase! */ 612 if (putchar('\7') == EOF) 613 exit(1); 614 } else { 615 if (fputs("\b \b", stdout) == EOF) 616 exit(1); 617 ptr--; 618 cnt--; 619 } 620 } 621 /* check for character validity and buffer overflow */ 622 else if (cnt == size || !isprint(ch)) { 623 /* not legal */ 624 if (putchar('\7') == EOF) 625 exit(1); 626 } else { 627 /* echo it and store it in the buffer */ 628 if (putchar(ch) == EOF) 629 exit(1); 630 ptr++; 631 cnt++; 632 if (cnt > maxcnt) 633 maxcnt = cnt; 634 } 635 } 636 637 /* all done -- null terminate the string */ 638 *ptr = '\0'; 639 640 /* return either inputted number or string length */ 641 putr(); 642 return (cnt == 0 ? -1 : cnt); 643 } 644 645 int 646 readline(char *buffer, int size) 647 { 648 size_t cnt; 649 650 /* allow room for null terminator */ 651 size -= 1; 652 653 if (smart_terminal) 654 getnstr(buffer, size); 655 else 656 return readlinedumb(buffer, size); 657 658 cnt = strlen(buffer); 659 if (cnt > 0 && buffer[cnt - 1] == '\n') 660 buffer[cnt - 1] = '\0'; 661 return (cnt == 0 ? -1 : cnt); 662 } 663 664 /* internal support routines */ 665 static int 666 string_count(char **pp) 667 { 668 int cnt; 669 670 cnt = 0; 671 while (*pp++ != NULL) 672 cnt++; 673 return (cnt); 674 } 675 676 #define COPYLEFT(to, from) \ 677 do { \ 678 len = strlcpy((to), (from), left); \ 679 if (len >= left) \ 680 return; \ 681 p += len; \ 682 left -= len; \ 683 } while (0) 684 685 static void 686 summary_format(char *buf, size_t left, int *numbers, char **names) 687 { 688 char *p, *thisname; 689 size_t len; 690 int num; 691 692 /* format each number followed by its string */ 693 p = buf; 694 while ((thisname = *names++) != NULL) { 695 /* get the number to format */ 696 num = *numbers++; 697 698 if (num >= 0) { 699 /* is this number in kilobytes? */ 700 if (thisname[0] == 'K') { 701 /* yes: format it as a memory value */ 702 COPYLEFT(p, format_k(num)); 703 704 /* 705 * skip over the K, since it was included by 706 * format_k 707 */ 708 COPYLEFT(p, thisname + 1); 709 } else if (num > 0) { 710 len = snprintf(p, left, "%d%s", num, thisname); 711 if (len == (size_t)-1 || len >= left) 712 return; 713 p += len; 714 left -= len; 715 } 716 } else { 717 /* 718 * Ignore negative numbers, but display corresponding 719 * string. 720 */ 721 COPYLEFT(p, thisname); 722 } 723 } 724 725 /* if the last two characters in the string are ", ", delete them */ 726 p -= 2; 727 if (p >= buf && p[0] == ',' && p[1] == ' ') 728 *p = '\0'; 729 } 730 731 /* 732 * printable(str) - make the string pointed to by "str" into one that is 733 * printable (i.e.: all ascii), by converting all non-printable 734 * characters into '?'. Replacements are done in place and a pointer 735 * to the original buffer is returned. 736 */ 737 char * 738 printable(char *str) 739 { 740 char *ptr, ch; 741 742 ptr = str; 743 while ((ch = *ptr) != '\0') { 744 if (!isprint(ch)) 745 *ptr = '?'; 746 ptr++; 747 } 748 return (str); 749 } 750 751 752 /* 753 * show_help() - display the help screen; invoked in response to 754 * either 'h' or '?'. 755 */ 756 void 757 show_help(void) 758 { 759 if (smart_terminal) { 760 clear(); 761 nl(); 762 } 763 printwp("These single-character commands are available:\n" 764 "\n" 765 "^L - redraw screen\n" 766 "<space> - update screen\n" 767 "+ - reset any g, p, or u filters\n" 768 "1 - display CPU statistics on a single line\n" 769 "C - toggle the display of command line arguments\n" 770 "d count - show `count' displays, then exit\n" 771 "e - list errors generated by last \"kill\" or \"renice\" command\n" 772 "h | ? - help; show this text\n" 773 "g string - filter on command name (g+ selects all commands)\n" 774 "I | i - toggle the display of idle processes\n" 775 "k [-sig] pid - send signal `-sig' to process `pid'\n" 776 "n|# count - show `count' processes\n" 777 "o field - specify sort order (size, res, cpu, time, pri, pid, command)\n" 778 "P pid - highlight process `pid' (P+ switches highlighting off)\n" 779 "p pid - display process by `pid' (p+ selects all processes)\n" 780 "q - quit\n" 781 "r count pid - renice process `pid' to nice value `count'\n" 782 "S - toggle the display of system processes\n" 783 "s time - change delay between displays to `time' seconds\n" 784 "T - toggle the display of threads\n" 785 "u user - display processes for `user' (u+ selects all users)\n" 786 "\n"); 787 788 if (smart_terminal) { 789 nonl(); 790 refresh(); 791 } 792 } 793 794 /* 795 * show_errors() - display on stdout the current log of errors. 796 */ 797 void 798 show_errors(void) 799 { 800 struct errs *errp = errs; 801 int cnt = 0; 802 803 if (smart_terminal) { 804 clear(); 805 nl(); 806 } 807 printwp("%d error%s:\n\n", errcnt, errcnt == 1 ? "" : "s"); 808 while (cnt++ < errcnt) { 809 printwp("%5s: %s\n", errp->arg, 810 errp->err == 0 ? "Not a number" : strerror(errp->err)); 811 errp++; 812 } 813 printwp("\n"); 814 if (smart_terminal) { 815 nonl(); 816 refresh(); 817 } 818 } 819 820 void 821 anykey(void) 822 { 823 int ch; 824 ssize_t len; 825 826 standoutp(); 827 addstrp("Hit any key to continue: "); 828 standendp(); 829 if (smart_terminal) 830 refresh(); 831 else 832 fflush(stdout); 833 while (1) { 834 len = read(STDIN_FILENO, &ch, 1); 835 if (len == -1 && errno == EINTR) 836 continue; 837 if (len == 0) 838 exit(1); 839 break; 840 } 841 } 842