/************************************************************************ * 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. * ************************************************************************/ #define NO_PROCDECL /* kludge to get this file through small v7 compiler */ #include "tune.h" /* to allow definition of debug flags there. */ #ifdef DEBUG /* General debug flag, turn on file-specific flag. */ # ifndef IO_DEBUG # define IO_DEBUG 1 # endif #else # if IO_DEBUG /* File-specific debug flag, turn on general flag. */ # define DEBUG # endif #endif #include "jove.h" RCS("$Id: io.c,v 14.32.0.12 1994/06/23 02:46:26 tom Exp tom $") #include "ctype.h" #define Extern #include "io.h" #ifndef ExecNow # include "maps.h" #endif char linebuf[LBSIZE]; #ifdef BIG char iobuff[BLKSIZ], genbuf[LBSIZE]; #else char *iobuff, *genbuf; #endif int DOLsave ZERO; /* Do Lsave flag. If lines aren't being save when you think they should have been, this flag is probably not being set, or is being cleared before lsave() was called. */ private int f_getputl __(( Line *_(line), File *_(fp) )); #ifdef BACKUPFILES private void file_backup __(( const char *_(fname) )); DEF_INT( "make-backup-files", BkupOnWrite, V_BOOL ) _IF(def BACKUPFILES)_IF(def PRIVATE) ZERO; #endif DEF_INT( "files-should-end-with-newline", EndWNewline, V_BOOL ) = YES; #if F_BINARY # if (unix || vms) # define BIN_INIT = YES # else # define BIN_INIT ZERO # endif /* {NOTE: we don't use F_BINARY in the _IF conditional since that is undefined by the time the DEF is included in setmaps.c et al.} */ DEF_INT( "binary-read-files", BinRdFile, V_BOOL ) _IF(!unix && !vms || 2*CRLF-1!=2*0-1) BIN_INIT; DEF_INT( "binary-write-files", BinWrtFile, V_BOOL ) _IF(!unix && !vms || 2*CRLF-1!=2*0-1) BIN_INIT; # undef BIN_INIT #endif private int io_mode; File * open_file(name, buf, how) const char *name; char *buf; register int how; { register File *fp; register const char *fname = rel_name(name); /* Check if this file is write-protected. If so, and we're opening the file for saving, ask the user if (s)he wants to overwrite it. */ if (access(fname, W_OK) < 0 && errno == EACCES) { struct stat st; if ((how & F_SAVE) && /* only if requested. */ #ifdef BACKUPFILES (how &= ~F_SAVE, TRUE) && /* Disable backup. */ #endif (stat(fname, &st) == 0) && /* Get mode flags */ /* Make sure we are allowed to change mode before asking. */ (chmod(fname, io_mode = st.st_mode) == 0)) { confirm("\"%s\" [Read only] Go ahead and %f anyway? ", pr_name(fname)); #ifdef BACKUPFILES how |= F_SAVE; /* Reenable backup. */ #else chmod(fname, io_mode | S_IWUSR); /* {S_IWUSR is sufficient since only the file's owner is allowed to chmod. (or root, but she is allowed to change the file anyway.)} */ #endif } how |= F_READONLY; /* Mark it read-only. */ } #ifdef BACKUPFILES if (how & F_SAVE) { /* Backup original file, if requested. (AFTER we checked the permissions, so we're reasonably sure the save will succeed, but BEFORE the file's mode is changed.) */ if (True(BkupOnWrite) && (exp > 0)) file_backup(fname); if (how & F_READONLY) chmod(fname, io_mode | S_IWUSR); } #endif if ((fp = f_open(fname, how, buf, BLKSIZ)) == NULL) { message(IOerr((how & F_READ) ? "open" : (how & F_APPEND) ? "append" : "create", fname)); if (how & F_COMPLAIN) complain((char *) 0); } else { if (!(how & F_QUIET)) { f_mess((fp->f_flags & F_READONLY) ? "\"%s\" [Read only]" : "\"%s\"", pr_name(fname)); } } io_chars = 0; io_lines = 0; return fp; } void close_file(file) File *file; { register File *fp; register int flags; if ((fp = file) == NULL) return; flags = fp->f_flags; /* Restore file modes if file was originally write-protected. */ if ((flags & (F_SAVE|F_READONLY)) == (F_SAVE|F_READONLY)) #ifdef BSD4_2 fchmod(fp->f_fd, io_mode); #else chmod(rel_name(fp->f_name), io_mode); #endif f_close(fp); /* Report AFTER f_close() since that may flush(). */ if (!(flags & F_QUIET)) add_mess(" %d line%n, %D character%n.", io_lines, io_chars); } /* Write the region from line1/char1 to line2/char2 to FP. This never CLOSES the file since we don't know if we want to. */ void putreg(fp, line1, char1, line2, char2, makesure) register File *fp; Line *line1, *line2; { register int c; register const char *lp; register Line *line; if (makesure) fixorder(&line1, &char1, &line2, &char2); line = line1; lp = lcontents(line) + char1; if (line == line2) { char2 -= char1; /* adjust char count of last line */ } else do { #if OBSOLETE_SINCE_14_30 register const char *base = lp; #endif while (c = *lp++) { #ifdef NULLCHARS if (c == '\n') c = '\0'; #endif putc(c, fp); } putc('\n', fp); #if OBSOLETE_SINCE_14_30 /* This is now handled by _flush() */ io_chars += (lp - base); /* includes newline */ # ifdef CRLF if (False(BinWrtFile)) io_chars++; /* includes */ # endif #endif /* OBSOLETE */ io_lines++; line = line->l_next; lp = lcontents(line); } while (line != line2); if (c = char2) { /* output remainder of last line */ #ifdef NULLCHARS register int n = c; do { if ((c = *lp++) == '\n') c = '\0'; putc(c, fp); } while (--n); #else fputnchar(lp, c, fp); #endif #if OBSOLETE_SINCE_14_30 io_chars += char2; #endif io_lines++; } flush(fp); } private void dofread __(( File *_(fp) )); private void dofread(fp) register File *fp; { char end[LBSIZE]; register Buffer *cb = curbuf; register Line *line = /*curline*/ cb->b_dot; register int char1 = /*curchar*/ cb->b_char; register char *tail = &linebuf[char1]; strcpy(end, tail); if (f_gets(fp, tail, LBSIZE - char1) == 0 && !f_eof(fp)) { SavLine(line, linebuf); do { line = listput(cb, line); } while (f_getputl(line, fp) == NIL); /* {{This truncates the file at the first line that is too long. Think about a more decent solution, e.g., mark truncated lines in the buffer, and set a flag in buffer to warn against this (+View mode?). It would still be a hack, but I don't feel like eliminating the line length limits right now. (or ever; it is too much a consequence of JOVEs basic design...)}} */ } /* empty files used to truncate the line... now this fixes it. */ linecopy(linebuf, cb->b_char = ltobuf(line, linebuf), end); IFixMarks(cb->b_dot, char1, line, cb->b_char); cb->b_dot = line; DOLsave++; makedirty(line); } void read_file(file, is_insert) const char *file; int is_insert; { register File *fp; register Buffer *cb = curbuf; #if F_BINARY # define open_mode (True(BinRdFile) ? F_READ|F_BINARY : F_READ) #else # define open_mode F_READ #endif { #ifndef ExecNow register data_obj *read_fail_macro = miscmap['R']; /* This is a bit of a kludge, in the absence of Macro variables... */ retry: #endif if (!is_insert) { initlist(cb); cb->b_ntbf = 0; set_ino(cb); } if ((fp = open_file(file, iobuff, open_mode)) == NULL) { #ifndef ExecNow if (read_fail_macro) { ExecNow(read_fail_macro); read_fail_macro = NULL; goto retry; } #endif if (!is_insert && errno == ENOENT) /* replace open-failure message from open_file() with something more intelligible. */ message("(new file)"); return; } } { Bufpos save; register Bufpos *bp = &save; DOTsave(bp); dofread(fp); if (!is_insert) { if (fp->f_flags & F_READONLY) cb->b_minor |= View; } else if (io_chars > 0) { set_mark(); modify(); } SetDot(bp); close_file(fp); } #undef open_mode } void write_file(fname, app) const char *fname; { register File *fp; { register int open_mode = F_APPEND|F_COMPLAIN|F_SAVE; if (!app) open_mode = F_WRITE|F_COMPLAIN|F_SAVE; #if F_BINARY if (True(BinWrtFile)) open_mode |= F_BINARY; #endif fp = open_file(fname, iobuff, open_mode); } { register Buffer *cb = curbuf; register Line *last = cb->b_last; register int llen; if (BufMinorMode(cb, OverWrite)) { /* ignore blank lines at end of file */ while(blnkp(lcontents(last)) && last->l_prev) last = last->l_prev; } putreg(fp, cb->b_first, 0, last, llen = length(last), NO); /* Make sure file ends with a newLine if so requested. */ if (True(EndWNewline) && llen > 0) putc('\n', fp); close_file(fp); set_ino(cb); if (!app) unmodify(); } } /* * `chk_file(filename, how)' -- combines filemunge (overwrite check) * and chk_mtime (file changed since last save) * checks with respect to current buffer (that was all that's really needed). * how == NULL means overwrite check only, how == "read" means no overwrite * check. * (a note about the two stat() calls below: the only situation where we can * call stat twice is when we deal with a linked file. This doesn't occur * too often, so I didn't bother avoiding the redundant call). */ void chk_file(filename, how) const char *filename, *how; { register Buffer *cb = curbuf; register const char *fname; struct stat stbuf; register struct stat *s = &stbuf; if ((fname = filename) == NULL) return; if ((cb->b_fname == NULL || strcmp(fname, cb->b_fname) != 0) && /* file names differ */ (zstat(fname, s) == 0) && /* and file exists */ !b_flink_p(cb, 0, s)) { /* and no link */ if ((how == NULL || /* "filemunge" */ strcmp(how, "read") != 0) && /* requested? */ !S_ISCHR(s->st_mode)) /* and disk-based */ confirm("\"%s\" already exists; overwrite it? ", fname); return; } if ((how != NULL) && /* "chk_mtime" requested? */ (cb->b_fname != NULL) && /* if we care ... */ (zstat(fname, s) == 0) && /* and file exists */ b_foutdated_p(cb, s)) { /* and there's trouble */ rbell(); redisplay(); /* Ring that bell! */ TOstart("*Warning*", TRUE); Typeout("\"%s\" now saved on disk is not what you last", pr_name(fname)); Typeout("visited or saved. Probably someone else is editing"); Typeout("your file at the same time."); Typeout(NULL); Typeout("Type \"y\" if I should %s, anyway.", how); TOstop(); confirm("Shall I go ahead and %s anyway? ", how); /* kludge: suppress bell from confirm */ unbell(); } } DEF_INT( "file-creation-mode", CreatMode, V_BASE8 ) = S_IRUSR|S_IWUSR | S_IRGRP|S_IWGRP | S_IROTH|S_IWOTH; DEF_CMD( "write-region", WrtReg, ARG(NO) ); DEF_CMD( "append-region", WrtReg, ARG(YES) ) { char fnamebuf[FILESIZE]; register const char *fname; register Mark *mp = CurMark(); register File *fp; register int open_mode = F_APPEND|F_COMPLAIN|F_SAVE; /* Won't get here if there isn't a Mark */ fname = ask_file((char *) 0, (char *) 0, fnamebuf); if (!(LastCmd->Type & ARG(YES))) { /* i.e., don't append */ open_mode = F_WRITE|F_COMPLAIN|F_SAVE; chk_file(fname, (char *)0); } #if F_BINARY if (True(BinWrtFile)) open_mode |= F_BINARY; #endif fp = open_file(fname, iobuff, open_mode); putreg(fp, mp->m_line, mp->m_char, curline, curchar, YES); close_file(fp); } DEF_INT( "allow-bad-filenames", OkayBadChars, V_BOOL ) ZERO; _IF(def PRIVATE) DEF_CMD( "write-file", WriteFile, NO ) { register const char *fname; char fnamebuf[FILESIZE]; register Buffer *cb = curbuf; #ifdef FILESELECTOR FSelMode = WRITE; #endif fname = ask_file((char *) 0, def_bfile(), fnamebuf); #ifndef TINY /* Don't allow bad characters when creating new files. */ if (False(OkayBadChars) && (cb->b_fname == NULL || strcmp(cb->b_fname, fname) != 0)) { register int c; while (c = *fname++) if ((unsigned) c < ' ' || index(BadFChars, c)) break; fname = fnamebuf; if (c) confirm("\"%s\" [bad character `%c'] %f anyway? ", fname, c); } #endif chk_file(fname, "write"); cb->b_type = B_FILE; /* In case it wasn't before. */ setfname(cb, fname); write_file(fname, NO); } DEF_CMD( "save-file", SaveFile, NO ) { register Buffer *cb = curbuf; register const char *fname; if (IsModified(cb)) { if ((fname = cb->b_fname) == NULL) WriteFile(); else { chk_file(fname, "save"); write_file(fname, NO); } } else message("No changes need to be written."); } void visit_file(fname, toline) register const char *fname; int toline; { register Buffer *cb = curbuf; register int lno = 0; chk_file(fname, "read"); /* Try to keep position if reverting to old version of file. */ if (cb->b_fname && strcmp(cb->b_fname, fname) == 0) lno = lineno(cb->b_dot); if (IsModified(cb) && yes_or_no_p(NIL, "Shall I make your changes to \"%b\" permanent? ", cb)) if (lno) /* i.e., filename unchanged. */ WriteFile(); else SaveFile(); unmodify(); setfname(cb, fname); read_file(fname, NO); if (toline) lno = toline; if (lno) to_line(lno); } DEF_CMD( "visit-file", ReadFile, NO ) { char fnamebuf[FILESIZE]; visit_file(ask_file((char *) 0, def_bfile(), fnamebuf), (exp_p) ? exp : 0); } DEF_CMD( "insert-file", InsFile, EDIT ) { char fnamebuf[FILESIZE]; read_file(ask_file((char *) 0, (char *) 0, fnamebuf), YES); } #ifndef TINY DEF_CMD( "append-buffer", AppendBuf, NO ) _IF(ndef TINY) { char fnamebuf[FILESIZE]; write_file(ask_file((char *) 0, (char *) 0, fnamebuf), YES); } #endif /*========================= Line buffer cache. =========================*/ #define Extern public #include "temp.h" private int nleft; /* number of good characters left in current block */ private disk_line DFree = 1; /* pointer to end of tmp file */ /* Get a line at `tl' in the tmp file into `buf' which should be LBSIZE long. */ private char *getblock __(( disk_line _(atl), int _(iof) )); int getline(addr, buf) disk_line addr; char *buf; { register char *bp, *lp; lp = buf; bp = getblock(addr >> 1, READ); while ((*lp++ = *bp++) && /* unwind loop (for speed) */ (*lp++ = *bp++) && (*lp++ = *bp++) && (*lp++ = *bp++)); return (lp - buf) - 1; } /* Put `buf' and return the disk address */ disk_line putline(buf) const char *buf; { register char *bp; register const char *lp; register int nl; disk_line free_ptr; free_ptr = DFree; bp = getblock(free_ptr, WRITE); nl = nleft; lp = buf; while (*bp++ = *lp++) { if (--nl == 0) { if (nleft >= LBSIZE) /* line too long. This Shouldn't happen */ error("?putline?"); free_ptr = forward_block(blk_round(free_ptr)); nl = nleft; /* the old one! */ bp = getblock(free_ptr, WRITE); lp = buf; do *bp++ = *lp++; while (--nl); nl = nleft - (lp - buf); } } DFree = free_ptr + (((lp - buf) + CH_SIZE - 1) >> CH_BITS); /* (lp - buf) includes the null */ return (free_ptr << 1); } /* The theory is that critical section of code inside this procedure will never cause a problem to occur. Basically, we need to ensure that two blocks are in memory at the same time, but I think that this can never screw up. */ #define lockblock(addr) #define unlockblock(addr) private int f_getputl(line, fp) Line *line; register File *fp; { register char *bp; register int c, nl; disk_line free_ptr; char *base; free_ptr = DFree; base = bp = getblock(free_ptr, WRITE); nl = nleft; #if (BLKSIZ != LBSIZE) if (nl > LBSIZE) nl = LBSIZE; #endif while ((c = getc(fp)) != '\n') { if (c <= 0) { if (c == '\0') /* sorry we don't read nulls */ #ifndef NULLCHARS continue; #else c = '\n'; else /* the next `if' c.q. `break' */ #endif /* * 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)) #endif break; } *bp++ = c; if (--nl == 0) { if (nleft >= LBSIZE) { /* line too long */ --bp; fp->f_cnt++, fp->f_ptr--; /* unget character */ break; } free_ptr = blk_round(free_ptr); lockblock(free_ptr); free_ptr = forward_block(free_ptr); nl = nleft; bp = getblock(free_ptr, WRITE); byte_copy(base, bp, nl); base = bp; bp += nl; nl = LBSIZE - nl; unlockblock(blk_round(free_ptr - 1)); /* old block!! */ } } *bp = '\0'; DFree = free_ptr + (((bp - base) + 1 + CH_SIZE - 1) >> CH_BITS); line->l_dline = (free_ptr << 1); io_lines++; if (nl == 0) { rbell(); add_mess(" [Line too long]"); return EOF; } if (c < 0) { if (bp == base) io_lines--; else add_mess(" [Incomplete last line]"); return EOF; } return NIL; } /* * Blocks are randomly accessed, so no need for primes. * Use power of 2 for speed. */ #define MASK(i) S(S(S(S(i,1),2),4),8) /* support macros to */ #define S(i,n) (i)|(i)>>n /* calculate bit mask */ #define HASHSIZE (MASK(NBUF-1))+1 #define HASH(bno) (bno & (HASHSIZE-1)) /* {{cut down on parens to avoid expr. overflow with stone-age compilers}} */ #if unix || vms Block bl_cache[NBUF]; /* deliberately non-private */ #endif private struct { int c_fd; /* Tmp. file handle */ const char *c_fname; /* Tmp. file name */ int c_max_bno, /* highest block in use */ c_nblocks; /* number of cache blocks */ Block *c_lru, /* head of LRU list */ *c_mru, /* tail of LRU list */ *c_hash[HASHSIZE]; /* hash table */ } cache = { -1, NullStr, 0, #if unix || vms NBUF, &bl_cache[NBUF-1], &bl_cache[0] #endif }; void d_cache_init() { register int bno; register Block *bp, /* Block pointer */ *bq = NULL, /* previous one */ **hp; /* Hash pointer */ #if unix || vms bno = NBUF-1; bp = bl_cache; #else /* !(unix || vms) */ bno = CacheSize; /* from tune.c, so we can patch it */ if ((bp = (Block *) malloc(bno * sizeof(Block))) == NULL) complain("[Can't allocate cache: %d*%d]", bno, BLKSIZ); cache.c_nblocks = bno; cache.c_lru = &bp[--bno]; cache.c_mru = &bp[0]; #endif /* unix || vms */ do { bp->bl_hash = *(hp = &cache.c_hash[HASH(bno)]); (*hp) = bp; bp->bl_dirty = 0; bp->bl_bno = bno; bp->bl_next = bq; bq = bp; bq->bl_prev = ++bp; } while (--bno >= 0); bq->bl_prev = NULL; } private void tmpinit_blkio __(( Block *_(bp), ssize_t (*_(iofcn))() )), real_blkio __(( Block *_(bp), ssize_t (*_(iofcn))() )); private void (*blkio)__(( Block *_(bp), ssize_t (*_(iofcn))() )) = tmpinit_blkio; private void real_blkio(bp, iofcn) register Block *bp; register ssize_t (*iofcn)__(( int _(fd), void *_(buf), size_t _(size) )); { register char *what = "seek"; register long pos = (long) bp->bl_bno << BLKSIZ_SHIFT; errno = 0; /* to avoid plain wrong messages on full filesystem */ if (lseek(cache.c_fd, pos, SEEK_SET) == pos) { if ((*iofcn)(cache.c_fd, bp->bl_buf, BLKSIZ) == BLKSIZ) return; what = "write"; if (iofcn == read) what = "read"; } error("Tmp file %s error (bno=%d,err=%s).", what, bp->bl_bno, syserr()); } /* * This initializes the tmp file, and then replaces itself with the * "real" blkio routine. */ private void tmpinit_blkio(bp, iofcn) Block *bp; ssize_t (*iofcn)(); { char buf[FILESIZE]; register char *tmp; cache.c_fname = tmp = mktemp(copystr(make_filename(buf, TmpFilePath, d_tempfile))); close(creat(tmp, S_IRUSR|S_IWUSR)); if ((cache.c_fd = open(tmp, O_RDWR)) < 0) complain("Warning: cannot create tmp file!"); blkio = real_blkio; real_blkio(bp, iofcn); } void tmpclose() { close(cache.c_fd); unlink(cache.c_fname); cache.c_fd = -1; } private void lru_unlink __(( Block *_(bp) )); private void lru_unlink(bp) register Block *bp; { register Block *next = bp->bl_next, *prev = bp->bl_prev; if (prev) prev->bl_next = next; else cache.c_lru = next; if (next) next->bl_prev = prev; else cache.c_mru = prev; } private void hash_unlink __(( Block *_(bp) )); private void hash_unlink(bp) register Block *bp; { /* * Now that we have the block, we remove it from its position in the * hash table, so we can THEN put it somewhere else with it's new * block assignment. */ register Block **hp = &cache.c_hash[HASH(bp->bl_bno)]; for (; (*hp) != bp; hp = &(*hp)->bl_hash) if ((*hp) == NULL) { s_mess("BUG: block %d missing!", bp->bl_bno); finish(-1); } (*hp) = bp->bl_hash; } void SyncTmp() { register Block *bp; #if !(unix || vms) /* * Write all dirty blocks in block order (since we don't trust * lseek() to seek beyond end-of-file). Remember where we left * off the last time. This works because we're only modifying * blocks at the end of the temp file. */ static int left_off ZERO; register int bno; for (bno = left_off; bno <= cache.c_max_bno; bno++) { if (bp = cache.c_hash[HASH(bno)]) do { if (bp->bl_bno == bno) { if (bp->bl_dirty) { (*blkio)(bp, write); bp->bl_dirty = 0; } break; } } while (bp = bp->bl_hash); } left_off = cache.c_max_bno; #else ASSERT(cache.c_lru); bp = cache.c_lru; /* lru list is never empty (unless we botched). */ do { if (bp->bl_dirty) { (*blkio)(bp, write); bp->bl_dirty = 0; } } while (bp = bp->bl_next); #endif } /* Get a block which contains the line with the address atl. Returns a pointer to the block and sets the global variable nleft (number of good characters left in the buffer). */ private char * getblock(atl, iof) disk_line atl; { register Block *bp; register int bno = daddr2bno(atl); #if (MAXBLOCKS > 0) if (bno >= MAX_BLOCKS - 1) error("Tmp file too large. Get help!"); #endif /* * Check most recently used Block first before doing something heavy. * (assume the cache is properly initialized so it's there) */ bp = cache.c_mru; if (bp->bl_bno != bno) { for (bp = cache.c_hash[HASH(bno)]; /*empty*/; bp = bp->bl_hash) { if (bp == NULL) { /* * The block we want doesn't reside in memory so we * take the least recently used block and use it. */ bp = cache.c_lru; #ifdef DYNCACHE # ifndef MIN_FREE # define MIN_FREE sizeof(Block) # endif /* * If this block is in use, try to allocate a new * block and add it to the cache. Make sure to * leave some memory free for other purposes. */ if (bp->bl_bno >= 0) { void *p; if (p = malloc(sizeof(Block) + MIN_FREE)) { free(p); bp = (Block *) malloc(sizeof(Block)); cache_block(bp); } } #endif if (bp->bl_dirty) /* The best block is dirty... */ SyncTmp(); /* * Make sure the block is in the proper hash chain. */ if (HASH(bno) != HASH(bp->bl_bno)) { register Block **hp = &cache.c_hash[HASH(bno)]; hash_unlink(bp); bp->bl_hash = (*hp); (*hp) = bp; } bp->bl_bno = bno; /* * Get the current contents of the block UNLESS this * is a new block that's never been looked at before, * i.e., it's past the end of the tmp file. */ if (bno <= cache.c_max_bno) (*blkio)(bp, read); else cache.c_max_bno = bno; break; } else if (bp->bl_bno == bno) { if (bno > cache.c_max_bno) cache.c_max_bno = bno; break; } } /* for */ /* * Now the requested block lives in memory, and it is not the * most recently used one, so unlink it from the LRU list and * make it Most Recently Used (by moving it to the end of the * LRU list). We assume here that there are at least 2 buffers * in the cache so that `cache.c_mru' is guaranteed to be * non-NULL. */ lru_unlink(bp); cache.c_mru->bl_next = bp; /* Place it at the end ... */ bp->bl_prev = cache.c_mru; bp->bl_next = NULL; /* so it's Most Recently Used */ cache.c_mru = bp; } bp->bl_dirty |= iof; #define off bno /* alias! */ off = daddr2off(atl); nleft = BLKSIZ - off; return bp->bl_buf + off; #undef off /* unalias! */ } char * lbptr(line) const Line *line; { return getblock(line->l_dline >> 1, READ); } /* steal/return a block from/to the cache. */ Block * cache_block(block) Block *block; { register Block *bp, *bq = cache.c_lru; if (bp = block) { /* this returns a block to the cache */ ++cache.c_nblocks; /* set block number to impossible value. */ bp->bl_hash = cache.c_hash[HASH(-1)]; cache.c_hash[HASH(-1)] = bp; bp->bl_bno = -1; bp->bl_dirty = 0; bp->bl_prev = NULL; bp->bl_next = bq; bq->bl_prev = bp; cache.c_lru = bp; } else if (cache.c_nblocks > MIN_BLOCKS) { /* steal only if there is something to steal from */ --cache.c_nblocks; bp = bq; if (bp->bl_dirty) SyncTmp(); lru_unlink(bp); hash_unlink(bp); } return bp; } /* save the current contents of linebuf, if it has changed */ /* [TRH] lsave() is modified so that it remembers information to undo changes to a line the first time it is lsave()d. Remember pointer to last saved line in Undo.l_prev, and its disk address in Undo.l_dline. */ private Line Undo ZERO; void lsave() { register Line *cl; register Buffer *cb; register char *tailp; /* for OverWrite kludge */ char tailc; if (!DOLsave || (cb = curbuf) == NULL) /* no work to do */ return; /* * Don't save trailing blanks when in overwrite mode. * (this is still a kludge, but better than the old one.) */ tailc = '\0'; if (BufMinorMode(cb, OverWrite)) { tailp = &linebuf[cb->b_char]; while (*tailp++) ; --tailp; /* skip to Eol */ while (tailp > linebuf) if (!isspace(*--tailp)) { ++tailp; break; } tailc = *tailp; *tailp = '\0'; /* temporarily truncate the line. */ } DOLsave = 0; cl = cb->b_dot; if (strcmp(lbptr(cl), linebuf) != 0) { if (Undo.l_prev != cl) { Undo.l_prev = cl; Undo.l_dline = cl->l_dline; } SavLine(cl, linebuf); } if (tailc) /* restore the blank tail... */ *tailp = tailc; } /* Undo changes in most recently changed line. lsave() so that Undo info is up to date if current line has changed. Check that line to undo is in current buffer. Swap disk addresses so that the undo can be undone too. Point is left at the start of undone line. The most recently saved version of the line is used if an argument is given. The function returns if there was nothing to Undo (but generates an error message itself) */ #if 0 /* cheat; we don't want it to be a void function */ DEF_CMD( "undo-last-changed-line", UndoLine, EDIT ) #endif int UndoLine() { register Line *cl; register Buffer *cb = curbuf; /* force most recent version of line if given an argument */ if (exp_p && (cl = Undo.l_prev) == cb->b_dot) Undo.l_prev = NULL; lsave(); /* this eventually stores info in Undo */ /* restore old situation if nothing changed on force */ if (exp_p && Undo.l_prev == NULL) Undo.l_prev = cl; if (!inlist(cb->b_first, cl = Undo.l_prev)) { message("[Nothing to Undo!]"); rbell(); return NO; } { register disk_line dline = cl->l_dline; cl->l_dline = Undo.l_dline | DIRTY; Undo.l_dline = dline; } #if 0 /* now, try to do something sensible about the marks... */ { register int curlen = getline(cl->l_dline, linebuf), orglen = length(&Undo); if (curlen < orglen) DFixMarks(cl, curlen, cl, orglen); else if (curlen > orglen) IFixMarks(cl, orglen, cl, curlen); } #else getline(cl->l_dline, linebuf); #endif /* try to do something sensible with dot; place it just at the first change in the line, but leave it alone if dot was before this and we stay on the same line. */ { register int nsame = numcomp(linebuf, lbptr(&Undo)); if (cb->b_dot != cl || nsame < cb->b_char) cb->b_char = nsame; cb->b_dot = cl; } return YES; } /* Reset Undo info */ void UndoReset() { Undo.l_prev = NULL; } #ifdef BACKUPFILES DEF_STR( "backup-file-prefix", BackupPrefix, 20, V_STRING ) _IF(def BACKUPFILES) = "#"; _IF(def PRIVATE) /* * make a backup copy of the file, if it exists. * (do a physical copy so that original keeps the same i-node) * set access/modified times of backup copy to those of original. * use iobuff as intermediate buffer. * For DOS systems, do a rename since there is no I-node to preserve * (and it is faster) * Backup only if file is older than the start of this JOVE session, * so you can safely do intermediate file saves. */ private void file_backup(fname) const char *fname; { extern time_t time0; register int i = -1; register int fd1, fd2; char backup[FILESIZE]; struct stat stbuf; # ifndef rel_name fname = rel_name(fname); # endif sprintf(basename(strcpy(backup, fname)), "%s%s", BackupPrefix[0] ? BackupPrefix : "#", basename(fname)); # if !unix /* * Warning: stat() must deliver UNIX times for this to work. * I know of at least one implementation that doesn't. * (ST Turbo C -- replaced this with my own implementation) */ if ((stat(fname, &stbuf) != 0 && errno == ENOENT) || !S_ISREG(stbuf.st_mode) || stbuf.st_mtime > time0) return; /* {chmod()s are needed for systems (like MSDOS and TOS) that refuse to remove/rename read-only files.} */ chmod(backup, S_IRUSR|S_IWUSR); unlink(backup); chmod(fname, stbuf.st_mode | S_IWUSR); if (rename(fname, backup) >= 0) { chmod(backup, stbuf.st_mode); return; } # else if ((fd1 = open(fname, 0)) < 0) return; fstat(fd1, &stbuf); /* should not fail, really! */ if (stbuf.st_mtime > time0) i = 0; else if (unlink(backup), (fd2 = creat(backup, stbuf.st_mode)) >= 0) { while ((i = read(fd1, iobuff, BLKSIZ)) > 0 && write(fd2, iobuff, i) == i); # ifdef BSD4_2 fsync(fd2); # endif close(fd2); /* now cheat -- set times of backup to that of original */ # ifdef V7 /* I'm in desperate lack of code space here... */ utime(backup, &stbuf.st_atime); # else { time_t times[2]; times[0] = stbuf.st_atime; times[1] = stbuf.st_mtime; utime(backup, times); } # endif } close(fd1); # endif /* unix */ if (i != 0) confirm("\"%s\" [%s backup] %f anyway? ", backup, (i < 0) ? "Can't create" : "Incomplete"); } #endif /* BACKUPFILES */ /*====================================================================== * $Log: io.c,v $ * Revision 14.32.0.12 1994/06/23 02:46:26 tom * (chk_file): fix name of scratch buffer. * * Revision 14.32.0.3 1993/07/23 03:20:47 tom * (f_gets): ungobble char when line is truncated. * * Revision 14.32 1993/06/21 21:52:29 tom * comment fix. * * Revision 14.31.0.2 1993/03/25 21:30:48 tom * allow compile-time CRLF option on non-DOS systems. * * Revision 14.31 1993/02/16 16:24:17 tom * standardize DEBUG options; move get_proc_line() to "proc.c"; fix backup * of read-only files on DOS; remove (void) casts; lotsa random optimizations. * * Revision 14.30 1993/02/06 00:48:31 tom * cleanup whitespace; some random optimizations; move CHDIR and filename * stuff to dirs.c; allow overwrite of read-only files (if confirmed) and * file-backup in {open,close}_file() if F_SAVE flag is given; * add "append-buffer". * * Revision 14.27 1992/09/22 15:23:10 tom * improve detection of changed-behind-our-back files; cleanup empty `#' * directives; use ssize_t consistently with read/write. * * Revision 14.26 1992/08/26 23:56:54 tom * make dofread() private; PRIVATE-ized some Variable defs; add RCS directives. * */