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