Index: stable/9/contrib/nvi/common/exf.c =================================================================== --- stable/9/contrib/nvi/common/exf.c (revision 290040) +++ stable/9/contrib/nvi/common/exf.c (revision 290041) @@ -1,1539 +1,1539 @@ /*- * Copyright (c) 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1992, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. * */ #include "config.h" #ifndef lint #if 0 static const char sccsid[] = "@(#)exf.c 10.49 (Berkeley) 10/10/96"; #endif static const char rcsid[] = "$FreeBSD$"; #endif /* not lint */ #include #include /* XXX: param.h may not have included types.h */ #include #include /* * We include , because the flock(2) and open(2) #defines * were found there on historical systems. We also include * because the open(2) #defines are found there on newer systems. */ #include #include #include #include #include #include #include #include #include #include #include "common.h" static int file_backup __P((SCR *, char *, char *)); static void file_cinit __P((SCR *)); static void file_comment __P((SCR *)); static int file_spath __P((SCR *, FREF *, struct stat *, int *)); /* * file_add -- * Insert a file name into the FREF list, if it doesn't already * appear in it. * * !!! * The "if it doesn't already appear" changes vi's semantics slightly. If * you do a "vi foo bar", and then execute "next bar baz", the edit of bar * will reflect the line/column of the previous edit session. Historic nvi * did not do this. The change is a logical extension of the change where * vi now remembers the last location in any file that it has ever edited, * not just the previously edited file. * * PUBLIC: FREF *file_add __P((SCR *, CHAR_T *)); */ FREF * file_add(sp, name) SCR *sp; CHAR_T *name; { GS *gp; FREF *frp, *tfrp; /* * Return it if it already exists. Note that we test against the * user's name, whatever that happens to be, including if it's a * temporary file. * * If the user added a file but was unable to initialize it, there * can be file list entries where the name field is NULL. Discard * them the next time we see them. */ gp = sp->gp; if (name != NULL) for (frp = gp->frefq.cqh_first; frp != (FREF *)&gp->frefq; frp = frp->q.cqe_next) { if (frp->name == NULL) { tfrp = frp->q.cqe_next; CIRCLEQ_REMOVE(&gp->frefq, frp, q); if (frp->name != NULL) free(frp->name); free(frp); frp = tfrp; continue; } if (!strcmp(frp->name, name)) return (frp); } /* Allocate and initialize the FREF structure. */ CALLOC(sp, frp, FREF *, 1, sizeof(FREF)); if (frp == NULL) return (NULL); /* * If no file name specified, or if the file name is a request * for something temporary, file_init() will allocate the file * name. Temporary files are always ignored. */ if (name != NULL && strcmp(name, TEMPORARY_FILE_STRING) && (frp->name = strdup(name)) == NULL) { free(frp); msgq(sp, M_SYSERR, NULL); return (NULL); } /* Append into the chain of file names. */ CIRCLEQ_INSERT_TAIL(&gp->frefq, frp, q); return (frp); } /* * file_init -- * Start editing a file, based on the FREF structure. If successsful, * let go of any previous file. Don't release the previous file until * absolutely sure we have the new one. * * PUBLIC: int file_init __P((SCR *, FREF *, char *, int)); */ int file_init(sp, frp, rcv_name, flags) SCR *sp; FREF *frp; char *rcv_name; int flags; { EXF *ep; RECNOINFO oinfo; struct stat sb; size_t psize; int fd, exists, open_err, readonly; char *oname, tname[MAXPATHLEN]; open_err = readonly = 0; /* * If the file is a recovery file, let the recovery code handle it. * Clear the FR_RECOVER flag first -- the recovery code does set up, * and then calls us! If the recovery call fails, it's probably * because the named file doesn't exist. So, move boldly forward, * presuming that there's an error message the user will get to see. */ if (F_ISSET(frp, FR_RECOVER)) { F_CLR(frp, FR_RECOVER); return (rcv_read(sp, frp)); } /* * Required FRP initialization; the only flag we keep is the * cursor information. */ F_CLR(frp, ~FR_CURSORSET); /* * Required EXF initialization: * Flush the line caches. * Default recover mail file fd to -1. * Set initial EXF flag bits. */ CALLOC_RET(sp, ep, EXF *, 1, sizeof(EXF)); ep->c_lno = ep->c_nlines = OOBLNO; ep->rcv_fd = ep->fcntl_fd = -1; F_SET(ep, F_FIRSTMODIFY); /* * Scan the user's path to find the file that we're going to * try and open. */ if (file_spath(sp, frp, &sb, &exists)) return (1); /* * If no name or backing file, for whatever reason, create a backing * temporary file, saving the temp file name so we can later unlink * it. If the user never named this file, copy the temporary file name * to the real name (we display that until the user renames it). */ oname = frp->name; if (LF_ISSET(FS_OPENERR) || oname == NULL || !exists) { - if (opts_empty(sp, O_DIRECTORY, 0)) + if (opts_empty(sp, O_TMP_DIRECTORY, 0)) goto err; (void)snprintf(tname, sizeof(tname), - "%s/vi.XXXXXXXXXX", O_STR(sp, O_DIRECTORY)); + "%s/vi.XXXXXXXXXX", O_STR(sp, O_TMP_DIRECTORY)); if ((fd = mkstemp(tname)) == -1) { msgq(sp, M_SYSERR, "237|Unable to create temporary file"); goto err; } (void)close(fd); if (frp->name == NULL) F_SET(frp, FR_TMPFILE); if ((frp->tname = strdup(tname)) == NULL || frp->name == NULL && (frp->name = strdup(tname)) == NULL) { if (frp->tname != NULL) free(frp->tname); msgq(sp, M_SYSERR, NULL); (void)unlink(tname); goto err; } oname = frp->tname; psize = 1024; if (!LF_ISSET(FS_OPENERR)) F_SET(frp, FR_NEWFILE); time(&ep->mtime); } else { /* * XXX * A seat of the pants calculation: try to keep the file in * 15 pages or less. Don't use a page size larger than 10K * (vi should have good locality) or smaller than 1K. */ psize = ((sb.st_size / 15) + 1023) / 1024; if (psize > 10) psize = 10; if (psize == 0) psize = 1; psize *= 1024; F_SET(ep, F_DEVSET); ep->mdev = sb.st_dev; ep->minode = sb.st_ino; ep->mtime = sb.st_mtime; if (!S_ISREG(sb.st_mode)) msgq_str(sp, M_ERR, oname, "238|Warning: %s is not a regular file"); } /* Set up recovery. */ memset(&oinfo, 0, sizeof(RECNOINFO)); oinfo.bval = '\n'; /* Always set. */ oinfo.psize = psize; oinfo.flags = F_ISSET(sp->gp, G_SNAPSHOT) ? R_SNAPSHOT : 0; if (rcv_name == NULL) { if (!rcv_tmp(sp, ep, frp->name)) oinfo.bfname = ep->rcv_path; } else { if ((ep->rcv_path = strdup(rcv_name)) == NULL) { msgq(sp, M_SYSERR, NULL); goto err; } oinfo.bfname = ep->rcv_path; F_SET(ep, F_MODIFIED); } /* Open a db structure. */ if ((ep->db = dbopen(rcv_name == NULL ? oname : NULL, O_NONBLOCK | O_RDONLY, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH, DB_RECNO, &oinfo)) == NULL) { msgq_str(sp, M_SYSERR, rcv_name == NULL ? oname : rcv_name, "%s"); /* * !!! * Historically, vi permitted users to edit files that couldn't * be read. This isn't useful for single files from a command * line, but it's quite useful for "vi *.c", since you can skip * past files that you can't read. */ open_err = 1; goto oerr; } /* * Do the remaining things that can cause failure of the new file, * mark and logging initialization. */ if (mark_init(sp, ep) || log_init(sp, ep)) goto err; /* * Set the alternate file name to be the file we're discarding. * * !!! * Temporary files can't become alternate files, so there's no file * name. This matches historical practice, although it could only * happen in historical vi as the result of the initial command, i.e. * if vi was executed without a file name. */ if (LF_ISSET(FS_SETALT)) set_alt_name(sp, sp->frp == NULL || F_ISSET(sp->frp, FR_TMPFILE) ? NULL : sp->frp->name); /* * Close the previous file; if that fails, close the new one and run * for the border. * * !!! * There's a nasty special case. If the user edits a temporary file, * and then does an ":e! %", we need to re-initialize the backing * file, but we can't change the name. (It's worse -- we're dealing * with *names* here, we can't even detect that it happened.) Set a * flag so that the file_end routine ignores the backing information * of the old file if it happens to be the same as the new one. * * !!! * Side-effect: after the call to file_end(), sp->frp may be NULL. */ if (sp->ep != NULL) { F_SET(frp, FR_DONTDELETE); if (file_end(sp, NULL, LF_ISSET(FS_FORCE))) { (void)file_end(sp, ep, 1); goto err; } F_CLR(frp, FR_DONTDELETE); } /* * Lock the file; if it's a recovery file, it should already be * locked. Note, we acquire the lock after the previous file * has been ended, so that we don't get an "already locked" error * for ":edit!". * * XXX * While the user can't interrupt us between the open and here, * there's a race between the dbopen() and the lock. Not much * we can do about it. * * XXX * We don't make a big deal of not being able to lock the file. As * locking rarely works over NFS, and often fails if the file was * mmap(2)'d, it's far too common to do anything like print an error * message, let alone make the file readonly. At some future time, * when locking is a little more reliable, this should change to be * an error. */ if (rcv_name == NULL) switch (file_lock(sp, oname, &ep->fcntl_fd, ep->db->fd(ep->db), 0)) { case LOCK_FAILED: F_SET(frp, FR_UNLOCKED); break; case LOCK_UNAVAIL: readonly = 1; msgq_str(sp, M_INFO, oname, "239|%s already locked, session is read-only"); break; case LOCK_SUCCESS: break; } /* * Historically, the readonly edit option was set per edit buffer in * vi, unless the -R command-line option was specified or the program * was executed as "view". (Well, to be truthful, if the letter 'w' * occurred anywhere in the program name, but let's not get into that.) * So, the persistant readonly state has to be stored in the screen * structure, and the edit option value toggles with the contents of * the edit buffer. If the persistant readonly flag is set, set the * readonly edit option. * * Otherwise, try and figure out if a file is readonly. This is a * dangerous thing to do. The kernel is the only arbiter of whether * or not a file is writeable, and the best that a user program can * do is guess. Obvious loopholes are files that are on a file system * mounted readonly (access catches this one on a few systems), or * alternate protection mechanisms, ACL's for example, that we can't * portably check. Lots of fun, and only here because users whined. * * !!! * Historic vi displayed the readonly message if none of the file * write bits were set, or if an an access(2) call on the path * failed. This seems reasonable. If the file is mode 444, root * users may want to know that the owner of the file did not expect * it to be written. * * Historic vi set the readonly bit if no write bits were set for * a file, even if the access call would have succeeded. This makes * the superuser force the write even when vi expects that it will * succeed. I'm less supportive of this semantic, but it's historic * practice and the conservative approach to vi'ing files as root. * * It would be nice if there was some way to update this when the user * does a "^Z; chmod ...". The problem is that we'd first have to * distinguish between readonly bits set because of file permissions * and those set for other reasons. That's not too hard, but deciding * when to reevaluate the permissions is trickier. An alternative * might be to turn off the readonly bit if the user forces a write * and it succeeds. * * XXX * Access(2) doesn't consider the effective uid/gid values. This * probably isn't a problem for vi when it's running standalone. */ if (readonly || F_ISSET(sp, SC_READONLY) || !F_ISSET(frp, FR_NEWFILE) && (!(sb.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH)) || access(frp->name, W_OK))) O_SET(sp, O_READONLY); else O_CLR(sp, O_READONLY); /* Switch... */ ++ep->refcnt; sp->ep = ep; sp->frp = frp; /* Set the initial cursor position, queue initial command. */ file_cinit(sp); /* Redraw the screen from scratch, schedule a welcome message. */ F_SET(sp, SC_SCR_REFORMAT | SC_STATUS); return (0); err: if (frp->name != NULL) { free(frp->name); frp->name = NULL; } if (frp->tname != NULL) { (void)unlink(frp->tname); free(frp->tname); frp->tname = NULL; } oerr: if (F_ISSET(ep, F_RCV_ON)) (void)unlink(ep->rcv_path); if (ep->rcv_path != NULL) { free(ep->rcv_path); ep->rcv_path = NULL; } if (ep->db != NULL) (void)ep->db->close(ep->db); free(ep); return (open_err ? file_init(sp, frp, rcv_name, flags | FS_OPENERR) : 1); } /* * file_spath -- * Scan the user's path to find the file that we're going to * try and open. */ static int file_spath(sp, frp, sbp, existsp) SCR *sp; FREF *frp; struct stat *sbp; int *existsp; { CHAR_T savech; size_t len; int found; char *name, *p, *t, path[MAXPATHLEN]; /* * If the name is NULL or an explicit reference (i.e., the first * component is . or ..) ignore the O_PATH option. */ name = frp->name; if (name == NULL) { *existsp = 0; return (0); } if (name[0] == '/' || name[0] == '.' && (name[1] == '/' || name[1] == '.' && name[2] == '/')) { *existsp = !stat(name, sbp); return (0); } /* Try . */ if (!stat(name, sbp)) { *existsp = 1; return (0); } /* Try the O_PATH option values. */ for (found = 0, p = t = O_STR(sp, O_PATH);; ++p) if (*p == ':' || *p == '\0') { if (t < p - 1) { savech = *p; *p = '\0'; len = snprintf(path, sizeof(path), "%s/%s", t, name); *p = savech; if (!stat(path, sbp)) { found = 1; break; } } t = p + 1; if (*p == '\0') break; } /* If we found it, build a new pathname and discard the old one. */ if (found) { MALLOC_RET(sp, p, char *, len + 1); memcpy(p, path, len + 1); free(frp->name); frp->name = p; } *existsp = found; return (0); } /* * file_cinit -- * Set up the initial cursor position. */ static void file_cinit(sp) SCR *sp; { GS *gp; MARK m; size_t len; int nb; /* Set some basic defaults. */ sp->lno = 1; sp->cno = 0; /* * Historically, initial commands (the -c option) weren't executed * until a file was loaded, e.g. "vi +10 nofile", followed by an * :edit or :tag command, would execute the +10 on the file loaded * by the subsequent command, (assuming that it existed). This * applied as well to files loaded using the tag commands, and we * follow that historic practice. Also, all initial commands were * ex commands and were always executed on the last line of the file. * * Otherwise, if no initial command for this file: * If in ex mode, move to the last line, first nonblank character. * If the file has previously been edited, move to the last known * position, and check it for validity. * Otherwise, move to the first line, first nonblank. * * This gets called by the file init code, because we may be in a * file of ex commands and we want to execute them from the right * location in the file. */ nb = 0; gp = sp->gp; if (gp->c_option != NULL && !F_ISSET(sp->frp, FR_NEWFILE)) { if (db_last(sp, &sp->lno)) return; if (sp->lno == 0) { sp->lno = 1; sp->cno = 0; } if (ex_run_str(sp, "-c option", gp->c_option, strlen(gp->c_option), 1, 1)) return; gp->c_option = NULL; } else if (F_ISSET(sp, SC_EX)) { if (db_last(sp, &sp->lno)) return; if (sp->lno == 0) { sp->lno = 1; sp->cno = 0; return; } nb = 1; } else { if (F_ISSET(sp->frp, FR_CURSORSET)) { sp->lno = sp->frp->lno; sp->cno = sp->frp->cno; /* If returning to a file in vi, center the line. */ F_SET(sp, SC_SCR_CENTER); } else { if (O_ISSET(sp, O_COMMENT)) file_comment(sp); else sp->lno = 1; nb = 1; } if (db_get(sp, sp->lno, 0, NULL, &len)) { sp->lno = 1; sp->cno = 0; return; } if (!nb && sp->cno > len) nb = 1; } if (nb) { sp->cno = 0; (void)nonblank(sp, sp->lno, &sp->cno); } /* * !!! * The initial column is also the most attractive column. */ sp->rcm = sp->cno; /* * !!! * Historically, vi initialized the absolute mark, but ex did not. * Which meant, that if the first command in ex mode was "visual", * or if an ex command was executed first (e.g. vi +10 file) vi was * entered without the mark being initialized. For consistency, if * the file isn't empty, we initialize it for everyone, believing * that it can't hurt, and is generally useful. Not initializing it * if the file is empty is historic practice, although it has always * been possible to set (and use) marks in empty vi files. */ m.lno = sp->lno; m.cno = sp->cno; (void)mark_set(sp, ABSMARK1, &m, 0); } /* * file_end -- * Stop editing a file. * * PUBLIC: int file_end __P((SCR *, EXF *, int)); */ int file_end(sp, ep, force) SCR *sp; EXF *ep; int force; { FREF *frp; /* * !!! * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER. * (If argument ep is NULL, use sp->ep.) * * If multiply referenced, just decrement the count and return. */ if (ep == NULL) ep = sp->ep; if (--ep->refcnt != 0) return (0); /* * * Clean up the FREF structure. * * Save the cursor location. * * XXX * It would be cleaner to do this somewhere else, but by the time * ex or vi knows that we're changing files it's already happened. */ frp = sp->frp; frp->lno = sp->lno; frp->cno = sp->cno; F_SET(frp, FR_CURSORSET); /* * We may no longer need the temporary backing file, so clean it * up. We don't need the FREF structure either, if the file was * never named, so lose it. * * !!! * Re: FR_DONTDELETE, see the comment above in file_init(). */ if (!F_ISSET(frp, FR_DONTDELETE) && frp->tname != NULL) { if (unlink(frp->tname)) msgq_str(sp, M_SYSERR, frp->tname, "240|%s: remove"); free(frp->tname); frp->tname = NULL; if (F_ISSET(frp, FR_TMPFILE)) { CIRCLEQ_REMOVE(&sp->gp->frefq, frp, q); if (frp->name != NULL) free(frp->name); free(frp); } sp->frp = NULL; } /* * Clean up the EXF structure. * * Close the db structure. */ if (ep->db->close != NULL && ep->db->close(ep->db) && !force) { msgq_str(sp, M_SYSERR, frp->name, "241|%s: close"); ++ep->refcnt; return (1); } /* COMMITTED TO THE CLOSE. THERE'S NO GOING BACK... */ /* Stop logging. */ (void)log_end(sp, ep); /* Free up any marks. */ (void)mark_end(sp, ep); /* * Delete recovery files, close the open descriptor, free recovery * memory. See recover.c for a description of the protocol. * * XXX * Unlink backup file first, we can detect that the recovery file * doesn't reference anything when the user tries to recover it. * There's a race, here, obviously, but it's fairly small. */ if (!F_ISSET(ep, F_RCV_NORM)) { if (ep->rcv_path != NULL && unlink(ep->rcv_path)) msgq_str(sp, M_SYSERR, ep->rcv_path, "242|%s: remove"); if (ep->rcv_mpath != NULL && unlink(ep->rcv_mpath)) msgq_str(sp, M_SYSERR, ep->rcv_mpath, "243|%s: remove"); } if (ep->fcntl_fd != -1) (void)close(ep->fcntl_fd); if (ep->rcv_fd != -1) (void)close(ep->rcv_fd); if (ep->rcv_path != NULL) free(ep->rcv_path); if (ep->rcv_mpath != NULL) free(ep->rcv_mpath); free(ep); return (0); } /* * file_write -- * Write the file to disk. Historic vi had fairly convoluted * semantics for whether or not writes would happen. That's * why all the flags. * * PUBLIC: int file_write __P((SCR *, MARK *, MARK *, char *, int)); */ int file_write(sp, fm, tm, name, flags) SCR *sp; MARK *fm, *tm; char *name; int flags; { enum { NEWFILE, OLDFILE } mtype; struct stat sb; EXF *ep; FILE *fp; FREF *frp; MARK from, to; size_t len; u_long nlno, nch; int fd, nf, noname, oflags, rval; char *p, *s, *t, buf[MAXPATHLEN + 64]; const char *msgstr; ep = sp->ep; frp = sp->frp; /* * Writing '%', or naming the current file explicitly, has the * same semantics as writing without a name. */ if (name == NULL || !strcmp(name, frp->name)) { noname = 1; name = frp->name; } else noname = 0; /* Can't write files marked read-only, unless forced. */ if (!LF_ISSET(FS_FORCE) && noname && O_ISSET(sp, O_READONLY)) { msgq(sp, M_ERR, LF_ISSET(FS_POSSIBLE) ? "244|Read-only file, not written; use ! to override" : "245|Read-only file, not written"); return (1); } /* If not forced, not appending, and "writeany" not set ... */ if (!LF_ISSET(FS_FORCE | FS_APPEND) && !O_ISSET(sp, O_WRITEANY)) { /* Don't overwrite anything but the original file. */ if ((!noname || F_ISSET(frp, FR_NAMECHANGE)) && !stat(name, &sb)) { msgq_str(sp, M_ERR, name, LF_ISSET(FS_POSSIBLE) ? "246|%s exists, not written; use ! to override" : "247|%s exists, not written"); return (1); } /* * Don't write part of any existing file. Only test for the * original file, the previous test catches anything else. */ if (!LF_ISSET(FS_ALL) && noname && !stat(name, &sb)) { msgq(sp, M_ERR, LF_ISSET(FS_POSSIBLE) ? "248|Partial file, not written; use ! to override" : "249|Partial file, not written"); return (1); } } /* * Figure out if the file already exists -- if it doesn't, we display * the "new file" message. The stat might not be necessary, but we * just repeat it because it's easier than hacking the previous tests. * The information is only used for the user message and modification * time test, so we can ignore the obvious race condition. * * One final test. If we're not forcing or appending the current file, * and we have a saved modification time, object if the file changed * since we last edited or wrote it, and make them force it. */ if (stat(name, &sb)) mtype = NEWFILE; else { if (noname && !LF_ISSET(FS_FORCE | FS_APPEND) && (F_ISSET(ep, F_DEVSET) && (sb.st_dev != ep->mdev || sb.st_ino != ep->minode) || sb.st_mtime != ep->mtime)) { msgq_str(sp, M_ERR, name, LF_ISSET(FS_POSSIBLE) ? "250|%s: file modified more recently than this copy; use ! to override" : "251|%s: file modified more recently than this copy"); return (1); } mtype = OLDFILE; } /* Set flags to create, write, and either append or truncate. */ oflags = O_CREAT | O_WRONLY | (LF_ISSET(FS_APPEND) ? O_APPEND : O_TRUNC); /* Backup the file if requested. */ if (!opts_empty(sp, O_BACKUP, 1) && file_backup(sp, name, O_STR(sp, O_BACKUP)) && !LF_ISSET(FS_FORCE)) return (1); /* Open the file. */ SIGBLOCK; if ((fd = open(name, oflags, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)) < 0) { if (errno == EACCES && LF_ISSET(FS_FORCE)) { /* * If the user owns the file but does not * have write permission on it, grant it * automatically for the duration of the * opening of the file, if possible. */ struct stat sb; mode_t fmode; if (stat(name, &sb) != 0) goto fail_open; fmode = sb.st_mode; if (!(sb.st_mode & S_IWUSR) && sb.st_uid == getuid()) fmode |= S_IWUSR; else goto fail_open; if (chmod(name, fmode) != 0) goto fail_open; fd = open(name, oflags, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); if (fd == -1) goto fail_open; (void)fchmod(fd, sb.st_mode); goto success_open; fail_open: errno = EACCES; } msgq_str(sp, M_SYSERR, name, "%s"); SIGUNBLOCK; return (1); } success_open: SIGUNBLOCK; /* Try and get a lock. */ if (!noname && file_lock(sp, NULL, NULL, fd, 0) == LOCK_UNAVAIL) msgq_str(sp, M_ERR, name, "252|%s: write lock was unavailable"); #if __linux__ /* * XXX * In libc 4.5.x, fdopen(fd, "w") clears the O_APPEND flag (if set). * This bug is fixed in libc 4.6.x. * * This code works around this problem for libc 4.5.x users. * Note that this code is harmless if you're using libc 4.6.x. */ if (LF_ISSET(FS_APPEND) && lseek(fd, (off_t)0, SEEK_END) < 0) { msgq(sp, M_SYSERR, name); return (1); } #endif /* * Use stdio for buffering. * * XXX * SVR4.2 requires the fdopen mode exactly match the original open * mode, i.e. you have to open with "a" if appending. */ if ((fp = fdopen(fd, LF_ISSET(FS_APPEND) ? "a" : "w")) == NULL) { msgq_str(sp, M_SYSERR, name, "%s"); (void)close(fd); return (1); } /* Build fake addresses, if necessary. */ if (fm == NULL) { from.lno = 1; from.cno = 0; fm = &from; if (db_last(sp, &to.lno)) return (1); to.cno = 0; tm = &to; } rval = ex_writefp(sp, name, fp, fm, tm, &nlno, &nch, 0); /* * Save the new last modification time -- even if the write fails * we re-init the time. That way the user can clean up the disk * and rewrite without having to force it. */ if (noname) if (stat(name, &sb)) time(&ep->mtime); else { F_SET(ep, F_DEVSET); ep->mdev = sb.st_dev; ep->minode = sb.st_ino; ep->mtime = sb.st_mtime; } /* * If the write failed, complain loudly. ex_writefp() has already * complained about the actual error, reinforce it if data was lost. */ if (rval) { if (!LF_ISSET(FS_APPEND)) msgq_str(sp, M_ERR, name, "254|%s: WARNING: FILE TRUNCATED"); return (1); } /* * Once we've actually written the file, it doesn't matter that the * file name was changed -- if it was, we've already whacked it. */ F_CLR(frp, FR_NAMECHANGE); /* * If wrote the entire file, and it wasn't by appending it to a file, * clear the modified bit. If the file was written to the original * file name and the file is a temporary, set the "no exit" bit. This * permits the user to write the file and use it in the context of the * filesystem, but still keeps them from discarding their changes by * exiting. */ if (LF_ISSET(FS_ALL) && !LF_ISSET(FS_APPEND)) { F_CLR(ep, F_MODIFIED); if (F_ISSET(frp, FR_TMPFILE)) if (noname) F_SET(frp, FR_TMPEXIT); else F_CLR(frp, FR_TMPEXIT); } p = msg_print(sp, name, &nf); switch (mtype) { case NEWFILE: msgstr = msg_cat(sp, "256|%s: new file: %lu lines, %lu characters", NULL); len = snprintf(buf, sizeof(buf), msgstr, p, nlno, nch); break; case OLDFILE: msgstr = msg_cat(sp, LF_ISSET(FS_APPEND) ? "315|%s: appended: %lu lines, %lu characters" : "257|%s: %lu lines, %lu characters", NULL); len = snprintf(buf, sizeof(buf), msgstr, p, nlno, nch); break; default: abort(); } /* * There's a nasty problem with long path names. Cscope and tags files * can result in long paths and vi will request a continuation key from * the user. Unfortunately, the user has typed ahead, and chaos will * result. If we assume that the characters in the filenames only take * a single screen column each, we can trim the filename. */ s = buf; if (len >= sp->cols) { for (s = buf, t = buf + strlen(p); s < t && (*s != '/' || len >= sp->cols - 3); ++s, --len); if (s == t) s = buf; else { *--s = '.'; /* Leading ellipses. */ *--s = '.'; *--s = '.'; } } msgq(sp, M_INFO, "%s", s); if (nf) FREE_SPACE(sp, p, 0); return (0); } /* * file_backup -- * Backup the about-to-be-written file. * * XXX * We do the backup by copying the entire file. It would be nice to do * a rename instead, but: (1) both files may not fit and we want to fail * before doing the rename; (2) the backup file may not be on the same * disk partition as the file being written; (3) there may be optional * file information (MACs, DACs, whatever) that we won't get right if we * recreate the file. So, let's not risk it. */ static int file_backup(sp, name, bname) SCR *sp; char *name, *bname; { struct dirent *dp; struct stat sb; DIR *dirp; EXCMD cmd; off_t off; size_t blen; int flags, maxnum, nr, num, nw, rfd, wfd, version; char *bp, *estr, *p, *pct, *slash, *t, *wfname, buf[8192]; rfd = wfd = -1; bp = estr = wfname = NULL; /* * Open the current file for reading. Do this first, so that * we don't exec a shell before the most likely failure point. * If it doesn't exist, it's okay, there's just nothing to back * up. */ errno = 0; if ((rfd = open(name, O_RDONLY, 0)) < 0) { if (errno == ENOENT) return (0); estr = name; goto err; } /* * If the name starts with an 'N' character, add a version number * to the name. Strip the leading N from the string passed to the * expansion routines, for no particular reason. It would be nice * to permit users to put the version number anywhere in the backup * name, but there isn't a special character that we can use in the * name, and giving a new character a special meaning leads to ugly * hacks both here and in the supporting ex routines. * * Shell and file name expand the option's value. */ argv_init(sp, &cmd); ex_cinit(&cmd, 0, 0, 0, 0, 0, NULL); if (bname[0] == 'N') { version = 1; ++bname; } else version = 0; if (argv_exp2(sp, &cmd, bname, strlen(bname))) return (1); /* * 0 args: impossible. * 1 args: use it. * >1 args: object, too many args. */ if (cmd.argc != 1) { msgq_str(sp, M_ERR, bname, "258|%s expanded into too many file names"); (void)close(rfd); return (1); } /* * If appending a version number, read through the directory, looking * for file names that match the name followed by a number. Make all * of the other % characters in name literal, so the user doesn't get * surprised and sscanf doesn't drop core indirecting through pointers * that don't exist. If any such files are found, increment its number * by one. */ if (version) { GET_SPACE_GOTO(sp, bp, blen, cmd.argv[0]->len * 2 + 50); for (t = bp, slash = NULL, p = cmd.argv[0]->bp; p[0] != '\0'; *t++ = *p++) if (p[0] == '%') { if (p[1] != '%') *t++ = '%'; } else if (p[0] == '/') slash = t; pct = t; *t++ = '%'; *t++ = 'd'; *t = '\0'; if (slash == NULL) { dirp = opendir("."); p = bp; } else { *slash = '\0'; dirp = opendir(bp); *slash = '/'; p = slash + 1; } if (dirp == NULL) { estr = cmd.argv[0]->bp; goto err; } for (maxnum = 0; (dp = readdir(dirp)) != NULL;) if (sscanf(dp->d_name, p, &num) == 1 && num > maxnum) maxnum = num; (void)closedir(dirp); /* Format the backup file name. */ (void)snprintf(pct, blen - (pct - bp), "%d", maxnum + 1); wfname = bp; } else { bp = NULL; wfname = cmd.argv[0]->bp; } /* Open the backup file, avoiding lurkers. */ if (stat(wfname, &sb) == 0) { if (!S_ISREG(sb.st_mode)) { msgq_str(sp, M_ERR, bname, "259|%s: not a regular file"); goto err; } if (sb.st_uid != getuid()) { msgq_str(sp, M_ERR, bname, "260|%s: not owned by you"); goto err; } if (sb.st_mode & (S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)) { msgq_str(sp, M_ERR, bname, "261|%s: accessible by a user other than the owner"); goto err; } flags = O_TRUNC; } else flags = O_CREAT | O_EXCL; if ((wfd = open(wfname, flags | O_WRONLY, S_IRUSR | S_IWUSR)) < 0) { estr = bname; goto err; } /* Copy the file's current contents to its backup value. */ while ((nr = read(rfd, buf, sizeof(buf))) > 0) for (off = 0; nr != 0; nr -= nw, off += nw) if ((nw = write(wfd, buf + off, nr)) < 0) { estr = wfname; goto err; } if (nr < 0) { estr = name; goto err; } if (close(rfd)) { estr = name; goto err; } if (close(wfd)) { estr = wfname; goto err; } if (bp != NULL) FREE_SPACE(sp, bp, blen); return (0); alloc_err: err: if (rfd != -1) (void)close(rfd); if (wfd != -1) { (void)unlink(wfname); (void)close(wfd); } if (estr) msgq_str(sp, M_SYSERR, estr, "%s"); if (bp != NULL) FREE_SPACE(sp, bp, blen); return (1); } /* * file_comment -- * Skip the first comment. */ static void file_comment(sp) SCR *sp; { recno_t lno; size_t len; char *p; for (lno = 1; !db_get(sp, lno, 0, &p, &len) && len == 0; ++lno); if (p == NULL) return; if (p[0] == '#') { F_SET(sp, SC_SCR_TOP); while (!db_get(sp, ++lno, 0, &p, &len)) if (len < 1 || p[0] != '#') { sp->lno = lno; return; } } else if (len > 1 && p[0] == '/' && p[1] == '*') { F_SET(sp, SC_SCR_TOP); do { for (; len > 1; --len, ++p) if (p[0] == '*' && p[1] == '/') { sp->lno = lno; return; } } while (!db_get(sp, ++lno, 0, &p, &len)); } else if (len > 1 && p[0] == '/' && p[1] == '/') { F_SET(sp, SC_SCR_TOP); p += 2; len -= 2; do { for (; len > 1; --len, ++p) if (p[0] == '/' && p[1] == '/') { sp->lno = lno; return; } } while (!db_get(sp, ++lno, 0, &p, &len)); } } /* * file_m1 -- * First modification check routine. The :next, :prev, :rewind, :tag, * :tagpush, :tagpop, ^^ modifications check. * * PUBLIC: int file_m1 __P((SCR *, int, int)); */ int file_m1(sp, force, flags) SCR *sp; int force, flags; { EXF *ep; ep = sp->ep; /* If no file loaded, return no modifications. */ if (ep == NULL) return (0); /* * If the file has been modified, we'll want to write it back or * fail. If autowrite is set, we'll write it back automatically, * unless force is also set. Otherwise, we fail unless forced or * there's another open screen on this file. */ if (F_ISSET(ep, F_MODIFIED)) if (O_ISSET(sp, O_AUTOWRITE)) { if (!force && file_aw(sp, flags)) return (1); } else if (ep->refcnt <= 1 && !force) { msgq(sp, M_ERR, LF_ISSET(FS_POSSIBLE) ? "262|File modified since last complete write; write or use ! to override" : "263|File modified since last complete write; write or use :edit! to override"); return (1); } return (file_m3(sp, force)); } /* * file_m2 -- * Second modification check routine. The :edit, :quit, :recover * modifications check. * * PUBLIC: int file_m2 __P((SCR *, int)); */ int file_m2(sp, force) SCR *sp; int force; { EXF *ep; ep = sp->ep; /* If no file loaded, return no modifications. */ if (ep == NULL) return (0); /* * If the file has been modified, we'll want to fail, unless forced * or there's another open screen on this file. */ if (F_ISSET(ep, F_MODIFIED) && ep->refcnt <= 1 && !force) { msgq(sp, M_ERR, "264|File modified since last complete write; write or use ! to override"); return (1); } return (file_m3(sp, force)); } /* * file_m3 -- * Third modification check routine. * * PUBLIC: int file_m3 __P((SCR *, int)); */ int file_m3(sp, force) SCR *sp; int force; { EXF *ep; ep = sp->ep; /* If no file loaded, return no modifications. */ if (ep == NULL) return (0); /* * Don't exit while in a temporary files if the file was ever modified. * The problem is that if the user does a ":wq", we write and quit, * unlinking the temporary file. Not what the user had in mind at all. * We permit writing to temporary files, so that user maps using file * system names work with temporary files. */ if (F_ISSET(sp->frp, FR_TMPEXIT) && ep->refcnt <= 1 && !force) { msgq(sp, M_ERR, "265|File is a temporary; exit will discard modifications"); return (1); } return (0); } /* * file_aw -- * Autowrite routine. If modified, autowrite is set and the readonly bit * is not set, write the file. A routine so there's a place to put the * comment. * * PUBLIC: int file_aw __P((SCR *, int)); */ int file_aw(sp, flags) SCR *sp; int flags; { if (!F_ISSET(sp->ep, F_MODIFIED)) return (0); if (!O_ISSET(sp, O_AUTOWRITE)) return (0); /* * !!! * Historic 4BSD vi attempted to write the file if autowrite was set, * regardless of the writeability of the file (as defined by the file * readonly flag). System V changed this as some point, not attempting * autowrite if the file was readonly. This feels like a bug fix to * me (e.g. the principle of least surprise is violated if readonly is * set and vi writes the file), so I'm compatible with System V. */ if (O_ISSET(sp, O_READONLY)) { msgq(sp, M_INFO, "266|File readonly, modifications not auto-written"); return (1); } return (file_write(sp, NULL, NULL, NULL, flags)); } /* * set_alt_name -- * Set the alternate pathname. * * Set the alternate pathname. It's a routine because I wanted some place * to hang this comment. The alternate pathname (normally referenced using * the special character '#' during file expansion and in the vi ^^ command) * is set by almost all ex commands that take file names as arguments. The * rules go something like this: * * 1: If any ex command takes a file name as an argument (except for the * :next command), the alternate pathname is set to that file name. * This excludes the command ":e" and ":w !command" as no file name * was specified. Note, historically, the :source command did not set * the alternate pathname. It does in nvi, for consistency. * * 2: However, if any ex command sets the current pathname, e.g. the * ":e file" or ":rew" commands succeed, then the alternate pathname * is set to the previous file's current pathname, if it had one. * This includes the ":file" command and excludes the ":e" command. * So, by rule #1 and rule #2, if ":edit foo" fails, the alternate * pathname will be "foo", if it succeeds, the alternate pathname will * be the previous current pathname. The ":e" command will not set * the alternate or current pathnames regardless. * * 3: However, if it's a read or write command with a file argument and * the current pathname has not yet been set, the file name becomes * the current pathname, and the alternate pathname is unchanged. * * If the user edits a temporary file, there may be times when there is no * alternative file name. A name argument of NULL turns it off. * * PUBLIC: void set_alt_name __P((SCR *, char *)); */ void set_alt_name(sp, name) SCR *sp; char *name; { if (sp->alt_name != NULL) free(sp->alt_name); if (name == NULL) sp->alt_name = NULL; else if ((sp->alt_name = strdup(name)) == NULL) msgq(sp, M_SYSERR, NULL); } /* * file_lock -- * Get an exclusive lock on a file. * * XXX * The default locking is flock(2) style, not fcntl(2). The latter is * known to fail badly on some systems, and its only advantage is that * it occasionally works over NFS. * * Furthermore, the semantics of fcntl(2) are wrong. The problems are * two-fold: you can't close any file descriptor associated with the file * without losing all of the locks, and you can't get an exclusive lock * unless you have the file open for writing. Someone ought to be shot, * but it's probably too late, they may already have reproduced. To get * around these problems, nvi opens the files for writing when it can and * acquires a second file descriptor when it can't. The recovery files * are examples of the former, they're always opened for writing. The DB * files can't be opened for writing because the semantics of DB are that * files opened for writing are flushed back to disk when the DB session * is ended. So, in that case we have to acquire an extra file descriptor. * * PUBLIC: lockr_t file_lock __P((SCR *, char *, int *, int, int)); */ lockr_t file_lock(sp, name, fdp, fd, iswrite) SCR *sp; char *name; int *fdp, fd, iswrite; { if (!O_ISSET(sp, O_LOCKFILES)) return (LOCK_SUCCESS); #ifdef HAVE_LOCK_FLOCK /* Hurrah! We've got flock(2). */ /* * !!! * We need to distinguish a lock not being available for the file * from the file system not supporting locking. Flock is documented * as returning EWOULDBLOCK; add EAGAIN for good measure, and assume * they are the former. There's no portable way to do this. */ errno = 0; if (!flock(fd, LOCK_EX | LOCK_NB)) { fcntl(fd, F_SETFD, 1); return (LOCK_SUCCESS); } return (errno == EAGAIN #ifdef EWOULDBLOCK || errno == EWOULDBLOCK #endif ? LOCK_UNAVAIL : LOCK_FAILED); #endif #ifdef HAVE_LOCK_FCNTL /* Gag me. We've got fcntl(2). */ { struct flock arg; int didopen, sverrno; arg.l_type = F_WRLCK; arg.l_whence = 0; /* SEEK_SET */ arg.l_start = arg.l_len = 0; arg.l_pid = 0; /* * If the file descriptor isn't opened for writing, it must fail. * If we fail because we can't get a read/write file descriptor, * we return LOCK_SUCCESS, believing that the file is readonly * and that will be sufficient to warn the user. */ if (!iswrite) { if (name == NULL || fdp == NULL) return (LOCK_FAILED); if ((fd = open(name, O_RDWR, 0)) == -1) return (LOCK_SUCCESS); *fdp = fd; didopen = 1; } errno = 0; if (!fcntl(fd, F_SETLK, &arg)) { fcntl(fd, F_SETFD, 1); return (LOCK_SUCCESS); } if (didopen) { sverrno = errno; (void)close(fd); errno = sverrno; } /* * !!! * We need to distinguish a lock not being available for the file * from the file system not supporting locking. Fcntl is documented * as returning EACCESS and EAGAIN; add EWOULDBLOCK for good measure, * and assume they are the former. There's no portable way to do this. */ return (errno == EACCES || errno == EAGAIN #ifdef EWOULDBLOCK || errno == EWOULDBLOCK #endif ? LOCK_UNAVAIL : LOCK_FAILED); } #endif #if !defined(HAVE_LOCK_FLOCK) && !defined(HAVE_LOCK_FCNTL) return (LOCK_SUCCESS); #endif } Index: stable/9/contrib/nvi/common/options.c =================================================================== --- stable/9/contrib/nvi/common/options.c (revision 290040) +++ stable/9/contrib/nvi/common/options.c (revision 290041) @@ -1,1148 +1,1148 @@ /*- * Copyright (c) 1991, 1993, 1994 * The Regents of the University of California. All rights reserved. * Copyright (c) 1991, 1993, 1994, 1995, 1996 * Keith Bostic. All rights reserved. * * See the LICENSE file for redistribution information. */ #include "config.h" #ifndef lint static const char sccsid[] = "@(#)options.c 10.51 (Berkeley) 10/14/96"; #endif /* not lint */ #include #include #include #include #include #include #include #include #include #include #include #include #include "common.h" #include "../vi/vi.h" #include "pathnames.h" static int opts_abbcmp __P((const void *, const void *)); static int opts_cmp __P((const void *, const void *)); static int opts_print __P((SCR *, OPTLIST const *)); /* * O'Reilly noted options and abbreviations are from "Learning the VI Editor", * Fifth Edition, May 1992. There's no way of knowing what systems they are * actually from. * * HPUX noted options and abbreviations are from "The Ultimate Guide to the * VI and EX Text Editors", 1990. */ OPTLIST const optlist[] = { /* O_ALTWERASE 4.4BSD */ {"altwerase", f_altwerase, OPT_0BOOL, 0}, /* O_AUTOINDENT 4BSD */ {"autoindent", NULL, OPT_0BOOL, 0}, /* O_AUTOPRINT 4BSD */ {"autoprint", NULL, OPT_1BOOL, 0}, /* O_AUTOWRITE 4BSD */ {"autowrite", NULL, OPT_0BOOL, 0}, /* O_BACKUP 4.4BSD */ {"backup", NULL, OPT_STR, 0}, /* O_BEAUTIFY 4BSD */ {"beautify", NULL, OPT_0BOOL, 0}, /* O_CDPATH 4.4BSD */ {"cdpath", NULL, OPT_STR, 0}, /* O_CEDIT 4.4BSD */ {"cedit", NULL, OPT_STR, 0}, /* O_COLUMNS 4.4BSD */ {"columns", f_columns, OPT_NUM, OPT_NOSAVE}, /* O_COMMENT 4.4BSD */ {"comment", NULL, OPT_0BOOL, 0}, -/* O_DIRECTORY 4BSD */ +/* O_TMP_DIRECTORY 4BSD */ {"directory", NULL, OPT_STR, 0}, /* O_EDCOMPATIBLE 4BSD */ {"edcompatible",NULL, OPT_0BOOL, 0}, /* O_ESCAPETIME 4.4BSD */ {"escapetime", NULL, OPT_NUM, 0}, /* O_ERRORBELLS 4BSD */ {"errorbells", NULL, OPT_0BOOL, 0}, /* O_EXRC System V (undocumented) */ {"exrc", NULL, OPT_0BOOL, 0}, /* O_EXTENDED 4.4BSD */ {"extended", f_recompile, OPT_0BOOL, 0}, /* O_FILEC 4.4BSD */ {"filec", NULL, OPT_STR, 0}, /* O_FLASH HPUX */ {"flash", NULL, OPT_1BOOL, 0}, #ifdef GTAGS /* O_GTAGSMODE FreeBSD2.2 */ {"gtagsmode", NULL, OPT_0BOOL, 0}, #endif /* O_HARDTABS 4BSD */ {"hardtabs", NULL, OPT_NUM, 0}, /* O_ICLOWER 4.4BSD */ {"iclower", f_recompile, OPT_0BOOL, 0}, /* O_IGNORECASE 4BSD */ {"ignorecase", f_recompile, OPT_0BOOL, 0}, /* O_KEYTIME 4.4BSD */ {"keytime", NULL, OPT_NUM, 0}, /* O_LEFTRIGHT 4.4BSD */ {"leftright", f_reformat, OPT_0BOOL, 0}, /* O_LINES 4.4BSD */ {"lines", f_lines, OPT_NUM, OPT_NOSAVE}, /* O_LISP 4BSD * XXX * When the lisp option is implemented, delete the OPT_NOSAVE flag, * so that :mkexrc dumps it. */ {"lisp", f_lisp, OPT_0BOOL, OPT_NOSAVE}, /* O_LIST 4BSD */ {"list", f_reformat, OPT_0BOOL, 0}, /* O_LOCKFILES 4.4BSD * XXX * Locking isn't reliable enough over NFS to require it, in addition, * it's a serious startup performance problem over some remote links. */ {"lock", NULL, OPT_1BOOL, 0}, /* O_MAGIC 4BSD */ {"magic", NULL, OPT_1BOOL, 0}, /* O_MATCHTIME 4.4BSD */ {"matchtime", NULL, OPT_NUM, 0}, /* O_MESG 4BSD */ {"mesg", NULL, OPT_1BOOL, 0}, /* O_MODELINE 4BSD * !!! * This has been documented in historical systems as both "modeline" * and as "modelines". Regardless of the name, this option represents * a security problem of mammoth proportions, not to mention a stunning * example of what your intro CS professor referred to as the perils of * mixing code and data. Don't add it, or I will kill you. */ {"modeline", NULL, OPT_0BOOL, OPT_NOSET}, /* O_MSGCAT 4.4BSD */ {"msgcat", f_msgcat, OPT_STR, 0}, /* O_NOPRINT 4.4BSD */ {"noprint", f_print, OPT_STR, 0}, /* O_NUMBER 4BSD */ {"number", f_reformat, OPT_0BOOL, 0}, /* O_OCTAL 4.4BSD */ {"octal", f_print, OPT_0BOOL, 0}, /* O_OPEN 4BSD */ {"open", NULL, OPT_1BOOL, 0}, /* O_OPTIMIZE 4BSD */ {"optimize", NULL, OPT_1BOOL, 0}, /* O_PARAGRAPHS 4BSD */ {"paragraphs", f_paragraph, OPT_STR, 0}, /* O_PATH 4.4BSD */ {"path", NULL, OPT_STR, 0}, /* O_PRINT 4.4BSD */ {"print", f_print, OPT_STR, 0}, /* O_PROMPT 4BSD */ {"prompt", NULL, OPT_1BOOL, 0}, /* O_READONLY 4BSD (undocumented) */ {"readonly", f_readonly, OPT_0BOOL, OPT_ALWAYS}, /* O_RECDIR 4.4BSD */ {"recdir", NULL, OPT_STR, 0}, /* O_REDRAW 4BSD */ {"redraw", NULL, OPT_0BOOL, 0}, /* O_REMAP 4BSD */ {"remap", NULL, OPT_1BOOL, 0}, /* O_REPORT 4BSD */ {"report", NULL, OPT_NUM, 0}, /* O_RULER 4.4BSD */ {"ruler", NULL, OPT_0BOOL, 0}, /* O_SCROLL 4BSD */ {"scroll", NULL, OPT_NUM, 0}, /* O_SEARCHINCR 4.4BSD */ {"searchincr", NULL, OPT_0BOOL, 0}, /* O_SECTIONS 4BSD */ {"sections", f_section, OPT_STR, 0}, /* O_SECURE 4.4BSD */ {"secure", NULL, OPT_0BOOL, OPT_NOUNSET}, /* O_SHELL 4BSD */ {"shell", NULL, OPT_STR, 0}, /* O_SHELLMETA 4.4BSD */ {"shellmeta", NULL, OPT_STR, 0}, /* O_SHIFTWIDTH 4BSD */ {"shiftwidth", NULL, OPT_NUM, OPT_NOZERO}, /* O_SHOWMATCH 4BSD */ {"showmatch", NULL, OPT_0BOOL, 0}, /* O_SHOWMODE 4.4BSD */ {"showmode", NULL, OPT_0BOOL, 0}, /* O_SIDESCROLL 4.4BSD */ {"sidescroll", NULL, OPT_NUM, OPT_NOZERO}, /* O_SLOWOPEN 4BSD */ {"slowopen", NULL, OPT_0BOOL, 0}, /* O_SOURCEANY 4BSD (undocumented) * !!! * Historic vi, on startup, source'd $HOME/.exrc and ./.exrc, if they * were owned by the user. The sourceany option was an undocumented * feature of historic vi which permitted the startup source'ing of * .exrc files the user didn't own. This is an obvious security problem, * and we ignore the option. */ {"sourceany", NULL, OPT_0BOOL, OPT_NOSET}, /* O_TABSTOP 4BSD */ {"tabstop", f_reformat, OPT_NUM, OPT_NOZERO}, /* O_TAGLENGTH 4BSD */ {"taglength", NULL, OPT_NUM, 0}, /* O_TAGS 4BSD */ {"tags", NULL, OPT_STR, 0}, /* O_TERM 4BSD * !!! * By default, the historic vi always displayed information about two * options, redraw and term. Term seems sufficient. */ {"term", NULL, OPT_STR, OPT_ADISP|OPT_NOSAVE}, /* O_TERSE 4BSD */ {"terse", NULL, OPT_0BOOL, 0}, /* O_TILDEOP 4.4BSD */ {"tildeop", NULL, OPT_0BOOL, 0}, /* O_TIMEOUT 4BSD (undocumented) */ {"timeout", NULL, OPT_1BOOL, 0}, /* O_TTYWERASE 4.4BSD */ {"ttywerase", f_ttywerase, OPT_0BOOL, 0}, /* O_VERBOSE 4.4BSD */ {"verbose", NULL, OPT_0BOOL, 0}, /* O_W1200 4BSD */ {"w1200", f_w1200, OPT_NUM, OPT_NDISP|OPT_NOSAVE}, /* O_W300 4BSD */ {"w300", f_w300, OPT_NUM, OPT_NDISP|OPT_NOSAVE}, /* O_W9600 4BSD */ {"w9600", f_w9600, OPT_NUM, OPT_NDISP|OPT_NOSAVE}, /* O_WARN 4BSD */ {"warn", NULL, OPT_1BOOL, 0}, /* O_WINDOW 4BSD */ {"window", f_window, OPT_NUM, 0}, /* O_WINDOWNAME 4BSD */ {"windowname", NULL, OPT_0BOOL, 0}, /* O_WRAPLEN 4.4BSD */ {"wraplen", NULL, OPT_NUM, 0}, /* O_WRAPMARGIN 4BSD */ {"wrapmargin", NULL, OPT_NUM, 0}, /* O_WRAPSCAN 4BSD */ {"wrapscan", NULL, OPT_1BOOL, 0}, /* O_WRITEANY 4BSD */ {"writeany", NULL, OPT_0BOOL, 0}, {NULL}, }; typedef struct abbrev { char *name; int offset; } OABBREV; static OABBREV const abbrev[] = { {"ai", O_AUTOINDENT}, /* 4BSD */ {"ap", O_AUTOPRINT}, /* 4BSD */ {"aw", O_AUTOWRITE}, /* 4BSD */ {"bf", O_BEAUTIFY}, /* 4BSD */ {"co", O_COLUMNS}, /* 4.4BSD */ - {"dir", O_DIRECTORY}, /* 4BSD */ + {"dir", O_TMP_DIRECTORY}, /* 4BSD */ {"eb", O_ERRORBELLS}, /* 4BSD */ {"ed", O_EDCOMPATIBLE}, /* 4BSD */ {"ex", O_EXRC}, /* System V (undocumented) */ #ifdef GTAGS {"gt", O_GTAGSMODE}, /* FreeBSD2.2 */ #endif {"ht", O_HARDTABS}, /* 4BSD */ {"ic", O_IGNORECASE}, /* 4BSD */ {"li", O_LINES}, /* 4.4BSD */ {"modelines", O_MODELINE}, /* HPUX */ {"nu", O_NUMBER}, /* 4BSD */ {"opt", O_OPTIMIZE}, /* 4BSD */ {"para", O_PARAGRAPHS}, /* 4BSD */ {"re", O_REDRAW}, /* O'Reilly */ {"ro", O_READONLY}, /* 4BSD (undocumented) */ {"scr", O_SCROLL}, /* 4BSD (undocumented) */ {"sect", O_SECTIONS}, /* O'Reilly */ {"sh", O_SHELL}, /* 4BSD */ {"slow", O_SLOWOPEN}, /* 4BSD */ {"sm", O_SHOWMATCH}, /* 4BSD */ {"smd", O_SHOWMODE}, /* 4BSD */ {"sw", O_SHIFTWIDTH}, /* 4BSD */ {"tag", O_TAGS}, /* 4BSD (undocumented) */ {"tl", O_TAGLENGTH}, /* 4BSD */ {"to", O_TIMEOUT}, /* 4BSD (undocumented) */ {"ts", O_TABSTOP}, /* 4BSD */ {"tty", O_TERM}, /* 4BSD (undocumented) */ {"ttytype", O_TERM}, /* 4BSD (undocumented) */ {"w", O_WINDOW}, /* O'Reilly */ {"wa", O_WRITEANY}, /* 4BSD */ {"wi", O_WINDOW}, /* 4BSD (undocumented) */ {"wl", O_WRAPLEN}, /* 4.4BSD */ {"wm", O_WRAPMARGIN}, /* 4BSD */ {"ws", O_WRAPSCAN}, /* 4BSD */ {NULL}, }; /* * opts_init -- * Initialize some of the options. * * PUBLIC: int opts_init __P((SCR *, int *)); */ int opts_init(sp, oargs) SCR *sp; int *oargs; { ARGS *argv[2], a, b; OPTLIST const *op; u_long v; int cnt, optindx; char *s, b1[1024]; a.bp = b1; b.bp = NULL; a.len = b.len = 0; argv[0] = &a; argv[1] = &b; /* Set numeric and string default values. */ #define OI(indx, str) { \ if (str != b1) /* GCC puts strings in text-space. */ \ (void)strcpy(b1, str); \ a.len = strlen(b1); \ if (opts_set(sp, argv, NULL)) { \ optindx = indx; \ goto err; \ } \ } /* * Indirect global options to global space. Specifically, set up * terminal, lines, columns first, they're used by other options. * Note, don't set the flags until we've set up the indirection. */ if (o_set(sp, O_TERM, 0, NULL, GO_TERM)) goto err; F_SET(&sp->opts[O_TERM], OPT_GLOBAL); if (o_set(sp, O_LINES, 0, NULL, GO_LINES)) goto err; F_SET(&sp->opts[O_LINES], OPT_GLOBAL); if (o_set(sp, O_COLUMNS, 0, NULL, GO_COLUMNS)) goto err; F_SET(&sp->opts[O_COLUMNS], OPT_GLOBAL); if (o_set(sp, O_SECURE, 0, NULL, GO_SECURE)) goto err; F_SET(&sp->opts[O_SECURE], OPT_GLOBAL); /* Initialize string values. */ (void)snprintf(b1, sizeof(b1), "cdpath=%s", (s = getenv("CDPATH")) == NULL ? ":" : s); OI(O_CDPATH, b1); /* * !!! * Vi historically stored temporary files in /var/tmp. We store them * in /tmp by default, hoping it's a memory based file system. There * are two ways to change this -- the user can set either the directory * option or the TMPDIR environmental variable. */ (void)snprintf(b1, sizeof(b1), "directory=%s", (s = getenv("TMPDIR")) == NULL ? _PATH_TMP : s); - OI(O_DIRECTORY, b1); + OI(O_TMP_DIRECTORY, b1); OI(O_ESCAPETIME, "escapetime=6"); OI(O_KEYTIME, "keytime=6"); OI(O_MATCHTIME, "matchtime=7"); (void)snprintf(b1, sizeof(b1), "msgcat=%s", _PATH_MSGCAT); OI(O_MSGCAT, b1); OI(O_REPORT, "report=5"); OI(O_PARAGRAPHS, "paragraphs=IPLPPPQPP LIpplpipbp"); (void)snprintf(b1, sizeof(b1), "path=%s", ""); OI(O_PATH, b1); (void)snprintf(b1, sizeof(b1), "recdir=%s", _PATH_PRESERVE); OI(O_RECDIR, b1); OI(O_SECTIONS, "sections=NHSHH HUnhsh"); (void)snprintf(b1, sizeof(b1), "shell=%s", (s = getenv("SHELL")) == NULL ? _PATH_BSHELL : s); OI(O_SHELL, b1); OI(O_SHELLMETA, "shellmeta=~{[*?$`'\"\\"); OI(O_SHIFTWIDTH, "shiftwidth=8"); OI(O_SIDESCROLL, "sidescroll=16"); OI(O_TABSTOP, "tabstop=8"); (void)snprintf(b1, sizeof(b1), "tags=%s", _PATH_TAGS); OI(O_TAGS, b1); /* * XXX * Initialize O_SCROLL here, after term; initializing term should * have created a LINES/COLUMNS value. */ if ((v = (O_VAL(sp, O_LINES) - 1) / 2) == 0) v = 1; (void)snprintf(b1, sizeof(b1), "scroll=%ld", v); OI(O_SCROLL, b1); /* * The default window option values are: * 8 if baud rate <= 600 * 16 if baud rate <= 1200 * LINES - 1 if baud rate > 1200 * * Note, the windows option code will correct any too-large value * or when the O_LINES value is 1. */ if (sp->gp->scr_baud(sp, &v)) return (1); if (v <= 600) v = 8; else if (v <= 1200) v = 16; else v = O_VAL(sp, O_LINES) - 1; (void)snprintf(b1, sizeof(b1), "window=%lu", v); OI(O_WINDOW, b1); /* * Set boolean default values, and copy all settings into the default * information. OS_NOFREE is set, we're copying, not replacing. */ for (op = optlist, cnt = 0; op->name != NULL; ++op, ++cnt) switch (op->type) { case OPT_0BOOL: break; case OPT_1BOOL: O_SET(sp, cnt); O_D_SET(sp, cnt); break; case OPT_NUM: o_set(sp, cnt, OS_DEF, NULL, O_VAL(sp, cnt)); break; case OPT_STR: if (O_STR(sp, cnt) != NULL && o_set(sp, cnt, OS_DEF | OS_NOFREE | OS_STRDUP, O_STR(sp, cnt), 0)) goto err; break; default: abort(); } /* * !!! * Some options can be initialized by the command name or the * command-line arguments. They don't set the default values, * it's historic practice. */ for (; *oargs != -1; ++oargs) OI(*oargs, optlist[*oargs].name); return (0); #undef OI err: msgq(sp, M_ERR, "031|Unable to set default %s option", optlist[optindx].name); return (1); } /* * opts_set -- * Change the values of one or more options. * * PUBLIC: int opts_set __P((SCR *, ARGS *[], char *)); */ int opts_set(sp, argv, usage) SCR *sp; ARGS *argv[]; char *usage; { enum optdisp disp; enum nresult nret; OPTLIST const *op; OPTION *spo; u_long value, turnoff; int ch, equals, nf, nf2, offset, qmark, rval; char *endp, *name, *p, *sep, *t; disp = NO_DISPLAY; for (rval = 0; argv[0]->len != 0; ++argv) { /* * The historic vi dumped the options for each occurrence of * "all" in the set list. Puhleeze. */ if (!strcmp(argv[0]->bp, "all")) { disp = ALL_DISPLAY; continue; } /* Find equals sign or question mark. */ for (sep = NULL, equals = qmark = 0, p = name = argv[0]->bp; (ch = *p) != '\0'; ++p) if (ch == '=' || ch == '?') { if (p == name) { if (usage != NULL) msgq(sp, M_ERR, "032|Usage: %s", usage); return (1); } sep = p; if (ch == '=') equals = 1; else qmark = 1; break; } turnoff = 0; op = NULL; if (sep != NULL) *sep++ = '\0'; /* Search for the name, then name without any leading "no". */ if ((op = opts_search(name)) == NULL && name[0] == 'n' && name[1] == 'o') { turnoff = 1; name += 2; op = opts_search(name); } if (op == NULL) { opts_nomatch(sp, name); rval = 1; continue; } /* Find current option values. */ offset = op - optlist; spo = sp->opts + offset; /* * !!! * Historically, the question mark could be a separate * argument. */ if (!equals && !qmark && argv[1]->len == 1 && argv[1]->bp[0] == '?') { ++argv; qmark = 1; } /* Set name, value. */ switch (op->type) { case OPT_0BOOL: case OPT_1BOOL: /* Some options may not be reset. */ if (F_ISSET(op, OPT_NOUNSET) && turnoff) { msgq_str(sp, M_ERR, name, "291|set: the %s option may not be turned off"); rval = 1; break; } /* Some options may not be set. */ if (F_ISSET(op, OPT_NOSET) && !turnoff) { msgq_str(sp, M_ERR, name, "313|set: the %s option may never be turned on"); rval = 1; break; } if (equals) { msgq_str(sp, M_ERR, name, "034|set: [no]%s option doesn't take a value"); rval = 1; break; } if (qmark) { if (!disp) disp = SELECT_DISPLAY; F_SET(spo, OPT_SELECTED); break; } /* * Do nothing if the value is unchanged, the underlying * functions can be expensive. */ if (!F_ISSET(op, OPT_ALWAYS)) if (turnoff) { if (!O_ISSET(sp, offset)) break; } else { if (O_ISSET(sp, offset)) break; } /* Report to subsystems. */ if (op->func != NULL && op->func(sp, spo, NULL, &turnoff) || ex_optchange(sp, offset, NULL, &turnoff) || v_optchange(sp, offset, NULL, &turnoff) || sp->gp->scr_optchange(sp, offset, NULL, &turnoff)) { rval = 1; break; } /* Set the value. */ if (turnoff) O_CLR(sp, offset); else O_SET(sp, offset); break; case OPT_NUM: if (turnoff) { msgq_str(sp, M_ERR, name, "035|set: %s option isn't a boolean"); rval = 1; break; } if (qmark || !equals) { if (!disp) disp = SELECT_DISPLAY; F_SET(spo, OPT_SELECTED); break; } if (!isdigit(sep[0])) goto badnum; if ((nret = nget_uslong(&value, sep, &endp, 10)) != NUM_OK) { p = msg_print(sp, name, &nf); t = msg_print(sp, sep, &nf2); switch (nret) { case NUM_ERR: msgq(sp, M_SYSERR, "036|set: %s option: %s", p, t); break; case NUM_OVER: msgq(sp, M_ERR, "037|set: %s option: %s: value overflow", p, t); break; case NUM_OK: case NUM_UNDER: abort(); } if (nf) FREE_SPACE(sp, p, 0); if (nf2) FREE_SPACE(sp, t, 0); rval = 1; break; } if (*endp && !isblank(*endp)) { badnum: p = msg_print(sp, name, &nf); t = msg_print(sp, sep, &nf2); msgq(sp, M_ERR, "038|set: %s option: %s is an illegal number", p, t); if (nf) FREE_SPACE(sp, p, 0); if (nf2) FREE_SPACE(sp, t, 0); rval = 1; break; } /* Some options may never be set to zero. */ if (F_ISSET(op, OPT_NOZERO) && value == 0) { msgq_str(sp, M_ERR, name, "314|set: the %s option may never be set to 0"); rval = 1; break; } /* * Do nothing if the value is unchanged, the underlying * functions can be expensive. */ if (!F_ISSET(op, OPT_ALWAYS) && O_VAL(sp, offset) == value) break; /* Report to subsystems. */ if (op->func != NULL && op->func(sp, spo, sep, &value) || ex_optchange(sp, offset, sep, &value) || v_optchange(sp, offset, sep, &value) || sp->gp->scr_optchange(sp, offset, sep, &value)) { rval = 1; break; } /* Set the value. */ if (o_set(sp, offset, 0, NULL, value)) rval = 1; break; case OPT_STR: if (turnoff) { msgq_str(sp, M_ERR, name, "039|set: %s option isn't a boolean"); rval = 1; break; } if (qmark || !equals) { if (!disp) disp = SELECT_DISPLAY; F_SET(spo, OPT_SELECTED); break; } /* * Do nothing if the value is unchanged, the underlying * functions can be expensive. */ if (!F_ISSET(op, OPT_ALWAYS) && O_STR(sp, offset) != NULL && !strcmp(O_STR(sp, offset), sep)) break; /* Report to subsystems. */ if (op->func != NULL && op->func(sp, spo, sep, NULL) || ex_optchange(sp, offset, sep, NULL) || v_optchange(sp, offset, sep, NULL) || sp->gp->scr_optchange(sp, offset, sep, NULL)) { rval = 1; break; } /* Set the value. */ if (o_set(sp, offset, OS_STRDUP, sep, 0)) rval = 1; break; default: abort(); } } if (disp != NO_DISPLAY) opts_dump(sp, disp); return (rval); } /* * o_set -- * Set an option's value. * * PUBLIC: int o_set __P((SCR *, int, u_int, char *, u_long)); */ int o_set(sp, opt, flags, str, val) SCR *sp; int opt; u_int flags; char *str; u_long val; { OPTION *op; /* Set a pointer to the options area. */ op = F_ISSET(&sp->opts[opt], OPT_GLOBAL) ? &sp->gp->opts[sp->opts[opt].o_cur.val] : &sp->opts[opt]; /* Copy the string, if requested. */ if (LF_ISSET(OS_STRDUP) && (str = strdup(str)) == NULL) { msgq(sp, M_SYSERR, NULL); return (1); } /* Free the previous string, if requested, and set the value. */ if LF_ISSET(OS_DEF) if (LF_ISSET(OS_STR | OS_STRDUP)) { if (!LF_ISSET(OS_NOFREE) && op->o_def.str != NULL) free(op->o_def.str); op->o_def.str = str; } else op->o_def.val = val; else if (LF_ISSET(OS_STR | OS_STRDUP)) { if (!LF_ISSET(OS_NOFREE) && op->o_cur.str != NULL) free(op->o_cur.str); op->o_cur.str = str; } else op->o_cur.val = val; return (0); } /* * opts_empty -- * Return 1 if the string option is invalid, 0 if it's OK. * * PUBLIC: int opts_empty __P((SCR *, int, int)); */ int opts_empty(sp, off, silent) SCR *sp; int off, silent; { char *p; if ((p = O_STR(sp, off)) == NULL || p[0] == '\0') { if (!silent) msgq_str(sp, M_ERR, optlist[off].name, "305|No %s edit option specified"); return (1); } return (0); } /* * opts_dump -- * List the current values of selected options. * * PUBLIC: void opts_dump __P((SCR *, enum optdisp)); */ void opts_dump(sp, type) SCR *sp; enum optdisp type; { OPTLIST const *op; int base, b_num, cnt, col, colwidth, curlen, s_num; int numcols, numrows, row; int b_op[O_OPTIONCOUNT], s_op[O_OPTIONCOUNT]; char nbuf[20]; /* * Options are output in two groups -- those that fit in a column and * those that don't. Output is done on 6 character "tab" boundaries * for no particular reason. (Since we don't output tab characters, * we can ignore the terminal's tab settings.) Ignore the user's tab * setting because we have no idea how reasonable it is. * * Find a column width we can live with, testing from 10 columns to 1. */ for (numcols = 10; numcols > 1; --numcols) { colwidth = sp->cols / numcols & ~(STANDARD_TAB - 1); if (colwidth >= 10) { colwidth = (colwidth + STANDARD_TAB) & ~(STANDARD_TAB - 1); numcols = sp->cols / colwidth; break; } colwidth = 0; } /* * Get the set of options to list, entering them into * the column list or the overflow list. */ for (b_num = s_num = 0, op = optlist; op->name != NULL; ++op) { cnt = op - optlist; /* If OPT_NDISP set, it's never displayed. */ if (F_ISSET(op, OPT_NDISP)) continue; switch (type) { case ALL_DISPLAY: /* Display all. */ break; case CHANGED_DISPLAY: /* Display changed. */ /* If OPT_ADISP set, it's always "changed". */ if (F_ISSET(op, OPT_ADISP)) break; switch (op->type) { case OPT_0BOOL: case OPT_1BOOL: case OPT_NUM: if (O_VAL(sp, cnt) == O_D_VAL(sp, cnt)) continue; break; case OPT_STR: if (O_STR(sp, cnt) == O_D_STR(sp, cnt) || O_D_STR(sp, cnt) != NULL && !strcmp(O_STR(sp, cnt), O_D_STR(sp, cnt))) continue; break; } break; case SELECT_DISPLAY: /* Display selected. */ if (!F_ISSET(&sp->opts[cnt], OPT_SELECTED)) continue; break; default: case NO_DISPLAY: abort(); } F_CLR(&sp->opts[cnt], OPT_SELECTED); curlen = strlen(op->name); switch (op->type) { case OPT_0BOOL: case OPT_1BOOL: if (!O_ISSET(sp, cnt)) curlen += 2; break; case OPT_NUM: (void)snprintf(nbuf, sizeof(nbuf), "%ld", O_VAL(sp, cnt)); curlen += strlen(nbuf); break; case OPT_STR: if (O_STR(sp, cnt) != NULL) curlen += strlen(O_STR(sp, cnt)); curlen += 3; break; } /* Offset by 2 so there's a gap. */ if (curlen <= colwidth - 2) s_op[s_num++] = cnt; else b_op[b_num++] = cnt; } if (s_num > 0) { /* Figure out the number of rows. */ if (s_num > numcols) { numrows = s_num / numcols; if (s_num % numcols) ++numrows; } else numrows = 1; /* Display the options in sorted order. */ for (row = 0; row < numrows;) { for (base = row, col = 0; col < numcols; ++col) { cnt = opts_print(sp, &optlist[s_op[base]]); if ((base += numrows) >= s_num) break; (void)ex_printf(sp, "%*s", (int)(colwidth - cnt), ""); } if (++row < numrows || b_num) (void)ex_puts(sp, "\n"); } } for (row = 0; row < b_num;) { (void)opts_print(sp, &optlist[b_op[row]]); if (++row < b_num) (void)ex_puts(sp, "\n"); } (void)ex_puts(sp, "\n"); } /* * opts_print -- * Print out an option. */ static int opts_print(sp, op) SCR *sp; OPTLIST const *op; { int curlen, offset; curlen = 0; offset = op - optlist; switch (op->type) { case OPT_0BOOL: case OPT_1BOOL: curlen += ex_printf(sp, "%s%s", O_ISSET(sp, offset) ? "" : "no", op->name); break; case OPT_NUM: curlen += ex_printf(sp, "%s=%ld", op->name, O_VAL(sp, offset)); break; case OPT_STR: curlen += ex_printf(sp, "%s=\"%s\"", op->name, O_STR(sp, offset) == NULL ? "" : O_STR(sp, offset)); break; } return (curlen); } /* * opts_save -- * Write the current configuration to a file. * * PUBLIC: int opts_save __P((SCR *, FILE *)); */ int opts_save(sp, fp) SCR *sp; FILE *fp; { OPTLIST const *op; int ch, cnt; char *p; for (op = optlist; op->name != NULL; ++op) { if (F_ISSET(op, OPT_NOSAVE)) continue; cnt = op - optlist; switch (op->type) { case OPT_0BOOL: case OPT_1BOOL: if (O_ISSET(sp, cnt)) (void)fprintf(fp, "set %s\n", op->name); else (void)fprintf(fp, "set no%s\n", op->name); break; case OPT_NUM: (void)fprintf(fp, "set %s=%-3ld\n", op->name, O_VAL(sp, cnt)); break; case OPT_STR: if (O_STR(sp, cnt) == NULL) break; (void)fprintf(fp, "set "); for (p = op->name; (ch = *p) != '\0'; ++p) { if (isblank(ch) || ch == '\\') (void)putc('\\', fp); (void)putc(ch, fp); } (void)putc('=', fp); for (p = O_STR(sp, cnt); (ch = *p) != '\0'; ++p) { if (isblank(ch) || ch == '\\') (void)putc('\\', fp); (void)putc(ch, fp); } (void)putc('\n', fp); break; } if (ferror(fp)) { msgq(sp, M_SYSERR, NULL); return (1); } } return (0); } /* * opts_search -- * Search for an option. * * PUBLIC: OPTLIST const *opts_search __P((char *)); */ OPTLIST const * opts_search(name) char *name; { OPTLIST const *op, *found; OABBREV atmp, *ap; OPTLIST otmp; size_t len; /* Check list of abbreviations. */ atmp.name = name; if ((ap = bsearch(&atmp, abbrev, sizeof(abbrev) / sizeof(OABBREV) - 1, sizeof(OABBREV), opts_abbcmp)) != NULL) return (optlist + ap->offset); /* Check list of options. */ otmp.name = name; if ((op = bsearch(&otmp, optlist, sizeof(optlist) / sizeof(OPTLIST) - 1, sizeof(OPTLIST), opts_cmp)) != NULL) return (op); /* * Check to see if the name is the prefix of one (and only one) * option. If so, return the option. */ len = strlen(name); for (found = NULL, op = optlist; op->name != NULL; ++op) { if (op->name[0] < name[0]) continue; if (op->name[0] > name[0]) break; if (!memcmp(op->name, name, len)) { if (found != NULL) return (NULL); found = op; } } return (found); } /* * opts_nomatch -- * Standard nomatch error message for options. * * PUBLIC: void opts_nomatch __P((SCR *, char *)); */ void opts_nomatch(sp, name) SCR *sp; char *name; { msgq_str(sp, M_ERR, name, "033|set: no %s option: 'set all' gives all option values"); } static int opts_abbcmp(a, b) const void *a, *b; { return(strcmp(((OABBREV *)a)->name, ((OABBREV *)b)->name)); } static int opts_cmp(a, b) const void *a, *b; { return(strcmp(((OPTLIST *)a)->name, ((OPTLIST *)b)->name)); } /* * opts_copy -- * Copy a screen's OPTION array. * * PUBLIC: int opts_copy __P((SCR *, SCR *)); */ int opts_copy(orig, sp) SCR *orig, *sp; { int cnt, rval; /* Copy most everything without change. */ memcpy(sp->opts, orig->opts, sizeof(orig->opts)); /* Copy the string edit options. */ for (cnt = rval = 0; cnt < O_OPTIONCOUNT; ++cnt) { if (optlist[cnt].type != OPT_STR || F_ISSET(&optlist[cnt], OPT_GLOBAL)) continue; /* * If never set, or already failed, NULL out the entries -- * have to continue after failure, otherwise would have two * screens referencing the same memory. */ if (rval || O_STR(sp, cnt) == NULL) { o_set(sp, cnt, OS_NOFREE | OS_STR, NULL, 0); o_set(sp, cnt, OS_DEF | OS_NOFREE | OS_STR, NULL, 0); continue; } /* Copy the current string. */ if (o_set(sp, cnt, OS_NOFREE | OS_STRDUP, O_STR(sp, cnt), 0)) { o_set(sp, cnt, OS_DEF | OS_NOFREE | OS_STR, NULL, 0); goto nomem; } /* Copy the default string. */ if (O_D_STR(sp, cnt) != NULL && o_set(sp, cnt, OS_DEF | OS_NOFREE | OS_STRDUP, O_D_STR(sp, cnt), 0)) { nomem: msgq(orig, M_SYSERR, NULL); rval = 1; } } return (rval); } /* * opts_free -- * Free all option strings * * PUBLIC: void opts_free __P((SCR *)); */ void opts_free(sp) SCR *sp; { int cnt; for (cnt = 0; cnt < O_OPTIONCOUNT; ++cnt) { if (optlist[cnt].type != OPT_STR || F_ISSET(&optlist[cnt], OPT_GLOBAL)) continue; if (O_STR(sp, cnt) != NULL) free(O_STR(sp, cnt)); if (O_D_STR(sp, cnt) != NULL) free(O_D_STR(sp, cnt)); } } Index: stable/9/contrib/nvi/include/options_def.h =================================================================== --- stable/9/contrib/nvi/include/options_def.h (revision 290040) +++ stable/9/contrib/nvi/include/options_def.h (revision 290041) @@ -1,80 +1,80 @@ #define O_ALTWERASE 0 #define O_AUTOINDENT 1 #define O_AUTOPRINT 2 #define O_AUTOWRITE 3 #define O_BACKUP 4 #define O_BEAUTIFY 5 #define O_CDPATH 6 #define O_CEDIT 7 #define O_COLUMNS 8 #define O_COMMENT 9 -#define O_DIRECTORY 10 +#define O_TMP_DIRECTORY 10 #define O_EDCOMPATIBLE 11 #define O_ESCAPETIME 12 #define O_ERRORBELLS 13 #define O_EXRC 14 #define O_EXTENDED 15 #define O_FILEC 16 #define O_FLASH 17 #define O_GTAGSMODE 18 #define O_HARDTABS 19 #define O_ICLOWER 20 #define O_IGNORECASE 21 #define O_KEYTIME 22 #define O_LEFTRIGHT 23 #define O_LINES 24 #define O_LISP 25 #define O_LIST 26 #define O_LOCKFILES 27 #define O_MAGIC 28 #define O_MATCHTIME 29 #define O_MESG 30 #define O_MODELINE 31 #define O_MSGCAT 32 #define O_NOPRINT 33 #define O_NUMBER 34 #define O_OCTAL 35 #define O_OPEN 36 #define O_OPTIMIZE 37 #define O_PARAGRAPHS 38 #define O_PATH 39 #define O_PRINT 40 #define O_PROMPT 41 #define O_READONLY 42 #define O_RECDIR 43 #define O_REDRAW 44 #define O_REMAP 45 #define O_REPORT 46 #define O_RULER 47 #define O_SCROLL 48 #define O_SEARCHINCR 49 #define O_SECTIONS 50 #define O_SECURE 51 #define O_SHELL 52 #define O_SHELLMETA 53 #define O_SHIFTWIDTH 54 #define O_SHOWMATCH 55 #define O_SHOWMODE 56 #define O_SIDESCROLL 57 #define O_SLOWOPEN 58 #define O_SOURCEANY 59 #define O_TABSTOP 60 #define O_TAGLENGTH 61 #define O_TAGS 62 #define O_TERM 63 #define O_TERSE 64 #define O_TILDEOP 65 #define O_TIMEOUT 66 #define O_TTYWERASE 67 #define O_VERBOSE 68 #define O_W1200 69 #define O_W300 70 #define O_W9600 71 #define O_WARN 72 #define O_WINDOW 73 #define O_WINDOWNAME 74 #define O_WRAPLEN 75 #define O_WRAPMARGIN 76 #define O_WRAPSCAN 77 #define O_WRITEANY 78 #define O_OPTIONCOUNT 79 Index: stable/9/contrib/nvi =================================================================== --- stable/9/contrib/nvi (revision 290040) +++ stable/9/contrib/nvi (revision 290041) Property changes on: stable/9/contrib/nvi ___________________________________________________________________ Modified: svn:mergeinfo ## -0,0 +0,1 ## Merged /head/contrib/nvi:r250667 Index: stable/9/contrib =================================================================== --- stable/9/contrib (revision 290040) +++ stable/9/contrib (revision 290041) Property changes on: stable/9/contrib ___________________________________________________________________ Modified: svn:mergeinfo ## -0,0 +0,1 ## Merged /head/contrib:r250667 Index: stable/9 =================================================================== --- stable/9 (revision 290040) +++ stable/9 (revision 290041) Property changes on: stable/9 ___________________________________________________________________ Modified: svn:mergeinfo ## -0,0 +0,1 ## Merged /head:r250667