ch.c revision 1.9
1/* 2 * Copyright (C) 1984-2011 Mark Nudelman 3 * 4 * You may distribute under the terms of either the GNU General Public 5 * License or the Less License, as specified in the README file. 6 * 7 * For more information about less, or for information on how to 8 * contact the author, see the README file. 9 */ 10 11 12/* 13 * Low level character input from the input file. 14 * We use these special purpose routines which optimize moving 15 * both forward and backward from the current read pointer. 16 */ 17 18#include "less.h" 19#if MSDOS_COMPILER==WIN32C 20#include <errno.h> 21#include <windows.h> 22#endif 23 24#if HAVE_STAT_INO 25#include <sys/stat.h> 26extern dev_t curr_dev; 27extern ino_t curr_ino; 28#endif 29 30typedef POSITION BLOCKNUM; 31 32public int ignore_eoi; 33 34/* 35 * Pool of buffers holding the most recently used blocks of the input file. 36 * The buffer pool is kept as a doubly-linked circular list, 37 * in order from most- to least-recently used. 38 * The circular list is anchored by the file state "thisfile". 39 */ 40struct bufnode { 41 struct bufnode *next, *prev; 42 struct bufnode *hnext, *hprev; 43}; 44 45#define LBUFSIZE 8192 46struct buf { 47 struct bufnode node; 48 BLOCKNUM block; 49 unsigned int datasize; 50 unsigned char data[LBUFSIZE]; 51}; 52#define bufnode_buf(bn) ((struct buf *) bn) 53 54/* 55 * The file state is maintained in a filestate structure. 56 * A pointer to the filestate is kept in the ifile structure. 57 */ 58#define BUFHASH_SIZE 64 59struct filestate { 60 struct bufnode buflist; 61 struct bufnode hashtbl[BUFHASH_SIZE]; 62 int file; 63 int flags; 64 POSITION fpos; 65 int nbufs; 66 BLOCKNUM block; 67 unsigned int offset; 68 POSITION fsize; 69}; 70 71#define ch_bufhead thisfile->buflist.next 72#define ch_buftail thisfile->buflist.prev 73#define ch_nbufs thisfile->nbufs 74#define ch_block thisfile->block 75#define ch_offset thisfile->offset 76#define ch_fpos thisfile->fpos 77#define ch_fsize thisfile->fsize 78#define ch_flags thisfile->flags 79#define ch_file thisfile->file 80 81#define END_OF_CHAIN (&thisfile->buflist) 82#define END_OF_HCHAIN(h) (&thisfile->hashtbl[h]) 83#define BUFHASH(blk) ((blk) & (BUFHASH_SIZE-1)) 84 85/* 86 * Macros to manipulate the list of buffers in thisfile->buflist. 87 */ 88#define FOR_BUFS(bn) \ 89 for (bn = ch_bufhead; bn != END_OF_CHAIN; bn = bn->next) 90 91#define BUF_RM(bn) \ 92 (bn)->next->prev = (bn)->prev; \ 93 (bn)->prev->next = (bn)->next; 94 95#define BUF_INS_HEAD(bn) \ 96 (bn)->next = ch_bufhead; \ 97 (bn)->prev = END_OF_CHAIN; \ 98 ch_bufhead->prev = (bn); \ 99 ch_bufhead = (bn); 100 101#define BUF_INS_TAIL(bn) \ 102 (bn)->next = END_OF_CHAIN; \ 103 (bn)->prev = ch_buftail; \ 104 ch_buftail->next = (bn); \ 105 ch_buftail = (bn); 106 107/* 108 * Macros to manipulate the list of buffers in thisfile->hashtbl[n]. 109 */ 110#define FOR_BUFS_IN_CHAIN(h,bn) \ 111 for (bn = thisfile->hashtbl[h].hnext; \ 112 bn != END_OF_HCHAIN(h); bn = bn->hnext) 113 114#define BUF_HASH_RM(bn) \ 115 (bn)->hnext->hprev = (bn)->hprev; \ 116 (bn)->hprev->hnext = (bn)->hnext; 117 118#define BUF_HASH_INS(bn,h) \ 119 (bn)->hnext = thisfile->hashtbl[h].hnext; \ 120 (bn)->hprev = END_OF_HCHAIN(h); \ 121 thisfile->hashtbl[h].hnext->hprev = (bn); \ 122 thisfile->hashtbl[h].hnext = (bn); 123 124static struct filestate *thisfile; 125static int ch_ungotchar = -1; 126static int maxbufs = -1; 127 128extern int autobuf; 129extern volatile sig_atomic_t sigs; 130extern int secure; 131extern int screen_trashed; 132extern int follow_mode; 133extern IFILE curr_ifile; 134#if LOGFILE 135extern int logfile; 136extern char *namelogfile; 137#endif 138 139static int ch_addbuf(); 140 141 142/* 143 * Get the character pointed to by the read pointer. 144 */ 145 int 146ch_get() 147{ 148 register struct buf *bp; 149 register struct bufnode *bn; 150 register int n; 151 register int slept; 152 register int h; 153 POSITION pos; 154 POSITION len; 155 156 if (thisfile == NULL) 157 return (EOI); 158 159 /* 160 * Quick check for the common case where 161 * the desired char is in the head buffer. 162 */ 163 if (ch_bufhead != END_OF_CHAIN) 164 { 165 bp = bufnode_buf(ch_bufhead); 166 if (ch_block == bp->block && ch_offset < bp->datasize) 167 return bp->data[ch_offset]; 168 } 169 170 slept = FALSE; 171 172 /* 173 * Look for a buffer holding the desired block. 174 */ 175 h = BUFHASH(ch_block); 176 FOR_BUFS_IN_CHAIN(h, bn) 177 { 178 bp = bufnode_buf(bn); 179 if (bp->block == ch_block) 180 { 181 if (ch_offset >= bp->datasize) 182 /* 183 * Need more data in this buffer. 184 */ 185 break; 186 goto found; 187 } 188 } 189 if (bn == END_OF_HCHAIN(h)) 190 { 191 /* 192 * Block is not in a buffer. 193 * Take the least recently used buffer 194 * and read the desired block into it. 195 * If the LRU buffer has data in it, 196 * then maybe allocate a new buffer. 197 */ 198 if (ch_buftail == END_OF_CHAIN || 199 bufnode_buf(ch_buftail)->block != -1) 200 { 201 /* 202 * There is no empty buffer to use. 203 * Allocate a new buffer if: 204 * 1. We can't seek on this file and -b is not in effect; or 205 * 2. We haven't allocated the max buffers for this file yet. 206 */ 207 if ((autobuf && !(ch_flags & CH_CANSEEK)) || 208 (maxbufs < 0 || ch_nbufs < maxbufs)) 209 if (ch_addbuf()) 210 /* 211 * Allocation failed: turn off autobuf. 212 */ 213 autobuf = OPT_OFF; 214 } 215 bn = ch_buftail; 216 bp = bufnode_buf(bn); 217 BUF_HASH_RM(bn); /* Remove from old hash chain. */ 218 bp->block = ch_block; 219 bp->datasize = 0; 220 BUF_HASH_INS(bn, h); /* Insert into new hash chain. */ 221 } 222 223 read_more: 224 pos = (ch_block * LBUFSIZE) + bp->datasize; 225 if ((len = ch_length()) != NULL_POSITION && pos >= len) 226 /* 227 * At end of file. 228 */ 229 return (EOI); 230 231 if (pos != ch_fpos) 232 { 233 /* 234 * Not at the correct position: must seek. 235 * If input is a pipe, we're in trouble (can't seek on a pipe). 236 * Some data has been lost: just return "?". 237 */ 238 if (!(ch_flags & CH_CANSEEK)) 239 return ('?'); 240 if (lseek(ch_file, (off_t)pos, SEEK_SET) == BAD_LSEEK) 241 { 242 error("seek error", NULL_PARG); 243 clear_eol(); 244 return (EOI); 245 } 246 ch_fpos = pos; 247 } 248 249 /* 250 * Read the block. 251 * If we read less than a full block, that's ok. 252 * We use partial block and pick up the rest next time. 253 */ 254 if (ch_ungotchar != -1) 255 { 256 bp->data[bp->datasize] = ch_ungotchar; 257 n = 1; 258 ch_ungotchar = -1; 259 } else 260 { 261 n = iread(ch_file, &bp->data[bp->datasize], 262 (unsigned int)(LBUFSIZE - bp->datasize)); 263 } 264 265 if (n == READ_INTR) 266 return (EOI); 267 if (n < 0) 268 { 269#if MSDOS_COMPILER==WIN32C 270 if (errno != EPIPE) 271#endif 272 { 273 error("read error", NULL_PARG); 274 clear_eol(); 275 } 276 n = 0; 277 } 278 279#if LOGFILE 280 /* 281 * If we have a log file, write the new data to it. 282 */ 283 if (!secure && logfile >= 0 && n > 0) 284 write(logfile, (char *) &bp->data[bp->datasize], n); 285#endif 286 287 ch_fpos += n; 288 bp->datasize += n; 289 290 /* 291 * If we have read to end of file, set ch_fsize to indicate 292 * the position of the end of file. 293 */ 294 if (n == 0) 295 { 296 ch_fsize = pos; 297 if (ignore_eoi) 298 { 299 /* 300 * We are ignoring EOF. 301 * Wait a while, then try again. 302 */ 303 if (!slept) 304 { 305 PARG parg; 306 parg.p_string = wait_message(); 307 ierror("%s", &parg); 308 } 309#if !MSDOS_COMPILER 310 sleep(1); 311#else 312#if MSDOS_COMPILER==WIN32C 313 Sleep(1000); 314#endif 315#endif 316 slept = TRUE; 317 318#if HAVE_STAT_INO 319 if (follow_mode == FOLLOW_NAME) 320 { 321 /* See whether the file's i-number has changed. 322 * If so, force the file to be closed and 323 * reopened. */ 324 struct stat st; 325 int r = stat(get_filename(curr_ifile), &st); 326 if (r == 0 && (st.st_ino != curr_ino || 327 st.st_dev != curr_dev)) 328 { 329 /* screen_trashed=2 causes 330 * make_display to reopen the file. */ 331 screen_trashed = 2; 332 return (EOI); 333 } 334 } 335#endif 336 } 337 if (sigs) 338 return (EOI); 339 } 340 341 found: 342 if (ch_bufhead != bn) 343 { 344 /* 345 * Move the buffer to the head of the buffer chain. 346 * This orders the buffer chain, most- to least-recently used. 347 */ 348 BUF_RM(bn); 349 BUF_INS_HEAD(bn); 350 351 /* 352 * Move to head of hash chain too. 353 */ 354 BUF_HASH_RM(bn); 355 BUF_HASH_INS(bn, h); 356 } 357 358 if (ch_offset >= bp->datasize) 359 /* 360 * After all that, we still don't have enough data. 361 * Go back and try again. 362 */ 363 goto read_more; 364 365 return (bp->data[ch_offset]); 366} 367 368/* 369 * ch_ungetchar is a rather kludgy and limited way to push 370 * a single char onto an input file descriptor. 371 */ 372 public void 373ch_ungetchar(c) 374 int c; 375{ 376 if (c != -1 && ch_ungotchar != -1) 377 error("ch_ungetchar overrun", NULL_PARG); 378 ch_ungotchar = c; 379} 380 381#if LOGFILE 382/* 383 * Close the logfile. 384 * If we haven't read all of standard input into it, do that now. 385 */ 386 public void 387end_logfile() 388{ 389 static int tried = FALSE; 390 391 if (logfile < 0) 392 return; 393 if (!tried && ch_fsize == NULL_POSITION) 394 { 395 tried = TRUE; 396 ierror("Finishing logfile", NULL_PARG); 397 while (ch_forw_get() != EOI) 398 if (ABORT_SIGS()) 399 break; 400 } 401 close(logfile); 402 logfile = -1; 403 namelogfile = NULL; 404} 405 406/* 407 * Start a log file AFTER less has already been running. 408 * Invoked from the - command; see toggle_option(). 409 * Write all the existing buffered data to the log file. 410 */ 411 public void 412sync_logfile() 413{ 414 register struct buf *bp; 415 register struct bufnode *bn; 416 int warned = FALSE; 417 BLOCKNUM block; 418 BLOCKNUM nblocks; 419 420 nblocks = (ch_fpos + LBUFSIZE - 1) / LBUFSIZE; 421 for (block = 0; block < nblocks; block++) 422 { 423 int wrote = FALSE; 424 FOR_BUFS(bn) 425 { 426 bp = bufnode_buf(bn); 427 if (bp->block == block) 428 { 429 write(logfile, (char *) bp->data, bp->datasize); 430 wrote = TRUE; 431 break; 432 } 433 } 434 if (!wrote && !warned) 435 { 436 error("Warning: log file is incomplete", 437 NULL_PARG); 438 warned = TRUE; 439 } 440 } 441} 442 443#endif 444 445/* 446 * Determine if a specific block is currently in one of the buffers. 447 */ 448 static int 449buffered(block) 450 BLOCKNUM block; 451{ 452 register struct buf *bp; 453 register struct bufnode *bn; 454 register int h; 455 456 h = BUFHASH(block); 457 FOR_BUFS_IN_CHAIN(h, bn) 458 { 459 bp = bufnode_buf(bn); 460 if (bp->block == block) 461 return (TRUE); 462 } 463 return (FALSE); 464} 465 466/* 467 * Seek to a specified position in the file. 468 * Return 0 if successful, non-zero if can't seek there. 469 */ 470 public int 471ch_seek(pos) 472 register POSITION pos; 473{ 474 BLOCKNUM new_block; 475 POSITION len; 476 477 if (thisfile == NULL) 478 return (0); 479 480 len = ch_length(); 481 if (pos < ch_zero() || (len != NULL_POSITION && pos > len)) 482 return (1); 483 484 new_block = pos / LBUFSIZE; 485 if (!(ch_flags & CH_CANSEEK) && pos != ch_fpos && !buffered(new_block)) 486 { 487 if (ch_fpos > pos) 488 return (1); 489 while (ch_fpos < pos) 490 { 491 if (ch_forw_get() == EOI) 492 return (1); 493 if (ABORT_SIGS()) 494 return (1); 495 } 496 return (0); 497 } 498 /* 499 * Set read pointer. 500 */ 501 ch_block = new_block; 502 ch_offset = pos % LBUFSIZE; 503 return (0); 504} 505 506/* 507 * Seek to the end of the file. 508 */ 509 public int 510ch_end_seek() 511{ 512 POSITION len; 513 514 if (thisfile == NULL) 515 return (0); 516 517 if (ch_flags & CH_CANSEEK) 518 ch_fsize = filesize(ch_file); 519 520 len = ch_length(); 521 if (len != NULL_POSITION) 522 return (ch_seek(len)); 523 524 /* 525 * Do it the slow way: read till end of data. 526 */ 527 while (ch_forw_get() != EOI) 528 if (ABORT_SIGS()) 529 return (1); 530 return (0); 531} 532 533/* 534 * Seek to the beginning of the file, or as close to it as we can get. 535 * We may not be able to seek there if input is a pipe and the 536 * beginning of the pipe is no longer buffered. 537 */ 538 public int 539ch_beg_seek() 540{ 541 register struct bufnode *bn; 542 register struct bufnode *firstbn; 543 544 /* 545 * Try a plain ch_seek first. 546 */ 547 if (ch_seek(ch_zero()) == 0) 548 return (0); 549 550 /* 551 * Can't get to position 0. 552 * Look thru the buffers for the one closest to position 0. 553 */ 554 firstbn = ch_bufhead; 555 if (firstbn == END_OF_CHAIN) 556 return (1); 557 FOR_BUFS(bn) 558 { 559 if (bufnode_buf(bn)->block < bufnode_buf(firstbn)->block) 560 firstbn = bn; 561 } 562 ch_block = bufnode_buf(firstbn)->block; 563 ch_offset = 0; 564 return (0); 565} 566 567/* 568 * Return the length of the file, if known. 569 */ 570 public POSITION 571ch_length() 572{ 573 if (thisfile == NULL) 574 return (NULL_POSITION); 575 if (ignore_eoi) 576 return (NULL_POSITION); 577 return (ch_fsize); 578} 579 580/* 581 * Return the current position in the file. 582 */ 583 public POSITION 584ch_tell() 585{ 586 if (thisfile == NULL) 587 return (NULL_POSITION); 588 return (ch_block * LBUFSIZE) + ch_offset; 589} 590 591/* 592 * Get the current char and post-increment the read pointer. 593 */ 594 public int 595ch_forw_get() 596{ 597 register int c; 598 599 if (thisfile == NULL) 600 return (EOI); 601 c = ch_get(); 602 if (c == EOI) 603 return (EOI); 604 if (ch_offset < LBUFSIZE-1) 605 ch_offset++; 606 else 607 { 608 ch_block ++; 609 ch_offset = 0; 610 } 611 return (c); 612} 613 614/* 615 * Pre-decrement the read pointer and get the new current char. 616 */ 617 public int 618ch_back_get() 619{ 620 if (thisfile == NULL) 621 return (EOI); 622 if (ch_offset > 0) 623 ch_offset --; 624 else 625 { 626 if (ch_block <= 0) 627 return (EOI); 628 if (!(ch_flags & CH_CANSEEK) && !buffered(ch_block-1)) 629 return (EOI); 630 ch_block--; 631 ch_offset = LBUFSIZE-1; 632 } 633 return (ch_get()); 634} 635 636/* 637 * Set max amount of buffer space. 638 * bufspace is in units of 1024 bytes. -1 mean no limit. 639 */ 640 public void 641ch_setbufspace(bufspace) 642 int bufspace; 643{ 644 if (bufspace < 0) 645 maxbufs = -1; 646 else 647 { 648 maxbufs = ((bufspace * 1024) + LBUFSIZE-1) / LBUFSIZE; 649 if (maxbufs < 1) 650 maxbufs = 1; 651 } 652} 653 654/* 655 * Flush (discard) any saved file state, including buffer contents. 656 */ 657 public void 658ch_flush() 659{ 660 register struct bufnode *bn; 661 662 if (thisfile == NULL) 663 return; 664 665 if (!(ch_flags & CH_CANSEEK)) 666 { 667 /* 668 * If input is a pipe, we don't flush buffer contents, 669 * since the contents can't be recovered. 670 */ 671 ch_fsize = NULL_POSITION; 672 return; 673 } 674 675 /* 676 * Initialize all the buffers. 677 */ 678 FOR_BUFS(bn) 679 { 680 bufnode_buf(bn)->block = -1; 681 } 682 683 /* 684 * Figure out the size of the file, if we can. 685 */ 686 ch_fsize = filesize(ch_file); 687 688 /* 689 * Seek to a known position: the beginning of the file. 690 */ 691 ch_fpos = 0; 692 ch_block = 0; /* ch_fpos / LBUFSIZE; */ 693 ch_offset = 0; /* ch_fpos % LBUFSIZE; */ 694 695#if 1 696 /* 697 * This is a kludge to workaround a Linux kernel bug: files in 698 * /proc have a size of 0 according to fstat() but have readable 699 * data. They are sometimes, but not always, seekable. 700 * Force them to be non-seekable here. 701 */ 702 if (ch_fsize == 0) 703 { 704 ch_fsize = NULL_POSITION; 705 ch_flags &= ~CH_CANSEEK; 706 } 707#endif 708 709 if (lseek(ch_file, (off_t)0, SEEK_SET) == BAD_LSEEK) 710 { 711 /* 712 * Warning only; even if the seek fails for some reason, 713 * there's a good chance we're at the beginning anyway. 714 * {{ I think this is bogus reasoning. }} 715 */ 716 error("seek error to 0", NULL_PARG); 717 } 718} 719 720/* 721 * Allocate a new buffer. 722 * The buffer is added to the tail of the buffer chain. 723 */ 724 static int 725ch_addbuf() 726{ 727 register struct buf *bp; 728 register struct bufnode *bn; 729 730 /* 731 * Allocate and initialize a new buffer and link it 732 * onto the tail of the buffer list. 733 */ 734 bp = (struct buf *) calloc(1, sizeof(struct buf)); 735 if (bp == NULL) 736 return (1); 737 ch_nbufs++; 738 bp->block = -1; 739 bn = &bp->node; 740 741 BUF_INS_TAIL(bn); 742 BUF_HASH_INS(bn, 0); 743 return (0); 744} 745 746/* 747 * 748 */ 749 static void 750init_hashtbl() 751{ 752 register int h; 753 754 for (h = 0; h < BUFHASH_SIZE; h++) 755 { 756 thisfile->hashtbl[h].hnext = END_OF_HCHAIN(h); 757 thisfile->hashtbl[h].hprev = END_OF_HCHAIN(h); 758 } 759} 760 761/* 762 * Delete all buffers for this file. 763 */ 764 static void 765ch_delbufs() 766{ 767 register struct bufnode *bn; 768 769 while (ch_bufhead != END_OF_CHAIN) 770 { 771 bn = ch_bufhead; 772 BUF_RM(bn); 773 free(bufnode_buf(bn)); 774 } 775 ch_nbufs = 0; 776 init_hashtbl(); 777} 778 779/* 780 * Is it possible to seek on a file descriptor? 781 */ 782 public int 783seekable(f) 784 int f; 785{ 786#if MSDOS_COMPILER 787 extern int fd0; 788 if (f == fd0 && !isatty(fd0)) 789 { 790 /* 791 * In MS-DOS, pipes are seekable. Check for 792 * standard input, and pretend it is not seekable. 793 */ 794 return (0); 795 } 796#endif 797 return (lseek(f, (off_t)1, SEEK_SET) != BAD_LSEEK); 798} 799 800/* 801 * Initialize file state for a new file. 802 */ 803 public void 804ch_init(f, flags) 805 int f; 806 int flags; 807{ 808 /* 809 * See if we already have a filestate for this file. 810 */ 811 thisfile = (struct filestate *) get_filestate(curr_ifile); 812 if (thisfile == NULL) 813 { 814 /* 815 * Allocate and initialize a new filestate. 816 */ 817 thisfile = (struct filestate *) 818 calloc(1, sizeof(struct filestate)); 819 thisfile->buflist.next = thisfile->buflist.prev = END_OF_CHAIN; 820 thisfile->nbufs = 0; 821 thisfile->flags = 0; 822 thisfile->fpos = 0; 823 thisfile->block = 0; 824 thisfile->offset = 0; 825 thisfile->file = -1; 826 thisfile->fsize = NULL_POSITION; 827 ch_flags = flags; 828 init_hashtbl(); 829 /* 830 * Try to seek; set CH_CANSEEK if it works. 831 */ 832 if ((flags & CH_CANSEEK) && !seekable(f)) 833 ch_flags &= ~CH_CANSEEK; 834 set_filestate(curr_ifile, (void *) thisfile); 835 } 836 if (thisfile->file == -1) 837 thisfile->file = f; 838 ch_flush(); 839} 840 841/* 842 * Close a filestate. 843 */ 844 public void 845ch_close() 846{ 847 int keepstate = FALSE; 848 849 if (thisfile == NULL) 850 return; 851 852 if (ch_flags & (CH_CANSEEK|CH_POPENED|CH_HELPFILE)) 853 { 854 /* 855 * We can seek or re-open, so we don't need to keep buffers. 856 */ 857 ch_delbufs(); 858 } else 859 keepstate = TRUE; 860 if (!(ch_flags & CH_KEEPOPEN)) 861 { 862 /* 863 * We don't need to keep the file descriptor open 864 * (because we can re-open it.) 865 * But don't really close it if it was opened via popen(), 866 * because pclose() wants to close it. 867 */ 868 if (!(ch_flags & CH_POPENED)) 869 close(ch_file); 870 ch_file = -1; 871 } else 872 keepstate = TRUE; 873 if (!keepstate) 874 { 875 /* 876 * We don't even need to keep the filestate structure. 877 */ 878 free(thisfile); 879 thisfile = NULL; 880 set_filestate(curr_ifile, (void *) NULL); 881 } 882} 883 884/* 885 * Return ch_flags for the current file. 886 */ 887 public int 888ch_getflags() 889{ 890 if (thisfile == NULL) 891 return (0); 892 return (ch_flags); 893} 894 895#if 0 896 public void 897ch_dump(struct filestate *fs) 898{ 899 struct buf *bp; 900 struct bufnode *bn; 901 unsigned char *s; 902 903 if (fs == NULL) 904 { 905 printf(" --no filestate\n"); 906 return; 907 } 908 printf(" file %d, flags %x, fpos %x, fsize %x, blk/off %x/%x\n", 909 fs->file, fs->flags, fs->fpos, 910 fs->fsize, fs->block, fs->offset); 911 printf(" %d bufs:\n", fs->nbufs); 912 for (bn = fs->next; bn != &fs->buflist; bn = bn->next) 913 { 914 bp = bufnode_buf(bn); 915 printf("%x: blk %x, size %x \"", 916 bp, bp->block, bp->datasize); 917 for (s = bp->data; s < bp->data + 30; s++) 918 if (*s >= ' ' && *s < 0x7F) 919 printf("%c", *s); 920 else 921 printf("."); 922 printf("\"\n"); 923 } 924} 925#endif 926