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