version 1.1.1.1, 1996/09/21 05:39:41 |
version 1.1.1.2, 2003/04/13 18:21:21 |
|
|
/* |
/* |
* Copyright (c) 1984,1985,1989,1994,1995 Mark Nudelman |
* Copyright (C) 1984-2002 Mark Nudelman |
* All rights reserved. |
|
* |
* |
* Redistribution and use in source and binary forms, with or without |
* You may distribute under the terms of either the GNU General Public |
* modification, are permitted provided that the following conditions |
* License or the Less License, as specified in the README file. |
* are met: |
|
* 1. Redistributions of source code must retain the above copyright |
|
* notice, this list of conditions and the following disclaimer. |
|
* 2. Redistributions in binary form must reproduce the above copyright |
|
* notice in the documentation and/or other materials provided with |
|
* the distribution. |
|
* |
* |
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY |
* For more information about less, or for information on how to |
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
* contact the author, see the README file. |
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE |
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT |
|
* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR |
|
* BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, |
|
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE |
|
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN |
|
* IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
*/ |
*/ |
|
|
|
|
|
|
*/ |
*/ |
|
|
#include "less.h" |
#include "less.h" |
|
#if MSDOS_COMPILER==WIN32C |
|
#include <errno.h> |
|
#include <windows.h> |
|
#endif |
|
|
|
typedef POSITION BLOCKNUM; |
|
|
public int ignore_eoi; |
public int ignore_eoi; |
|
|
/* |
/* |
|
|
* in order from most- to least-recently used. |
* in order from most- to least-recently used. |
* The circular list is anchored by the file state "thisfile". |
* The circular list is anchored by the file state "thisfile". |
*/ |
*/ |
#define LBUFSIZE 1024 |
#define LBUFSIZE 8192 |
struct buf { |
struct buf { |
struct buf *next, *prev; /* Must be first to match struct filestate */ |
struct buf *next, *prev; |
long block; |
struct buf *hnext, *hprev; |
|
BLOCKNUM block; |
unsigned int datasize; |
unsigned int datasize; |
unsigned char data[LBUFSIZE]; |
unsigned char data[LBUFSIZE]; |
}; |
}; |
|
|
|
struct buflist { |
|
/* -- Following members must match struct buf */ |
|
struct buf *buf_next, *buf_prev; |
|
struct buf *buf_hnext, *buf_hprev; |
|
}; |
|
|
/* |
/* |
* The file state is maintained in a filestate structure. |
* The file state is maintained in a filestate structure. |
* A pointer to the filestate is kept in the ifile structure. |
* A pointer to the filestate is kept in the ifile structure. |
*/ |
*/ |
|
#define BUFHASH_SIZE 64 |
struct filestate { |
struct filestate { |
/* -- Following members must match struct buf */ |
|
struct buf *buf_next, *buf_prev; |
struct buf *buf_next, *buf_prev; |
long buf_block; |
struct buflist hashtbl[BUFHASH_SIZE]; |
/* -- End of struct buf copy */ |
|
int file; |
int file; |
int flags; |
int flags; |
POSITION fpos; |
POSITION fpos; |
int nbufs; |
int nbufs; |
long block; |
BLOCKNUM block; |
int offset; |
unsigned int offset; |
POSITION fsize; |
POSITION fsize; |
}; |
}; |
|
|
|
|
#define END_OF_CHAIN ((struct buf *)thisfile) |
|
#define ch_bufhead thisfile->buf_next |
#define ch_bufhead thisfile->buf_next |
#define ch_buftail thisfile->buf_prev |
#define ch_buftail thisfile->buf_prev |
#define ch_nbufs thisfile->nbufs |
#define ch_nbufs thisfile->nbufs |
|
|
#define ch_flags thisfile->flags |
#define ch_flags thisfile->flags |
#define ch_file thisfile->file |
#define ch_file thisfile->file |
|
|
|
#define END_OF_CHAIN ((struct buf *)&thisfile->buf_next) |
|
#define END_OF_HCHAIN(h) ((struct buf *)&thisfile->hashtbl[h]) |
|
#define BUFHASH(blk) ((blk) & (BUFHASH_SIZE-1)) |
|
|
|
#define FOR_BUFS_IN_CHAIN(h,bp) \ |
|
for (bp = thisfile->hashtbl[h].buf_hnext; \ |
|
bp != END_OF_HCHAIN(h); bp = bp->hnext) |
|
|
|
#define HASH_RM(bp) \ |
|
(bp)->hnext->hprev = (bp)->hprev; \ |
|
(bp)->hprev->hnext = (bp)->hnext; |
|
|
|
#define HASH_INS(bp,h) \ |
|
(bp)->hnext = thisfile->hashtbl[h].buf_hnext; \ |
|
(bp)->hprev = END_OF_HCHAIN(h); \ |
|
thisfile->hashtbl[h].buf_hnext->hprev = (bp); \ |
|
thisfile->hashtbl[h].buf_hnext = (bp); |
|
|
static struct filestate *thisfile; |
static struct filestate *thisfile; |
static int ch_ungotchar = -1; |
static int ch_ungotchar = -1; |
|
static int maxbufs = -1; |
|
|
extern int autobuf; |
extern int autobuf; |
extern int sigs; |
extern int sigs; |
extern int cbufs; |
extern int secure; |
|
extern constant char helpdata[]; |
|
extern constant int size_helpdata; |
extern IFILE curr_ifile; |
extern IFILE curr_ifile; |
#if LOGFILE |
#if LOGFILE |
extern int logfile; |
extern int logfile; |
|
|
register struct buf *bp; |
register struct buf *bp; |
register int n; |
register int n; |
register int slept; |
register int slept; |
|
register int h; |
POSITION pos; |
POSITION pos; |
POSITION len; |
POSITION len; |
|
|
|
|
/* |
/* |
* Look for a buffer holding the desired block. |
* Look for a buffer holding the desired block. |
*/ |
*/ |
for (bp = ch_bufhead; bp != END_OF_CHAIN; bp = bp->next) |
h = BUFHASH(ch_block); |
|
FOR_BUFS_IN_CHAIN(h, bp) |
|
{ |
if (bp->block == ch_block) |
if (bp->block == ch_block) |
{ |
{ |
if (ch_offset >= bp->datasize) |
if (ch_offset >= bp->datasize) |
|
|
goto read_more; |
goto read_more; |
goto found; |
goto found; |
} |
} |
|
} |
/* |
/* |
* Block is not in a buffer. |
* Block is not in a buffer. |
* Take the least recently used buffer |
* Take the least recently used buffer |
|
|
* If the LRU buffer has data in it, |
* If the LRU buffer has data in it, |
* then maybe allocate a new buffer. |
* then maybe allocate a new buffer. |
*/ |
*/ |
if (ch_buftail == END_OF_CHAIN || ch_buftail->block != (long)(-1)) |
if (ch_buftail == END_OF_CHAIN || ch_buftail->block != -1) |
{ |
{ |
/* |
/* |
* There is no empty buffer to use. |
* There is no empty buffer to use. |
|
|
* 2. We haven't allocated the max buffers for this file yet. |
* 2. We haven't allocated the max buffers for this file yet. |
*/ |
*/ |
if ((autobuf && !(ch_flags & CH_CANSEEK)) || |
if ((autobuf && !(ch_flags & CH_CANSEEK)) || |
(cbufs == -1 || ch_nbufs < cbufs)) |
(maxbufs < 0 || ch_nbufs < maxbufs)) |
if (ch_addbuf()) |
if (ch_addbuf()) |
/* |
/* |
* Allocation failed: turn off autobuf. |
* Allocation failed: turn off autobuf. |
|
|
autobuf = OPT_OFF; |
autobuf = OPT_OFF; |
} |
} |
bp = ch_buftail; |
bp = ch_buftail; |
|
HASH_RM(bp); /* Remove from old hash chain. */ |
bp->block = ch_block; |
bp->block = ch_block; |
bp->datasize = 0; |
bp->datasize = 0; |
|
HASH_INS(bp, h); /* Insert into new hash chain. */ |
|
|
read_more: |
read_more: |
pos = (ch_block * LBUFSIZE) + bp->datasize; |
pos = (ch_block * LBUFSIZE) + bp->datasize; |
|
|
* If we read less than a full block, that's ok. |
* If we read less than a full block, that's ok. |
* We use partial block and pick up the rest next time. |
* We use partial block and pick up the rest next time. |
*/ |
*/ |
if (ch_ungotchar == -1) |
if (ch_ungotchar != -1) |
{ |
{ |
n = iread(ch_file, &bp->data[bp->datasize], |
|
(unsigned int)(LBUFSIZE - bp->datasize)); |
|
} else |
|
{ |
|
bp->data[bp->datasize] = ch_ungotchar; |
bp->data[bp->datasize] = ch_ungotchar; |
n = 1; |
n = 1; |
ch_ungotchar = -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) |
if (n == READ_INTR) |
return (EOI); |
return (EOI); |
if (n < 0) |
if (n < 0) |
{ |
{ |
error("read error", NULL_PARG); |
#if MSDOS_COMPILER==WIN32C |
clear_eol(); |
if (errno != EPIPE) |
|
#endif |
|
{ |
|
error("read error", NULL_PARG); |
|
clear_eol(); |
|
} |
n = 0; |
n = 0; |
} |
} |
|
|
|
|
/* |
/* |
* If we have a log file, write the new data to it. |
* If we have a log file, write the new data to it. |
*/ |
*/ |
if (logfile >= 0 && n > 0) |
if (!secure && logfile >= 0 && n > 0) |
write(logfile, (char *) &bp->data[bp->datasize], n); |
write(logfile, (char *) &bp->data[bp->datasize], n); |
#endif |
#endif |
|
|
|
|
* Wait a while, then try again. |
* Wait a while, then try again. |
*/ |
*/ |
if (!slept) |
if (!slept) |
ierror("Waiting for data", NULL_PARG); |
{ |
#if !MSOFTC |
PARG parg; |
|
parg.p_string = wait_message(); |
|
ierror("%s", &parg); |
|
} |
|
#if !MSDOS_COMPILER |
sleep(1); |
sleep(1); |
|
#else |
|
#if MSDOS_COMPILER==WIN32C |
|
Sleep(1000); |
#endif |
#endif |
|
#endif |
slept = TRUE; |
slept = TRUE; |
} |
} |
if (ABORT_SIGS()) |
if (sigs) |
return (EOI); |
return (EOI); |
} |
} |
|
|
|
|
*/ |
*/ |
bp->next->prev = bp->prev; |
bp->next->prev = bp->prev; |
bp->prev->next = bp->next; |
bp->prev->next = bp->next; |
|
|
bp->next = ch_bufhead; |
bp->next = ch_bufhead; |
bp->prev = END_OF_CHAIN; |
bp->prev = END_OF_CHAIN; |
ch_bufhead->prev = bp; |
ch_bufhead->prev = bp; |
ch_bufhead = bp; |
ch_bufhead = bp; |
|
|
|
/* |
|
* Move to head of hash chain too. |
|
*/ |
|
HASH_RM(bp); |
|
HASH_INS(bp, h); |
} |
} |
|
|
if (ch_offset >= bp->datasize) |
if (ch_offset >= bp->datasize) |
|
|
{ |
{ |
register struct buf *bp; |
register struct buf *bp; |
int warned = FALSE; |
int warned = FALSE; |
long block; |
BLOCKNUM block; |
long nblocks; |
BLOCKNUM nblocks; |
|
|
nblocks = (ch_fpos + LBUFSIZE - 1) / LBUFSIZE; |
nblocks = (ch_fpos + LBUFSIZE - 1) / LBUFSIZE; |
for (block = 0; block < nblocks; block++) |
for (block = 0; block < nblocks; block++) |
|
|
*/ |
*/ |
static int |
static int |
buffered(block) |
buffered(block) |
long block; |
BLOCKNUM block; |
{ |
{ |
register struct buf *bp; |
register struct buf *bp; |
|
register int h; |
|
|
for (bp = ch_bufhead; bp != END_OF_CHAIN; bp = bp->next) |
h = BUFHASH(block); |
|
FOR_BUFS_IN_CHAIN(h, bp) |
|
{ |
if (bp->block == block) |
if (bp->block == block) |
return (TRUE); |
return (TRUE); |
|
} |
return (FALSE); |
return (FALSE); |
} |
} |
|
|
|
|
ch_seek(pos) |
ch_seek(pos) |
register POSITION pos; |
register POSITION pos; |
{ |
{ |
long new_block; |
BLOCKNUM new_block; |
POSITION len; |
POSITION len; |
|
|
len = ch_length(); |
len = ch_length(); |
|
|
{ |
{ |
if (ignore_eoi) |
if (ignore_eoi) |
return (NULL_POSITION); |
return (NULL_POSITION); |
|
if (ch_flags & CH_HELPFILE) |
|
return (size_helpdata); |
return (ch_fsize); |
return (ch_fsize); |
} |
} |
|
|
/* |
/* |
* Return the current position in the file. |
* Return the current position in the file. |
*/ |
*/ |
#define tellpos(blk,off) ((POSITION)((((long)(blk)) * LBUFSIZE) + (off))) |
|
|
|
public POSITION |
public POSITION |
ch_tell() |
ch_tell() |
{ |
{ |
return (tellpos(ch_block, ch_offset)); |
return (ch_block * LBUFSIZE) + ch_offset; |
} |
} |
|
|
/* |
/* |
|
|
} |
} |
|
|
/* |
/* |
* Allocate buffers. |
* Set max amount of buffer space. |
* Caller wants us to have a total of at least want_nbufs buffers. |
* bufspace is in units of 1024 bytes. -1 mean no limit. |
*/ |
*/ |
public int |
public void |
ch_nbuf(want_nbufs) |
ch_setbufspace(bufspace) |
int want_nbufs; |
int bufspace; |
{ |
{ |
PARG parg; |
if (bufspace < 0) |
|
maxbufs = -1; |
while (ch_nbufs < want_nbufs) |
else |
{ |
{ |
if (ch_addbuf()) |
maxbufs = ((bufspace * 1024) + LBUFSIZE-1) / LBUFSIZE; |
{ |
if (maxbufs < 1) |
/* |
maxbufs = 1; |
* 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(QUIT_ERROR); |
|
break; |
|
} |
|
} |
} |
return (ch_nbufs); |
|
} |
} |
|
|
/* |
/* |
|
|
* Initialize all the buffers. |
* Initialize all the buffers. |
*/ |
*/ |
for (bp = ch_bufhead; bp != END_OF_CHAIN; bp = bp->next) |
for (bp = ch_bufhead; bp != END_OF_CHAIN; bp = bp->next) |
bp->block = (long)(-1); |
bp->block = -1; |
|
|
/* |
/* |
* Figure out the size of the file, if we can. |
* Figure out the size of the file, if we can. |
|
|
ch_block = 0; /* ch_fpos / LBUFSIZE; */ |
ch_block = 0; /* ch_fpos / LBUFSIZE; */ |
ch_offset = 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, 0) == BAD_LSEEK) |
if (lseek(ch_file, (off_t)0, 0) == BAD_LSEEK) |
{ |
{ |
/* |
/* |
|
|
if (bp == NULL) |
if (bp == NULL) |
return (1); |
return (1); |
ch_nbufs++; |
ch_nbufs++; |
bp->block = (long)(-1); |
bp->block = -1; |
bp->next = END_OF_CHAIN; |
bp->next = END_OF_CHAIN; |
bp->prev = ch_buftail; |
bp->prev = ch_buftail; |
ch_buftail->next = bp; |
ch_buftail->next = bp; |
ch_buftail = bp; |
ch_buftail = bp; |
|
HASH_INS(bp, 0); |
return (0); |
return (0); |
} |
} |
|
|
/* |
/* |
|
* |
|
*/ |
|
static void |
|
init_hashtbl() |
|
{ |
|
register int h; |
|
|
|
for (h = 0; h < BUFHASH_SIZE; h++) |
|
{ |
|
thisfile->hashtbl[h].buf_hnext = END_OF_HCHAIN(h); |
|
thisfile->hashtbl[h].buf_hprev = END_OF_HCHAIN(h); |
|
} |
|
} |
|
|
|
/* |
* Delete all buffers for this file. |
* Delete all buffers for this file. |
*/ |
*/ |
static void |
static void |
|
|
free(bp); |
free(bp); |
} |
} |
ch_nbufs = 0; |
ch_nbufs = 0; |
|
init_hashtbl(); |
} |
} |
|
|
/* |
/* |
|
|
seekable(f) |
seekable(f) |
int 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, 0) != BAD_LSEEK); |
return (lseek(f, (off_t)1, 0) != BAD_LSEEK); |
} |
} |
|
|
|
|
thisfile = (struct filestate *) |
thisfile = (struct filestate *) |
calloc(1, sizeof(struct filestate)); |
calloc(1, sizeof(struct filestate)); |
thisfile->buf_next = thisfile->buf_prev = END_OF_CHAIN; |
thisfile->buf_next = thisfile->buf_prev = END_OF_CHAIN; |
thisfile->buf_block = (long)(-1); |
|
thisfile->nbufs = 0; |
thisfile->nbufs = 0; |
thisfile->flags = 0; |
thisfile->flags = 0; |
thisfile->fpos = 0; |
thisfile->fpos = 0; |
|
|
thisfile->file = -1; |
thisfile->file = -1; |
thisfile->fsize = NULL_POSITION; |
thisfile->fsize = NULL_POSITION; |
ch_flags = flags; |
ch_flags = flags; |
|
init_hashtbl(); |
/* |
/* |
* Try to seek; set CH_CANSEEK if it works. |
* Try to seek; set CH_CANSEEK if it works. |
*/ |
*/ |
if (seekable(f)) |
if ((flags & CH_CANSEEK) && !seekable(f)) |
ch_flags |= CH_CANSEEK; |
ch_flags &= ~CH_CANSEEK; |
set_filestate(curr_ifile, (void *) thisfile); |
set_filestate(curr_ifile, (void *) thisfile); |
} |
} |
if (thisfile->file == -1) |
if (thisfile->file == -1) |
|
|
{ |
{ |
int keepstate = FALSE; |
int keepstate = FALSE; |
|
|
if (ch_flags & (CH_CANSEEK|CH_POPENED)) |
if (ch_flags & (CH_CANSEEK|CH_POPENED|CH_HELPFILE)) |
{ |
{ |
/* |
/* |
* We can seek or re-open, so we don't need to keep buffers. |
* We can seek or re-open, so we don't need to keep buffers. |
|
|
* But don't really close it if it was opened via popen(), |
* But don't really close it if it was opened via popen(), |
* because pclose() wants to close it. |
* because pclose() wants to close it. |
*/ |
*/ |
if (!(ch_flags & CH_POPENED)) |
if (!(ch_flags & (CH_POPENED|CH_HELPFILE))) |
close(ch_file); |
close(ch_file); |
ch_file = -1; |
ch_file = -1; |
} else |
} else |