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