722 lines
15 KiB
C
722 lines
15 KiB
C
/* Copyright (c) 1985 Ceriel J.H. Jacobs */
|
|
|
|
# ifndef lint
|
|
static char rcsid[] = "$Header$";
|
|
# endif
|
|
|
|
# define _GETLINE_
|
|
|
|
# include <errno.h>
|
|
# include "in_all.h"
|
|
# include "getline.h"
|
|
# include "options.h"
|
|
# include "process.h"
|
|
# include "term.h"
|
|
# include "main.h"
|
|
# include "display.h"
|
|
# include "output.h"
|
|
# include "assert.h"
|
|
|
|
extern int errno;
|
|
|
|
# define BLOCKSIZE 2048 /* size of blocks */
|
|
# define CHUNK 50 /* # of blockheaders allocated at a time */
|
|
|
|
/*
|
|
* The blockheaders of the blocks that are in core are kept in a linked list.
|
|
* The last added block is indicated by b_head,
|
|
* the tail of the list is indicated by b_tail.
|
|
* The links go from b_tail to b_head.
|
|
* The blockheaders are all in an array, in the order of the line numbers.
|
|
* Also, the blockheaders must always be in core, so they have to be rather
|
|
* small. On systems with a small address space, yap can run out of core,
|
|
* and panic. However, this should only happen with very large files (>> 1M).
|
|
*/
|
|
|
|
struct block {
|
|
int b_flags; /* Contains the following flags: */
|
|
# define DUMPED 01 /* block dumped on temporary file */
|
|
# define PARTLY 02 /* block not filled completely (eof) */
|
|
int b_next; /* ptr in linked list */
|
|
long b_end; /* line number of last line in block */
|
|
char * b_info; /* the block */
|
|
int * b_offs; /* line offsets within the block */
|
|
long b_foff; /* offset of block in file */
|
|
};
|
|
|
|
static struct block * blocklist, /* beginning of the list of blocks */
|
|
* maxblocklist, /* first free entry in the list */
|
|
* topblocklist; /* end of allocated core for the list */
|
|
static int b_head,
|
|
b_tail;
|
|
static int tfdes, ifdes; /* File descriptors for temporary's */
|
|
static long lastreadline; /* lineno of last line read */
|
|
static int ENDseen;
|
|
|
|
STATIC VOID readblock();
|
|
STATIC VOID nextblock();
|
|
STATIC char *re_alloc();
|
|
|
|
STATIC struct block *
|
|
new_block()
|
|
{
|
|
register struct block *pblock = maxblocklist - 1;
|
|
|
|
if (!maxblocklist || !(pblock->b_flags & PARTLY)) {
|
|
/*
|
|
* There is no last block, or it was filled completely,
|
|
* so allocate a new blockheader.
|
|
*/
|
|
register int siz;
|
|
|
|
pblock = blocklist;
|
|
if (maxblocklist == topblocklist) {
|
|
/*
|
|
* No blockheaders left. Allocate new ones
|
|
*/
|
|
siz = topblocklist - pblock;
|
|
blocklist = pblock = (struct block *)
|
|
re_alloc((char *) pblock,
|
|
(unsigned) (siz * sizeof(*pblock)),
|
|
(unsigned) ((siz + CHUNK) * sizeof(*pblock)));
|
|
pblock += siz;
|
|
topblocklist = pblock + CHUNK;
|
|
maxblocklist = pblock;
|
|
for (; pblock < topblocklist; pblock++) {
|
|
pblock->b_end = 0;
|
|
pblock->b_info = 0;
|
|
pblock->b_flags = 0;
|
|
}
|
|
if (!siz) {
|
|
/*
|
|
* Create dummy header cell.
|
|
*/
|
|
maxblocklist++;
|
|
}
|
|
}
|
|
pblock = maxblocklist++;
|
|
}
|
|
nextblock(pblock);
|
|
return pblock;
|
|
}
|
|
|
|
/*
|
|
* Return the block in which line 'n' of the current file can be found.
|
|
* If "disable_interrupt" = 0, the call may be interrupted, in which
|
|
* case it returns 0.
|
|
*/
|
|
|
|
STATIC struct block *
|
|
getblock(n, disable_interrupt) register long n; {
|
|
register struct block * pblock;
|
|
|
|
if (stdf < 0) {
|
|
/*
|
|
* Not file descriptor, so return end of file
|
|
*/
|
|
return 0;
|
|
}
|
|
pblock = maxblocklist - 1;
|
|
if (n < lastreadline ||
|
|
(n == lastreadline && !(pblock->b_flags & PARTLY))) {
|
|
/*
|
|
* The line asked for has been read already.
|
|
* Perform binary search in the blocklist to find the block
|
|
* where it's in.
|
|
*/
|
|
register struct block *min, *mid;
|
|
|
|
min = blocklist + 1;
|
|
do {
|
|
mid = min + (pblock - min) / 2;
|
|
if (n > mid->b_end) {
|
|
min = mid + 1;
|
|
}
|
|
else pblock = mid;
|
|
} while (min < pblock);
|
|
/* Found, pblock is now a reference to the block wanted */
|
|
if (!pblock->b_info) readblock(pblock);
|
|
return pblock;
|
|
}
|
|
|
|
/*
|
|
* The line was'nt read yet, so read blocks until found
|
|
*/
|
|
for (;;) {
|
|
if (interrupt && !disable_interrupt) return 0;
|
|
pblock = new_block();
|
|
if (pblock->b_end >= n) {
|
|
return pblock;
|
|
}
|
|
if (pblock->b_flags & PARTLY) {
|
|
/*
|
|
* We did not find it, and the last block could not be
|
|
* read completely, so return 0;
|
|
*/
|
|
return 0;
|
|
}
|
|
}
|
|
/* NOTREACHED */
|
|
}
|
|
|
|
char *
|
|
getline(n, disable_interrupt) long n; {
|
|
register struct block *pblock;
|
|
|
|
if (!(pblock = getblock(n, disable_interrupt))) {
|
|
return (char *) 0;
|
|
}
|
|
return pblock->b_info + pblock->b_offs[n - ((pblock-1)->b_end + 1)];
|
|
}
|
|
|
|
/*
|
|
* Find the last line of the input, and return its number
|
|
*/
|
|
|
|
long
|
|
to_lastline() {
|
|
|
|
for (;;) {
|
|
if (!getline(lastreadline + 1, 0)) {
|
|
/*
|
|
* "lastreadline" always contains the linenumber of
|
|
* the last line read. So, if the call to getline
|
|
* succeeds, "lastreadline" is affected
|
|
*/
|
|
if (interrupt) return -1L;
|
|
return lastreadline;
|
|
}
|
|
}
|
|
/* NOTREACHED */
|
|
}
|
|
|
|
#if MAXNBLOCKS
|
|
int nblocks; /* Count number of large blocks */
|
|
#endif
|
|
|
|
/*
|
|
* Allocate some memory. If unavailable, free some and try again.
|
|
* If all fails, panic.
|
|
*/
|
|
|
|
char *
|
|
alloc(size, isblock) unsigned size; {
|
|
|
|
register char *pmem;
|
|
register struct block *pblock, *bllist;
|
|
char *malloc();
|
|
long lseek();
|
|
register long i;
|
|
|
|
bllist = blocklist;
|
|
while (
|
|
#if MAXNBLOCKS
|
|
(isblock && nblocks >= MAXNBLOCKS) ||
|
|
#endif
|
|
!(pmem = malloc(size)) /* No space */
|
|
) {
|
|
if (b_tail == 0) {
|
|
/*
|
|
* Also, no blocks in core. Pity
|
|
*/
|
|
panic("No core");
|
|
}
|
|
#if MAXNBLOCKS
|
|
nblocks--;
|
|
#endif
|
|
pblock = bllist + b_tail;
|
|
b_tail = pblock->b_next;
|
|
if (!nopipe && !(pblock->b_flags & DUMPED)) {
|
|
/*
|
|
* Dump the block on a temporary file
|
|
*/
|
|
if (!tfdes) {
|
|
/*
|
|
* create and open temporary files
|
|
*/
|
|
tfdes = opentemp(0);
|
|
ifdes = opentemp(1);
|
|
}
|
|
pblock->b_flags |= DUMPED;
|
|
/*
|
|
* Find out where to dump the block, and dump it
|
|
*/
|
|
i = (pblock-1)->b_end * sizeof(int);
|
|
(VOID) lseek(tfdes,
|
|
((long) BLOCKSIZE * (pblock - bllist)), 0);
|
|
if (write(tfdes, pblock->b_info, BLOCKSIZE)
|
|
!= BLOCKSIZE) {
|
|
panic("write failed");
|
|
}
|
|
/*
|
|
* Also dump the offsets of the lines in the block
|
|
*/
|
|
(VOID) lseek(ifdes, i, 0);
|
|
i = pblock->b_end * sizeof(int) - i;
|
|
if (write(ifdes, (char *) pblock->b_offs, (int) i)
|
|
!= (int) i) {
|
|
panic("Write failed");
|
|
}
|
|
}
|
|
/*
|
|
* Now that the block is dumped, the space taken by it can
|
|
* be freed
|
|
*/
|
|
free((char *) pblock->b_offs);
|
|
free(pblock->b_info);
|
|
pblock->b_info = (char *) 0;
|
|
}
|
|
#if MAXNBLOCKS
|
|
if (isblock) nblocks++;
|
|
#endif
|
|
return pmem;
|
|
}
|
|
|
|
/*
|
|
* Re-allocate the memorychunk pointed to by ptr, to let it
|
|
* grow or shrink.
|
|
* realloc of the standard C library is useless, as it is destructive
|
|
* if the malloc fails.
|
|
*/
|
|
|
|
STATIC char *
|
|
re_alloc(ptr,oldsize, newsize)
|
|
char *ptr; unsigned oldsize; unsigned newsize; {
|
|
register char *pmem;
|
|
register char *c1, *c2;
|
|
|
|
/*
|
|
* We could be smarter here, by checking if newsize < oldsize, and in
|
|
* that case using realloc, but this depends on realloc using the
|
|
* same block if the block shrinks. The question is, wether all
|
|
* reallocs in the world do this.
|
|
*/
|
|
pmem = alloc(newsize, 0);
|
|
if (oldsize) {
|
|
/*
|
|
* This test makes re_alloc also work if there was no old block
|
|
*/
|
|
c1 = pmem;
|
|
c2 = ptr;
|
|
if (newsize > oldsize) {
|
|
newsize = oldsize;
|
|
}
|
|
while (newsize--) {
|
|
*c1++ = *c2++;
|
|
}
|
|
free(ptr);
|
|
}
|
|
return pmem;
|
|
}
|
|
|
|
/*
|
|
* Append a block to the linked list of blockheaders of blocks that are
|
|
* in core.
|
|
*/
|
|
|
|
STATIC VOID
|
|
addtolist(pblock) register struct block *pblock; {
|
|
register struct block *bllist = blocklist;
|
|
|
|
pblock->b_next = 0;
|
|
(bllist + b_head)->b_next = pblock - bllist;
|
|
b_head = pblock - bllist;
|
|
if (!b_tail) {
|
|
/*
|
|
* The list was empty, initialize
|
|
*/
|
|
b_tail = b_head;
|
|
}
|
|
}
|
|
|
|
static char *saved;
|
|
static long filldegree;
|
|
|
|
/*
|
|
* Try to read the block indicated by pblock
|
|
*/
|
|
|
|
STATIC VOID
|
|
nextblock(pblock) register struct block *pblock; {
|
|
register char *c, /* Run through pblock->b_info */
|
|
*c1; /* indicate end of pblock->b_info */
|
|
register int *poff; /* pointer in line-offset list */
|
|
register int cnt; /* # of characters read */
|
|
register unsigned siz; /* Size of allocated line-offset list */
|
|
static unsigned savedsiz; /* saved "siz" */
|
|
static int *savedpoff; /* saved "poff" */
|
|
static char *savedc1; /* saved "c1" */
|
|
|
|
if (pblock->b_flags & PARTLY) {
|
|
/*
|
|
* The block was already partly filled. Initialize locals
|
|
* accordingly
|
|
*/
|
|
poff = savedpoff;
|
|
siz = savedsiz;
|
|
pblock->b_flags = 0;
|
|
c1 = savedc1;
|
|
if (c1 == pblock->b_info || *(c1 - 1)) {
|
|
/*
|
|
* We had incremented "lastreadline" temporarily,
|
|
* because the last line could not be completely read
|
|
* last time we tried. Undo this increment
|
|
*/
|
|
poff--;
|
|
--lastreadline;
|
|
}
|
|
}
|
|
else {
|
|
if (nopipe) pblock->b_foff = lseek(stdf, 0L, 1);
|
|
if (saved) {
|
|
/*
|
|
* There were leftovers from the previous block
|
|
*/
|
|
pblock->b_info = saved;
|
|
if (nopipe) pblock->b_foff -= savedc1 - saved;
|
|
c1 = savedc1;
|
|
saved = 0;
|
|
}
|
|
else { /* Allocate new block */
|
|
pblock->b_info = c1 = alloc(BLOCKSIZE + 1, 1);
|
|
}
|
|
/*
|
|
* Allocate some space for line-offsets
|
|
*/
|
|
pblock->b_offs = poff = (int *)
|
|
alloc((unsigned) (100 * sizeof(int)), 0);
|
|
siz = 99;
|
|
*poff++ = 0;
|
|
}
|
|
c = c1;
|
|
for (;;) {
|
|
/*
|
|
* Read loop
|
|
*/
|
|
cnt = read(stdf, c1, BLOCKSIZE - (c1 - pblock->b_info));
|
|
if (cnt < 0) {
|
|
/*
|
|
* Interrupted read
|
|
*/
|
|
if (errno == EINTR) continue;
|
|
error("Could not read input file");
|
|
cnt = 0;
|
|
}
|
|
c1 += cnt;
|
|
if (c1 != pblock->b_info + BLOCKSIZE) {
|
|
ENDseen = 1;
|
|
pblock->b_flags |= PARTLY;
|
|
}
|
|
break;
|
|
}
|
|
assert(c <= c1);
|
|
while (c < c1) {
|
|
/*
|
|
* Now process the block
|
|
*/
|
|
*c &= 0177; /* Most significant bit ignored */
|
|
if (*c == '\n') {
|
|
/*
|
|
* Newlines are replaced by '\0', so that "getline"
|
|
* can deliver one line at a time
|
|
*/
|
|
*c = 0;
|
|
lastreadline++;
|
|
/*
|
|
* Remember the line-offset
|
|
*/
|
|
if (poff == pblock->b_offs + siz) {
|
|
/*
|
|
* No space for it, allocate some more
|
|
*/
|
|
pblock->b_offs = (int *)
|
|
re_alloc((char *) pblock->b_offs,
|
|
(siz+1) * sizeof(int),
|
|
(siz + 51) * sizeof(int));
|
|
poff = pblock->b_offs + siz;
|
|
siz += 50;
|
|
}
|
|
*poff++ = c - pblock->b_info + 1;
|
|
}
|
|
else if (*c == '\0') {
|
|
/*
|
|
* 0-bytes are replaced by 0200, because newlines are
|
|
* replaced by 0, and 0200 & 0177 gives again 0 ...
|
|
*/
|
|
*c = 0200;
|
|
}
|
|
c++;
|
|
}
|
|
assert(c==c1);
|
|
*c = 0;
|
|
if (c != pblock->b_info && *(c-1) != 0) {
|
|
/*
|
|
* The last line read does not end with a newline, so add one
|
|
*/
|
|
lastreadline++;
|
|
*poff++ = c - pblock->b_info + 1;
|
|
if (!(pblock->b_flags & PARTLY) && *(poff - 2) != 0) {
|
|
/*
|
|
* Save the started line; it will be in the next block.
|
|
* Remove the newline we added just now.
|
|
*/
|
|
saved = c1 = alloc(BLOCKSIZE + 1, 1);
|
|
c = pblock->b_info + *(--poff - 1);
|
|
while (*c) *c1++ = *c++;
|
|
c = pblock->b_info + *(poff - 1);
|
|
savedc1 = c1;
|
|
--lastreadline;
|
|
}
|
|
}
|
|
pblock->b_end = lastreadline;
|
|
if (pblock->b_flags & PARTLY) {
|
|
/*
|
|
* Take care, that we can call "nextblock" again, to fill in
|
|
* the rest of this block
|
|
*/
|
|
savedsiz = siz;
|
|
savedpoff = poff;
|
|
savedc1 = c;
|
|
if (c == pblock->b_info) {
|
|
lastreadline++;
|
|
pblock->b_end = 0;
|
|
}
|
|
}
|
|
else {
|
|
/*
|
|
* Not completely read blocks are not in the linked list,
|
|
* so can never be "swapped out".
|
|
*/
|
|
addtolist(pblock);
|
|
cnt = pblock - blocklist;
|
|
filldegree = ((c-pblock->b_info) + (cnt-1) * filldegree) / cnt;
|
|
}
|
|
assert(pblock->b_end - (pblock-1)->b_end <= poff - pblock->b_offs);
|
|
}
|
|
|
|
/*
|
|
* Allocate core for the block, and read it back from
|
|
* the temporary file.
|
|
*/
|
|
|
|
STATIC VOID
|
|
readblock(pblock) register struct block *pblock; {
|
|
|
|
register int size;
|
|
register long i;
|
|
|
|
/*
|
|
* Find out where the block is, and read it
|
|
*/
|
|
pblock->b_info = alloc(BLOCKSIZE + 1, 1);
|
|
i = (pblock - 1)->b_end * sizeof(int);
|
|
size = (int) (pblock->b_end * sizeof(int) - i);
|
|
pblock->b_offs = (int *) alloc((unsigned) size, 0);
|
|
if (nopipe) {
|
|
register char *c;
|
|
register int line_index;
|
|
int cnt;
|
|
long l = lseek(stdf, 0L, 1);
|
|
|
|
(VOID) lseek(stdf, pblock->b_foff, 0);
|
|
cnt = read(stdf, pblock->b_info, BLOCKSIZE);
|
|
(VOID) lseek(stdf, l, 0);
|
|
c = pblock->b_info;
|
|
pblock->b_offs[0] = 0;
|
|
line_index = 1;
|
|
size /= sizeof(int);
|
|
while (c < pblock->b_info + cnt) {
|
|
*c &= 0177;
|
|
if (*c == '\n') {
|
|
*c = '\0';
|
|
if (line_index < size)
|
|
pblock->b_offs[line_index++] =
|
|
(c - pblock->b_info) + 1;
|
|
}
|
|
else if (*c == '\0') *c = 0200;
|
|
c++;
|
|
}
|
|
*c = '\0';
|
|
}
|
|
else {
|
|
(VOID) lseek(tfdes, (long) ((long) BLOCKSIZE * (pblock - blocklist)),0);
|
|
if (read(tfdes, pblock->b_info,BLOCKSIZE) != BLOCKSIZE) {
|
|
panic("read error");
|
|
}
|
|
/*
|
|
* Find out where the line-offset list is, and read it
|
|
*/
|
|
(VOID) lseek(ifdes, i, 0);
|
|
if (read(ifdes, (char *) pblock->b_offs, size) != size) {
|
|
panic("read error");
|
|
}
|
|
pblock->b_info[BLOCKSIZE] = '\0';
|
|
}
|
|
/*
|
|
* Add this block to the list of incore blocks
|
|
*/
|
|
addtolist(pblock);
|
|
}
|
|
|
|
/*
|
|
* Called after processing a file.
|
|
* Free all core.
|
|
*/
|
|
|
|
VOID
|
|
do_clean() {
|
|
|
|
register struct block *pblock;
|
|
register char *p;
|
|
|
|
for (pblock = blocklist; pblock < maxblocklist; pblock++) {
|
|
if (p = pblock->b_info) {
|
|
free(p);
|
|
free((char *) pblock->b_offs);
|
|
}
|
|
}
|
|
if (p = (char *) blocklist) {
|
|
free(p);
|
|
}
|
|
blocklist = 0;
|
|
maxblocklist = 0;
|
|
topblocklist = 0;
|
|
lastreadline = 0;
|
|
filldegree = 0;
|
|
ENDseen = 0;
|
|
if (p = saved) free(p);
|
|
saved = 0;
|
|
b_head = 0;
|
|
b_tail = 0;
|
|
# if MAXNBLOCKS
|
|
nblocks = 0;
|
|
# endif
|
|
}
|
|
|
|
/*
|
|
* Close a file with file-descriptor "file", if it indeed is one
|
|
*/
|
|
|
|
STATIC VOID
|
|
cls(file) {
|
|
if (file) (VOID) close(file);
|
|
}
|
|
|
|
/*
|
|
* Close all files
|
|
*/
|
|
|
|
VOID
|
|
cls_files() {
|
|
|
|
cls(tfdes);
|
|
cls(ifdes);
|
|
cls(stdf);
|
|
}
|
|
|
|
/*
|
|
* Get a character. If possible, do some workahead.
|
|
*/
|
|
|
|
int
|
|
getch() {
|
|
# if USG_OPEN
|
|
# include <fcntl.h>
|
|
# include <sys/stat.h>
|
|
|
|
register int i,j;
|
|
struct stat buf;
|
|
# else
|
|
# ifdef FIONREAD
|
|
# include <sys/stat.h>
|
|
|
|
struct stat buf;
|
|
long i;
|
|
# endif
|
|
# endif
|
|
|
|
char c;
|
|
int retval;
|
|
|
|
flush();
|
|
if (startcomm) {
|
|
/*
|
|
* Command line option command
|
|
*/
|
|
if (*startcomm) return *startcomm++;
|
|
return '\n';
|
|
}
|
|
# if USG_OPEN
|
|
if (stdf >= 0) {
|
|
/*
|
|
* Make reads from the terminal non-blocking, so that
|
|
* we can see if the user typed something
|
|
*/
|
|
i = fcntl(0,F_GETFL,0);
|
|
if (i != -1 && fcntl(0, F_SETFL, i|O_NDELAY) != -1) {
|
|
j = 0;
|
|
while (! ENDseen &&
|
|
((j = read(0,&c,1)) == 0
|
|
#ifdef EWOULDBLOCK
|
|
|| (j < 0 && errno == EWOULDBLOCK)
|
|
#endif
|
|
)
|
|
&&
|
|
(nopipe ||
|
|
(fstat(stdf,&buf) >= 0 && buf.st_size > 0))) {
|
|
/*
|
|
* Do some read ahead, after making sure there
|
|
* is input and the user did not type a command
|
|
*/
|
|
new_block();
|
|
}
|
|
(VOID) fcntl(0,F_SETFL,i);
|
|
if (j < 0) {
|
|
/*
|
|
* Could this have happened?
|
|
* I'm not sure, because the read is
|
|
* nonblocking. Can it be interrupted then?
|
|
*/
|
|
return -1;
|
|
}
|
|
if (j > 0) return c;
|
|
}
|
|
}
|
|
# else
|
|
# ifdef FIONREAD
|
|
if (stdf >= 0) {
|
|
/*
|
|
* See if there are any characters waiting in the terminal input
|
|
* queue. If there are not, read ahead.
|
|
*/
|
|
while (! ENDseen &&
|
|
( ioctl(0, FIONREAD, (char *) &i) >= 0 && i == 0) &&
|
|
( nopipe || fstat(stdf,&buf) >= 0 && buf.st_size > 0)) {
|
|
/*
|
|
* While the user does'nt type anything, and there is
|
|
* input to be processed, work ahead
|
|
*/
|
|
if (interrupt) return -1;
|
|
new_block();
|
|
}
|
|
}
|
|
# endif
|
|
# endif
|
|
if (read(0,&c,1) <= 0) retval = -1; else retval = c & 0177;
|
|
return retval;
|
|
}
|
|
|
|
/*
|
|
* Get the position of line "ln" in the file.
|
|
*/
|
|
|
|
long
|
|
getpos(ln) long ln; {
|
|
register struct block *pblock;
|
|
register long i;
|
|
|
|
pblock = getblock(ln,1);
|
|
assert(pblock != 0);
|
|
i = filldegree * (pblock - blocklist);
|
|
return i - (filldegree - pblock->b_offs[ln - (pblock-1)->b_end]);
|
|
}
|