]> git.cworth.org Git - apitrace/blobdiff - thirdparty/less/ch.c
Bundle less (version 444) for Windows.
[apitrace] / thirdparty / less / ch.c
diff --git a/thirdparty/less/ch.c b/thirdparty/less/ch.c
new file mode 100644 (file)
index 0000000..1b84ec1
--- /dev/null
@@ -0,0 +1,933 @@
+/*
+ * Copyright (C) 1984-2011  Mark Nudelman
+ *
+ * You may distribute under the terms of either the GNU General Public
+ * License or the Less License, as specified in the README file.
+ *
+ * For more information about less, or for information on how to 
+ * contact the author, see the README file.
+ */
+
+
+/*
+ * Low level character input from the input file.
+ * We use these special purpose routines which optimize moving
+ * both forward and backward from the current read pointer.
+ */
+
+#include "less.h"
+#if MSDOS_COMPILER==WIN32C
+#include <errno.h>
+#include <windows.h>
+#endif
+
+#if HAVE_STAT_INO
+#include <sys/stat.h>
+extern dev_t curr_dev;
+extern ino_t curr_ino;
+#endif
+
+typedef POSITION BLOCKNUM;
+
+public int ignore_eoi;
+
+/*
+ * Pool of buffers holding the most recently used blocks of the input file.
+ * The buffer pool is kept as a doubly-linked circular list,
+ * in order from most- to least-recently used.
+ * The circular list is anchored by the file state "thisfile".
+ */
+struct bufnode {
+       struct bufnode *next, *prev;
+       struct bufnode *hnext, *hprev;
+};
+
+#define        LBUFSIZE        8192
+struct buf {
+       struct bufnode node;
+       BLOCKNUM block;
+       unsigned int datasize;
+       unsigned char data[LBUFSIZE];
+};
+#define bufnode_buf(bn)  ((struct buf *) bn)
+
+/*
+ * The file state is maintained in a filestate structure.
+ * A pointer to the filestate is kept in the ifile structure.
+ */
+#define        BUFHASH_SIZE    64
+struct filestate {
+       struct bufnode buflist;
+       struct bufnode hashtbl[BUFHASH_SIZE];
+       int file;
+       int flags;
+       POSITION fpos;
+       int nbufs;
+       BLOCKNUM block;
+       unsigned int offset;
+       POSITION fsize;
+};
+
+#define        ch_bufhead      thisfile->buflist.next
+#define        ch_buftail      thisfile->buflist.prev
+#define        ch_nbufs        thisfile->nbufs
+#define        ch_block        thisfile->block
+#define        ch_offset       thisfile->offset
+#define        ch_fpos         thisfile->fpos
+#define        ch_fsize        thisfile->fsize
+#define        ch_flags        thisfile->flags
+#define        ch_file         thisfile->file
+
+#define        END_OF_CHAIN    (&thisfile->buflist)
+#define        END_OF_HCHAIN(h) (&thisfile->hashtbl[h])
+#define BUFHASH(blk)   ((blk) & (BUFHASH_SIZE-1))
+
+/*
+ * Macros to manipulate the list of buffers in thisfile->buflist.
+ */
+#define        FOR_BUFS(bn) \
+       for (bn = ch_bufhead;  bn != END_OF_CHAIN;  bn = bn->next)
+
+#define BUF_RM(bn) \
+       (bn)->next->prev = (bn)->prev; \
+       (bn)->prev->next = (bn)->next;
+
+#define BUF_INS_HEAD(bn) \
+       (bn)->next = ch_bufhead; \
+       (bn)->prev = END_OF_CHAIN; \
+       ch_bufhead->prev = (bn); \
+       ch_bufhead = (bn);
+
+#define BUF_INS_TAIL(bn) \
+       (bn)->next = END_OF_CHAIN; \
+       (bn)->prev = ch_buftail; \
+       ch_buftail->next = (bn); \
+       ch_buftail = (bn);
+
+/*
+ * Macros to manipulate the list of buffers in thisfile->hashtbl[n].
+ */
+#define        FOR_BUFS_IN_CHAIN(h,bn) \
+       for (bn = thisfile->hashtbl[h].hnext;  \
+            bn != END_OF_HCHAIN(h);  bn = bn->hnext)
+
+#define        BUF_HASH_RM(bn) \
+       (bn)->hnext->hprev = (bn)->hprev; \
+       (bn)->hprev->hnext = (bn)->hnext;
+
+#define        BUF_HASH_INS(bn,h) \
+       (bn)->hnext = thisfile->hashtbl[h].hnext; \
+       (bn)->hprev = END_OF_HCHAIN(h); \
+       thisfile->hashtbl[h].hnext->hprev = (bn); \
+       thisfile->hashtbl[h].hnext = (bn);
+
+static struct filestate *thisfile;
+static int ch_ungotchar = -1;
+static int maxbufs = -1;
+
+extern int autobuf;
+extern int sigs;
+extern int secure;
+extern int screen_trashed;
+extern int follow_mode;
+extern constant char helpdata[];
+extern constant int size_helpdata;
+extern IFILE curr_ifile;
+#if LOGFILE
+extern int logfile;
+extern char *namelogfile;
+#endif
+
+static int ch_addbuf();
+
+
+/*
+ * Get the character pointed to by the read pointer.
+ */
+       int
+ch_get()
+{
+       register struct buf *bp;
+       register struct bufnode *bn;
+       register int n;
+       register int slept;
+       register int h;
+       POSITION pos;
+       POSITION len;
+
+       if (thisfile == NULL)
+               return (EOI);
+
+       /*
+        * Quick check for the common case where 
+        * the desired char is in the head buffer.
+        */
+       if (ch_bufhead != END_OF_CHAIN)
+       {
+               bp = bufnode_buf(ch_bufhead);
+               if (ch_block == bp->block && ch_offset < bp->datasize)
+                       return bp->data[ch_offset];
+       }
+
+       slept = FALSE;
+
+       /*
+        * Look for a buffer holding the desired block.
+        */
+       h = BUFHASH(ch_block);
+       FOR_BUFS_IN_CHAIN(h, bn)
+       {
+               bp = bufnode_buf(bn);
+               if (bp->block == ch_block)
+               {
+                       if (ch_offset >= bp->datasize)
+                               /*
+                                * Need more data in this buffer.
+                                */
+                               break;
+                       goto found;
+               }
+       }
+       if (bn == END_OF_HCHAIN(h))
+       {
+               /*
+                * Block is not in a buffer.  
+                * Take the least recently used buffer 
+                * and read the desired block into it.
+                * If the LRU buffer has data in it, 
+                * then maybe allocate a new buffer.
+                */
+               if (ch_buftail == END_OF_CHAIN || 
+                       bufnode_buf(ch_buftail)->block != -1)
+               {
+                       /*
+                        * There is no empty buffer to use.
+                        * Allocate a new buffer if:
+                        * 1. We can't seek on this file and -b is not in effect; or
+                        * 2. We haven't allocated the max buffers for this file yet.
+                        */
+                       if ((autobuf && !(ch_flags & CH_CANSEEK)) ||
+                               (maxbufs < 0 || ch_nbufs < maxbufs))
+                               if (ch_addbuf())
+                                       /*
+                                        * Allocation failed: turn off autobuf.
+                                        */
+                                       autobuf = OPT_OFF;
+               }
+               bn = ch_buftail;
+               bp = bufnode_buf(bn);
+               BUF_HASH_RM(bn); /* Remove from old hash chain. */
+               bp->block = ch_block;
+               bp->datasize = 0;
+               BUF_HASH_INS(bn, h); /* Insert into new hash chain. */
+       }
+
+    read_more:
+       pos = (ch_block * LBUFSIZE) + bp->datasize;
+       if ((len = ch_length()) != NULL_POSITION && pos >= len)
+               /*
+                * At end of file.
+                */
+               return (EOI);
+
+       if (pos != ch_fpos)
+       {
+               /*
+                * Not at the correct position: must seek.
+                * If input is a pipe, we're in trouble (can't seek on a pipe).
+                * Some data has been lost: just return "?".
+                */
+               if (!(ch_flags & CH_CANSEEK))
+                       return ('?');
+               if (lseek(ch_file, (off_t)pos, SEEK_SET) == BAD_LSEEK)
+               {
+                       error("seek error", NULL_PARG);
+                       clear_eol();
+                       return (EOI);
+               }
+               ch_fpos = pos;
+       }
+
+       /*
+        * Read the block.
+        * If we read less than a full block, that's ok.
+        * We use partial block and pick up the rest next time.
+        */
+       if (ch_ungotchar != -1)
+       {
+               bp->data[bp->datasize] = ch_ungotchar;
+               n = 1;
+               ch_ungotchar = -1;
+       } else if (ch_flags & CH_HELPFILE)
+       {
+               bp->data[bp->datasize] = helpdata[ch_fpos];
+               n = 1;
+       } else
+       {
+               n = iread(ch_file, &bp->data[bp->datasize], 
+                       (unsigned int)(LBUFSIZE - bp->datasize));
+       }
+
+       if (n == READ_INTR)
+               return (EOI);
+       if (n < 0)
+       {
+#if MSDOS_COMPILER==WIN32C
+               if (errno != EPIPE)
+#endif
+               {
+                       error("read error", NULL_PARG);
+                       clear_eol();
+               }
+               n = 0;
+       }
+
+#if LOGFILE
+       /*
+        * If we have a log file, write the new data to it.
+        */
+       if (!secure && logfile >= 0 && n > 0)
+               write(logfile, (char *) &bp->data[bp->datasize], n);
+#endif
+
+       ch_fpos += n;
+       bp->datasize += n;
+
+       /*
+        * If we have read to end of file, set ch_fsize to indicate
+        * the position of the end of file.
+        */
+       if (n == 0)
+       {
+               ch_fsize = pos;
+               if (ignore_eoi)
+               {
+                       /*
+                        * We are ignoring EOF.
+                        * Wait a while, then try again.
+                        */
+                       if (!slept)
+                       {
+                               PARG parg;
+                               parg.p_string = wait_message();
+                               ierror("%s", &parg);
+                       }
+#if !MSDOS_COMPILER
+                       sleep(1);
+#else
+#if MSDOS_COMPILER==WIN32C
+                       Sleep(1000);
+#endif
+#endif
+                       slept = TRUE;
+
+#if HAVE_STAT_INO
+                       if (follow_mode == FOLLOW_NAME)
+                       {
+                               /* See whether the file's i-number has changed.
+                                * If so, force the file to be closed and
+                                * reopened. */
+                               struct stat st;
+                               int r = stat(get_filename(curr_ifile), &st);
+                               if (r == 0 && (st.st_ino != curr_ino ||
+                                       st.st_dev != curr_dev))
+                               {
+                                       /* screen_trashed=2 causes
+                                        * make_display to reopen the file. */
+                                       screen_trashed = 2;
+                                       return (EOI);
+                               }
+                       }
+#endif
+               }
+               if (sigs)
+                       return (EOI);
+       }
+
+    found:
+       if (ch_bufhead != bn)
+       {
+               /*
+                * Move the buffer to the head of the buffer chain.
+                * This orders the buffer chain, most- to least-recently used.
+                */
+               BUF_RM(bn);
+               BUF_INS_HEAD(bn);
+
+               /*
+                * Move to head of hash chain too.
+                */
+               BUF_HASH_RM(bn);
+               BUF_HASH_INS(bn, h);
+       }
+
+       if (ch_offset >= bp->datasize)
+               /*
+                * After all that, we still don't have enough data.
+                * Go back and try again.
+                */
+               goto read_more;
+
+       return (bp->data[ch_offset]);
+}
+
+/*
+ * ch_ungetchar is a rather kludgy and limited way to push 
+ * a single char onto an input file descriptor.
+ */
+       public void
+ch_ungetchar(c)
+       int c;
+{
+       if (c != -1 && ch_ungotchar != -1)
+               error("ch_ungetchar overrun", NULL_PARG);
+       ch_ungotchar = c;
+}
+
+#if LOGFILE
+/*
+ * Close the logfile.
+ * If we haven't read all of standard input into it, do that now.
+ */
+       public void
+end_logfile()
+{
+       static int tried = FALSE;
+
+       if (logfile < 0)
+               return;
+       if (!tried && ch_fsize == NULL_POSITION)
+       {
+               tried = TRUE;
+               ierror("Finishing logfile", NULL_PARG);
+               while (ch_forw_get() != EOI)
+                       if (ABORT_SIGS())
+                               break;
+       }
+       close(logfile);
+       logfile = -1;
+       namelogfile = NULL;
+}
+
+/*
+ * Start a log file AFTER less has already been running.
+ * Invoked from the - command; see toggle_option().
+ * Write all the existing buffered data to the log file.
+ */
+       public void
+sync_logfile()
+{
+       register struct buf *bp;
+       register struct bufnode *bn;
+       int warned = FALSE;
+       BLOCKNUM block;
+       BLOCKNUM nblocks;
+
+       nblocks = (ch_fpos + LBUFSIZE - 1) / LBUFSIZE;
+       for (block = 0;  block < nblocks;  block++)
+       {
+               int wrote = FALSE;
+               FOR_BUFS(bn)
+               {
+                       bp = bufnode_buf(bn);
+                       if (bp->block == block)
+                       {
+                               write(logfile, (char *) bp->data, bp->datasize);
+                               wrote = TRUE;
+                               break;
+                       }
+               }
+               if (!wrote && !warned)
+               {
+                       error("Warning: log file is incomplete",
+                               NULL_PARG);
+                       warned = TRUE;
+               }
+       }
+}
+
+#endif
+
+/*
+ * Determine if a specific block is currently in one of the buffers.
+ */
+       static int
+buffered(block)
+       BLOCKNUM block;
+{
+       register struct buf *bp;
+       register struct bufnode *bn;
+       register int h;
+
+       h = BUFHASH(block);
+       FOR_BUFS_IN_CHAIN(h, bn)
+       {
+               bp = bufnode_buf(bn);
+               if (bp->block == block)
+                       return (TRUE);
+       }
+       return (FALSE);
+}
+
+/*
+ * Seek to a specified position in the file.
+ * Return 0 if successful, non-zero if can't seek there.
+ */
+       public int
+ch_seek(pos)
+       register POSITION pos;
+{
+       BLOCKNUM new_block;
+       POSITION len;
+
+       if (thisfile == NULL)
+               return (0);
+
+       len = ch_length();
+       if (pos < ch_zero() || (len != NULL_POSITION && pos > len))
+               return (1);
+
+       new_block = pos / LBUFSIZE;
+       if (!(ch_flags & CH_CANSEEK) && pos != ch_fpos && !buffered(new_block))
+       {
+               if (ch_fpos > pos)
+                       return (1);
+               while (ch_fpos < pos)
+               {
+                       if (ch_forw_get() == EOI)
+                               return (1);
+                       if (ABORT_SIGS())
+                               return (1);
+               }
+               return (0);
+       }
+       /*
+        * Set read pointer.
+        */
+       ch_block = new_block;
+       ch_offset = pos % LBUFSIZE;
+       return (0);
+}
+
+/*
+ * Seek to the end of the file.
+ */
+       public int
+ch_end_seek()
+{
+       POSITION len;
+
+       if (thisfile == NULL)
+               return (0);
+
+       if (ch_flags & CH_CANSEEK)
+               ch_fsize = filesize(ch_file);
+
+       len = ch_length();
+       if (len != NULL_POSITION)
+               return (ch_seek(len));
+
+       /*
+        * Do it the slow way: read till end of data.
+        */
+       while (ch_forw_get() != EOI)
+               if (ABORT_SIGS())
+                       return (1);
+       return (0);
+}
+
+/*
+ * Seek to the beginning of the file, or as close to it as we can get.
+ * We may not be able to seek there if input is a pipe and the
+ * beginning of the pipe is no longer buffered.
+ */
+       public int
+ch_beg_seek()
+{
+       register struct bufnode *bn;
+       register struct bufnode *firstbn;
+
+       /*
+        * Try a plain ch_seek first.
+        */
+       if (ch_seek(ch_zero()) == 0)
+               return (0);
+
+       /*
+        * Can't get to position 0.
+        * Look thru the buffers for the one closest to position 0.
+        */
+       firstbn = ch_bufhead;
+       if (firstbn == END_OF_CHAIN)
+               return (1);
+       FOR_BUFS(bn)
+       {
+               if (bufnode_buf(bn)->block < bufnode_buf(firstbn)->block)
+                       firstbn = bn;
+       }
+       ch_block = bufnode_buf(firstbn)->block;
+       ch_offset = 0;
+       return (0);
+}
+
+/*
+ * Return the length of the file, if known.
+ */
+       public POSITION
+ch_length()
+{
+       if (thisfile == NULL)
+               return (NULL_POSITION);
+       if (ignore_eoi)
+               return (NULL_POSITION);
+       if (ch_flags & CH_HELPFILE)
+               return (size_helpdata);
+       return (ch_fsize);
+}
+
+/*
+ * Return the current position in the file.
+ */
+       public POSITION
+ch_tell()
+{
+       if (thisfile == NULL)
+               return (NULL_POSITION);
+       return (ch_block * LBUFSIZE) + ch_offset;
+}
+
+/*
+ * Get the current char and post-increment the read pointer.
+ */
+       public int
+ch_forw_get()
+{
+       register int c;
+
+       if (thisfile == NULL)
+               return (EOI);
+       c = ch_get();
+       if (c == EOI)
+               return (EOI);
+       if (ch_offset < LBUFSIZE-1)
+               ch_offset++;
+       else
+       {
+               ch_block ++;
+               ch_offset = 0;
+       }
+       return (c);
+}
+
+/*
+ * Pre-decrement the read pointer and get the new current char.
+ */
+       public int
+ch_back_get()
+{
+       if (thisfile == NULL)
+               return (EOI);
+       if (ch_offset > 0)
+               ch_offset --;
+       else
+       {
+               if (ch_block <= 0)
+                       return (EOI);
+               if (!(ch_flags & CH_CANSEEK) && !buffered(ch_block-1))
+                       return (EOI);
+               ch_block--;
+               ch_offset = LBUFSIZE-1;
+       }
+       return (ch_get());
+}
+
+/*
+ * Set max amount of buffer space.
+ * bufspace is in units of 1024 bytes.  -1 mean no limit.
+ */
+       public void
+ch_setbufspace(bufspace)
+       int bufspace;
+{
+       if (bufspace < 0)
+               maxbufs = -1;
+       else
+       {
+               maxbufs = ((bufspace * 1024) + LBUFSIZE-1) / LBUFSIZE;
+               if (maxbufs < 1)
+                       maxbufs = 1;
+       }
+}
+
+/*
+ * Flush (discard) any saved file state, including buffer contents.
+ */
+       public void
+ch_flush()
+{
+       register struct bufnode *bn;
+
+       if (thisfile == NULL)
+               return;
+
+       if (!(ch_flags & CH_CANSEEK))
+       {
+               /*
+                * If input is a pipe, we don't flush buffer contents,
+                * since the contents can't be recovered.
+                */
+               ch_fsize = NULL_POSITION;
+               return;
+       }
+
+       /*
+        * Initialize all the buffers.
+        */
+       FOR_BUFS(bn)
+       {
+               bufnode_buf(bn)->block = -1;
+       }
+
+       /*
+        * Figure out the size of the file, if we can.
+        */
+       ch_fsize = filesize(ch_file);
+
+       /*
+        * Seek to a known position: the beginning of the file.
+        */
+       ch_fpos = 0;
+       ch_block = 0; /* ch_fpos / LBUFSIZE; */
+       ch_offset = 0; /* ch_fpos % LBUFSIZE; */
+
+#if 1
+       /*
+        * This is a kludge to workaround a Linux kernel bug: files in
+        * /proc have a size of 0 according to fstat() but have readable 
+        * data.  They are sometimes, but not always, seekable.
+        * Force them to be non-seekable here.
+        */
+       if (ch_fsize == 0)
+       {
+               ch_fsize = NULL_POSITION;
+               ch_flags &= ~CH_CANSEEK;
+       }
+#endif
+
+       if (lseek(ch_file, (off_t)0, SEEK_SET) == BAD_LSEEK)
+       {
+               /*
+                * Warning only; even if the seek fails for some reason,
+                * there's a good chance we're at the beginning anyway.
+                * {{ I think this is bogus reasoning. }}
+                */
+               error("seek error to 0", NULL_PARG);
+       }
+}
+
+/*
+ * Allocate a new buffer.
+ * The buffer is added to the tail of the buffer chain.
+ */
+       static int
+ch_addbuf()
+{
+       register struct buf *bp;
+       register struct bufnode *bn;
+
+       /*
+        * Allocate and initialize a new buffer and link it 
+        * onto the tail of the buffer list.
+        */
+       bp = (struct buf *) calloc(1, sizeof(struct buf));
+       if (bp == NULL)
+               return (1);
+       ch_nbufs++;
+       bp->block = -1;
+       bn = &bp->node;
+
+       BUF_INS_TAIL(bn);
+       BUF_HASH_INS(bn, 0);
+       return (0);
+}
+
+/*
+ *
+ */
+       static void
+init_hashtbl()
+{
+       register int h;
+
+       for (h = 0;  h < BUFHASH_SIZE;  h++)
+       {
+               thisfile->hashtbl[h].hnext = END_OF_HCHAIN(h);
+               thisfile->hashtbl[h].hprev = END_OF_HCHAIN(h);
+       }
+}
+
+/*
+ * Delete all buffers for this file.
+ */
+       static void
+ch_delbufs()
+{
+       register struct bufnode *bn;
+
+       while (ch_bufhead != END_OF_CHAIN)
+       {
+               bn = ch_bufhead;
+               BUF_RM(bn);
+               free(bufnode_buf(bn));
+       }
+       ch_nbufs = 0;
+       init_hashtbl();
+}
+
+/*
+ * Is it possible to seek on a file descriptor?
+ */
+       public int
+seekable(f)
+       int f;
+{
+#if MSDOS_COMPILER
+       extern int fd0;
+       if (f == fd0 && !isatty(fd0))
+       {
+               /*
+                * In MS-DOS, pipes are seekable.  Check for
+                * standard input, and pretend it is not seekable.
+                */
+               return (0);
+       }
+#endif
+       return (lseek(f, (off_t)1, SEEK_SET) != BAD_LSEEK);
+}
+
+/*
+ * Initialize file state for a new file.
+ */
+       public void
+ch_init(f, flags)
+       int f;
+       int flags;
+{
+       /*
+        * See if we already have a filestate for this file.
+        */
+       thisfile = (struct filestate *) get_filestate(curr_ifile);
+       if (thisfile == NULL)
+       {
+               /*
+                * Allocate and initialize a new filestate.
+                */
+               thisfile = (struct filestate *) 
+                               calloc(1, sizeof(struct filestate));
+               thisfile->buflist.next = thisfile->buflist.prev = END_OF_CHAIN;
+               thisfile->nbufs = 0;
+               thisfile->flags = 0;
+               thisfile->fpos = 0;
+               thisfile->block = 0;
+               thisfile->offset = 0;
+               thisfile->file = -1;
+               thisfile->fsize = NULL_POSITION;
+               ch_flags = flags;
+               init_hashtbl();
+               /*
+                * Try to seek; set CH_CANSEEK if it works.
+                */
+               if ((flags & CH_CANSEEK) && !seekable(f))
+                       ch_flags &= ~CH_CANSEEK;
+               set_filestate(curr_ifile, (void *) thisfile);
+       }
+       if (thisfile->file == -1)
+               thisfile->file = f;
+       ch_flush();
+}
+
+/*
+ * Close a filestate.
+ */
+       public void
+ch_close()
+{
+       int keepstate = FALSE;
+
+       if (thisfile == NULL)
+               return;
+
+       if (ch_flags & (CH_CANSEEK|CH_POPENED|CH_HELPFILE))
+       {
+               /*
+                * We can seek or re-open, so we don't need to keep buffers.
+                */
+               ch_delbufs();
+       } else
+               keepstate = TRUE;
+       if (!(ch_flags & CH_KEEPOPEN))
+       {
+               /*
+                * We don't need to keep the file descriptor open
+                * (because we can re-open it.)
+                * But don't really close it if it was opened via popen(),
+                * because pclose() wants to close it.
+                */
+               if (!(ch_flags & (CH_POPENED|CH_HELPFILE)))
+                       close(ch_file);
+               ch_file = -1;
+       } else
+               keepstate = TRUE;
+       if (!keepstate)
+       {
+               /*
+                * We don't even need to keep the filestate structure.
+                */
+               free(thisfile);
+               thisfile = NULL;
+               set_filestate(curr_ifile, (void *) NULL);
+       }
+}
+
+/*
+ * Return ch_flags for the current file.
+ */
+       public int
+ch_getflags()
+{
+       if (thisfile == NULL)
+               return (0);
+       return (ch_flags);
+}
+
+#if 0
+       public void
+ch_dump(struct filestate *fs)
+{
+       struct buf *bp;
+       struct bufnode *bn;
+       unsigned char *s;
+
+       if (fs == NULL)
+       {
+               printf(" --no filestate\n");
+               return;
+       }
+       printf(" file %d, flags %x, fpos %x, fsize %x, blk/off %x/%x\n",
+               fs->file, fs->flags, fs->fpos, 
+               fs->fsize, fs->block, fs->offset);
+       printf(" %d bufs:\n", fs->nbufs);
+       for (bn = fs->next; bn != &fs->buflist;  bn = bn->next)
+       {
+               bp = bufnode_buf(bn);
+               printf("%x: blk %x, size %x \"",
+                       bp, bp->block, bp->datasize);
+               for (s = bp->data;  s < bp->data + 30;  s++)
+                       if (*s >= ' ' && *s < 0x7F)
+                               printf("%c", *s);
+                       else
+                               printf(".");
+               printf("\"\n");
+       }
+}
+#endif