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