/************************************************************************* * This program is copyright (C) 1985, 1986 by Jonathan Payne. It is * * provided to you without charge for use only on a licensed Unix * * system. You may copy JOVE provided that this notice is included with * * the copy. You may not sell copies of this program or versions * * modified for use on microcomputer systems, unless the copies are * * included with a Unix system distribution and the source is provided. * *************************************************************************/ #include "jove.h" #include "io.h" #include "termcap.h" #ifdef IPROCS # include #endif #include #include #include long io_chars; /* number of chars in this open_file */ int io_lines; /* number of lines in this open_file */ private int tellall; /* display file io info? */ #ifdef VMUNIX char iobuff[LBSIZE], genbuf[LBSIZE], linebuf[LBSIZE]; #else char *iobuff, *genbuf, *linebuf; #endif #ifdef BACKUPFILES int BkupOnWrite = 0; #endif close_file(fp) File *fp; { if (fp) { f_close(fp); if (tellall != QUIET) add_mess(" %d lines, %D characters.", 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. */ int EndWNewline = 1; putreg(fp, line1, char1, line2, char2, makesure) register File *fp; Line *line1, *line2; { register int c; register char *lp; if (makesure) (void) fixorder(&line1, &char1, &line2, &char2); while (line1 != line2->l_next) { lp = lcontents(line1) + char1; if (line1 == line2) fputnchar(lp, (char2 - char1), fp); else while (c = *lp++) { putc(c, fp); io_chars++; } if (line1 != line2) { io_lines++; io_chars++; putc('\n', fp); } line1 = line1->l_next; char1 = 0; } flush(fp); } read_file(file, is_insert) char *file; { Bufpos save; File *fp; if (!is_insert) { curbuf->b_ntbf = 0; set_ino(curbuf); } fp = open_file(file, iobuff, F_READ, !COMPLAIN, !QUIET); if (fp == NIL) { if (!is_insert && errno == ENOENT) s_mess("(new file)"); else s_mess(IOerr("open", file)); return; } DOTsave(&save); dofread(fp); SetDot(&save); if (is_insert && io_chars > 0) modify(); getDOT(); close_file(fp); } dofread(fp) register File *fp; { char end[LBSIZE]; int xeof = 0; Line *savel = curline; int savec = curchar; strcpy(end, linebuf + curchar); xeof = f_gets(fp, linebuf + curchar, LBSIZE - curchar); SavLine(curline, linebuf); if (!xeof) do { xeof = f_gets(fp, linebuf, LBSIZE); curline = listput(curbuf, curline); curline->l_dline = putline(linebuf) | DIRTY; } while (!xeof); linecopy(linebuf, (curchar = strlen(linebuf)), end); SavLine(curline, linebuf); IFixMarks(savel, savec, curline, curchar); } SaveFile() { if (IsModified(curbuf)) { if (curbuf->b_fname == 0) WriteFile(); else { filemunge(curbuf->b_fname); chk_mtime(curbuf->b_fname, "save"); file_write(curbuf->b_fname, 0); unmodify(); } } else message("No changes need to be written."); } char *HomeDir; /* home directory */ int HomeLen = -1; /* length of home directory string */ #ifndef CHDIR char * pr_name(fname) char *fname; { if (fname == 0) return 0; if (strncmp(fname, HomeDir, HomeLen) == 0) { static char name_buf[100]; sprintf(name_buf, "~%s", fname + HomeLen); return name_buf; } return fname; } #else #define NDIRS 5 private char *DirStack[NDIRS] = {0}; private int DirSP = 0; /* Directory stack pointer */ #define PWD (DirStack[DirSP]) char * pwd() { return PWD; } char * pr_name(fname) char *fname; { int n; if (fname == 0) return 0; n = numcomp(fname, PWD); if ((PWD[n] == 0) && /* Matched to end of PWD */ (fname[n] == '/')) return fname + n + 1; if (strcmp(HomeDir, "/") != 0 && strncmp(fname, HomeDir, HomeLen) == 0) { static char name_buf[100]; sprintf(name_buf, "~%s", fname + HomeLen); return name_buf; } return fname; /* return entire path name */ } Chdir() { char dirbuf[FILESIZE]; (void) ask_file(PWD, dirbuf); if (chdir(dirbuf) == -1) { s_mess("cd: cannot change into %s.", dirbuf); return; } UpdModLine++; setCWD(dirbuf); } #ifndef JOB_CONTROL char * getwd() { Buffer *old = curbuf; char *ret_val; SetBuf(do_select((Window *) 0, "pwd-output")); curbuf->b_type = B_PROCESS; (void) UnixToBuf("pwd-output", NO, 0, YES, "/bin/pwd", "pwd", 0); ToFirst(); ret_val = sprint(linebuf); SetBuf(old); return ret_val; } #endif setCWD(d) char *d; { if (PWD == 0) PWD = malloc((unsigned) strlen(d) + 1); else { extern char *ralloc(); PWD = ralloc(PWD, strlen(d) + 1); } strcpy(PWD, d); } getCWD() { char *cwd = getenv("CWD"); #ifdef JOB_CONTROL extern char *getwd(); char pathname[FILESIZE]; #endif if (cwd == 0) #ifdef JOB_CONTROL cwd = getwd(pathname); #else cwd = getwd(); #endif setCWD(cwd); } prDIRS() { register int i; s_mess(": %f "); for (i = DirSP; i >= 0; i--) add_mess("%s ", pr_name(DirStack[i])); } prCWD() { s_mess(": %f => \"%s\"", PWD); } Pushd() { char *newdir, dirbuf[FILESIZE]; newdir = ask_file(NullStr, dirbuf); /* Parses directories ... */ UpdModLine++; if (*newdir == 0) { /* Wants to swap top two entries */ char *old_top; if (DirSP == 0) complain("pushd: no other directory."); old_top = PWD; DirStack[DirSP] = DirStack[DirSP - 1]; DirStack[DirSP - 1] = old_top; (void) chdir(PWD); } else { if (chdir(dirbuf) == -1) { s_mess("pushd: cannot change into %s.", dirbuf); return; } if (DirSP + 1 >= NDIRS) complain("pushd: full stack; max of %d pushes.", NDIRS); DirSP++; setCWD(dirbuf); } prDIRS(); } Popd() { if (DirSP == 0) complain("popd: directory stack is empty."); UpdModLine++; free(PWD); PWD = 0; DirSP--; (void) chdir(PWD); /* If this doesn't work, we's in deep shit. */ prDIRS(); } private char * dbackup(base, offset, c) register char *base, *offset, c; { while (offset > base && *--offset != c) ; return offset; } dfollow(file, into) char *file, *into; { char *dp, *sp; if (*file == '/') { /* Absolute pathname */ strcpy(into, "/"); file++; } else strcpy(into, PWD); dp = into + strlen(into); sp = file; do { if (*file == 0) break; if (sp = index(file, '/')) *sp = 0; if (strcmp(file, ".") == 0) ; /* So it will get to the end of the loop */ else if (strcmp(file, "..") == 0) { *(dp = dbackup(into, dp, '/')) = 0; if (dp == into) strcpy(into, "/"), dp = into + 1; } else { if (into[strlen(into) - 1] != '/') (void) strcat(into, "/"); (void) strcat(into, file); dp += strlen(file); /* stay at the end */ } file = sp + 1; } while (sp != 0); } #endif CHDIR get_hdir(user, buf) register char *user, *buf; { char fbuf[LBSIZE], pattern[100]; register int u_len; File *fp; u_len = strlen(user); fp = open_file("/etc/passwd", fbuf, F_READ, COMPLAIN, QUIET); sprintf(pattern, "%s:[^:]*:[^:]*:[^:]*:[^:]*:\\([^:]*\\):", user); while (f_gets(fp, genbuf, LBSIZE) != EOF) if ((strncmp(genbuf, user, u_len) == 0) && (LookingAt(pattern, genbuf, 0))) { putmatch(1, buf, FILESIZE); close_file(fp); return; } f_close(fp); complain("[unknown user: %s]", user); } PathParse(name, intobuf) char *name, *intobuf; { char localbuf[FILESIZE]; intobuf[0] = localbuf[0] = '\0'; if (*name == '\0') return; if (*name == '~') { if (name[1] == '/' || name[1] == '\0') { strcpy(localbuf, HomeDir); name++; } else { char *uendp = index(name, '/'), unamebuf[30]; if (uendp == 0) uendp = name + strlen(name); name = name + 1; null_ncpy(unamebuf, name, uendp - name); get_hdir(unamebuf, localbuf); name = uendp; } } else if (*name == '\\') name++; (void) strcat(localbuf, name); #ifdef CHDIR dfollow(localbuf, intobuf); #else strcpy(intobuf, localbuf); #endif } filemunge(newname) char *newname; { struct stat stbuf; if (newname == 0) return; if (stat(newname, &stbuf)) return; if ((stbuf.st_ino != curbuf->b_ino) && ((stbuf.st_mode & S_IFMT) != S_IFCHR) && (strcmp(newname, curbuf->b_fname) != 0)) { rbell(); confirm("\"%s\" already exists; overwrite it? ", newname); } } WrtReg() { DoWriteReg(0); } AppReg() { DoWriteReg(1); } int CreatMode = DFLT_MODE; DoWriteReg(app) { char fnamebuf[FILESIZE], *fname; Mark *mp = CurMark(); File *fp; /* Won't get here if there isn't a Mark */ fname = ask_file((char *) 0, fnamebuf); #ifdef BACKUPFILES if (!app) { filemunge(fname); if (BkupOnWrite) file_backup(fname); } #else if (!app) filemunge(fname); #endif fp = open_file(fname, iobuff, app ? F_APPEND : F_WRITE, COMPLAIN, !QUIET); putreg(fp, mp->m_line, mp->m_char, curline, curchar, YES); close_file(fp); } int OkayBadChars = 0; WriteFile() { char *fname, fnamebuf[FILESIZE]; fname = ask_file(curbuf->b_fname, fnamebuf); /* Don't allow bad characters when creating new files. */ if (!OkayBadChars && strcmp(curbuf->b_fname, fnamebuf) != 0) { static char *badchars = "!$^&*()~`{}\"'\\|<>? "; register char *cp = fnamebuf; register int c; while (c = *cp++) if (c < ' ' || c == '\177' || index(badchars, c)) complain("'%p': bad character in filename.", c); } chk_mtime(fname, "write"); filemunge(fname); if (curbuf->b_type != B_IPROCESS) curbuf->b_type = B_FILE; /* In case it wasn't before. */ setfname(curbuf, fname); file_write(fname, 0); unmodify(); } File * open_file(fname, buf, how, ifbad, loudness) register char *fname; char *buf; register int how; { register File *fp; io_chars = 0; io_lines = 0; tellall = loudness; fp = f_open(fname, how, buf, LBSIZE); if (fp == NIL) { message(IOerr((how == F_READ) ? "open" : "create", fname)); if (ifbad == COMPLAIN) complain((char *) 0); } else { int readonly = FALSE; if (access(fname, W_OK) == -1 && errno != ENOENT) readonly = TRUE; if (loudness != QUIET) f_mess("\"%s\"%s", pr_name(fname), readonly ? " [Read only]" : NullStr); } return fp; } /* Check to see if the file has been modified since it was last written. If so, make sure they know what they're doing. I hate to use another stat(), but to use confirm we gotta do this before we open the file. */ chk_mtime(fname, how) char *fname, *how; { struct stat stbuf; Buffer *b; char *mesg = "Shall I go ahead and %s anyway? "; if ((curbuf->b_mtime != 0) && /* if we care ... */ (b = file_exists(fname)) && /* we already have this file */ (b == curbuf) && /* and it's the current buffer */ (stat(fname, &stbuf) != -1) && /* and we can stat it */ (stbuf.st_mtime != b->b_mtime)) { /* 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. Type \"y\" if I should"); Typeout("%s anyway.", how); f_mess(mesg, how); TOstop(); confirm(mesg, how); } } file_write(fname, app) char *fname; { File *fp; #ifdef BACKUPFILES if (!app && BkupOnWrite) file_backup(fname); #endif fp = open_file(fname, iobuff, app ? F_APPEND : F_WRITE, COMPLAIN, !QUIET); if (EndWNewline) { /* Make sure file ends with a newLine */ Bufpos save; DOTsave(&save); ToLast(); if (length(curline)) /* Not a blank Line */ DoTimes(LineInsert(), 1); /* Make it blank */ SetDot(&save); } putreg(fp, curbuf->b_first, 0, curbuf->b_last, length(curbuf->b_last), NO); set_ino(curbuf); close_file(fp); } ReadFile() { char *fname, fnamebuf[FILESIZE]; fname = ask_file(curbuf->b_fname, fnamebuf); chk_mtime(fname, "read"); if (IsModified(curbuf)) { char *y_or_n; int c; for (;;) { rbell(); y_or_n = ask(NullStr, "Shall I make your changes to \"%s\" permanent? ", curbuf->b_name); c = Upper(*y_or_n); if (c == 'Y' || c == 'N') break; } if (c == 'Y') SaveFile(); } unmodify(); initlist(curbuf); setfname(curbuf, fname); read_file(fname, 0); } InsFile() { char *fname, fnamebuf[FILESIZE]; fname = ask_file(curbuf->b_fname, fnamebuf); read_file(fname, 1); } #include "temp.h" int DOLsave = 0; /* 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. */ int nleft, /* Number of good characters left in current block */ tmpfd; disk_line tline; /* Pointer to end of tmp file */ char *tfname; tmpinit() { tfname = mktemp(TMPFILE); (void) close(creat(tfname, 0600)); tmpfd = open(tfname, 2); if (tmpfd == -1) { printf("%s?\n", tfname); finish(0); } block_init(); tline = 2; } tmpclose() { (void) close(tmpfd); tmpfd = -1; (void) unlink(tfname); } /* Get a line at `tl' in the tmp file into `buf' which should be LBSIZE long. */ int Jr_Len; /* Length of Just Read Line. */ char * getline(tl, buf) disk_line tl; char *buf; { register char *bp, *lp; register int nl; lp = buf; bp = getblock(tl, READ); nl = nleft; tl &= ~OFFMSK; while (*lp++ = *bp++) { if (--nl == 0) { /* += INCRMT moves tl to the next block in the tmp file. */ bp = getblock(tl += INCRMT, READ); nl = nleft; } } Jr_Len = (lp - buf) - 1; return buf; } /* Put `buf' and return the disk address */ int nretries = 0; disk_line putline(buf) char *buf; { register char *bp, *lp; register int nl; disk_line tl; lp = buf; tl = tline; bp = getblock(tl, WRITE); nl = nleft; tl &= ~OFFMSK; while (*bp = *lp++) { if (*bp++ == '\n') { *--bp = 0; break; } if (--nl == 0) { tline = (tl += INCRMT); bp = getblock(tl, WRITE); lp = buf; /* start over ... */ nretries++; nl = nleft; } } tl = tline; tline += (((lp - buf) + BNDRY - 1) >> SHFT) & 077776; return tl; } typedef struct block { short b_dirty, b_bno; char b_buf[BUFSIZ]; struct block *b_LRUnext, *b_LRUprev, *b_HASHnext; } Block; #define HASHSIZE 7 /* Primes work best (so I'm told) */ #define B_HASH(bno) (bno % HASHSIZE) private Block b_cache[NBUF], *bht[HASHSIZE] = {0}, /* Block hash table */ *f_block = 0, *l_block = 0; private int max_bno = -1, NBlocks; private block_init() { register Block *bp, /* Block pointer */ **hp; /* Hash pointer */ register short bno; for (bp = b_cache, bno = NBUF; --bno >= 0; bp++) { NBlocks++; bp->b_dirty = 0; bp->b_bno = bno; if (l_block == 0) l_block = bp; bp->b_LRUprev = 0; bp->b_LRUnext = f_block; if (f_block != 0) f_block->b_LRUprev = bp; f_block = bp; bp->b_HASHnext = *(hp = &bht[B_HASH(bno)]); *hp = bp; } } private Block * lookup(bno) register short bno; { register Block *bp; for (bp = bht[B_HASH(bno)]; bp != 0; bp = bp->b_HASHnext) if (bp->b_bno == bno) break; return bp; } private LRUunlink(b) register Block *b; { if (b->b_LRUprev == 0) f_block = b->b_LRUnext; else b->b_LRUprev->b_LRUnext = b->b_LRUnext; if (b->b_LRUnext == 0) l_block = b->b_LRUprev; else b->b_LRUnext->b_LRUprev = b->b_LRUprev; } private Block * b_unlink(bp) register Block *bp; { register Block *hp, *prev = 0; LRUunlink(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. */ for (hp = bht[B_HASH(bp->b_bno)]; hp != 0; prev = hp, hp = hp->b_HASHnext) if (hp == bp) break; if (hp == 0) { printf("\rBlock %d missing!", bp->b_bno); finish(0); } if (prev) prev->b_HASHnext = hp->b_HASHnext; else bht[B_HASH(bp->b_bno)] = hp->b_HASHnext; if (bp->b_dirty) { /* Do, now, the delayed write */ blkio(bp, write); bp->b_dirty = 0; } return bp; } /* Get a block which contains at least part of 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). */ char * getblock(atl, iof) disk_line atl; { register int bno, off; register Block *bp; static Block *lastb = 0; bno = (atl >> OFFBTS) & BLKMSK; off = (atl << SHFT) & LBTMSK; if (bno >= NMBLKS) error("Tmp file too large. Get help!"); nleft = BUFSIZ - off; if (lastb != 0 && lastb->b_bno == bno) return lastb->b_buf + off; /* The requested block already lives in memory, so we move it to the end of the LRU list (making it Most Recently Used) and then return a pointer to it. */ if (bp = lookup(bno)) { if (bp != l_block) { LRUunlink(bp); if (l_block == 0) f_block = l_block = bp; else l_block->b_LRUnext = bp; bp->b_LRUprev = l_block; l_block = bp; bp->b_LRUnext = 0; } if (bp->b_bno > max_bno) max_bno = bp->b_bno; bp->b_dirty |= iof; lastb = bp; return bp->b_buf + off; } /* The block we want doesn't reside in memory so we take the least recently used clean block (if there is one) and use it. */ bp = f_block; if (bp->b_dirty) /* The best block is dirty ... */ SyncTmp(); bp = b_unlink(bp); if (l_block == 0) l_block = f_block = bp; else l_block->b_LRUnext = bp; /* Place it at the end ... */ bp->b_LRUprev = l_block; l_block = bp; bp->b_LRUnext = 0; /* so it's Most Recently Used */ bp->b_dirty = iof; bp->b_bno = bno; bp->b_HASHnext = bht[B_HASH(bno)]; bht[B_HASH(bno)] = bp; /* 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 (bp->b_bno <= max_bno) blkio(bp, read); else max_bno = bno; lastb = bp; return bp->b_buf + off; } char * lbptr(line) Line *line; { return getblock(line->l_dline, READ); } private blkio(b, iofcn) register Block *b; register int (*iofcn)(); { (void) lseek(tmpfd, (long) ((unsigned) b->b_bno) * BUFSIZ, 0); if ((*iofcn)(tmpfd, b->b_buf, BUFSIZ) != BUFSIZ) error("Tmp file %s error.", (iofcn == read) ? "read" : "write"); } SyncTmp() { register Block *b; for (b = f_block; b != 0; b = b->b_LRUnext) if (b->b_dirty) { blkio(b, write); b->b_dirty = 0; } } /* save the current contents of linebuf, if it has changed */ lsave() { if (curbuf == 0 || !DOLsave) /* Nothing modified recently */ return; if (strcmp(lbptr(curline), linebuf) != 0) SavLine(curline, linebuf); /* Put linebuf on the disk. */ DOLsave = 0; } #ifdef BACKUPFILES file_backup(fname) char *fname; { char *s; register int i; int fd1, fd2; char tmp1[BUFSIZ], tmp2[BUFSIZ]; strcpy(tmp1, fname); if ((s = rindex(tmp1, '/')) == NULL) sprintf(tmp2, "#%s", fname); else { *s++ = NULL; sprintf(tmp2, "%s/#%s", tmp1, s); } if ((fd1 = open(fname, 0)) < 0) return; if ((fd2 = creat(tmp2, CreatMode)) < 0) { (void) close(fd1); return; } while ((i = read(fd1, tmp1, sizeof(tmp1))) > 0) write(fd2, tmp1, i); #ifdef BSD4_2 (void) fsync(fd2); #endif (void) close(fd2); (void) close(fd1); } #endif