gno/bin/less/ch.c
gdr-ftp 784e3de7cd Initial checkin of aroff, binprint, center, less, ls, make, makemake,
passwd, ps, purge, shutdown, stty, upper, and vi.  These sources are
for the versions of the utils shipped with GNO v2.0.4.
1998-03-09 08:30:21 +00:00

577 lines
11 KiB
C

/*
* 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.
*/
#pragma noroot
#include "less.h"
#ifdef _ORCAC_
segment "LoadSegONE";
#include <fcntl.h>
#endif
public int file = -1; /* File descriptor of the input file */
public int ignore_eoi;
/*
* Pool of buffers holding the most recently used blocks of the input file.
*/
#ifdef BUFSIZ
#undef BUFSIZ
#define BUFSIZ 1840 /* was 1024, but 80x23 == 1840 */
#endif
struct buf {
struct buf *next, *prev; /* Must be first to match struct filestate */
long block;
unsigned int datasize;
unsigned char data[BUFSIZ];
};
/*
* 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".
*
* The file state is maintained in a filestate structure.
* There are two such structures, one used when input is a pipe
* and the other when input is an ordinary file.
* This is so that we can leave a pipe, look and other files,
* and return to the pipe without losing buffered data.
* Buffered data can be reconstructed for a non-pipe file by
* simply re-reading the file, but a pipe cannot be re-read.
*/
struct filestate {
struct buf *next, *prev; /* Must be first to match struct buf */
POSITION fpos;
int nbufs;
long block;
int offset;
POSITION fsize;
};
#define END_OF_CHAIN ((struct buf *)thisfile)
#define buf_head thisfile->next
#define buf_tail thisfile->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
static struct filestate pipefile =
{ (struct buf *)&pipefile, (struct buf *)&pipefile };
static struct filestate nonpipefile =
{ (struct buf *)&nonpipefile, (struct buf *)&nonpipefile };
static struct filestate *thisfile;
extern int ispipe;
extern int autobuf;
extern int sigs;
#if LOGFILE
extern int logfile;
extern char *namelogfile;
#endif
static int fch_get(void);
static int buffered(long block);
static int ch_addbuf(int nnew);
/*
* Get the character pointed to by the read pointer.
* ch_get() is a macro which is more efficient to call
* than fch_get (the function), in the usual case
* that the block desired is at the head of the chain.
*/
#define ch_get() ((ch_block == buf_head->block && \
ch_offset < buf_head->datasize) ? \
buf_head->data[ch_offset] : fch_get())
static int
fch_get(void)
{
register struct buf *bp;
register int n;
register int slept;
POSITION pos;
POSITION len;
slept = 0;
/*
* Look for a buffer holding the desired block.
*/
for (bp = buf_head; bp != END_OF_CHAIN; bp = bp->next)
if (bp->block == ch_block)
{
if (ch_offset >= bp->datasize)
/*
* Need more data in this buffer.
*/
goto read_more;
goto found;
}
/*
* 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,
* and autobuf is true, and input is a pipe,
* then try to allocate a new buffer first.
*/
if (autobuf && ispipe && buf_tail->block != (long)(-1))
if (ch_addbuf(1))
/*
* Allocation failed: turn off autobuf.
*/
autobuf = 0;
bp = buf_tail;
bp->block = ch_block;
bp->datasize = 0;
read_more:
pos = (ch_block * BUFSIZ) + 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 (ispipe)
return ('?');
if (lseek(file, (offset_t)pos, 0) == BAD_LSEEK)
{
error("seek error", NULL_PARG);
quit(1);
}
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.
*/
n = iread(file, &bp->data[bp->datasize],
(unsigned int)(BUFSIZ - bp->datasize));
if (n == READ_INTR)
return (EOI);
if (n < 0)
{
error("read error", NULL_PARG);
quit(1);
}
ch_fpos += n;
#if LOGFILE
/*
* If we have a log file, write the new data to it.
*/
if (logfile >= 0 && n > 0)
write(logfile, (char *) &bp->data[bp->datasize], n);
#endif
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)
ierror("Waiting for data", NULL_PARG);
sleep(1);
slept = 1;
}
if (sigs)
return (EOI);
}
found:
if (buf_head != bp)
{
/*
* Move the buffer to the head of the buffer chain.
* This orders the buffer chain, most- to least-recently used.
*/
bp->next->prev = bp->prev;
bp->prev->next = bp->next;
bp->next = buf_head;
bp->prev = END_OF_CHAIN;
buf_head->prev = bp;
buf_head = bp;
}
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]);
}
#if LOGFILE
/*
* Close the logfile.
* If we haven't read all of standard input into it, do that now.
*/
public void
end_logfile(void)
{
static int tried = 0;
if (logfile < 0)
return;
if (!tried && ch_fsize == NULL_POSITION)
{
tried = 1;
ierror("Finishing logfile", NULL_PARG);
while (ch_forw_get() != EOI)
if (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(void)
{
register struct buf *bp;
long block;
long last_block;
last_block = (ch_fpos + BUFSIZ - 1) / BUFSIZ;
for (block = 0; block <= last_block; block++)
for (bp = buf_head; bp != END_OF_CHAIN; bp = bp->next)
if (bp->block == block)
{
write(logfile, (char *) bp->data, bp->datasize);
break;
}
}
#endif
/*
* Determine if a specific block is currently in one of the buffers.
*/
static int
buffered(block)
long block;
{
register struct buf *bp;
for (bp = buf_head; bp != END_OF_CHAIN; bp = bp->next)
if (bp->block == block)
return (1);
return (0);
}
/*
* 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;
{
long new_block;
POSITION len;
len = ch_length();
if (pos < ch_zero() || (len != NULL_POSITION && pos > len))
return (1);
new_block = pos / BUFSIZ;
if (ispipe && pos != ch_fpos && !buffered(new_block))
return (1);
/*
* Set read pointer.
*/
ch_block = new_block;
ch_offset = pos % BUFSIZ;
return (0);
}
/*
* Seek to the end of the file.
*/
public int
ch_end_seek(void)
{
POSITION len;
if (!ispipe)
ch_fsize = filesize(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 (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(void)
{
register struct buf *bp, *firstbp;
/*
* 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.
*/
firstbp = bp = buf_head;
if (bp == END_OF_CHAIN)
return (1);
while ((bp = bp->next) != END_OF_CHAIN)
if (bp->block < firstbp->block)
firstbp = bp;
ch_block = firstbp->block;
ch_offset = 0;
return (0);
}
/*
* Return the length of the file, if known.
*/
public POSITION
ch_length(void)
{
if (ignore_eoi)
return (NULL_POSITION);
return (ch_fsize);
}
/*
* Return the current position in the file.
*/
#define tellpos(blk,off) ((POSITION)((((long)(blk)) * BUFSIZ) + (off)))
public POSITION
ch_tell(void)
{
return (tellpos(ch_block, ch_offset));
}
/*
* Get the current char and post-increment the read pointer.
*/
public int
ch_forw_get(void)
{
register int c;
c = ch_get();
if (c == EOI)
return (EOI);
if (ch_offset < BUFSIZ-1)
ch_offset++;
else
{
#if __ZOFFSET /* NOT WORKING */
if (ch_fsize != NULL_POSITION &&
tellpos(ch_block+1, 0) >= ch_fsize)
return (EOI);
#endif
ch_block ++;
ch_offset = 0;
}
return (c);
}
/*
* Pre-decrement the read pointer and get the new current char.
*/
public int
ch_back_get(void)
{
if (ch_offset > 0)
ch_offset --;
else
{
#if __ZOFFSET /* NOT WORKING */
if (tellpos(ch_block-1, BUFSIZ-1) < ch_zero())
return (EOI);
#else
if (ch_block <= 0)
return (EOI);
#endif
if (ispipe && !buffered(ch_block-1))
return (EOI);
ch_block--;
ch_offset = BUFSIZ-1;
}
return (ch_get());
}
/*
* Allocate buffers.
* Caller wants us to have a total of at least want_nbufs buffers.
*/
public int
ch_nbuf(want_nbufs)
int want_nbufs;
{
PARG parg;
if (ch_nbufs < want_nbufs && ch_addbuf(want_nbufs - ch_nbufs))
{
/*
* Cannot allocate enough buffers.
* If we don't have ANY, then quit.
* Otherwise, just report the error and return.
*/
parg.p_int = want_nbufs - ch_nbufs;
error("Cannot allocate %d buffers", &parg);
if (ch_nbufs == 0)
quit(1);
}
return (ch_nbufs);
}
/*
* Flush any saved file state, including buffer contents.
*/
public void
ch_flush(void)
{
register struct buf *bp;
if (ispipe)
{
/*
* 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 (bp = buf_head; bp != END_OF_CHAIN; bp = bp->next)
bp->block = (long)(-1);
/*
* Figure out the size of the file, if we can.
*/
ch_fsize = filesize(file);
/*
* Seek to a known position: the beginning of the file.
*/
ch_fpos = 0;
ch_block = ch_fpos / BUFSIZ;
ch_offset = ch_fpos % BUFSIZ;
if (lseek(file, (offset_t)0, 0) == 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 some new buffers.
* The buffers are added to the tail of the buffer chain.
*/
static int
ch_addbuf(nnew)
int nnew;
{
register struct buf *bp;
register struct buf *newbufs;
/*
* We don't have enough buffers.
* Allocate some new ones.
*/
newbufs = (struct buf *) calloc(nnew, sizeof(struct buf));
if (newbufs == NULL)
return (1);
/*
* Initialize the new buffers and link them together.
* Link them all onto the tail of the buffer list.
*/
ch_nbufs += nnew;
for (bp = &newbufs[0]; bp < &newbufs[nnew]; bp++)
{
bp->next = bp + 1;
bp->prev = bp - 1;
bp->block = (long)(-1);
}
newbufs[nnew-1].next = END_OF_CHAIN;
newbufs[0].prev = buf_tail;
buf_tail->next = &newbufs[0];
buf_tail = &newbufs[nnew-1];
return (0);
}
/*
* Use the pipe file state.
*/
public void
ch_pipe(void)
{
thisfile = &pipefile;
}
/*
* Use the non-pipe file state.
*/
public void
ch_nonpipe(void)
{
thisfile = &nonpipefile;
}