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