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