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