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