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