/************************************************************************ * This program is Copyright (C) 1986 by Jonathan Payne. JOVE is * * provided to you without charge, and with no warranty. You may give * * away copies of JOVE, including sources, provided that this notice is * * included in all the files. * ************************************************************************/ #include "jove.h" RCS("$Id: fp.c,v 14.32.0.3 1993/07/23 03:20:59 tom Exp tom $") #include "io.h" #define MAXFILES 20 /* good enough for my purposes */ private File _openfiles[MAXFILES] ZERO; /* this deals with screen output before the terminal buffer is setup. */ private char one_buf; private File _stdout = {1, 1, 1, F_WRITE|F_LOCKED|F_BINARY, &one_buf, &one_buf}; public File *stdout = &_stdout; File * fd_open(name, flags, fd, buffer, buf_size) const char *name; register int flags; char *buffer; int buf_size; { register File *fp; register char *bp; for (fp = _openfiles; fp->f_flags != 0; fp++) if (fp == &_openfiles[MAXFILES-1]) complain("[Too many open files!]"); fp->f_bufsize = buf_size; fp->f_cnt = 0; fp->f_fd = fd; if ((bp = buffer) == NULL) { bp = emalloc(buf_size); flags |= F_MYBUF; } fp->f_flags = flags; fp->f_base = fp->f_ptr = bp; fp->f_name = copystr(name); return fp; } File * f_open(name, flags, buffer, buf_size) const char *name; char *buffer; { register int fd; register const char *fname = rel_name(name); switch (F_MODE(flags)) { default: fd = -1; break; case F_READ: fd = open(fname, O_RDONLY); break; case F_APPEND: if ((fd = open(fname, O_WRONLY)) >= 0) { lseek(fd, (off_t) 0, SEEK_END); break; } /* fall through... */ case F_WRITE: fd = creat(fname, CreatMode); break; } if (fd < 0) return NULL; return fd_open(name, flags, fd, buffer, buf_size); } void f_close(fp) register File *fp; { flush(fp); #ifdef BSD4_2 if (fp->f_flags & (F_WRITE|F_APPEND)) fsync(fp->f_fd); #endif close(fp->f_fd); if (fp->f_flags & F_MYBUF) free(fp->f_base); free(fp->f_name); fp->f_flags = 0; /* indicates that we're available */ } void gc_openfiles() { register File *fp = _openfiles; register int flags; do { if ((flags = fp->f_flags) != 0 && (flags & F_LOCKED) == 0) f_close(fp); } while (++fp < &_openfiles[MAXFILES]); } filbuf(fp) register File *fp; { if (fp->f_flags & (F_EOF|F_ERR)) return EOF; { register int n; #ifdef CRLF retry: #endif n = read(fp->f_fd, fp->f_ptr = fp->f_base, fp->f_bufsize); if ((fp->f_cnt = n) <= 0) { register int eflag = F_EOF; if (n < 0) { add_mess("[Read error %s]", syserr()); eflag = F_ERR; } fp->f_flags |= eflag; return EOF; } io_chars += n; } #if F_BINARY if (!(fp->f_flags & F_BINARY)) { # ifdef CRLF /* collapse pairs to */ register char *sp = fp->f_base, *dp = sp, *ep = sp + fp->f_cnt; int delayed_cr = 0; if (fp->f_flags & F_PEND_CR) { /* do we have a pending CR? */ fp->f_flags &= ~F_PEND_CR; if (*sp != LF) delayed_cr++; } do { if ((*dp++ = *sp++) == CR) { if (sp == ep) { fp->f_flags |= F_PEND_CR; --dp; break; } if (*sp == LF) --dp; } } while (sp < ep); fp->f_cnt = dp - fp->f_base; if (delayed_cr) return CR; /* this can happen only if we have a single CR at EOF */ if (fp->f_cnt == 0) goto retry; # endif /* CRLF */ # ifdef MAC /* convert in and vice versa */ register char *s = fp->f_base, *e = sp + fp->f_cnt; while (s < e) { switch (*s++) { case CR: s[-1] = LF; break; case LF: s[-1] = CR; break; } } # endif } #endif /* F_BINARY */ --fp->f_cnt; /* we made sure that fp->f_cnt > 0 if we got here. */ return *fp->f_ptr++; } void putstr(s) register const char *s; { register int c; register File *f = stdout; while (c = *s++) putc(c, f); } void fputnchar(buf, n, fp) const void_* buf; register int n; register File *fp; { register const char *s = buf; while (--n >= 0) putc(*s++, fp); } /* get a number of characters -- returns 0 if successful, EOF on failure */ fgetnchar(buf, n, fp) void_* buf; register int n; register File *fp; { register char *s = buf; while (--n >= 0) *s++ = getc(fp); return (fp->f_flags & F_EOF) ? EOF : 0; } #define NOCHAR -1024 /* "impossible" char code (even for signed chars) */ void flusho() { _flush(NOCHAR, stdout); } void flush(fp) File *fp; { _flush(NOCHAR, fp); } _flush(c, fp) register File *fp; { register int n; register char *s, *e; #ifdef CRLF char transl_buf[BLKSIZ+1]; #endif if (fp->f_flags & (F_READ | F_STRING | F_ERR)) return; e = fp->f_ptr; s = fp->f_base; errno = 0; #if F_BINARY if (!(fp->f_flags & F_BINARY)) { # ifdef CRLF /* expand to */ register char *d = transl_buf, *prev = s; register char ch; while (s < e) { if ((ch = *s++) == LF) *d++ = CR; *d++ = ch; if (d >= &transl_buf[BLKSIZ]) { n = d - transl_buf; d = transl_buf; if (write(fp->f_fd, d, n) != n) { fp->f_flags |= F_ERR; error("[I/O error(%s); file=%s, fd=%d]", syserr(), fp->f_name, fp->f_fd); } io_chars += n; prev = s; } } /* * Leave remainder for the next time (so that we will * generally keep in line with physical block boundary) * UNLESS we did not write anything at all, or this was * a partially filled buffer (i.e. explicit fflush, or * upon closing a file), or the previous write exactly * filled the request. */ if ((s = prev) < e) { if (s > fp->f_base && fp->f_cnt < 0) { fp->f_cnt = fp->f_bufsize - (e - s); /* relocate remainder to start of StdIObuf */ d = fp->f_base; do *d++ = *s++; while (s < e); fp->f_ptr = d; goto ret; /* yuck! */ } /* cheat: let 's' and 'e' point into translated buf */ s = transl_buf; e = d; } # endif /* DOS */ # ifdef MAC /* convert in and vice versa */ while (s < e) { switch (*s++) { case CR: s[-1] = LF; break; case LF: s[-1] = CR; break; } } s = fp->f_base; # endif /* MAC */ } #endif /* F_BINARY */ if ((n = (e - s)) > 0 && write(fp->f_fd, s, n) != n && fp != stdout) { fp->f_flags |= F_ERR; error("[I/O error(%s); file=%s, fd=%d]", syserr(), fp->f_name, fp->f_fd); } io_chars += n; fp->f_cnt = fp->f_bufsize; fp->f_ptr = fp->f_base; ret: if (c != NOCHAR) /* there is always room in the buffer */ --fp->f_cnt, *fp->f_ptr++ = c; if (fp == stdout) OkayAbort = YES; } f_gets(fp, buf, size) register File *fp; char *buf; { register char *cp = buf; register int c; register char *endp = cp + size - 1; if (fp->f_flags & F_EOF) return EOF; while ((c = getc(fp)) != '\n') { if (c <= 0) { if (c == '\0') { #ifdef NULLCHARS c = '\n'; /* kludge to read nulls */ #else continue; /* sorry we don't read nulls */ #endif } else /* * presumably we have c == EOF here, but on systems * that sign-extend characters it may be a valid * character. So check File flags if there's really * something rotten. */ #if (!__CHAR_UNSIGNED__) if (fp->f_flags & (F_ERR|F_EOF)) /* the next block */ #endif { *cp = '\0'; if (cp != buf && (fp->f_flags & F_EOF)) { /* * if we are a real EOF (not read error), * show message and return EOF the *next* time. */ add_mess(" [Incomplete last line]"); break; } return EOF; } } if (cp >= endp) { add_mess(" [Line too long]"); rbell(); fp->f_cnt++, fp->f_ptr--; /* unget character */ *cp = '\0'; return EOF; } *cp++ = c; } *cp = '\0'; io_lines++; return NIL; /* this means okay */ } #include "temp.h" /* for BLKSIZ_SHIFT */ void f_seekblk(fp, bno) register File *fp; { fp->f_flags &= ~(F_ERR|F_EOF); fp->f_cnt = 0; lseek(fp->f_fd, (long) bno << BLKSIZ_SHIFT, SEEK_SET); } /* * Do a binary search on a File. This assumes the entries in the file are * in lexical order. `searchstr' gives the entry to match, `prefix_len' is * the minimum length of a match in order to qualify as an entry at all, * (it can be zero, as it in fact is for searches in tagfiles), and * `match_len' gives the length of an exact match. A further restriction * to an exact match is that the match should be followed by whitespace * (in fact by any control character). * * This is a generalization of the Fast Tags code in JOVE 4.14. * The help file now benefits from it too! * * Warning: there is some very low-level File fiddling going on here which * is not for the weak at heart! */ void f_bsearch(fp, searchstr, prefix_len, match_len) register File *fp; const char *searchstr; /* search string: prefix+actual match */ int prefix_len; /* length of required prefix */ int match_len; /* length of match string */ { struct stat stbuf; register int c; register unsigned mid, /* bno of midpoint */ upper, /* bno of upper bound */ lower, /* bno of lower bound */ curr, /* current block */ matchlim; if (fstat(fp->f_fd, &stbuf) < 0) return; lower = 0; mid = 0; upper = (int)(stbuf.st_size >> BLKSIZ_SHIFT); #ifndef TINY /* * Yet another kludge: if we are BEFORE the entry and we match * more than `matchlim' characters, we declare it a match right * away. This tends to avoid overshoot. We determine the * --somewhat arbitrary-- value of `matchlim' here. */ matchlim = (c = prefix_len) + upper / 26; /* heuristics... */ /* * We try to be choose the initial midpoint cleverly, * i.e., for the most common case of lowercase-only keys. */ c = searchstr[c/*prefix_len*/]; curr = (upper * ((c < 'a') ? 3 : (c <= 'z') ? (c & 037) + 3 : 29)) >> 5; #else curr = (upper + lower) >> 1; #endif /* * Invariant: if there is a line matching the search string, it * begins somewhere after position lower, and begins at or before * upper. There is one possible exception: if lower is 0, the * match might be on the very first line. * * The loop condition is chosen so that when the loop terminates, * the file pointer is positioned at lower, so we can plunge into * the sequential search right away. */ for (; (upper > lower + 1 || mid > lower); curr = (upper + lower) >> 1) { mid = curr; f_seekblk(fp, mid); nextblock: /* Ugh! do it by hand... */ c = filbuf(fp); if (mid == 0) /* unget character */ fp->f_cnt++, fp->f_ptr--; else skip_to_nl: /* skip to start of line */ while (c != '\n' && --fp->f_cnt >= 0) c = *fp->f_ptr++; if (fp->f_cnt <= match_len) { /* * There are too few characters left to make up a * match, so go load the next block, unless we * are near the end of the file. In that case we * assume we are AFTER the search string. */ if (++curr < upper) goto nextblock; /*~~~Yuck!~~~*/ upper = mid; /* original midpoint */ continue; } #define match c /* alias */ /* peek into the File's buffer (Ouch!) */ match = numcomp(searchstr, fp->f_ptr); /* if prefix didn't match, try next line */ if (match < prefix_len) { fp->f_cnt--; c = *fp->f_ptr++; goto skip_to_nl; /*~~~Spagetti!~~~*/ } /* we got an exact match, so bail out */ if (match >= match_len && (fp->f_ptr[match_len] <= ' ')) break; /* got a partial match here, see which way it goes */ if (fp->f_ptr[match] < searchstr[match]) { #ifndef TINY if (match > matchlim) break; #endif lower = curr; /* we are BEFORE entry */ } else upper = mid; /* we are AFTER entry */ } } /* * search next occurrence of a pattern in File. * (use genbuf as line buffer so the match is not lost upon exit) * The file will be positioned at the line following the matching line * or at EOF if the match fails. This is efficient enough to replace * a lot of ad-hoc file search routines. * returns line number in file if successful, 0 on failure. */ #include "re.h" int re_fsearch(fp, pattern) register File *fp; const char *pattern; { register char *line = genbuf; REcompile(pattern, YES, compbuf); while (f_gets(fp, line, LBSIZE) == 0 /*!EOF*/) { if (re_sindex(line, 0, compbuf)) return io_lines; } return NO; } /*====================================================================== * $Log: fp.c,v $ * Revision 14.32.0.3 1993/07/23 03:20:59 tom * (f_gets): ungobble char when line is truncated. * * Revision 14.32 1993/06/09 01:02:20 tom * (f_gets): remove F_IGN_0 kludge. * * Revision 14.31.0.2 1993/03/25 21:30:47 tom * allow compile-time CRLF option on non-DOS systems. * * Revision 14.31 1993/02/16 05:47:55 tom * remove (void) casts; lotsa random optimizations. * * Revision 14.30 1993/01/26 18:43:09 tom * cleanup whitespace; some random optimizations; flush() now handles io_chars. * * Revision 14.28 1992/10/24 01:24:18 tom * convert to "port{ansi,defs}.h" conventions. * * Revision 14.26 1992/08/26 23:56:52 tom * add RCS directives. * */