aboutsummaryrefslogtreecommitdiffstats
path: root/common
diff options
context:
space:
mode:
authorPeter Wemm <peter@FreeBSD.org>2013-08-11 08:38:10 +0000
committerPeter Wemm <peter@FreeBSD.org>2013-08-11 08:38:10 +0000
commit84ed61ee234d2654ec965be5bfdda4269f9dc4fd (patch)
treefcfc8dee7b416cacdea763f18f34e0930234186a /common
parent17c17f89c51e74499873b6115c9516b3302f2eb6 (diff)
downloadsrc-84ed61ee234d2654ec965be5bfdda4269f9dc4fd.tar.gz
src-84ed61ee234d2654ec965be5bfdda4269f9dc4fd.zip
Post-cvs2svn flatten pass.
Notes
Notes: svn path=/vendor/nvi/dist/; revision=254210
Diffstat (limited to 'common')
-rw-r--r--common/api.c525
-rw-r--r--common/args.h29
-rw-r--r--common/common.h96
-rw-r--r--common/cut.c368
-rw-r--r--common/cut.h77
-rw-r--r--common/delete.c160
-rw-r--r--common/exf.c1498
-rw-r--r--common/exf.h82
-rw-r--r--common/gs.h210
-rw-r--r--common/key.c865
-rw-r--r--common/key.h222
-rw-r--r--common/line.c576
-rw-r--r--common/log.c717
-rw-r--r--common/log.h20
-rw-r--r--common/main.c617
-rw-r--r--common/mark.c277
-rw-r--r--common/mark.h42
-rw-r--r--common/mem.h168
-rw-r--r--common/msg.c895
-rw-r--r--common/msg.h65
-rw-r--r--common/options.awk9
-rw-r--r--common/options.c1141
-rw-r--r--common/options.h101
-rw-r--r--common/options_f.c367
-rw-r--r--common/put.c231
-rw-r--r--common/recover.c878
-rw-r--r--common/screen.c233
-rw-r--r--common/screen.h203
-rw-r--r--common/search.c492
-rw-r--r--common/seq.c395
-rw-r--r--common/seq.h44
-rw-r--r--common/util.c230
-rw-r--r--common/util.h56
33 files changed, 11889 insertions, 0 deletions
diff --git a/common/api.c b/common/api.c
new file mode 100644
index 000000000000..35d9f0c8f66e
--- /dev/null
+++ b/common/api.c
@@ -0,0 +1,525 @@
+/*-
+ * 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.
+ * Copyright (c) 1995
+ * George V. Neville-Neil. All rights reserved.
+ *
+ * See the LICENSE file for redistribution information.
+ */
+
+#include "config.h"
+
+#ifndef lint
+static const char sccsid[] = "@(#)api.c 8.26 (Berkeley) 10/14/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/time.h>
+
+#include <bitstring.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "../common/common.h"
+
+extern GS *__global_list; /* XXX */
+
+/*
+ * api_fscreen --
+ * Return a pointer to the screen specified by the screen id
+ * or a file name.
+ *
+ * PUBLIC: SCR *api_fscreen __P((int, char *));
+ */
+SCR *
+api_fscreen(id, name)
+ int id;
+ char *name;
+{
+ GS *gp;
+ SCR *tsp;
+
+ gp = __global_list;
+
+ /* Search the displayed list. */
+ for (tsp = gp->dq.cqh_first;
+ tsp != (void *)&gp->dq; tsp = tsp->q.cqe_next)
+ if (name == NULL) {
+ if (id == tsp->id)
+ return (tsp);
+ } else if (!strcmp(name, tsp->frp->name))
+ return (tsp);
+
+ /* Search the hidden list. */
+ for (tsp = gp->hq.cqh_first;
+ tsp != (void *)&gp->hq; tsp = tsp->q.cqe_next)
+ if (name == NULL) {
+ if (id == tsp->id)
+ return (tsp);
+ } else if (!strcmp(name, tsp->frp->name))
+ return (tsp);
+ return (NULL);
+}
+
+/*
+ * api_aline --
+ * Append a line.
+ *
+ * PUBLIC: int api_aline __P((SCR *, recno_t, char *, size_t));
+ */
+int
+api_aline(sp, lno, line, len)
+ SCR *sp;
+ recno_t lno;
+ char *line;
+ size_t len;
+{
+ return (db_append(sp, 1, lno, line, len));
+}
+
+/*
+ * api_dline --
+ * Delete a line.
+ *
+ * PUBLIC: int api_dline __P((SCR *, recno_t));
+ */
+int
+api_dline(sp, lno)
+ SCR *sp;
+ recno_t lno;
+{
+ return (db_delete(sp, lno));
+}
+
+/*
+ * api_gline --
+ * Get a line.
+ *
+ * PUBLIC: int api_gline __P((SCR *, recno_t, char **, size_t *));
+ */
+int
+api_gline(sp, lno, linepp, lenp)
+ SCR *sp;
+ recno_t lno;
+ char **linepp;
+ size_t *lenp;
+{
+ int isempty;
+
+ if (db_eget(sp, lno, linepp, lenp, &isempty)) {
+ if (isempty)
+ msgq(sp, M_ERR, "209|The file is empty");
+ return (1);
+ }
+ return (0);
+}
+
+/*
+ * api_iline --
+ * Insert a line.
+ *
+ * PUBLIC: int api_iline __P((SCR *, recno_t, char *, size_t));
+ */
+int
+api_iline(sp, lno, line, len)
+ SCR *sp;
+ recno_t lno;
+ char *line;
+ size_t len;
+{
+ return (db_insert(sp, lno, line, len));
+}
+
+/*
+ * api_lline --
+ * Return the line number of the last line in the file.
+ *
+ * PUBLIC: int api_lline __P((SCR *, recno_t *));
+ */
+int
+api_lline(sp, lnop)
+ SCR *sp;
+ recno_t *lnop;
+{
+ return (db_last(sp, lnop));
+}
+
+/*
+ * api_sline --
+ * Set a line.
+ *
+ * PUBLIC: int api_sline __P((SCR *, recno_t, char *, size_t));
+ */
+int
+api_sline(sp, lno, line, len)
+ SCR *sp;
+ recno_t lno;
+ char *line;
+ size_t len;
+{
+ return (db_set(sp, lno, line, len));
+}
+
+/*
+ * api_getmark --
+ * Get the mark.
+ *
+ * PUBLIC: int api_getmark __P((SCR *, int, MARK *));
+ */
+int
+api_getmark(sp, markname, mp)
+ SCR *sp;
+ int markname;
+ MARK *mp;
+{
+ return (mark_get(sp, (ARG_CHAR_T)markname, mp, M_ERR));
+}
+
+/*
+ * api_setmark --
+ * Set the mark.
+ *
+ * PUBLIC: int api_setmark __P((SCR *, int, MARK *));
+ */
+int
+api_setmark(sp, markname, mp)
+ SCR *sp;
+ int markname;
+ MARK *mp;
+{
+ return (mark_set(sp, (ARG_CHAR_T)markname, mp, 1));
+}
+
+/*
+ * api_nextmark --
+ * Return the first mark if next not set, otherwise return the
+ * subsequent mark.
+ *
+ * PUBLIC: int api_nextmark __P((SCR *, int, char *));
+ */
+int
+api_nextmark(sp, next, namep)
+ SCR *sp;
+ int next;
+ char *namep;
+{
+ LMARK *mp;
+
+ mp = sp->ep->marks.lh_first;
+ if (next)
+ for (; mp != NULL; mp = mp->q.le_next)
+ if (mp->name == *namep) {
+ mp = mp->q.le_next;
+ break;
+ }
+ if (mp == NULL)
+ return (1);
+ *namep = mp->name;
+ return (0);
+}
+
+/*
+ * api_getcursor --
+ * Get the cursor.
+ *
+ * PUBLIC: int api_getcursor __P((SCR *, MARK *));
+ */
+int
+api_getcursor(sp, mp)
+ SCR *sp;
+ MARK *mp;
+{
+ mp->lno = sp->lno;
+ mp->cno = sp->cno;
+ return (0);
+}
+
+/*
+ * api_setcursor --
+ * Set the cursor.
+ *
+ * PUBLIC: int api_setcursor __P((SCR *, MARK *));
+ */
+int
+api_setcursor(sp, mp)
+ SCR *sp;
+ MARK *mp;
+{
+ size_t len;
+
+ if (db_get(sp, mp->lno, DBG_FATAL, NULL, &len))
+ return (1);
+ if (mp->cno < 0 || mp->cno > len) {
+ msgq(sp, M_ERR, "Cursor set to nonexistent column");
+ return (1);
+ }
+
+ /* Set the cursor. */
+ sp->lno = mp->lno;
+ sp->cno = mp->cno;
+ return (0);
+}
+
+/*
+ * api_emessage --
+ * Print an error message.
+ *
+ * PUBLIC: void api_emessage __P((SCR *, char *));
+ */
+void
+api_emessage(sp, text)
+ SCR *sp;
+ char *text;
+{
+ msgq(sp, M_ERR, "%s", text);
+}
+
+/*
+ * api_imessage --
+ * Print an informational message.
+ *
+ * PUBLIC: void api_imessage __P((SCR *, char *));
+ */
+void
+api_imessage(sp, text)
+ SCR *sp;
+ char *text;
+{
+ msgq(sp, M_INFO, "%s", text);
+}
+
+/*
+ * api_edit
+ * Create a new screen and return its id
+ * or edit a new file in the current screen.
+ *
+ * PUBLIC: int api_edit __P((SCR *, char *, SCR **, int));
+ */
+int
+api_edit(sp, file, spp, newscreen)
+ SCR *sp;
+ char *file;
+ SCR **spp;
+ int newscreen;
+{
+ ARGS *ap[2], a;
+ EXCMD cmd;
+
+ if (file) {
+ ex_cinit(&cmd, C_EDIT, 0, OOBLNO, OOBLNO, 0, ap);
+ ex_cadd(&cmd, &a, file, strlen(file));
+ } else
+ ex_cinit(&cmd, C_EDIT, 0, OOBLNO, OOBLNO, 0, NULL);
+ if (newscreen)
+ cmd.flags |= E_NEWSCREEN; /* XXX */
+ if (cmd.cmd->fn(sp, &cmd))
+ return (1);
+ *spp = sp->nextdisp;
+ return (0);
+}
+
+/*
+ * api_escreen
+ * End a screen.
+ *
+ * PUBLIC: int api_escreen __P((SCR *));
+ */
+int
+api_escreen(sp)
+ SCR *sp;
+{
+ EXCMD cmd;
+
+ /*
+ * XXX
+ * If the interpreter exits anything other than the current
+ * screen, vi isn't going to update everything correctly.
+ */
+ ex_cinit(&cmd, C_QUIT, 0, OOBLNO, OOBLNO, 0, NULL);
+ return (cmd.cmd->fn(sp, &cmd));
+}
+
+/*
+ * api_swscreen --
+ * Switch to a new screen.
+ *
+ * PUBLIC: int api_swscreen __P((SCR *, SCR *));
+ */
+int
+api_swscreen(sp, new)
+ SCR *sp, *new;
+{
+ /*
+ * XXX
+ * If the interpreter switches from anything other than the
+ * current screen, vi isn't going to update everything correctly.
+ */
+ sp->nextdisp = new;
+ F_SET(sp, SC_SSWITCH);
+
+ return (0);
+}
+
+/*
+ * api_map --
+ * Map a key.
+ *
+ * PUBLIC: int api_map __P((SCR *, char *, char *, size_t));
+ */
+int
+api_map(sp, name, map, len)
+ SCR *sp;
+ char *name, *map;
+ size_t len;
+{
+ ARGS *ap[3], a, b;
+ EXCMD cmd;
+
+ ex_cinit(&cmd, C_MAP, 0, OOBLNO, OOBLNO, 0, ap);
+ ex_cadd(&cmd, &a, name, strlen(name));
+ ex_cadd(&cmd, &b, map, len);
+ return (cmd.cmd->fn(sp, &cmd));
+}
+
+/*
+ * api_unmap --
+ * Unmap a key.
+ *
+ * PUBLIC: int api_unmap __P((SCR *, char *));
+ */
+int
+api_unmap(sp, name)
+ SCR *sp;
+ char *name;
+{
+ ARGS *ap[2], a;
+ EXCMD cmd;
+
+ ex_cinit(&cmd, C_UNMAP, 0, OOBLNO, OOBLNO, 0, ap);
+ ex_cadd(&cmd, &a, name, strlen(name));
+ return (cmd.cmd->fn(sp, &cmd));
+}
+
+/*
+ * api_opts_get --
+ * Return a option value as a string, in allocated memory.
+ * If the option is of type boolean, boolvalue is (un)set
+ * according to the value; otherwise boolvalue is -1.
+ *
+ * PUBLIC: int api_opts_get __P((SCR *, char *, char **, int *));
+ */
+int
+api_opts_get(sp, name, value, boolvalue)
+ SCR *sp;
+ char *name, **value;
+ int *boolvalue;
+{
+ OPTLIST const *op;
+ int offset;
+
+ if ((op = opts_search(name)) == NULL) {
+ opts_nomatch(sp, name);
+ return (1);
+ }
+
+ offset = op - optlist;
+ if (boolvalue != NULL)
+ *boolvalue = -1;
+ switch (op->type) {
+ case OPT_0BOOL:
+ case OPT_1BOOL:
+ MALLOC_RET(sp, *value, char *, strlen(op->name) + 2 + 1);
+ (void)sprintf(*value,
+ "%s%s", O_ISSET(sp, offset) ? "" : "no", op->name);
+ if (boolvalue != NULL)
+ *boolvalue = O_ISSET(sp, offset);
+ break;
+ case OPT_NUM:
+ MALLOC_RET(sp, *value, char *, 20);
+ (void)sprintf(*value, "%lu", (u_long)O_VAL(sp, offset));
+ break;
+ case OPT_STR:
+ if (O_STR(sp, offset) == NULL) {
+ MALLOC_RET(sp, *value, char *, 2);
+ value[0] = '\0';
+ } else {
+ MALLOC_RET(sp,
+ *value, char *, strlen(O_STR(sp, offset)) + 1);
+ (void)sprintf(*value, "%s", O_STR(sp, offset));
+ }
+ break;
+ }
+ return (0);
+}
+
+/*
+ * api_opts_set --
+ * Set options.
+ *
+ * PUBLIC: int api_opts_set __P((SCR *, char *, char *, u_long, int));
+ */
+int
+api_opts_set(sp, name, str_value, num_value, bool_value)
+ SCR *sp;
+ char *name, *str_value;
+ u_long num_value;
+ int bool_value;
+{
+ ARGS *ap[2], a, b;
+ OPTLIST const *op;
+ int rval;
+ size_t blen;
+ char *bp;
+
+ if ((op = opts_search(name)) == NULL) {
+ opts_nomatch(sp, name);
+ return (1);
+ }
+
+ switch (op->type) {
+ case OPT_0BOOL:
+ case OPT_1BOOL:
+ GET_SPACE_RET(sp, bp, blen, 64);
+ a.len = snprintf(bp, 64, "%s%s", bool_value ? "" : "no", name);
+ break;
+ case OPT_NUM:
+ GET_SPACE_RET(sp, bp, blen, 64);
+ a.len = snprintf(bp, 64, "%s=%lu", name, num_value);
+ break;
+ case OPT_STR:
+ GET_SPACE_RET(sp, bp, blen, 1024);
+ a.len = snprintf(bp, 1024, "%s=%s", name, str_value);
+ break;
+ }
+ a.bp = bp;
+ b.len = 0;
+ b.bp = NULL;
+ ap[0] = &a;
+ ap[1] = &b;
+ rval = opts_set(sp, ap, NULL);
+
+ FREE_SPACE(sp, bp, blen);
+
+ return (rval);
+}
+
+/*
+ * api_run_str --
+ * Execute a string as an ex command.
+ *
+ * PUBLIC: int api_run_str __P((SCR *, char *));
+ */
+int
+api_run_str(sp, cmd)
+ SCR *sp;
+ char *cmd;
+{
+ return (ex_run_str(sp, NULL, cmd, strlen(cmd), 0, 0));
+}
diff --git a/common/args.h b/common/args.h
new file mode 100644
index 000000000000..e84dc2ca04c1
--- /dev/null
+++ b/common/args.h
@@ -0,0 +1,29 @@
+/*-
+ * Copyright (c) 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 1993, 1994, 1995, 1996
+ * Keith Bostic. All rights reserved.
+ *
+ * See the LICENSE file for redistribution information.
+ *
+ * @(#)args.h 10.2 (Berkeley) 3/6/96
+ */
+
+/*
+ * Structure for building "argc/argv" vector of arguments.
+ *
+ * !!!
+ * All arguments are nul terminated as well as having an associated length.
+ * The argument vector is NOT necessarily NULL terminated. The proper way
+ * to check the number of arguments is to use the argc value in the EXCMDARG
+ * structure or to walk the array until an ARGS structure with a length of 0
+ * is found.
+ */
+typedef struct _args {
+ CHAR_T *bp; /* Argument. */
+ size_t blen; /* Buffer length. */
+ size_t len; /* Argument length. */
+
+#define A_ALLOCATED 0x01 /* If allocated space. */
+ u_int8_t flags;
+} ARGS;
diff --git a/common/common.h b/common/common.h
new file mode 100644
index 000000000000..0e13fc80b844
--- /dev/null
+++ b/common/common.h
@@ -0,0 +1,96 @@
+/*-
+ * 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.
+ *
+ * @(#)common.h 10.13 (Berkeley) 9/25/96
+ */
+
+/*
+ * Porting information built at configuration time. Included before
+ * any of nvi's include files.
+ */
+#include "port.h"
+
+/*
+ * Pseudo-local includes. These are files that are unlikely to exist
+ * on most machines to which we're porting vi, and we want to include
+ * them in a very specific order, regardless.
+ */
+#include <db.h>
+#include <regex.h>
+
+/*
+ * Forward structure declarations. Not pretty, but the include files
+ * are far too interrelated for a clean solution.
+ */
+typedef struct _cb CB;
+typedef struct _csc CSC;
+typedef struct _event EVENT;
+typedef struct _excmd EXCMD;
+typedef struct _exf EXF;
+typedef struct _fref FREF;
+typedef struct _gs GS;
+typedef struct _lmark LMARK;
+typedef struct _mark MARK;
+typedef struct _msg MSGS;
+typedef struct _option OPTION;
+typedef struct _optlist OPTLIST;
+typedef struct _scr SCR;
+typedef struct _script SCRIPT;
+typedef struct _seq SEQ;
+typedef struct _tag TAG;
+typedef struct _tagf TAGF;
+typedef struct _tagq TAGQ;
+typedef struct _text TEXT;
+
+/* Autoindent state. */
+typedef enum { C_NOTSET, C_CARATSET, C_NOCHANGE, C_ZEROSET } carat_t;
+
+/* Busy message types. */
+typedef enum { BUSY_ON = 1, BUSY_OFF, BUSY_UPDATE } busy_t;
+
+/*
+ * Routines that return a confirmation return:
+ *
+ * CONF_NO User answered no.
+ * CONF_QUIT User answered quit, eof or an error.
+ * CONF_YES User answered yes.
+ */
+typedef enum { CONF_NO, CONF_QUIT, CONF_YES } conf_t;
+
+/* Directions. */
+typedef enum { NOTSET, FORWARD, BACKWARD } dir_t;
+
+/* Line operations. */
+typedef enum { LINE_APPEND, LINE_DELETE, LINE_INSERT, LINE_RESET } lnop_t;
+
+/* Lock return values. */
+typedef enum { LOCK_FAILED, LOCK_SUCCESS, LOCK_UNAVAIL } lockr_t;
+
+/* Sequence types. */
+typedef enum { SEQ_ABBREV, SEQ_COMMAND, SEQ_INPUT } seq_t;
+
+/*
+ * Local includes.
+ */
+#include "key.h" /* Required by args.h. */
+#include "args.h" /* Required by options.h. */
+#include "options.h" /* Required by screen.h. */
+
+#include "msg.h" /* Required by gs.h. */
+#include "cut.h" /* Required by gs.h. */
+#include "seq.h" /* Required by screen.h. */
+#include "util.h" /* Required by ex.h. */
+#include "mark.h" /* Required by gs.h. */
+#include "../ex/ex.h" /* Required by gs.h. */
+#include "gs.h" /* Required by screen.h. */
+#include "screen.h" /* Required by exf.h. */
+#include "exf.h"
+#include "log.h"
+#include "mem.h"
+
+#include "com_extern.h"
diff --git a/common/cut.c b/common/cut.c
new file mode 100644
index 000000000000..faceecd11166
--- /dev/null
+++ b/common/cut.c
@@ -0,0 +1,368 @@
+/*-
+ * 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
+static const char sccsid[] = "@(#)cut.c 10.10 (Berkeley) 9/15/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+
+#include <bitstring.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "common.h"
+
+static void cb_rotate __P((SCR *));
+
+/*
+ * cut --
+ * Put a range of lines/columns into a TEXT buffer.
+ *
+ * There are two buffer areas, both found in the global structure. The first
+ * is the linked list of all the buffers the user has named, the second is the
+ * unnamed buffer storage. There is a pointer, too, which is the current
+ * default buffer, i.e. it may point to the unnamed buffer or a named buffer
+ * depending on into what buffer the last text was cut. Logically, in both
+ * delete and yank operations, if the user names a buffer, the text is cut
+ * into it. If it's a delete of information on more than a single line, the
+ * contents of the numbered buffers are rotated up one, the contents of the
+ * buffer named '9' are discarded, and the text is cut into the buffer named
+ * '1'. The text is always cut into the unnamed buffer.
+ *
+ * In all cases, upper-case buffer names are the same as lower-case names,
+ * with the exception that they cause the buffer to be appended to instead
+ * of replaced. Note, however, that if text is appended to a buffer, the
+ * default buffer only contains the appended text, not the entire contents
+ * of the buffer.
+ *
+ * !!!
+ * The contents of the default buffer would disappear after most operations
+ * in historic vi. It's unclear that this is useful, so we don't bother.
+ *
+ * When users explicitly cut text into the numeric buffers, historic vi became
+ * genuinely strange. I've never been able to figure out what was supposed to
+ * happen. It behaved differently if you deleted text than if you yanked text,
+ * and, in the latter case, the text was appended to the buffer instead of
+ * replacing the contents. Hopefully it's not worth getting right, and here
+ * we just treat the numeric buffers like any other named buffer.
+ *
+ * PUBLIC: int cut __P((SCR *, CHAR_T *, MARK *, MARK *, int));
+ */
+int
+cut(sp, namep, fm, tm, flags)
+ SCR *sp;
+ CHAR_T *namep;
+ MARK *fm, *tm;
+ int flags;
+{
+ CB *cbp;
+ CHAR_T name;
+ recno_t lno;
+ int append, copy_one, copy_def;
+
+ /*
+ * If the user specified a buffer, put it there. (This may require
+ * a copy into the numeric buffers. We do the copy so that we don't
+ * have to reference count and so we don't have to deal with things
+ * like appends to buffers that are used multiple times.)
+ *
+ * Otherwise, if it's supposed to be put in a numeric buffer (usually
+ * a delete) put it there. The rules for putting things in numeric
+ * buffers were historically a little strange. There were three cases.
+ *
+ * 1: Some motions are always line mode motions, which means
+ * that the cut always goes into the numeric buffers.
+ * 2: Some motions aren't line mode motions, e.g. d10w, but
+ * can cross line boundaries. For these commands, if the
+ * cut crosses a line boundary, it goes into the numeric
+ * buffers. This includes most of the commands.
+ * 3: Some motions aren't line mode motions, e.g. d`<char>,
+ * but always go into the numeric buffers, regardless. This
+ * was the commands: % ` / ? ( ) N n { } -- and nvi adds ^A.
+ *
+ * Otherwise, put it in the unnamed buffer.
+ */
+ append = copy_one = copy_def = 0;
+ if (namep != NULL) {
+ name = *namep;
+ if (LF_ISSET(CUT_NUMREQ) || LF_ISSET(CUT_NUMOPT) &&
+ (LF_ISSET(CUT_LINEMODE) || fm->lno != tm->lno)) {
+ copy_one = 1;
+ cb_rotate(sp);
+ }
+ if ((append = isupper(name)) == 1) {
+ if (!copy_one)
+ copy_def = 1;
+ name = tolower(name);
+ }
+namecb: CBNAME(sp, cbp, name);
+ } else if (LF_ISSET(CUT_NUMREQ) || LF_ISSET(CUT_NUMOPT) &&
+ (LF_ISSET(CUT_LINEMODE) || fm->lno != tm->lno)) {
+ name = '1';
+ cb_rotate(sp);
+ goto namecb;
+ } else
+ cbp = &sp->gp->dcb_store;
+
+copyloop:
+ /*
+ * If this is a new buffer, create it and add it into the list.
+ * Otherwise, if it's not an append, free its current contents.
+ */
+ if (cbp == NULL) {
+ CALLOC_RET(sp, cbp, CB *, 1, sizeof(CB));
+ cbp->name = name;
+ CIRCLEQ_INIT(&cbp->textq);
+ LIST_INSERT_HEAD(&sp->gp->cutq, cbp, q);
+ } else if (!append) {
+ text_lfree(&cbp->textq);
+ cbp->len = 0;
+ cbp->flags = 0;
+ }
+
+
+#define ENTIRE_LINE 0
+ /* In line mode, it's pretty easy, just cut the lines. */
+ if (LF_ISSET(CUT_LINEMODE)) {
+ cbp->flags |= CB_LMODE;
+ for (lno = fm->lno; lno <= tm->lno; ++lno)
+ if (cut_line(sp, lno, 0, 0, cbp))
+ goto cut_line_err;
+ } else {
+ /*
+ * Get the first line. A length of 0 causes cut_line
+ * to cut from the MARK to the end of the line.
+ */
+ if (cut_line(sp, fm->lno, fm->cno, fm->lno != tm->lno ?
+ ENTIRE_LINE : (tm->cno - fm->cno) + 1, cbp))
+ goto cut_line_err;
+
+ /* Get the intermediate lines. */
+ for (lno = fm->lno; ++lno < tm->lno;)
+ if (cut_line(sp, lno, 0, ENTIRE_LINE, cbp))
+ goto cut_line_err;
+
+ /* Get the last line. */
+ if (tm->lno != fm->lno &&
+ cut_line(sp, lno, 0, tm->cno + 1, cbp))
+ goto cut_line_err;
+ }
+
+ append = 0; /* Only append to the named buffer. */
+ sp->gp->dcbp = cbp; /* Repoint the default buffer on each pass. */
+
+ if (copy_one) { /* Copy into numeric buffer 1. */
+ name = '1';
+ CBNAME(sp, cbp, name);
+ copy_one = 0;
+ goto copyloop;
+ }
+ if (copy_def) { /* Copy into the default buffer. */
+ cbp = &sp->gp->dcb_store;
+ copy_def = 0;
+ goto copyloop;
+ }
+ return (0);
+
+cut_line_err:
+ text_lfree(&cbp->textq);
+ cbp->len = 0;
+ cbp->flags = 0;
+ return (1);
+}
+
+/*
+ * cb_rotate --
+ * Rotate the numbered buffers up one.
+ */
+static void
+cb_rotate(sp)
+ SCR *sp;
+{
+ CB *cbp, *del_cbp;
+
+ del_cbp = NULL;
+ for (cbp = sp->gp->cutq.lh_first; cbp != NULL; cbp = cbp->q.le_next)
+ switch(cbp->name) {
+ case '1':
+ cbp->name = '2';
+ break;
+ case '2':
+ cbp->name = '3';
+ break;
+ case '3':
+ cbp->name = '4';
+ break;
+ case '4':
+ cbp->name = '5';
+ break;
+ case '5':
+ cbp->name = '6';
+ break;
+ case '6':
+ cbp->name = '7';
+ break;
+ case '7':
+ cbp->name = '8';
+ break;
+ case '8':
+ cbp->name = '9';
+ break;
+ case '9':
+ del_cbp = cbp;
+ break;
+ }
+ if (del_cbp != NULL) {
+ LIST_REMOVE(del_cbp, q);
+ text_lfree(&del_cbp->textq);
+ free(del_cbp);
+ }
+}
+
+/*
+ * cut_line --
+ * Cut a portion of a single line.
+ *
+ * PUBLIC: int cut_line __P((SCR *, recno_t, size_t, size_t, CB *));
+ */
+int
+cut_line(sp, lno, fcno, clen, cbp)
+ SCR *sp;
+ recno_t lno;
+ size_t fcno, clen;
+ CB *cbp;
+{
+ TEXT *tp;
+ size_t len;
+ char *p;
+
+ /* Get the line. */
+ if (db_get(sp, lno, DBG_FATAL, &p, &len))
+ return (1);
+
+ /* Create a TEXT structure that can hold the entire line. */
+ if ((tp = text_init(sp, NULL, 0, len)) == NULL)
+ return (1);
+
+ /*
+ * If the line isn't empty and it's not the entire line,
+ * copy the portion we want, and reset the TEXT length.
+ */
+ if (len != 0) {
+ if (clen == 0)
+ clen = len - fcno;
+ memcpy(tp->lb, p + fcno, clen);
+ tp->len = clen;
+ }
+
+ /* Append to the end of the cut buffer. */
+ CIRCLEQ_INSERT_TAIL(&cbp->textq, tp, q);
+ cbp->len += tp->len;
+
+ return (0);
+}
+
+/*
+ * cut_close --
+ * Discard all cut buffers.
+ *
+ * PUBLIC: void cut_close __P((GS *));
+ */
+void
+cut_close(gp)
+ GS *gp;
+{
+ CB *cbp;
+
+ /* Free cut buffer list. */
+ while ((cbp = gp->cutq.lh_first) != NULL) {
+ if (cbp->textq.cqh_first != (void *)&cbp->textq)
+ text_lfree(&cbp->textq);
+ LIST_REMOVE(cbp, q);
+ free(cbp);
+ }
+
+ /* Free default cut storage. */
+ cbp = &gp->dcb_store;
+ if (cbp->textq.cqh_first != (void *)&cbp->textq)
+ text_lfree(&cbp->textq);
+}
+
+/*
+ * text_init --
+ * Allocate a new TEXT structure.
+ *
+ * PUBLIC: TEXT *text_init __P((SCR *, const char *, size_t, size_t));
+ */
+TEXT *
+text_init(sp, p, len, total_len)
+ SCR *sp;
+ const char *p;
+ size_t len, total_len;
+{
+ TEXT *tp;
+
+ CALLOC(sp, tp, TEXT *, 1, sizeof(TEXT));
+ if (tp == NULL)
+ return (NULL);
+ /* ANSI C doesn't define a call to malloc(3) for 0 bytes. */
+ if ((tp->lb_len = total_len) != 0) {
+ MALLOC(sp, tp->lb, CHAR_T *, tp->lb_len);
+ if (tp->lb == NULL) {
+ free(tp);
+ return (NULL);
+ }
+ if (p != NULL && len != 0)
+ memcpy(tp->lb, p, len);
+ }
+ tp->len = len;
+ return (tp);
+}
+
+/*
+ * text_lfree --
+ * Free a chain of text structures.
+ *
+ * PUBLIC: void text_lfree __P((TEXTH *));
+ */
+void
+text_lfree(headp)
+ TEXTH *headp;
+{
+ TEXT *tp;
+
+ while ((tp = headp->cqh_first) != (void *)headp) {
+ CIRCLEQ_REMOVE(headp, tp, q);
+ text_free(tp);
+ }
+}
+
+/*
+ * text_free --
+ * Free a text structure.
+ *
+ * PUBLIC: void text_free __P((TEXT *));
+ */
+void
+text_free(tp)
+ TEXT *tp;
+{
+ if (tp->lb != NULL)
+ free(tp->lb);
+ free(tp);
+}
diff --git a/common/cut.h b/common/cut.h
new file mode 100644
index 000000000000..43f3ca817efd
--- /dev/null
+++ b/common/cut.h
@@ -0,0 +1,77 @@
+/*-
+ * 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.
+ *
+ * @(#)cut.h 10.5 (Berkeley) 4/3/96
+ */
+
+typedef struct _texth TEXTH; /* TEXT list head structure. */
+CIRCLEQ_HEAD(_texth, _text);
+
+/* Cut buffers. */
+struct _cb {
+ LIST_ENTRY(_cb) q; /* Linked list of cut buffers. */
+ TEXTH textq; /* Linked list of TEXT structures. */
+ CHAR_T name; /* Cut buffer name. */
+ size_t len; /* Total length of cut text. */
+
+#define CB_LMODE 0x01 /* Cut was in line mode. */
+ u_int8_t flags;
+};
+
+/* Lines/blocks of text. */
+struct _text { /* Text: a linked list of lines. */
+ CIRCLEQ_ENTRY(_text) q; /* Linked list of text structures. */
+ char *lb; /* Line buffer. */
+ size_t lb_len; /* Line buffer length. */
+ size_t len; /* Line length. */
+
+ /* These fields are used by the vi text input routine. */
+ recno_t lno; /* 1-N: file line. */
+ size_t cno; /* 0-N: file character in line. */
+ size_t ai; /* 0-N: autoindent bytes. */
+ size_t insert; /* 0-N: bytes to insert (push). */
+ size_t offset; /* 0-N: initial, unerasable chars. */
+ size_t owrite; /* 0-N: chars to overwrite. */
+ size_t R_erase; /* 0-N: 'R' erase count. */
+ size_t sv_cno; /* 0-N: Saved line cursor. */
+ size_t sv_len; /* 0-N: Saved line length. */
+
+ /*
+ * These fields returns information from the vi text input routine.
+ *
+ * The termination condition. Note, this field is only valid if the
+ * text input routine returns success.
+ * TERM_BS: User backspaced over the prompt.
+ * TERM_CEDIT: User entered <edit-char>.
+ * TERM_CR: User entered <carriage-return>; no data.
+ * TERM_ESC: User entered <escape>; no data.
+ * TERM_OK: Data available.
+ * TERM_SEARCH: Incremental search.
+ */
+ enum {
+ TERM_BS, TERM_CEDIT, TERM_CR, TERM_ESC, TERM_OK, TERM_SEARCH
+ } term;
+};
+
+/*
+ * Get named buffer 'name'.
+ * Translate upper-case buffer names to lower-case buffer names.
+ */
+#define CBNAME(sp, cbp, nch) { \
+ CHAR_T L__name; \
+ L__name = isupper(nch) ? tolower(nch) : (nch); \
+ for (cbp = sp->gp->cutq.lh_first; \
+ cbp != NULL; cbp = cbp->q.le_next) \
+ if (cbp->name == L__name) \
+ break; \
+}
+
+/* Flags to the cut() routine. */
+#define CUT_LINEMODE 0x01 /* Cut in line mode. */
+#define CUT_NUMOPT 0x02 /* Numeric buffer: optional. */
+#define CUT_NUMREQ 0x04 /* Numeric buffer: required. */
diff --git a/common/delete.c b/common/delete.c
new file mode 100644
index 000000000000..001788f9bb38
--- /dev/null
+++ b/common/delete.c
@@ -0,0 +1,160 @@
+/*-
+ * 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
+static const char sccsid[] = "@(#)delete.c 10.12 (Berkeley) 10/23/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+
+#include <bitstring.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "common.h"
+
+/*
+ * del --
+ * Delete a range of text.
+ *
+ * PUBLIC: int del __P((SCR *, MARK *, MARK *, int));
+ */
+int
+del(sp, fm, tm, lmode)
+ SCR *sp;
+ MARK *fm, *tm;
+ int lmode;
+{
+ recno_t lno;
+ size_t blen, len, nlen, tlen;
+ char *bp, *p;
+ int eof, rval;
+
+ bp = NULL;
+
+ /* Case 1 -- delete in line mode. */
+ if (lmode) {
+ for (lno = tm->lno; lno >= fm->lno; --lno) {
+ if (db_delete(sp, lno))
+ return (1);
+ ++sp->rptlines[L_DELETED];
+ if (lno % INTERRUPT_CHECK == 0 && INTERRUPTED(sp))
+ break;
+ }
+ goto done;
+ }
+
+ /*
+ * Case 2 -- delete to EOF. This is a special case because it's
+ * easier to pick it off than try and find it in the other cases.
+ */
+ if (db_last(sp, &lno))
+ return (1);
+ if (tm->lno >= lno) {
+ if (tm->lno == lno) {
+ if (db_get(sp, lno, DBG_FATAL, &p, &len))
+ return (1);
+ eof = tm->cno >= len ? 1 : 0;
+ } else
+ eof = 1;
+ if (eof) {
+ for (lno = tm->lno; lno > fm->lno; --lno) {
+ if (db_delete(sp, lno))
+ return (1);
+ ++sp->rptlines[L_DELETED];
+ if (lno %
+ INTERRUPT_CHECK == 0 && INTERRUPTED(sp))
+ break;
+ }
+ if (db_get(sp, fm->lno, DBG_FATAL, &p, &len))
+ return (1);
+ GET_SPACE_RET(sp, bp, blen, fm->cno);
+ memcpy(bp, p, fm->cno);
+ if (db_set(sp, fm->lno, bp, fm->cno))
+ return (1);
+ goto done;
+ }
+ }
+
+ /* Case 3 -- delete within a single line. */
+ if (tm->lno == fm->lno) {
+ if (db_get(sp, fm->lno, DBG_FATAL, &p, &len))
+ return (1);
+ GET_SPACE_RET(sp, bp, blen, len);
+ if (fm->cno != 0)
+ memcpy(bp, p, fm->cno);
+ memcpy(bp + fm->cno, p + (tm->cno + 1), len - (tm->cno + 1));
+ if (db_set(sp, fm->lno,
+ bp, len - ((tm->cno - fm->cno) + 1)))
+ goto err;
+ goto done;
+ }
+
+ /*
+ * Case 4 -- delete over multiple lines.
+ *
+ * Copy the start partial line into place.
+ */
+ if ((tlen = fm->cno) != 0) {
+ if (db_get(sp, fm->lno, DBG_FATAL, &p, NULL))
+ return (1);
+ GET_SPACE_RET(sp, bp, blen, tlen + 256);
+ memcpy(bp, p, tlen);
+ }
+
+ /* Copy the end partial line into place. */
+ if (db_get(sp, tm->lno, DBG_FATAL, &p, &len))
+ goto err;
+ if (len != 0 && tm->cno != len - 1) {
+ /*
+ * XXX
+ * We can overflow memory here, if the total length is greater
+ * than SIZE_T_MAX. The only portable way I've found to test
+ * is depending on the overflow being less than the value.
+ */
+ nlen = (len - (tm->cno + 1)) + tlen;
+ if (tlen > nlen) {
+ msgq(sp, M_ERR, "002|Line length overflow");
+ goto err;
+ }
+ if (tlen == 0) {
+ GET_SPACE_RET(sp, bp, blen, nlen);
+ } else
+ ADD_SPACE_RET(sp, bp, blen, nlen);
+
+ memcpy(bp + tlen, p + (tm->cno + 1), len - (tm->cno + 1));
+ tlen += len - (tm->cno + 1);
+ }
+
+ /* Set the current line. */
+ if (db_set(sp, fm->lno, bp, tlen))
+ goto err;
+
+ /* Delete the last and intermediate lines. */
+ for (lno = tm->lno; lno > fm->lno; --lno) {
+ if (db_delete(sp, lno))
+ goto err;
+ ++sp->rptlines[L_DELETED];
+ if (lno % INTERRUPT_CHECK == 0 && INTERRUPTED(sp))
+ break;
+ }
+
+done: rval = 0;
+ if (0)
+err: rval = 1;
+ if (bp != NULL)
+ FREE_SPACE(sp, bp, blen);
+ return (rval);
+}
diff --git a/common/exf.c b/common/exf.c
new file mode 100644
index 000000000000..2993b0f4a8a5
--- /dev/null
+++ b/common/exf.c
@@ -0,0 +1,1498 @@
+/*-
+ * 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
+static const char sccsid[] = "@(#)exf.c 10.49 (Berkeley) 10/10/96";
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/types.h> /* XXX: param.h may not have included types.h */
+#include <sys/queue.h>
+#include <sys/stat.h>
+
+/*
+ * We include <sys/file.h>, because the flock(2) and open(2) #defines
+ * were found there on historical systems. We also include <fcntl.h>
+ * because the open(2) #defines are found there on newer systems.
+ */
+#include <sys/file.h>
+
+#include <bitstring.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#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))
+ goto err;
+ (void)snprintf(tname, sizeof(tname),
+ "%s/vi.XXXXXX", O_STR(sp, O_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) {
+ msgq_str(sp, M_SYSERR, name, "%s");
+ SIGUNBLOCK;
+ return (1);
+ }
+ 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);
+ 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;
+ return (flock(fd, LOCK_EX | LOCK_NB) ? errno == EAGAIN
+#ifdef EWOULDBLOCK
+ || errno == EWOULDBLOCK
+#endif
+ ? LOCK_UNAVAIL : LOCK_FAILED : LOCK_SUCCESS);
+#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))
+ 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
+}
diff --git a/common/exf.h b/common/exf.h
new file mode 100644
index 000000000000..cdfaa8294485
--- /dev/null
+++ b/common/exf.h
@@ -0,0 +1,82 @@
+/*-
+ * 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.
+ *
+ * @(#)exf.h 10.7 (Berkeley) 7/9/96
+ */
+ /* Undo direction. */
+/*
+ * exf --
+ * The file structure.
+ */
+struct _exf {
+ int refcnt; /* Reference count. */
+
+ /* Underlying database state. */
+ DB *db; /* File db structure. */
+ char *c_lp; /* Cached line. */
+ size_t c_len; /* Cached line length. */
+ recno_t c_lno; /* Cached line number. */
+ recno_t c_nlines; /* Cached lines in the file. */
+
+ DB *log; /* Log db structure. */
+ char *l_lp; /* Log buffer. */
+ size_t l_len; /* Log buffer length. */
+ recno_t l_high; /* Log last + 1 record number. */
+ recno_t l_cur; /* Log current record number. */
+ MARK l_cursor; /* Log cursor position. */
+ dir_t lundo; /* Last undo direction. */
+
+ LIST_HEAD(_markh, _lmark) marks;/* Linked list of file MARK's. */
+
+ /*
+ * XXX
+ * Mtime should be a struct timespec, but time_t is more portable.
+ */
+ dev_t mdev; /* Device. */
+ ino_t minode; /* Inode. */
+ time_t mtime; /* Last modification time. */
+
+ int fcntl_fd; /* Fcntl locking fd; see exf.c. */
+
+ /*
+ * Recovery in general, and these fields specifically, are described
+ * in recover.c.
+ */
+#define RCV_PERIOD 120 /* Sync every two minutes. */
+ char *rcv_path; /* Recover file name. */
+ char *rcv_mpath; /* Recover mail file name. */
+ int rcv_fd; /* Locked mail file descriptor. */
+
+#define F_DEVSET 0x001 /* mdev/minode fields initialized. */
+#define F_FIRSTMODIFY 0x002 /* File not yet modified. */
+#define F_MODIFIED 0x004 /* File is currently dirty. */
+#define F_MULTILOCK 0x008 /* Multiple processes running, lock. */
+#define F_NOLOG 0x010 /* Logging turned off. */
+#define F_RCV_NORM 0x020 /* Don't delete recovery files. */
+#define F_RCV_ON 0x040 /* Recovery is possible. */
+#define F_UNDO 0x080 /* No change since last undo. */
+ u_int8_t flags;
+};
+
+/* Flags to db_get(). */
+#define DBG_FATAL 0x001 /* If DNE, error message. */
+#define DBG_NOCACHE 0x002 /* Ignore the front-end cache. */
+
+/* Flags to file_init() and file_write(). */
+#define FS_ALL 0x001 /* Write the entire file. */
+#define FS_APPEND 0x002 /* Append to the file. */
+#define FS_FORCE 0x004 /* Force is set. */
+#define FS_OPENERR 0x008 /* Open failed, try it again. */
+#define FS_POSSIBLE 0x010 /* Force could have been set. */
+#define FS_SETALT 0x020 /* Set alternate file name. */
+
+/* Flags to rcv_sync(). */
+#define RCV_EMAIL 0x01 /* Send the user email, IFF file modified. */
+#define RCV_ENDSESSION 0x02 /* End the file session. */
+#define RCV_PRESERVE 0x04 /* Preserve backup file, IFF file modified. */
+#define RCV_SNAPSHOT 0x08 /* Snapshot the recovery, and send email. */
diff --git a/common/gs.h b/common/gs.h
new file mode 100644
index 000000000000..e5a43a656ac2
--- /dev/null
+++ b/common/gs.h
@@ -0,0 +1,210 @@
+/*-
+ * Copyright (c) 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 1993, 1994, 1995, 1996
+ * Keith Bostic. All rights reserved.
+ *
+ * See the LICENSE file for redistribution information.
+ *
+ * @(#)gs.h 10.34 (Berkeley) 9/24/96
+ */
+
+#define TEMPORARY_FILE_STRING "/tmp" /* Default temporary file name. */
+
+/*
+ * File reference structure (FREF). The structure contains the name of the
+ * file, along with the information that follows the name.
+ *
+ * !!!
+ * The read-only bit follows the file name, not the file itself.
+ */
+struct _fref {
+ CIRCLEQ_ENTRY(_fref) q; /* Linked list of file references. */
+ char *name; /* File name. */
+ char *tname; /* Backing temporary file name. */
+
+ recno_t lno; /* 1-N: file cursor line. */
+ size_t cno; /* 0-N: file cursor column. */
+
+#define FR_CURSORSET 0x0001 /* If lno/cno values valid. */
+#define FR_DONTDELETE 0x0002 /* Don't delete the temporary file. */
+#define FR_EXNAMED 0x0004 /* Read/write renamed the file. */
+#define FR_NAMECHANGE 0x0008 /* If the name changed. */
+#define FR_NEWFILE 0x0010 /* File doesn't really exist yet. */
+#define FR_RECOVER 0x0020 /* File is being recovered. */
+#define FR_TMPEXIT 0x0040 /* Modified temporary file, no exit. */
+#define FR_TMPFILE 0x0080 /* If file has no name. */
+#define FR_UNLOCKED 0x0100 /* File couldn't be locked. */
+ u_int16_t flags;
+};
+
+/* Action arguments to scr_exadjust(). */
+typedef enum { EX_TERM_CE, EX_TERM_SCROLL } exadj_t;
+
+/* Screen attribute arguments to scr_attr(). */
+typedef enum { SA_ALTERNATE, SA_INVERSE } scr_attr_t;
+
+/* Key type arguments to scr_keyval(). */
+typedef enum { KEY_VEOF, KEY_VERASE, KEY_VKILL, KEY_VWERASE } scr_keyval_t;
+
+/*
+ * GS:
+ *
+ * Structure that describes global state of the running program.
+ */
+struct _gs {
+ char *progname; /* Programe name. */
+
+ int id; /* Last allocated screen id. */
+ CIRCLEQ_HEAD(_dqh, _scr) dq; /* Displayed screens. */
+ CIRCLEQ_HEAD(_hqh, _scr) hq; /* Hidden screens. */
+
+ SCR *ccl_sp; /* Colon command-line screen. */
+
+ void *perl_interp; /* Perl interpreter. */
+ void *tcl_interp; /* Tcl_Interp *: Tcl interpreter. */
+
+ void *cl_private; /* Curses support private area. */
+ void *ip_private; /* IP support private area. */
+ void *tk_private; /* Tk/Tcl support private area. */
+
+ /* File references. */
+ CIRCLEQ_HEAD(_frefh, _fref) frefq;
+
+#define GO_COLUMNS 0 /* Global options: columns. */
+#define GO_LINES 1 /* Global options: lines. */
+#define GO_SECURE 2 /* Global options: secure. */
+#define GO_TERM 3 /* Global options: terminal type. */
+ OPTION opts[GO_TERM + 1];
+
+ DB *msg; /* Message catalog DB. */
+ MSGH msgq; /* User message list. */
+#define DEFAULT_NOPRINT '\1' /* Emergency non-printable character. */
+ CHAR_T noprint; /* Cached, unprintable character. */
+
+ char *tmp_bp; /* Temporary buffer. */
+ size_t tmp_blen; /* Temporary buffer size. */
+
+ /*
+ * Ex command structures (EXCMD). Defined here because ex commands
+ * exist outside of any particular screen or file.
+ */
+#define EXCMD_RUNNING(gp) ((gp)->ecq.lh_first->clen != 0)
+ LIST_HEAD(_excmdh, _excmd) ecq; /* Ex command linked list. */
+ EXCMD excmd; /* Default ex command structure. */
+ char *if_name; /* Current associated file. */
+ recno_t if_lno; /* Current associated line number. */
+
+ char *c_option; /* Ex initial, command-line command. */
+
+#ifdef DEBUG
+ FILE *tracefp; /* Trace file pointer. */
+#endif
+
+ EVENT *i_event; /* Array of input events. */
+ size_t i_nelem; /* Number of array elements. */
+ size_t i_cnt; /* Count of events. */
+ size_t i_next; /* Offset of next event. */
+
+ CB *dcbp; /* Default cut buffer pointer. */
+ CB dcb_store; /* Default cut buffer storage. */
+ LIST_HEAD(_cuth, _cb) cutq; /* Linked list of cut buffers. */
+
+#define MAX_BIT_SEQ 128 /* Max + 1 fast check character. */
+ LIST_HEAD(_seqh, _seq) seqq; /* Linked list of maps, abbrevs. */
+ bitstr_t bit_decl(seqb, MAX_BIT_SEQ);
+
+#define MAX_FAST_KEY 254 /* Max fast check character.*/
+#define KEY_LEN(sp, ch) \
+ ((unsigned char)(ch) <= MAX_FAST_KEY ? \
+ sp->gp->cname[(unsigned char)ch].len : v_key_len(sp, ch))
+#define KEY_NAME(sp, ch) \
+ ((unsigned char)(ch) <= MAX_FAST_KEY ? \
+ sp->gp->cname[(unsigned char)ch].name : v_key_name(sp, ch))
+ struct {
+ CHAR_T name[MAX_CHARACTER_COLUMNS + 1];
+ u_int8_t len;
+ } cname[MAX_FAST_KEY + 1]; /* Fast lookup table. */
+
+#define KEY_VAL(sp, ch) \
+ ((unsigned char)(ch) <= MAX_FAST_KEY ? \
+ sp->gp->special_key[(unsigned char)ch] : \
+ (unsigned char)(ch) > sp->gp->max_special ? 0 : v_key_val(sp,ch))
+ CHAR_T max_special; /* Max special character. */
+ u_char /* Fast lookup table. */
+ special_key[MAX_FAST_KEY + 1];
+
+/* Flags. */
+#define G_ABBREV 0x0001 /* If have abbreviations. */
+#define G_BELLSCHED 0x0002 /* Bell scheduled. */
+#define G_INTERRUPTED 0x0004 /* Interrupted. */
+#define G_RECOVER_SET 0x0008 /* Recover system initialized. */
+#define G_SCRIPTED 0x0010 /* Ex script session. */
+#define G_SCRWIN 0x0020 /* Scripting windows running. */
+#define G_SNAPSHOT 0x0040 /* Always snapshot files. */
+#define G_SRESTART 0x0080 /* Screen restarted. */
+#define G_TMP_INUSE 0x0100 /* Temporary buffer in use. */
+ u_int32_t flags;
+
+ /* Screen interface functions. */
+ /* Add a string to the screen. */
+ int (*scr_addstr) __P((SCR *, const char *, size_t));
+ /* Toggle a screen attribute. */
+ int (*scr_attr) __P((SCR *, scr_attr_t, int));
+ /* Terminal baud rate. */
+ int (*scr_baud) __P((SCR *, u_long *));
+ /* Beep/bell/flash the terminal. */
+ int (*scr_bell) __P((SCR *));
+ /* Display a busy message. */
+ void (*scr_busy) __P((SCR *, const char *, busy_t));
+ /* Clear to the end of the line. */
+ int (*scr_clrtoeol) __P((SCR *));
+ /* Return the cursor location. */
+ int (*scr_cursor) __P((SCR *, size_t *, size_t *));
+ /* Delete a line. */
+ int (*scr_deleteln) __P((SCR *));
+ /* Get a keyboard event. */
+ int (*scr_event) __P((SCR *, EVENT *, u_int32_t, int));
+ /* Ex: screen adjustment routine. */
+ int (*scr_ex_adjust) __P((SCR *, exadj_t));
+ int (*scr_fmap) /* Set a function key. */
+ __P((SCR *, seq_t, CHAR_T *, size_t, CHAR_T *, size_t));
+ /* Get terminal key value. */
+ int (*scr_keyval) __P((SCR *, scr_keyval_t, CHAR_T *, int *));
+ /* Insert a line. */
+ int (*scr_insertln) __P((SCR *));
+ /* Handle an option change. */
+ int (*scr_optchange) __P((SCR *, int, char *, u_long *));
+ /* Move the cursor. */
+ int (*scr_move) __P((SCR *, size_t, size_t));
+ /* Message or ex output. */
+ void (*scr_msg) __P((SCR *, mtype_t, char *, size_t));
+ /* Refresh the screen. */
+ int (*scr_refresh) __P((SCR *, int));
+ /* Rename the file. */
+ int (*scr_rename) __P((SCR *, char *, int));
+ /* Set the screen type. */
+ int (*scr_screen) __P((SCR *, u_int32_t));
+ /* Suspend the editor. */
+ int (*scr_suspend) __P((SCR *, int *));
+ /* Print usage message. */
+ void (*scr_usage) __P((void));
+};
+
+/*
+ * XXX
+ * Block signals if there are asynchronous events. Used to keep DB system calls
+ * from being interrupted and not restarted, as that will result in consistency
+ * problems. This should be handled by DB.
+ */
+#ifdef BLOCK_SIGNALS
+#include <signal.h>
+extern sigset_t __sigblockset;
+#define SIGBLOCK \
+ (void)sigprocmask(SIG_BLOCK, &__sigblockset, NULL)
+#define SIGUNBLOCK \
+ (void)sigprocmask(SIG_UNBLOCK, &__sigblockset, NULL);
+#else
+#define SIGBLOCK
+#define SIGUNBLOCK
+#endif
diff --git a/common/key.c b/common/key.c
new file mode 100644
index 000000000000..e1311ab571b0
--- /dev/null
+++ b/common/key.c
@@ -0,0 +1,865 @@
+/*-
+ * 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[] = "@(#)key.c 10.33 (Berkeley) 9/24/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/time.h>
+
+#include <bitstring.h>
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <locale.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "common.h"
+#include "../vi/vi.h"
+
+static int v_event_append __P((SCR *, EVENT *));
+static int v_event_grow __P((SCR *, int));
+static int v_key_cmp __P((const void *, const void *));
+static void v_keyval __P((SCR *, int, scr_keyval_t));
+static void v_sync __P((SCR *, int));
+
+/*
+ * !!!
+ * Historic vi always used:
+ *
+ * ^D: autoindent deletion
+ * ^H: last character deletion
+ * ^W: last word deletion
+ * ^Q: quote the next character (if not used in flow control).
+ * ^V: quote the next character
+ *
+ * regardless of the user's choices for these characters. The user's erase
+ * and kill characters worked in addition to these characters. Nvi wires
+ * down the above characters, but in addition permits the VEOF, VERASE, VKILL
+ * and VWERASE characters described by the user's termios structure.
+ *
+ * Ex was not consistent with this scheme, as it historically ran in tty
+ * cooked mode. This meant that the scroll command and autoindent erase
+ * characters were mapped to the user's EOF character, and the character
+ * and word deletion characters were the user's tty character and word
+ * deletion characters. This implementation makes it all consistent, as
+ * described above for vi.
+ *
+ * !!!
+ * This means that all screens share a special key set.
+ */
+KEYLIST keylist[] = {
+ {K_BACKSLASH, '\\'}, /* \ */
+ {K_CARAT, '^'}, /* ^ */
+ {K_CNTRLD, '\004'}, /* ^D */
+ {K_CNTRLR, '\022'}, /* ^R */
+ {K_CNTRLT, '\024'}, /* ^T */
+ {K_CNTRLZ, '\032'}, /* ^Z */
+ {K_COLON, ':'}, /* : */
+ {K_CR, '\r'}, /* \r */
+ {K_ESCAPE, '\033'}, /* ^[ */
+ {K_FORMFEED, '\f'}, /* \f */
+ {K_HEXCHAR, '\030'}, /* ^X */
+ {K_NL, '\n'}, /* \n */
+ {K_RIGHTBRACE, '}'}, /* } */
+ {K_RIGHTPAREN, ')'}, /* ) */
+ {K_TAB, '\t'}, /* \t */
+ {K_VERASE, '\b'}, /* \b */
+ {K_VKILL, '\025'}, /* ^U */
+ {K_VLNEXT, '\021'}, /* ^Q */
+ {K_VLNEXT, '\026'}, /* ^V */
+ {K_VWERASE, '\027'}, /* ^W */
+ {K_ZERO, '0'}, /* 0 */
+
+#define ADDITIONAL_CHARACTERS 4
+ {K_NOTUSED, 0}, /* VEOF, VERASE, VKILL, VWERASE */
+ {K_NOTUSED, 0},
+ {K_NOTUSED, 0},
+ {K_NOTUSED, 0},
+};
+static int nkeylist =
+ (sizeof(keylist) / sizeof(keylist[0])) - ADDITIONAL_CHARACTERS;
+
+/*
+ * v_key_init --
+ * Initialize the special key lookup table.
+ *
+ * PUBLIC: int v_key_init __P((SCR *));
+ */
+int
+v_key_init(sp)
+ SCR *sp;
+{
+ CHAR_T ch;
+ GS *gp;
+ KEYLIST *kp;
+ int cnt;
+
+ gp = sp->gp;
+
+ /*
+ * XXX
+ * 8-bit only, for now. Recompilation should get you any 8-bit
+ * character set, as long as nul isn't a character.
+ */
+ (void)setlocale(LC_ALL, "");
+#if __linux__
+ /*
+ * In libc 4.5.26, setlocale(LC_ALL, ""), doesn't setup the table
+ * for ctype(3c) correctly. 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.
+ */
+ (void)setlocale(LC_CTYPE, "");
+#endif
+ v_key_ilookup(sp);
+
+ v_keyval(sp, K_CNTRLD, KEY_VEOF);
+ v_keyval(sp, K_VERASE, KEY_VERASE);
+ v_keyval(sp, K_VKILL, KEY_VKILL);
+ v_keyval(sp, K_VWERASE, KEY_VWERASE);
+
+ /* Sort the special key list. */
+ qsort(keylist, nkeylist, sizeof(keylist[0]), v_key_cmp);
+
+ /* Initialize the fast lookup table. */
+ for (gp->max_special = 0, kp = keylist, cnt = nkeylist; cnt--; ++kp) {
+ if (gp->max_special < kp->value)
+ gp->max_special = kp->value;
+ if (kp->ch <= MAX_FAST_KEY)
+ gp->special_key[kp->ch] = kp->value;
+ }
+
+ /* Find a non-printable character to use as a message separator. */
+ for (ch = 1; ch <= MAX_CHAR_T; ++ch)
+ if (!isprint(ch)) {
+ gp->noprint = ch;
+ break;
+ }
+ if (ch != gp->noprint) {
+ msgq(sp, M_ERR, "079|No non-printable character found");
+ return (1);
+ }
+ return (0);
+}
+
+/*
+ * v_keyval --
+ * Set key values.
+ *
+ * We've left some open slots in the keylist table, and if these values exist,
+ * we put them into place. Note, they may reset (or duplicate) values already
+ * in the table, so we check for that first.
+ */
+static void
+v_keyval(sp, val, name)
+ SCR *sp;
+ int val;
+ scr_keyval_t name;
+{
+ KEYLIST *kp;
+ CHAR_T ch;
+ int dne;
+
+ /* Get the key's value from the screen. */
+ if (sp->gp->scr_keyval(sp, name, &ch, &dne))
+ return;
+ if (dne)
+ return;
+
+ /* Check for duplication. */
+ for (kp = keylist; kp->value != K_NOTUSED; ++kp)
+ if (kp->ch == ch) {
+ kp->value = val;
+ return;
+ }
+
+ /* Add a new entry. */
+ if (kp->value == K_NOTUSED) {
+ keylist[nkeylist].ch = ch;
+ keylist[nkeylist].value = val;
+ ++nkeylist;
+ }
+}
+
+/*
+ * v_key_ilookup --
+ * Build the fast-lookup key display array.
+ *
+ * PUBLIC: void v_key_ilookup __P((SCR *));
+ */
+void
+v_key_ilookup(sp)
+ SCR *sp;
+{
+ CHAR_T ch, *p, *t;
+ GS *gp;
+ size_t len;
+
+ for (gp = sp->gp, ch = 0; ch <= MAX_FAST_KEY; ++ch)
+ for (p = gp->cname[ch].name, t = v_key_name(sp, ch),
+ len = gp->cname[ch].len = sp->clen; len--;)
+ *p++ = *t++;
+}
+
+/*
+ * v_key_len --
+ * Return the length of the string that will display the key.
+ * This routine is the backup for the KEY_LEN() macro.
+ *
+ * PUBLIC: size_t v_key_len __P((SCR *, ARG_CHAR_T));
+ */
+size_t
+v_key_len(sp, ch)
+ SCR *sp;
+ ARG_CHAR_T ch;
+{
+ (void)v_key_name(sp, ch);
+ return (sp->clen);
+}
+
+/*
+ * v_key_name --
+ * Return the string that will display the key. This routine
+ * is the backup for the KEY_NAME() macro.
+ *
+ * PUBLIC: CHAR_T *v_key_name __P((SCR *, ARG_CHAR_T));
+ */
+CHAR_T *
+v_key_name(sp, ach)
+ SCR *sp;
+ ARG_CHAR_T ach;
+{
+ static const CHAR_T hexdigit[] = "0123456789abcdef";
+ static const CHAR_T octdigit[] = "01234567";
+ CHAR_T ch, *chp, mask;
+ size_t len;
+ int cnt, shift;
+
+ ch = ach;
+
+ /* See if the character was explicitly declared printable or not. */
+ if ((chp = O_STR(sp, O_PRINT)) != NULL)
+ for (; *chp != '\0'; ++chp)
+ if (*chp == ch)
+ goto pr;
+ if ((chp = O_STR(sp, O_NOPRINT)) != NULL)
+ for (; *chp != '\0'; ++chp)
+ if (*chp == ch)
+ goto nopr;
+
+ /*
+ * Historical (ARPA standard) mappings. Printable characters are left
+ * alone. Control characters less than 0x20 are represented as '^'
+ * followed by the character offset from the '@' character in the ASCII
+ * character set. Del (0x7f) is represented as '^' followed by '?'.
+ *
+ * XXX
+ * The following code depends on the current locale being identical to
+ * the ASCII map from 0x40 to 0x5f (since 0x1f + 0x40 == 0x5f). I'm
+ * told that this is a reasonable assumption...
+ *
+ * XXX
+ * This code will only work with CHAR_T's that are multiples of 8-bit
+ * bytes.
+ *
+ * XXX
+ * NB: There's an assumption here that all printable characters take
+ * up a single column on the screen. This is not always correct.
+ */
+ if (isprint(ch)) {
+pr: sp->cname[0] = ch;
+ len = 1;
+ goto done;
+ }
+nopr: if (iscntrl(ch) && (ch < 0x20 || ch == 0x7f)) {
+ sp->cname[0] = '^';
+ sp->cname[1] = ch == 0x7f ? '?' : '@' + ch;
+ len = 2;
+ } else if (O_ISSET(sp, O_OCTAL)) {
+#define BITS (sizeof(CHAR_T) * 8)
+#define SHIFT (BITS - BITS % 3)
+#define TOPMASK (BITS % 3 == 2 ? 3 : 1) << (BITS - BITS % 3)
+ sp->cname[0] = '\\';
+ sp->cname[1] = octdigit[(ch & TOPMASK) >> SHIFT];
+ shift = SHIFT - 3;
+ for (len = 2, mask = 7 << (SHIFT - 3),
+ cnt = BITS / 3; cnt-- > 0; mask >>= 3, shift -= 3)
+ sp->cname[len++] = octdigit[(ch & mask) >> shift];
+ } else {
+ sp->cname[0] = '\\';
+ sp->cname[1] = 'x';
+ for (len = 2, chp = (u_int8_t *)&ch,
+ cnt = sizeof(CHAR_T); cnt-- > 0; ++chp) {
+ sp->cname[len++] = hexdigit[(*chp & 0xf0) >> 4];
+ sp->cname[len++] = hexdigit[*chp & 0x0f];
+ }
+ }
+done: sp->cname[sp->clen = len] = '\0';
+ return (sp->cname);
+}
+
+/*
+ * v_key_val --
+ * Fill in the value for a key. This routine is the backup
+ * for the KEY_VAL() macro.
+ *
+ * PUBLIC: int v_key_val __P((SCR *, ARG_CHAR_T));
+ */
+int
+v_key_val(sp, ch)
+ SCR *sp;
+ ARG_CHAR_T ch;
+{
+ KEYLIST k, *kp;
+
+ k.ch = ch;
+ kp = bsearch(&k, keylist, nkeylist, sizeof(keylist[0]), v_key_cmp);
+ return (kp == NULL ? K_NOTUSED : kp->value);
+}
+
+/*
+ * v_event_push --
+ * Push events/keys onto the front of the buffer.
+ *
+ * There is a single input buffer in ex/vi. Characters are put onto the
+ * end of the buffer by the terminal input routines, and pushed onto the
+ * front of the buffer by various other functions in ex/vi. Each key has
+ * an associated flag value, which indicates if it has already been quoted,
+ * and if it is the result of a mapping or an abbreviation.
+ *
+ * PUBLIC: int v_event_push __P((SCR *, EVENT *, CHAR_T *, size_t, u_int));
+ */
+int
+v_event_push(sp, p_evp, p_s, nitems, flags)
+ SCR *sp;
+ EVENT *p_evp; /* Push event. */
+ CHAR_T *p_s; /* Push characters. */
+ size_t nitems; /* Number of items to push. */
+ u_int flags; /* CH_* flags. */
+{
+ EVENT *evp;
+ GS *gp;
+ size_t total;
+
+ /* If we have room, stuff the items into the buffer. */
+ gp = sp->gp;
+ if (nitems <= gp->i_next ||
+ (gp->i_event != NULL && gp->i_cnt == 0 && nitems <= gp->i_nelem)) {
+ if (gp->i_cnt != 0)
+ gp->i_next -= nitems;
+ goto copy;
+ }
+
+ /*
+ * If there are currently items in the queue, shift them up,
+ * leaving some extra room. Get enough space plus a little
+ * extra.
+ */
+#define TERM_PUSH_SHIFT 30
+ total = gp->i_cnt + gp->i_next + nitems + TERM_PUSH_SHIFT;
+ if (total >= gp->i_nelem && v_event_grow(sp, MAX(total, 64)))
+ return (1);
+ if (gp->i_cnt)
+ MEMMOVE(gp->i_event + TERM_PUSH_SHIFT + nitems,
+ gp->i_event + gp->i_next, gp->i_cnt);
+ gp->i_next = TERM_PUSH_SHIFT;
+
+ /* Put the new items into the queue. */
+copy: gp->i_cnt += nitems;
+ for (evp = gp->i_event + gp->i_next; nitems--; ++evp) {
+ if (p_evp != NULL)
+ *evp = *p_evp++;
+ else {
+ evp->e_event = E_CHARACTER;
+ evp->e_c = *p_s++;
+ evp->e_value = KEY_VAL(sp, evp->e_c);
+ F_INIT(&evp->e_ch, flags);
+ }
+ }
+ return (0);
+}
+
+/*
+ * v_event_append --
+ * Append events onto the tail of the buffer.
+ */
+static int
+v_event_append(sp, argp)
+ SCR *sp;
+ EVENT *argp;
+{
+ CHAR_T *s; /* Characters. */
+ EVENT *evp;
+ GS *gp;
+ size_t nevents; /* Number of events. */
+
+ /* Grow the buffer as necessary. */
+ nevents = argp->e_event == E_STRING ? argp->e_len : 1;
+ gp = sp->gp;
+ if (gp->i_event == NULL ||
+ nevents > gp->i_nelem - (gp->i_next + gp->i_cnt))
+ v_event_grow(sp, MAX(nevents, 64));
+ evp = gp->i_event + gp->i_next + gp->i_cnt;
+ gp->i_cnt += nevents;
+
+ /* Transform strings of characters into single events. */
+ if (argp->e_event == E_STRING)
+ for (s = argp->e_csp; nevents--; ++evp) {
+ evp->e_event = E_CHARACTER;
+ evp->e_c = *s++;
+ evp->e_value = KEY_VAL(sp, evp->e_c);
+ evp->e_flags = 0;
+ }
+ else
+ *evp = *argp;
+ return (0);
+}
+
+/* Remove events from the queue. */
+#define QREM(len) { \
+ if ((gp->i_cnt -= len) == 0) \
+ gp->i_next = 0; \
+ else \
+ gp->i_next += len; \
+}
+
+/*
+ * v_event_get --
+ * Return the next event.
+ *
+ * !!!
+ * The flag EC_NODIGIT probably needs some explanation. First, the idea of
+ * mapping keys is that one or more keystrokes act like a function key.
+ * What's going on is that vi is reading a number, and the character following
+ * the number may or may not be mapped (EC_MAPCOMMAND). For example, if the
+ * user is entering the z command, a valid command is "z40+", and we don't want
+ * to map the '+', i.e. if '+' is mapped to "xxx", we don't want to change it
+ * into "z40xxx". However, if the user enters "35x", we want to put all of the
+ * characters through the mapping code.
+ *
+ * Historical practice is a bit muddled here. (Surprise!) It always permitted
+ * mapping digits as long as they weren't the first character of the map, e.g.
+ * ":map ^A1 xxx" was okay. It also permitted the mapping of the digits 1-9
+ * (the digit 0 was a special case as it doesn't indicate the start of a count)
+ * as the first character of the map, but then ignored those mappings. While
+ * it's probably stupid to map digits, vi isn't your mother.
+ *
+ * The way this works is that the EC_MAPNODIGIT causes term_key to return the
+ * end-of-digit without "looking" at the next character, i.e. leaving it as the
+ * user entered it. Presumably, the next term_key call will tell us how the
+ * user wants it handled.
+ *
+ * There is one more complication. Users might map keys to digits, and, as
+ * it's described above, the commands:
+ *
+ * :map g 1G
+ * d2g
+ *
+ * would return the keys "d2<end-of-digits>1G", when the user probably wanted
+ * "d21<end-of-digits>G". So, if a map starts off with a digit we continue as
+ * before, otherwise, we pretend we haven't mapped the character, and return
+ * <end-of-digits>.
+ *
+ * Now that that's out of the way, let's talk about Energizer Bunny macros.
+ * It's easy to create macros that expand to a loop, e.g. map x 3x. It's
+ * fairly easy to detect this example, because it's all internal to term_key.
+ * If we're expanding a macro and it gets big enough, at some point we can
+ * assume it's looping and kill it. The examples that are tough are the ones
+ * where the parser is involved, e.g. map x "ayyx"byy. We do an expansion
+ * on 'x', and get "ayyx"byy. We then return the first 4 characters, and then
+ * find the looping macro again. There is no way that we can detect this
+ * without doing a full parse of the command, because the character that might
+ * cause the loop (in this case 'x') may be a literal character, e.g. the map
+ * map x "ayy"xyy"byy is perfectly legal and won't cause a loop.
+ *
+ * Historic vi tried to detect looping macros by disallowing obvious cases in
+ * the map command, maps that that ended with the same letter as they started
+ * (which wrongly disallowed "map x 'x"), and detecting macros that expanded
+ * too many times before keys were returned to the command parser. It didn't
+ * get many (most?) of the tricky cases right, however, and it was certainly
+ * possible to create macros that ran forever. And, even if it did figure out
+ * what was going on, the user was usually tossed into ex mode. Finally, any
+ * changes made before vi realized that the macro was recursing were left in
+ * place. We recover gracefully, but the only recourse the user has in an
+ * infinite macro loop is to interrupt.
+ *
+ * !!!
+ * It is historic practice that mapping characters to themselves as the first
+ * part of the mapped string was legal, and did not cause infinite loops, i.e.
+ * ":map! { {^M^T" and ":map n nz." were known to work. The initial, matching
+ * characters were returned instead of being remapped.
+ *
+ * !!!
+ * It is also historic practice that the macro "map ] ]]^" caused a single ]
+ * keypress to behave as the command ]] (the ^ got the map past the vi check
+ * for "tail recursion"). Conversely, the mapping "map n nn^" went recursive.
+ * What happened was that, in the historic vi, maps were expanded as the keys
+ * were retrieved, but not all at once and not centrally. So, the keypress ]
+ * pushed ]]^ on the stack, and then the first ] from the stack was passed to
+ * the ]] command code. The ]] command then retrieved a key without entering
+ * the mapping code. This could bite us anytime a user has a map that depends
+ * on secondary keys NOT being mapped. I can't see any possible way to make
+ * this work in here without the complete abandonment of Rationality Itself.
+ *
+ * XXX
+ * The final issue is recovery. It would be possible to undo all of the work
+ * that was done by the macro if we entered a record into the log so that we
+ * knew when the macro started, and, in fact, this might be worth doing at some
+ * point. Given that this might make the log grow unacceptably (consider that
+ * cursor keys are done with maps), for now we leave any changes made in place.
+ *
+ * PUBLIC: int v_event_get __P((SCR *, EVENT *, int, u_int32_t));
+ */
+int
+v_event_get(sp, argp, timeout, flags)
+ SCR *sp;
+ EVENT *argp;
+ int timeout;
+ u_int32_t flags;
+{
+ EVENT *evp, ev;
+ GS *gp;
+ SEQ *qp;
+ int init_nomap, ispartial, istimeout, remap_cnt;
+
+ gp = sp->gp;
+
+ /* If simply checking for interrupts, argp may be NULL. */
+ if (argp == NULL)
+ argp = &ev;
+
+retry: istimeout = remap_cnt = 0;
+
+ /*
+ * If the queue isn't empty and we're timing out for characters,
+ * return immediately.
+ */
+ if (gp->i_cnt != 0 && LF_ISSET(EC_TIMEOUT))
+ return (0);
+
+ /*
+ * If the queue is empty, we're checking for interrupts, or we're
+ * timing out for characters, get more events.
+ */
+ if (gp->i_cnt == 0 || LF_ISSET(EC_INTERRUPT | EC_TIMEOUT)) {
+ /*
+ * If we're reading new characters, check any scripting
+ * windows for input.
+ */
+ if (F_ISSET(gp, G_SCRWIN) && sscr_input(sp))
+ return (1);
+loop: if (gp->scr_event(sp, argp,
+ LF_ISSET(EC_INTERRUPT | EC_QUOTED | EC_RAW), timeout))
+ return (1);
+ switch (argp->e_event) {
+ case E_ERR:
+ case E_SIGHUP:
+ case E_SIGTERM:
+ /*
+ * Fatal conditions cause the file to be synced to
+ * disk immediately.
+ */
+ v_sync(sp, RCV_ENDSESSION | RCV_PRESERVE |
+ (argp->e_event == E_SIGTERM ? 0: RCV_EMAIL));
+ return (1);
+ case E_TIMEOUT:
+ istimeout = 1;
+ break;
+ case E_INTERRUPT:
+ /* Set the global interrupt flag. */
+ F_SET(sp->gp, G_INTERRUPTED);
+
+ /*
+ * If the caller was interested in interrupts, return
+ * immediately.
+ */
+ if (LF_ISSET(EC_INTERRUPT))
+ return (0);
+ goto append;
+ default:
+append: if (v_event_append(sp, argp))
+ return (1);
+ break;
+ }
+ }
+
+ /*
+ * If the caller was only interested in interrupts or timeouts, return
+ * immediately. (We may have gotten characters, and that's okay, they
+ * were queued up for later use.)
+ */
+ if (LF_ISSET(EC_INTERRUPT | EC_TIMEOUT))
+ return (0);
+
+newmap: evp = &gp->i_event[gp->i_next];
+
+ /*
+ * If the next event in the queue isn't a character event, return
+ * it, we're done.
+ */
+ if (evp->e_event != E_CHARACTER) {
+ *argp = *evp;
+ QREM(1);
+ return (0);
+ }
+
+ /*
+ * If the key isn't mappable because:
+ *
+ * + ... the timeout has expired
+ * + ... it's not a mappable key
+ * + ... neither the command or input map flags are set
+ * + ... there are no maps that can apply to it
+ *
+ * return it forthwith.
+ */
+ if (istimeout || F_ISSET(&evp->e_ch, CH_NOMAP) ||
+ !LF_ISSET(EC_MAPCOMMAND | EC_MAPINPUT) ||
+ evp->e_c < MAX_BIT_SEQ && !bit_test(gp->seqb, evp->e_c))
+ goto nomap;
+
+ /* Search the map. */
+ qp = seq_find(sp, NULL, evp, NULL, gp->i_cnt,
+ LF_ISSET(EC_MAPCOMMAND) ? SEQ_COMMAND : SEQ_INPUT, &ispartial);
+
+ /*
+ * If get a partial match, get more characters and retry the map.
+ * If time out without further characters, return the characters
+ * unmapped.
+ *
+ * !!!
+ * <escape> characters are a problem. Cursor keys start with <escape>
+ * characters, so there's almost always a map in place that begins with
+ * an <escape> character. If we timeout <escape> keys in the same way
+ * that we timeout other keys, the user will get a noticeable pause as
+ * they enter <escape> to terminate input mode. If key timeout is set
+ * for a slow link, users will get an even longer pause. Nvi used to
+ * simply timeout <escape> characters at 1/10th of a second, but this
+ * loses over PPP links where the latency is greater than 100Ms.
+ */
+ if (ispartial) {
+ if (O_ISSET(sp, O_TIMEOUT))
+ timeout = (evp->e_value == K_ESCAPE ?
+ O_VAL(sp, O_ESCAPETIME) :
+ O_VAL(sp, O_KEYTIME)) * 100;
+ else
+ timeout = 0;
+ goto loop;
+ }
+
+ /* If no map, return the character. */
+ if (qp == NULL) {
+nomap: if (!isdigit(evp->e_c) && LF_ISSET(EC_MAPNODIGIT))
+ goto not_digit;
+ *argp = *evp;
+ QREM(1);
+ return (0);
+ }
+
+ /*
+ * If looking for the end of a digit string, and the first character
+ * of the map is it, pretend we haven't seen the character.
+ */
+ if (LF_ISSET(EC_MAPNODIGIT) &&
+ qp->output != NULL && !isdigit(qp->output[0])) {
+not_digit: argp->e_c = CH_NOT_DIGIT;
+ argp->e_value = K_NOTUSED;
+ argp->e_event = E_CHARACTER;
+ F_INIT(&argp->e_ch, 0);
+ return (0);
+ }
+
+ /* Find out if the initial segments are identical. */
+ init_nomap = !e_memcmp(qp->output, &gp->i_event[gp->i_next], qp->ilen);
+
+ /* Delete the mapped characters from the queue. */
+ QREM(qp->ilen);
+
+ /* If keys mapped to nothing, go get more. */
+ if (qp->output == NULL)
+ goto retry;
+
+ /* If remapping characters... */
+ if (O_ISSET(sp, O_REMAP)) {
+ /*
+ * Periodically check for interrupts. Always check the first
+ * time through, because it's possible to set up a map that
+ * will return a character every time, but will expand to more,
+ * e.g. "map! a aaaa" will always return a 'a', but we'll never
+ * get anywhere useful.
+ */
+ if ((++remap_cnt == 1 || remap_cnt % 10 == 0) &&
+ (gp->scr_event(sp, &ev,
+ EC_INTERRUPT, 0) || ev.e_event == E_INTERRUPT)) {
+ F_SET(sp->gp, G_INTERRUPTED);
+ argp->e_event = E_INTERRUPT;
+ return (0);
+ }
+
+ /*
+ * If an initial part of the characters mapped, they are not
+ * further remapped -- return the first one. Push the rest
+ * of the characters, or all of the characters if no initial
+ * part mapped, back on the queue.
+ */
+ if (init_nomap) {
+ if (v_event_push(sp, NULL, qp->output + qp->ilen,
+ qp->olen - qp->ilen, CH_MAPPED))
+ return (1);
+ if (v_event_push(sp, NULL,
+ qp->output, qp->ilen, CH_NOMAP | CH_MAPPED))
+ return (1);
+ evp = &gp->i_event[gp->i_next];
+ goto nomap;
+ }
+ if (v_event_push(sp, NULL, qp->output, qp->olen, CH_MAPPED))
+ return (1);
+ goto newmap;
+ }
+
+ /* Else, push the characters on the queue and return one. */
+ if (v_event_push(sp, NULL, qp->output, qp->olen, CH_MAPPED | CH_NOMAP))
+ return (1);
+
+ goto nomap;
+}
+
+/*
+ * v_sync --
+ * Walk the screen lists, sync'ing files to their backup copies.
+ */
+static void
+v_sync(sp, flags)
+ SCR *sp;
+ int flags;
+{
+ GS *gp;
+
+ gp = sp->gp;
+ for (sp = gp->dq.cqh_first; sp != (void *)&gp->dq; sp = sp->q.cqe_next)
+ rcv_sync(sp, flags);
+ for (sp = gp->hq.cqh_first; sp != (void *)&gp->hq; sp = sp->q.cqe_next)
+ rcv_sync(sp, flags);
+}
+
+/*
+ * v_event_err --
+ * Unexpected event.
+ *
+ * PUBLIC: void v_event_err __P((SCR *, EVENT *));
+ */
+void
+v_event_err(sp, evp)
+ SCR *sp;
+ EVENT *evp;
+{
+ switch (evp->e_event) {
+ case E_CHARACTER:
+ msgq(sp, M_ERR, "276|Unexpected character event");
+ break;
+ case E_EOF:
+ msgq(sp, M_ERR, "277|Unexpected end-of-file event");
+ break;
+ case E_INTERRUPT:
+ msgq(sp, M_ERR, "279|Unexpected interrupt event");
+ break;
+ case E_QUIT:
+ msgq(sp, M_ERR, "280|Unexpected quit event");
+ break;
+ case E_REPAINT:
+ msgq(sp, M_ERR, "281|Unexpected repaint event");
+ break;
+ case E_STRING:
+ msgq(sp, M_ERR, "285|Unexpected string event");
+ break;
+ case E_TIMEOUT:
+ msgq(sp, M_ERR, "286|Unexpected timeout event");
+ break;
+ case E_WRESIZE:
+ msgq(sp, M_ERR, "316|Unexpected resize event");
+ break;
+ case E_WRITE:
+ msgq(sp, M_ERR, "287|Unexpected write event");
+ break;
+
+ /*
+ * Theoretically, none of these can occur, as they're handled at the
+ * top editor level.
+ */
+ case E_ERR:
+ case E_SIGHUP:
+ case E_SIGTERM:
+ default:
+ abort();
+ }
+
+ /* Free any allocated memory. */
+ if (evp->e_asp != NULL)
+ free(evp->e_asp);
+}
+
+/*
+ * v_event_flush --
+ * Flush any flagged keys, returning if any keys were flushed.
+ *
+ * PUBLIC: int v_event_flush __P((SCR *, u_int));
+ */
+int
+v_event_flush(sp, flags)
+ SCR *sp;
+ u_int flags;
+{
+ GS *gp;
+ int rval;
+
+ for (rval = 0, gp = sp->gp; gp->i_cnt != 0 &&
+ F_ISSET(&gp->i_event[gp->i_next].e_ch, flags); rval = 1)
+ QREM(1);
+ return (rval);
+}
+
+/*
+ * v_event_grow --
+ * Grow the terminal queue.
+ */
+static int
+v_event_grow(sp, add)
+ SCR *sp;
+ int add;
+{
+ GS *gp;
+ size_t new_nelem, olen;
+
+ gp = sp->gp;
+ new_nelem = gp->i_nelem + add;
+ olen = gp->i_nelem * sizeof(gp->i_event[0]);
+ BINC_RET(sp, gp->i_event, olen, new_nelem * sizeof(gp->i_event[0]));
+ gp->i_nelem = olen / sizeof(gp->i_event[0]);
+ return (0);
+}
+
+/*
+ * v_key_cmp --
+ * Compare two keys for sorting.
+ */
+static int
+v_key_cmp(ap, bp)
+ const void *ap, *bp;
+{
+ return (((KEYLIST *)ap)->ch - ((KEYLIST *)bp)->ch);
+}
diff --git a/common/key.h b/common/key.h
new file mode 100644
index 000000000000..76fb64f8e1ec
--- /dev/null
+++ b/common/key.h
@@ -0,0 +1,222 @@
+/*-
+ * 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.
+ *
+ * @(#)key.h 10.18 (Berkeley) 6/30/96
+ */
+
+/*
+ * Fundamental character types.
+ *
+ * CHAR_T An integral type that can hold any character.
+ * ARG_CHAR_T The type of a CHAR_T when passed as an argument using
+ * traditional promotion rules. It should also be able
+ * to be compared against any CHAR_T for equality without
+ * problems.
+ * MAX_CHAR_T The maximum value of any character.
+ *
+ * If no integral type can hold a character, don't even try the port.
+ */
+typedef u_char CHAR_T;
+typedef u_int ARG_CHAR_T;
+#define MAX_CHAR_T 0xff
+
+/* The maximum number of columns any character can take up on a screen. */
+#define MAX_CHARACTER_COLUMNS 4
+
+/*
+ * Event types.
+ *
+ * The program structure depends on the event loop being able to return
+ * E_EOF/E_ERR multiple times -- eventually enough things will end due
+ * to the events that vi will reach the command level for the screen, at
+ * which point the exit flags will be set and vi will exit.
+ */
+typedef enum {
+ E_NOTUSED = 0, /* Not set. */
+ E_CHARACTER, /* Input character: e_c set. */
+ E_EOF, /* End of input (NOT ^D). */
+ E_ERR, /* Input error. */
+ E_INTERRUPT, /* Interrupt. */
+ E_QUIT, /* Quit. */
+ E_REPAINT, /* Repaint: e_flno, e_tlno set. */
+ E_SIGHUP, /* SIGHUP. */
+ E_SIGTERM, /* SIGTERM. */
+ E_STRING, /* Input string: e_csp, e_len set. */
+ E_TIMEOUT, /* Timeout. */
+ E_WRESIZE, /* Window resize. */
+ E_WRITE /* Write. */
+} e_event_t;
+
+/*
+ * Character values.
+ */
+typedef enum {
+ K_NOTUSED = 0, /* Not set. */
+ K_BACKSLASH, /* \ */
+ K_CARAT, /* ^ */
+ K_CNTRLD, /* ^D */
+ K_CNTRLR, /* ^R */
+ K_CNTRLT, /* ^T */
+ K_CNTRLZ, /* ^Z */
+ K_COLON, /* : */
+ K_CR, /* \r */
+ K_ESCAPE, /* ^[ */
+ K_FORMFEED, /* \f */
+ K_HEXCHAR, /* ^X */
+ K_NL, /* \n */
+ K_RIGHTBRACE, /* } */
+ K_RIGHTPAREN, /* ) */
+ K_TAB, /* \t */
+ K_VERASE, /* set from tty: default ^H */
+ K_VKILL, /* set from tty: default ^U */
+ K_VLNEXT, /* set from tty: default ^V */
+ K_VWERASE, /* set from tty: default ^W */
+ K_ZERO /* 0 */
+} e_key_t;
+
+struct _event {
+ TAILQ_ENTRY(_event) q; /* Linked list of events. */
+ e_event_t e_event; /* Event type. */
+ union {
+ struct { /* Input character. */
+ CHAR_T c; /* Character. */
+ e_key_t value; /* Key type. */
+
+#define CH_ABBREVIATED 0x01 /* Character is from an abbreviation. */
+#define CH_MAPPED 0x02 /* Character is from a map. */
+#define CH_NOMAP 0x04 /* Do not map the character. */
+#define CH_QUOTED 0x08 /* Character is already quoted. */
+ u_int8_t flags;
+ } _e_ch;
+#define e_ch _u_event._e_ch /* !!! The structure, not the char. */
+#define e_c _u_event._e_ch.c
+#define e_value _u_event._e_ch.value
+#define e_flags _u_event._e_ch.flags
+
+ struct { /* Screen position, size. */
+ size_t lno1; /* Line number. */
+ size_t cno1; /* Column number. */
+ size_t lno2; /* Line number. */
+ size_t cno2; /* Column number. */
+ } _e_mark;
+#define e_lno _u_event._e_mark.lno1 /* Single location. */
+#define e_cno _u_event._e_mark.cno1
+#define e_flno _u_event._e_mark.lno1 /* Text region. */
+#define e_fcno _u_event._e_mark.cno1
+#define e_tlno _u_event._e_mark.lno2
+#define e_tcno _u_event._e_mark.cno2
+
+ struct { /* Input string. */
+ CHAR_T *asp; /* Allocated string. */
+ CHAR_T *csp; /* String. */
+ size_t len; /* String length. */
+ } _e_str;
+#define e_asp _u_event._e_str.asp
+#define e_csp _u_event._e_str.csp
+#define e_len _u_event._e_str.len
+ } _u_event;
+};
+
+typedef struct _keylist {
+ e_key_t value; /* Special value. */
+ CHAR_T ch; /* Key. */
+} KEYLIST;
+extern KEYLIST keylist[];
+
+ /* Return if more keys in queue. */
+#define KEYS_WAITING(sp) ((sp)->gp->i_cnt != 0)
+#define MAPPED_KEYS_WAITING(sp) \
+ (KEYS_WAITING(sp) && \
+ F_ISSET(&sp->gp->i_event[sp->gp->i_next].e_ch, CH_MAPPED))
+
+/*
+ * Ex/vi commands are generally separated by whitespace characters. We
+ * can't use the standard isspace(3) macro because it returns true for
+ * characters like ^K in the ASCII character set. The 4.4BSD isblank(3)
+ * macro does exactly what we want, but it's not portable yet.
+ *
+ * XXX
+ * Note side effect, ch is evaluated multiple times.
+ */
+#ifndef isblank
+#define isblank(ch) ((ch) == ' ' || (ch) == '\t')
+#endif
+
+/* The "standard" tab width, for displaying things to users. */
+#define STANDARD_TAB 6
+
+/* Various special characters, messages. */
+#define CH_BSEARCH '?' /* Backward search prompt. */
+#define CH_CURSOR ' ' /* Cursor character. */
+#define CH_ENDMARK '$' /* End of a range. */
+#define CH_EXPROMPT ':' /* Ex prompt. */
+#define CH_FSEARCH '/' /* Forward search prompt. */
+#define CH_HEX '\030' /* Leading hex character. */
+#define CH_LITERAL '\026' /* ASCII ^V. */
+#define CH_NO 'n' /* No. */
+#define CH_NOT_DIGIT 'a' /* A non-isdigit() character. */
+#define CH_QUIT 'q' /* Quit. */
+#define CH_YES 'y' /* Yes. */
+
+/*
+ * Checking for interrupts means that we look at the bit that gets set if the
+ * screen code supports asynchronous events, and call back into the event code
+ * so that non-asynchronous screens get a chance to post the interrupt.
+ *
+ * INTERRUPT_CHECK is the number of lines "operated" on before checking for
+ * interrupts.
+ */
+#define INTERRUPT_CHECK 100
+#define INTERRUPTED(sp) \
+ (F_ISSET((sp)->gp, G_INTERRUPTED) || \
+ (!v_event_get(sp, NULL, 0, EC_INTERRUPT) && \
+ F_ISSET((sp)->gp, G_INTERRUPTED)))
+#define CLR_INTERRUPT(sp) \
+ F_CLR((sp)->gp, G_INTERRUPTED)
+
+/* Flags describing types of characters being requested. */
+#define EC_INTERRUPT 0x001 /* Checking for interrupts. */
+#define EC_MAPCOMMAND 0x002 /* Apply the command map. */
+#define EC_MAPINPUT 0x004 /* Apply the input map. */
+#define EC_MAPNODIGIT 0x008 /* Return to a digit. */
+#define EC_QUOTED 0x010 /* Try to quote next character */
+#define EC_RAW 0x020 /* Any next character. XXX: not used. */
+#define EC_TIMEOUT 0x040 /* Timeout to next character. */
+
+/* Flags describing text input special cases. */
+#define TXT_ADDNEWLINE 0x00000001 /* Replay starts on a new line. */
+#define TXT_AICHARS 0x00000002 /* Leading autoindent chars. */
+#define TXT_ALTWERASE 0x00000004 /* Option: altwerase. */
+#define TXT_APPENDEOL 0x00000008 /* Appending after EOL. */
+#define TXT_AUTOINDENT 0x00000010 /* Autoindent set this line. */
+#define TXT_BACKSLASH 0x00000020 /* Backslashes escape characters. */
+#define TXT_BEAUTIFY 0x00000040 /* Only printable characters. */
+#define TXT_BS 0x00000080 /* Backspace returns the buffer. */
+#define TXT_CEDIT 0x00000100 /* Can return TERM_CEDIT. */
+#define TXT_CNTRLD 0x00000200 /* Control-D is a command. */
+#define TXT_CNTRLT 0x00000400 /* Control-T is an indent special. */
+#define TXT_CR 0x00000800 /* CR returns the buffer. */
+#define TXT_DOTTERM 0x00001000 /* Leading '.' terminates the input. */
+#define TXT_EMARK 0x00002000 /* End of replacement mark. */
+#define TXT_EOFCHAR 0x00004000 /* ICANON set, return EOF character. */
+#define TXT_ESCAPE 0x00008000 /* Escape returns the buffer. */
+#define TXT_FILEC 0x00010000 /* Option: filec. */
+#define TXT_INFOLINE 0x00020000 /* Editing the info line. */
+#define TXT_MAPINPUT 0x00040000 /* Apply the input map. */
+#define TXT_NLECHO 0x00080000 /* Echo the newline. */
+#define TXT_NUMBER 0x00100000 /* Number the line. */
+#define TXT_OVERWRITE 0x00200000 /* Overwrite characters. */
+#define TXT_PROMPT 0x00400000 /* Display a prompt. */
+#define TXT_RECORD 0x00800000 /* Record for replay. */
+#define TXT_REPLACE 0x01000000 /* Replace; don't delete overwrite. */
+#define TXT_REPLAY 0x02000000 /* Replay the last input. */
+#define TXT_RESOLVE 0x04000000 /* Resolve the text into the file. */
+#define TXT_SEARCHINCR 0x08000000 /* Incremental search. */
+#define TXT_SHOWMATCH 0x10000000 /* Option: showmatch. */
+#define TXT_TTYWERASE 0x20000000 /* Option: ttywerase. */
+#define TXT_WRAPMARGIN 0x40000000 /* Option: wrapmargin. */
diff --git a/common/line.c b/common/line.c
new file mode 100644
index 000000000000..bcb9e0c86bcb
--- /dev/null
+++ b/common/line.c
@@ -0,0 +1,576 @@
+/*-
+ * 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
+static const char sccsid[] = "@(#)line.c 10.21 (Berkeley) 9/15/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/time.h>
+
+#include <bitstring.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "common.h"
+#include "../vi/vi.h"
+
+static int scr_update __P((SCR *, recno_t, lnop_t, int));
+
+/*
+ * db_eget --
+ * Front-end to db_get, special case handling for empty files.
+ *
+ * PUBLIC: int db_eget __P((SCR *, recno_t, char **, size_t *, int *));
+ */
+int
+db_eget(sp, lno, pp, lenp, isemptyp)
+ SCR *sp;
+ recno_t lno; /* Line number. */
+ char **pp; /* Pointer store. */
+ size_t *lenp; /* Length store. */
+ int *isemptyp;
+{
+ recno_t l1;
+
+ if (isemptyp != NULL)
+ *isemptyp = 0;
+
+ /* If the line exists, simply return it. */
+ if (!db_get(sp, lno, 0, pp, lenp))
+ return (0);
+
+ /*
+ * If the user asked for line 0 or line 1, i.e. the only possible
+ * line in an empty file, find the last line of the file; db_last
+ * fails loudly.
+ */
+ if ((lno == 0 || lno == 1) && db_last(sp, &l1))
+ return (1);
+
+ /* If the file isn't empty, fail loudly. */
+ if (lno != 0 && lno != 1 || l1 != 0) {
+ db_err(sp, lno);
+ return (1);
+ }
+
+ if (isemptyp != NULL)
+ *isemptyp = 1;
+
+ return (1);
+}
+
+/*
+ * db_get --
+ * Look in the text buffers for a line, followed by the cache, followed
+ * by the database.
+ *
+ * PUBLIC: int db_get __P((SCR *, recno_t, u_int32_t, char **, size_t *));
+ */
+int
+db_get(sp, lno, flags, pp, lenp)
+ SCR *sp;
+ recno_t lno; /* Line number. */
+ u_int32_t flags;
+ char **pp; /* Pointer store. */
+ size_t *lenp; /* Length store. */
+{
+ DBT data, key;
+ EXF *ep;
+ TEXT *tp;
+ recno_t l1, l2;
+
+ /*
+ * The underlying recno stuff handles zero by returning NULL, but
+ * have to have an OOB condition for the look-aside into the input
+ * buffer anyway.
+ */
+ if (lno == 0)
+ goto err1;
+
+ /* Check for no underlying file. */
+ if ((ep = sp->ep) == NULL) {
+ ex_emsg(sp, NULL, EXM_NOFILEYET);
+ goto err3;
+ }
+
+ if (LF_ISSET(DBG_NOCACHE))
+ goto nocache;
+
+ /*
+ * Look-aside into the TEXT buffers and see if the line we want
+ * is there.
+ */
+ if (F_ISSET(sp, SC_TINPUT)) {
+ l1 = ((TEXT *)sp->tiq.cqh_first)->lno;
+ l2 = ((TEXT *)sp->tiq.cqh_last)->lno;
+ if (l1 <= lno && l2 >= lno) {
+#if defined(DEBUG) && 0
+ TRACE(sp, "retrieve TEXT buffer line %lu\n", (u_long)lno);
+#endif
+ for (tp = sp->tiq.cqh_first;
+ tp->lno != lno; tp = tp->q.cqe_next);
+ if (lenp != NULL)
+ *lenp = tp->len;
+ if (pp != NULL)
+ *pp = tp->lb;
+ return (0);
+ }
+ /*
+ * Adjust the line number for the number of lines used
+ * by the text input buffers.
+ */
+ if (lno > l2)
+ lno -= l2 - l1;
+ }
+
+ /* Look-aside into the cache, and see if the line we want is there. */
+ if (lno == ep->c_lno) {
+#if defined(DEBUG) && 0
+ TRACE(sp, "retrieve cached line %lu\n", (u_long)lno);
+#endif
+ if (lenp != NULL)
+ *lenp = ep->c_len;
+ if (pp != NULL)
+ *pp = ep->c_lp;
+ return (0);
+ }
+ ep->c_lno = OOBLNO;
+
+nocache:
+ /* Get the line from the underlying database. */
+ key.data = &lno;
+ key.size = sizeof(lno);
+ switch (ep->db->get(ep->db, &key, &data, 0)) {
+ case -1:
+ goto err2;
+ case 1:
+err1: if (LF_ISSET(DBG_FATAL))
+err2: db_err(sp, lno);
+err3: if (lenp != NULL)
+ *lenp = 0;
+ if (pp != NULL)
+ *pp = NULL;
+ return (1);
+ }
+
+ /* Reset the cache. */
+ ep->c_lno = lno;
+ ep->c_len = data.size;
+ ep->c_lp = data.data;
+
+#if defined(DEBUG) && 0
+ TRACE(sp, "retrieve DB line %lu\n", (u_long)lno);
+#endif
+ if (lenp != NULL)
+ *lenp = data.size;
+ if (pp != NULL)
+ *pp = ep->c_lp;
+ return (0);
+}
+
+/*
+ * db_delete --
+ * Delete a line from the file.
+ *
+ * PUBLIC: int db_delete __P((SCR *, recno_t));
+ */
+int
+db_delete(sp, lno)
+ SCR *sp;
+ recno_t lno;
+{
+ DBT key;
+ EXF *ep;
+
+#if defined(DEBUG) && 0
+ TRACE(sp, "delete line %lu\n", (u_long)lno);
+#endif
+ /* Check for no underlying file. */
+ if ((ep = sp->ep) == NULL) {
+ ex_emsg(sp, NULL, EXM_NOFILEYET);
+ return (1);
+ }
+
+ /* Update marks, @ and global commands. */
+ if (mark_insdel(sp, LINE_DELETE, lno))
+ return (1);
+ if (ex_g_insdel(sp, LINE_DELETE, lno))
+ return (1);
+
+ /* Log change. */
+ log_line(sp, lno, LOG_LINE_DELETE);
+
+ /* Update file. */
+ key.data = &lno;
+ key.size = sizeof(lno);
+ SIGBLOCK;
+ if (ep->db->del(ep->db, &key, 0) == 1) {
+ msgq(sp, M_SYSERR,
+ "003|unable to delete line %lu", (u_long)lno);
+ return (1);
+ }
+ SIGUNBLOCK;
+
+ /* Flush the cache, update line count, before screen update. */
+ if (lno <= ep->c_lno)
+ ep->c_lno = OOBLNO;
+ if (ep->c_nlines != OOBLNO)
+ --ep->c_nlines;
+
+ /* File now modified. */
+ if (F_ISSET(ep, F_FIRSTMODIFY))
+ (void)rcv_init(sp);
+ F_SET(ep, F_MODIFIED);
+
+ /* Update screen. */
+ return (scr_update(sp, lno, LINE_DELETE, 1));
+}
+
+/*
+ * db_append --
+ * Append a line into the file.
+ *
+ * PUBLIC: int db_append __P((SCR *, int, recno_t, char *, size_t));
+ */
+int
+db_append(sp, update, lno, p, len)
+ SCR *sp;
+ int update;
+ recno_t lno;
+ char *p;
+ size_t len;
+{
+ DBT data, key;
+ EXF *ep;
+ int rval;
+
+#if defined(DEBUG) && 0
+ TRACE(sp, "append to %lu: len %u {%.*s}\n", lno, len, MIN(len, 20), p);
+#endif
+ /* Check for no underlying file. */
+ if ((ep = sp->ep) == NULL) {
+ ex_emsg(sp, NULL, EXM_NOFILEYET);
+ return (1);
+ }
+
+ /* Update file. */
+ key.data = &lno;
+ key.size = sizeof(lno);
+ data.data = p;
+ data.size = len;
+ SIGBLOCK;
+ if (ep->db->put(ep->db, &key, &data, R_IAFTER) == -1) {
+ msgq(sp, M_SYSERR,
+ "004|unable to append to line %lu", (u_long)lno);
+ return (1);
+ }
+ SIGUNBLOCK;
+
+ /* Flush the cache, update line count, before screen update. */
+ if (lno < ep->c_lno)
+ ep->c_lno = OOBLNO;
+ if (ep->c_nlines != OOBLNO)
+ ++ep->c_nlines;
+
+ /* File now dirty. */
+ if (F_ISSET(ep, F_FIRSTMODIFY))
+ (void)rcv_init(sp);
+ F_SET(ep, F_MODIFIED);
+
+ /* Log change. */
+ log_line(sp, lno + 1, LOG_LINE_APPEND);
+
+ /* Update marks, @ and global commands. */
+ rval = 0;
+ if (mark_insdel(sp, LINE_INSERT, lno + 1))
+ rval = 1;
+ if (ex_g_insdel(sp, LINE_INSERT, lno + 1))
+ rval = 1;
+
+ /*
+ * Update screen.
+ *
+ * XXX
+ * Nasty hack. If multiple lines are input by the user, they aren't
+ * committed until an <ESC> is entered. The problem is the screen was
+ * updated/scrolled as each line was entered. So, when this routine
+ * is called to copy the new lines from the cut buffer into the file,
+ * it has to know not to update the screen again.
+ */
+ return (scr_update(sp, lno, LINE_APPEND, update) || rval);
+}
+
+/*
+ * db_insert --
+ * Insert a line into the file.
+ *
+ * PUBLIC: int db_insert __P((SCR *, recno_t, char *, size_t));
+ */
+int
+db_insert(sp, lno, p, len)
+ SCR *sp;
+ recno_t lno;
+ char *p;
+ size_t len;
+{
+ DBT data, key;
+ EXF *ep;
+ int rval;
+
+#if defined(DEBUG) && 0
+ TRACE(sp, "insert before %lu: len %lu {%.*s}\n",
+ (u_long)lno, (u_long)len, MIN(len, 20), p);
+#endif
+ /* Check for no underlying file. */
+ if ((ep = sp->ep) == NULL) {
+ ex_emsg(sp, NULL, EXM_NOFILEYET);
+ return (1);
+ }
+
+ /* Update file. */
+ key.data = &lno;
+ key.size = sizeof(lno);
+ data.data = p;
+ data.size = len;
+ SIGBLOCK;
+ if (ep->db->put(ep->db, &key, &data, R_IBEFORE) == -1) {
+ msgq(sp, M_SYSERR,
+ "005|unable to insert at line %lu", (u_long)lno);
+ return (1);
+ }
+ SIGUNBLOCK;
+
+ /* Flush the cache, update line count, before screen update. */
+ if (lno >= ep->c_lno)
+ ep->c_lno = OOBLNO;
+ if (ep->c_nlines != OOBLNO)
+ ++ep->c_nlines;
+
+ /* File now dirty. */
+ if (F_ISSET(ep, F_FIRSTMODIFY))
+ (void)rcv_init(sp);
+ F_SET(ep, F_MODIFIED);
+
+ /* Log change. */
+ log_line(sp, lno, LOG_LINE_INSERT);
+
+ /* Update marks, @ and global commands. */
+ rval = 0;
+ if (mark_insdel(sp, LINE_INSERT, lno))
+ rval = 1;
+ if (ex_g_insdel(sp, LINE_INSERT, lno))
+ rval = 1;
+
+ /* Update screen. */
+ return (scr_update(sp, lno, LINE_INSERT, 1) || rval);
+}
+
+/*
+ * db_set --
+ * Store a line in the file.
+ *
+ * PUBLIC: int db_set __P((SCR *, recno_t, char *, size_t));
+ */
+int
+db_set(sp, lno, p, len)
+ SCR *sp;
+ recno_t lno;
+ char *p;
+ size_t len;
+{
+ DBT data, key;
+ EXF *ep;
+
+#if defined(DEBUG) && 0
+ TRACE(sp, "replace line %lu: len %lu {%.*s}\n",
+ (u_long)lno, (u_long)len, MIN(len, 20), p);
+#endif
+
+ /* Check for no underlying file. */
+ if ((ep = sp->ep) == NULL) {
+ ex_emsg(sp, NULL, EXM_NOFILEYET);
+ return (1);
+ }
+
+ /* Log before change. */
+ log_line(sp, lno, LOG_LINE_RESET_B);
+
+ /* Update file. */
+ key.data = &lno;
+ key.size = sizeof(lno);
+ data.data = p;
+ data.size = len;
+ SIGBLOCK;
+ if (ep->db->put(ep->db, &key, &data, 0) == -1) {
+ msgq(sp, M_SYSERR,
+ "006|unable to store line %lu", (u_long)lno);
+ return (1);
+ }
+ SIGUNBLOCK;
+
+ /* Flush the cache, before logging or screen update. */
+ if (lno == ep->c_lno)
+ ep->c_lno = OOBLNO;
+
+ /* File now dirty. */
+ if (F_ISSET(ep, F_FIRSTMODIFY))
+ (void)rcv_init(sp);
+ F_SET(ep, F_MODIFIED);
+
+ /* Log after change. */
+ log_line(sp, lno, LOG_LINE_RESET_F);
+
+ /* Update screen. */
+ return (scr_update(sp, lno, LINE_RESET, 1));
+}
+
+/*
+ * db_exist --
+ * Return if a line exists.
+ *
+ * PUBLIC: int db_exist __P((SCR *, recno_t));
+ */
+int
+db_exist(sp, lno)
+ SCR *sp;
+ recno_t lno;
+{
+ EXF *ep;
+
+ /* Check for no underlying file. */
+ if ((ep = sp->ep) == NULL) {
+ ex_emsg(sp, NULL, EXM_NOFILEYET);
+ return (1);
+ }
+
+ if (lno == OOBLNO)
+ return (0);
+
+ /*
+ * Check the last-line number cache. Adjust the cached line
+ * number for the lines used by the text input buffers.
+ */
+ if (ep->c_nlines != OOBLNO)
+ return (lno <= (F_ISSET(sp, SC_TINPUT) ?
+ ep->c_nlines + (((TEXT *)sp->tiq.cqh_last)->lno -
+ ((TEXT *)sp->tiq.cqh_first)->lno) : ep->c_nlines));
+
+ /* Go get the line. */
+ return (!db_get(sp, lno, 0, NULL, NULL));
+}
+
+/*
+ * db_last --
+ * Return the number of lines in the file.
+ *
+ * PUBLIC: int db_last __P((SCR *, recno_t *));
+ */
+int
+db_last(sp, lnop)
+ SCR *sp;
+ recno_t *lnop;
+{
+ DBT data, key;
+ EXF *ep;
+ recno_t lno;
+
+ /* Check for no underlying file. */
+ if ((ep = sp->ep) == NULL) {
+ ex_emsg(sp, NULL, EXM_NOFILEYET);
+ return (1);
+ }
+
+ /*
+ * Check the last-line number cache. Adjust the cached line
+ * number for the lines used by the text input buffers.
+ */
+ if (ep->c_nlines != OOBLNO) {
+ *lnop = ep->c_nlines;
+ if (F_ISSET(sp, SC_TINPUT))
+ *lnop += ((TEXT *)sp->tiq.cqh_last)->lno -
+ ((TEXT *)sp->tiq.cqh_first)->lno;
+ return (0);
+ }
+
+ key.data = &lno;
+ key.size = sizeof(lno);
+
+ switch (ep->db->seq(ep->db, &key, &data, R_LAST)) {
+ case -1:
+ msgq(sp, M_SYSERR, "007|unable to get last line");
+ *lnop = 0;
+ return (1);
+ case 1:
+ *lnop = 0;
+ return (0);
+ default:
+ break;
+ }
+
+ /* Fill the cache. */
+ memcpy(&lno, key.data, sizeof(lno));
+ ep->c_nlines = ep->c_lno = lno;
+ ep->c_len = data.size;
+ ep->c_lp = data.data;
+
+ /* Return the value. */
+ *lnop = (F_ISSET(sp, SC_TINPUT) &&
+ ((TEXT *)sp->tiq.cqh_last)->lno > lno ?
+ ((TEXT *)sp->tiq.cqh_last)->lno : lno);
+ return (0);
+}
+
+/*
+ * db_err --
+ * Report a line error.
+ *
+ * PUBLIC: void db_err __P((SCR *, recno_t));
+ */
+void
+db_err(sp, lno)
+ SCR *sp;
+ recno_t lno;
+{
+ msgq(sp, M_ERR,
+ "008|Error: unable to retrieve line %lu", (u_long)lno);
+}
+
+/*
+ * scr_update --
+ * Update all of the screens that are backed by the file that
+ * just changed.
+ */
+static int
+scr_update(sp, lno, op, current)
+ SCR *sp;
+ recno_t lno;
+ lnop_t op;
+ int current;
+{
+ EXF *ep;
+ SCR *tsp;
+
+ if (F_ISSET(sp, SC_EX))
+ return (0);
+
+ ep = sp->ep;
+ if (ep->refcnt != 1)
+ for (tsp = sp->gp->dq.cqh_first;
+ tsp != (void *)&sp->gp->dq; tsp = tsp->q.cqe_next)
+ if (sp != tsp && tsp->ep == ep)
+ if (vs_change(tsp, lno, op))
+ return (1);
+ return (current ? vs_change(sp, lno, op) : 0);
+}
diff --git a/common/log.c b/common/log.c
new file mode 100644
index 000000000000..9a9fe793ffb8
--- /dev/null
+++ b/common/log.c
@@ -0,0 +1,717 @@
+/*-
+ * 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
+static const char sccsid[] = "@(#)log.c 10.8 (Berkeley) 3/6/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/stat.h>
+
+#include <bitstring.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "common.h"
+
+/*
+ * The log consists of records, each containing a type byte and a variable
+ * length byte string, as follows:
+ *
+ * LOG_CURSOR_INIT MARK
+ * LOG_CURSOR_END MARK
+ * LOG_LINE_APPEND recno_t char *
+ * LOG_LINE_DELETE recno_t char *
+ * LOG_LINE_INSERT recno_t char *
+ * LOG_LINE_RESET_F recno_t char *
+ * LOG_LINE_RESET_B recno_t char *
+ * LOG_MARK LMARK
+ *
+ * We do before image physical logging. This means that the editor layer
+ * MAY NOT modify records in place, even if simply deleting or overwriting
+ * characters. Since the smallest unit of logging is a line, we're using
+ * up lots of space. This may eventually have to be reduced, probably by
+ * doing logical logging, which is a much cooler database phrase.
+ *
+ * The implementation of the historic vi 'u' command, using roll-forward and
+ * roll-back, is simple. Each set of changes has a LOG_CURSOR_INIT record,
+ * followed by a number of other records, followed by a LOG_CURSOR_END record.
+ * LOG_LINE_RESET records come in pairs. The first is a LOG_LINE_RESET_B
+ * record, and is the line before the change. The second is LOG_LINE_RESET_F,
+ * and is the line after the change. Roll-back is done by backing up to the
+ * first LOG_CURSOR_INIT record before a change. Roll-forward is done in a
+ * similar fashion.
+ *
+ * The 'U' command is implemented by rolling backward to a LOG_CURSOR_END
+ * record for a line different from the current one. It should be noted that
+ * this means that a subsequent 'u' command will make a change based on the
+ * new position of the log's cursor. This is okay, and, in fact, historic vi
+ * behaved that way.
+ */
+
+static int log_cursor1 __P((SCR *, int));
+static void log_err __P((SCR *, char *, int));
+#if defined(DEBUG) && 0
+static void log_trace __P((SCR *, char *, recno_t, u_char *));
+#endif
+
+/* Try and restart the log on failure, i.e. if we run out of memory. */
+#define LOG_ERR { \
+ log_err(sp, __FILE__, __LINE__); \
+ return (1); \
+}
+
+/*
+ * log_init --
+ * Initialize the logging subsystem.
+ *
+ * PUBLIC: int log_init __P((SCR *, EXF *));
+ */
+int
+log_init(sp, ep)
+ SCR *sp;
+ EXF *ep;
+{
+ /*
+ * !!!
+ * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER.
+ *
+ * Initialize the buffer. The logging subsystem has its own
+ * buffers because the global ones are almost by definition
+ * going to be in use when the log runs.
+ */
+ ep->l_lp = NULL;
+ ep->l_len = 0;
+ ep->l_cursor.lno = 1; /* XXX Any valid recno. */
+ ep->l_cursor.cno = 0;
+ ep->l_high = ep->l_cur = 1;
+
+ ep->log = dbopen(NULL, O_CREAT | O_NONBLOCK | O_RDWR,
+ S_IRUSR | S_IWUSR, DB_RECNO, NULL);
+ if (ep->log == NULL) {
+ msgq(sp, M_SYSERR, "009|Log file");
+ F_SET(ep, F_NOLOG);
+ return (1);
+ }
+
+ return (0);
+}
+
+/*
+ * log_end --
+ * Close the logging subsystem.
+ *
+ * PUBLIC: int log_end __P((SCR *, EXF *));
+ */
+int
+log_end(sp, ep)
+ SCR *sp;
+ EXF *ep;
+{
+ /*
+ * !!!
+ * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER.
+ */
+ if (ep->log != NULL) {
+ (void)(ep->log->close)(ep->log);
+ ep->log = NULL;
+ }
+ if (ep->l_lp != NULL) {
+ free(ep->l_lp);
+ ep->l_lp = NULL;
+ }
+ ep->l_len = 0;
+ ep->l_cursor.lno = 1; /* XXX Any valid recno. */
+ ep->l_cursor.cno = 0;
+ ep->l_high = ep->l_cur = 1;
+ return (0);
+}
+
+/*
+ * log_cursor --
+ * Log the current cursor position, starting an event.
+ *
+ * PUBLIC: int log_cursor __P((SCR *));
+ */
+int
+log_cursor(sp)
+ SCR *sp;
+{
+ EXF *ep;
+
+ ep = sp->ep;
+ if (F_ISSET(ep, F_NOLOG))
+ return (0);
+
+ /*
+ * If any changes were made since the last cursor init,
+ * put out the ending cursor record.
+ */
+ if (ep->l_cursor.lno == OOBLNO) {
+ ep->l_cursor.lno = sp->lno;
+ ep->l_cursor.cno = sp->cno;
+ return (log_cursor1(sp, LOG_CURSOR_END));
+ }
+ ep->l_cursor.lno = sp->lno;
+ ep->l_cursor.cno = sp->cno;
+ return (0);
+}
+
+/*
+ * log_cursor1 --
+ * Actually push a cursor record out.
+ */
+static int
+log_cursor1(sp, type)
+ SCR *sp;
+ int type;
+{
+ DBT data, key;
+ EXF *ep;
+
+ ep = sp->ep;
+ BINC_RET(sp, ep->l_lp, ep->l_len, sizeof(u_char) + sizeof(MARK));
+ ep->l_lp[0] = type;
+ memmove(ep->l_lp + sizeof(u_char), &ep->l_cursor, sizeof(MARK));
+
+ key.data = &ep->l_cur;
+ key.size = sizeof(recno_t);
+ data.data = ep->l_lp;
+ data.size = sizeof(u_char) + sizeof(MARK);
+ if (ep->log->put(ep->log, &key, &data, 0) == -1)
+ LOG_ERR;
+
+#if defined(DEBUG) && 0
+ TRACE(sp, "%lu: %s: %u/%u\n", ep->l_cur,
+ type == LOG_CURSOR_INIT ? "log_cursor_init" : "log_cursor_end",
+ sp->lno, sp->cno);
+#endif
+ /* Reset high water mark. */
+ ep->l_high = ++ep->l_cur;
+
+ return (0);
+}
+
+/*
+ * log_line --
+ * Log a line change.
+ *
+ * PUBLIC: int log_line __P((SCR *, recno_t, u_int));
+ */
+int
+log_line(sp, lno, action)
+ SCR *sp;
+ recno_t lno;
+ u_int action;
+{
+ DBT data, key;
+ EXF *ep;
+ size_t len;
+ char *lp;
+
+ ep = sp->ep;
+ if (F_ISSET(ep, F_NOLOG))
+ return (0);
+
+ /*
+ * XXX
+ *
+ * Kluge for vi. Clear the EXF undo flag so that the
+ * next 'u' command does a roll-back, regardless.
+ */
+ F_CLR(ep, F_UNDO);
+
+ /* Put out one initial cursor record per set of changes. */
+ if (ep->l_cursor.lno != OOBLNO) {
+ if (log_cursor1(sp, LOG_CURSOR_INIT))
+ return (1);
+ ep->l_cursor.lno = OOBLNO;
+ }
+
+ /*
+ * Put out the changes. If it's a LOG_LINE_RESET_B call, it's a
+ * special case, avoid the caches. Also, if it fails and it's
+ * line 1, it just means that the user started with an empty file,
+ * so fake an empty length line.
+ */
+ if (action == LOG_LINE_RESET_B) {
+ if (db_get(sp, lno, DBG_NOCACHE, &lp, &len)) {
+ if (lno != 1) {
+ db_err(sp, lno);
+ return (1);
+ }
+ len = 0;
+ lp = "";
+ }
+ } else
+ if (db_get(sp, lno, DBG_FATAL, &lp, &len))
+ return (1);
+ BINC_RET(sp,
+ ep->l_lp, ep->l_len, len + sizeof(u_char) + sizeof(recno_t));
+ ep->l_lp[0] = action;
+ memmove(ep->l_lp + sizeof(u_char), &lno, sizeof(recno_t));
+ memmove(ep->l_lp + sizeof(u_char) + sizeof(recno_t), lp, len);
+
+ key.data = &ep->l_cur;
+ key.size = sizeof(recno_t);
+ data.data = ep->l_lp;
+ data.size = len + sizeof(u_char) + sizeof(recno_t);
+ if (ep->log->put(ep->log, &key, &data, 0) == -1)
+ LOG_ERR;
+
+#if defined(DEBUG) && 0
+ switch (action) {
+ case LOG_LINE_APPEND:
+ TRACE(sp, "%u: log_line: append: %lu {%u}\n",
+ ep->l_cur, lno, len);
+ break;
+ case LOG_LINE_DELETE:
+ TRACE(sp, "%lu: log_line: delete: %lu {%u}\n",
+ ep->l_cur, lno, len);
+ break;
+ case LOG_LINE_INSERT:
+ TRACE(sp, "%lu: log_line: insert: %lu {%u}\n",
+ ep->l_cur, lno, len);
+ break;
+ case LOG_LINE_RESET_F:
+ TRACE(sp, "%lu: log_line: reset_f: %lu {%u}\n",
+ ep->l_cur, lno, len);
+ break;
+ case LOG_LINE_RESET_B:
+ TRACE(sp, "%lu: log_line: reset_b: %lu {%u}\n",
+ ep->l_cur, lno, len);
+ break;
+ }
+#endif
+ /* Reset high water mark. */
+ ep->l_high = ++ep->l_cur;
+
+ return (0);
+}
+
+/*
+ * log_mark --
+ * Log a mark position. For the log to work, we assume that there
+ * aren't any operations that just put out a log record -- this
+ * would mean that undo operations would only reset marks, and not
+ * cause any other change.
+ *
+ * PUBLIC: int log_mark __P((SCR *, LMARK *));
+ */
+int
+log_mark(sp, lmp)
+ SCR *sp;
+ LMARK *lmp;
+{
+ DBT data, key;
+ EXF *ep;
+
+ ep = sp->ep;
+ if (F_ISSET(ep, F_NOLOG))
+ return (0);
+
+ /* Put out one initial cursor record per set of changes. */
+ if (ep->l_cursor.lno != OOBLNO) {
+ if (log_cursor1(sp, LOG_CURSOR_INIT))
+ return (1);
+ ep->l_cursor.lno = OOBLNO;
+ }
+
+ BINC_RET(sp, ep->l_lp,
+ ep->l_len, sizeof(u_char) + sizeof(LMARK));
+ ep->l_lp[0] = LOG_MARK;
+ memmove(ep->l_lp + sizeof(u_char), lmp, sizeof(LMARK));
+
+ key.data = &ep->l_cur;
+ key.size = sizeof(recno_t);
+ data.data = ep->l_lp;
+ data.size = sizeof(u_char) + sizeof(LMARK);
+ if (ep->log->put(ep->log, &key, &data, 0) == -1)
+ LOG_ERR;
+
+#if defined(DEBUG) && 0
+ TRACE(sp, "%lu: mark %c: %lu/%u\n",
+ ep->l_cur, lmp->name, lmp->lno, lmp->cno);
+#endif
+ /* Reset high water mark. */
+ ep->l_high = ++ep->l_cur;
+ return (0);
+}
+
+/*
+ * Log_backward --
+ * Roll the log backward one operation.
+ *
+ * PUBLIC: int log_backward __P((SCR *, MARK *));
+ */
+int
+log_backward(sp, rp)
+ SCR *sp;
+ MARK *rp;
+{
+ DBT key, data;
+ EXF *ep;
+ LMARK lm;
+ MARK m;
+ recno_t lno;
+ int didop;
+ u_char *p;
+
+ ep = sp->ep;
+ if (F_ISSET(ep, F_NOLOG)) {
+ msgq(sp, M_ERR,
+ "010|Logging not being performed, undo not possible");
+ return (1);
+ }
+
+ if (ep->l_cur == 1) {
+ msgq(sp, M_BERR, "011|No changes to undo");
+ return (1);
+ }
+
+ F_SET(ep, F_NOLOG); /* Turn off logging. */
+
+ key.data = &ep->l_cur; /* Initialize db request. */
+ key.size = sizeof(recno_t);
+ for (didop = 0;;) {
+ --ep->l_cur;
+ if (ep->log->get(ep->log, &key, &data, 0))
+ LOG_ERR;
+#if defined(DEBUG) && 0
+ log_trace(sp, "log_backward", ep->l_cur, data.data);
+#endif
+ switch (*(p = (u_char *)data.data)) {
+ case LOG_CURSOR_INIT:
+ if (didop) {
+ memmove(rp, p + sizeof(u_char), sizeof(MARK));
+ F_CLR(ep, F_NOLOG);
+ return (0);
+ }
+ break;
+ case LOG_CURSOR_END:
+ break;
+ case LOG_LINE_APPEND:
+ case LOG_LINE_INSERT:
+ didop = 1;
+ memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
+ if (db_delete(sp, lno))
+ goto err;
+ ++sp->rptlines[L_DELETED];
+ break;
+ case LOG_LINE_DELETE:
+ didop = 1;
+ memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
+ if (db_insert(sp, lno, p + sizeof(u_char) +
+ sizeof(recno_t), data.size - sizeof(u_char) -
+ sizeof(recno_t)))
+ goto err;
+ ++sp->rptlines[L_ADDED];
+ break;
+ case LOG_LINE_RESET_F:
+ break;
+ case LOG_LINE_RESET_B:
+ didop = 1;
+ memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
+ if (db_set(sp, lno, p + sizeof(u_char) +
+ sizeof(recno_t), data.size - sizeof(u_char) -
+ sizeof(recno_t)))
+ goto err;
+ if (sp->rptlchange != lno) {
+ sp->rptlchange = lno;
+ ++sp->rptlines[L_CHANGED];
+ }
+ break;
+ case LOG_MARK:
+ didop = 1;
+ memmove(&lm, p + sizeof(u_char), sizeof(LMARK));
+ m.lno = lm.lno;
+ m.cno = lm.cno;
+ if (mark_set(sp, lm.name, &m, 0))
+ goto err;
+ break;
+ default:
+ abort();
+ }
+ }
+
+err: F_CLR(ep, F_NOLOG);
+ return (1);
+}
+
+/*
+ * Log_setline --
+ * Reset the line to its original appearance.
+ *
+ * XXX
+ * There's a bug in this code due to our not logging cursor movements
+ * unless a change was made. If you do a change, move off the line,
+ * then move back on and do a 'U', the line will be restored to the way
+ * it was before the original change.
+ *
+ * PUBLIC: int log_setline __P((SCR *));
+ */
+int
+log_setline(sp)
+ SCR *sp;
+{
+ DBT key, data;
+ EXF *ep;
+ LMARK lm;
+ MARK m;
+ recno_t lno;
+ u_char *p;
+
+ ep = sp->ep;
+ if (F_ISSET(ep, F_NOLOG)) {
+ msgq(sp, M_ERR,
+ "012|Logging not being performed, undo not possible");
+ return (1);
+ }
+
+ if (ep->l_cur == 1)
+ return (1);
+
+ F_SET(ep, F_NOLOG); /* Turn off logging. */
+
+ key.data = &ep->l_cur; /* Initialize db request. */
+ key.size = sizeof(recno_t);
+
+ for (;;) {
+ --ep->l_cur;
+ if (ep->log->get(ep->log, &key, &data, 0))
+ LOG_ERR;
+#if defined(DEBUG) && 0
+ log_trace(sp, "log_setline", ep->l_cur, data.data);
+#endif
+ switch (*(p = (u_char *)data.data)) {
+ case LOG_CURSOR_INIT:
+ memmove(&m, p + sizeof(u_char), sizeof(MARK));
+ if (m.lno != sp->lno || ep->l_cur == 1) {
+ F_CLR(ep, F_NOLOG);
+ return (0);
+ }
+ break;
+ case LOG_CURSOR_END:
+ memmove(&m, p + sizeof(u_char), sizeof(MARK));
+ if (m.lno != sp->lno) {
+ ++ep->l_cur;
+ F_CLR(ep, F_NOLOG);
+ return (0);
+ }
+ break;
+ case LOG_LINE_APPEND:
+ case LOG_LINE_INSERT:
+ case LOG_LINE_DELETE:
+ case LOG_LINE_RESET_F:
+ break;
+ case LOG_LINE_RESET_B:
+ memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
+ if (lno == sp->lno &&
+ db_set(sp, lno, p + sizeof(u_char) +
+ sizeof(recno_t), data.size - sizeof(u_char) -
+ sizeof(recno_t)))
+ goto err;
+ if (sp->rptlchange != lno) {
+ sp->rptlchange = lno;
+ ++sp->rptlines[L_CHANGED];
+ }
+ case LOG_MARK:
+ memmove(&lm, p + sizeof(u_char), sizeof(LMARK));
+ m.lno = lm.lno;
+ m.cno = lm.cno;
+ if (mark_set(sp, lm.name, &m, 0))
+ goto err;
+ break;
+ default:
+ abort();
+ }
+ }
+
+err: F_CLR(ep, F_NOLOG);
+ return (1);
+}
+
+/*
+ * Log_forward --
+ * Roll the log forward one operation.
+ *
+ * PUBLIC: int log_forward __P((SCR *, MARK *));
+ */
+int
+log_forward(sp, rp)
+ SCR *sp;
+ MARK *rp;
+{
+ DBT key, data;
+ EXF *ep;
+ LMARK lm;
+ MARK m;
+ recno_t lno;
+ int didop;
+ u_char *p;
+
+ ep = sp->ep;
+ if (F_ISSET(ep, F_NOLOG)) {
+ msgq(sp, M_ERR,
+ "013|Logging not being performed, roll-forward not possible");
+ return (1);
+ }
+
+ if (ep->l_cur == ep->l_high) {
+ msgq(sp, M_BERR, "014|No changes to re-do");
+ return (1);
+ }
+
+ F_SET(ep, F_NOLOG); /* Turn off logging. */
+
+ key.data = &ep->l_cur; /* Initialize db request. */
+ key.size = sizeof(recno_t);
+ for (didop = 0;;) {
+ ++ep->l_cur;
+ if (ep->log->get(ep->log, &key, &data, 0))
+ LOG_ERR;
+#if defined(DEBUG) && 0
+ log_trace(sp, "log_forward", ep->l_cur, data.data);
+#endif
+ switch (*(p = (u_char *)data.data)) {
+ case LOG_CURSOR_END:
+ if (didop) {
+ ++ep->l_cur;
+ memmove(rp, p + sizeof(u_char), sizeof(MARK));
+ F_CLR(ep, F_NOLOG);
+ return (0);
+ }
+ break;
+ case LOG_CURSOR_INIT:
+ break;
+ case LOG_LINE_APPEND:
+ case LOG_LINE_INSERT:
+ didop = 1;
+ memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
+ if (db_insert(sp, lno, p + sizeof(u_char) +
+ sizeof(recno_t), data.size - sizeof(u_char) -
+ sizeof(recno_t)))
+ goto err;
+ ++sp->rptlines[L_ADDED];
+ break;
+ case LOG_LINE_DELETE:
+ didop = 1;
+ memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
+ if (db_delete(sp, lno))
+ goto err;
+ ++sp->rptlines[L_DELETED];
+ break;
+ case LOG_LINE_RESET_B:
+ break;
+ case LOG_LINE_RESET_F:
+ didop = 1;
+ memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
+ if (db_set(sp, lno, p + sizeof(u_char) +
+ sizeof(recno_t), data.size - sizeof(u_char) -
+ sizeof(recno_t)))
+ goto err;
+ if (sp->rptlchange != lno) {
+ sp->rptlchange = lno;
+ ++sp->rptlines[L_CHANGED];
+ }
+ break;
+ case LOG_MARK:
+ didop = 1;
+ memmove(&lm, p + sizeof(u_char), sizeof(LMARK));
+ m.lno = lm.lno;
+ m.cno = lm.cno;
+ if (mark_set(sp, lm.name, &m, 0))
+ goto err;
+ break;
+ default:
+ abort();
+ }
+ }
+
+err: F_CLR(ep, F_NOLOG);
+ return (1);
+}
+
+/*
+ * log_err --
+ * Try and restart the log on failure, i.e. if we run out of memory.
+ */
+static void
+log_err(sp, file, line)
+ SCR *sp;
+ char *file;
+ int line;
+{
+ EXF *ep;
+
+ msgq(sp, M_SYSERR, "015|%s/%d: log put error", tail(file), line);
+ ep = sp->ep;
+ (void)ep->log->close(ep->log);
+ if (!log_init(sp, ep))
+ msgq(sp, M_ERR, "267|Log restarted");
+}
+
+#if defined(DEBUG) && 0
+static void
+log_trace(sp, msg, rno, p)
+ SCR *sp;
+ char *msg;
+ recno_t rno;
+ u_char *p;
+{
+ LMARK lm;
+ MARK m;
+ recno_t lno;
+
+ switch (*p) {
+ case LOG_CURSOR_INIT:
+ memmove(&m, p + sizeof(u_char), sizeof(MARK));
+ TRACE(sp, "%lu: %s: C_INIT: %u/%u\n", rno, msg, m.lno, m.cno);
+ break;
+ case LOG_CURSOR_END:
+ memmove(&m, p + sizeof(u_char), sizeof(MARK));
+ TRACE(sp, "%lu: %s: C_END: %u/%u\n", rno, msg, m.lno, m.cno);
+ break;
+ case LOG_LINE_APPEND:
+ memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
+ TRACE(sp, "%lu: %s: APPEND: %lu\n", rno, msg, lno);
+ break;
+ case LOG_LINE_INSERT:
+ memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
+ TRACE(sp, "%lu: %s: INSERT: %lu\n", rno, msg, lno);
+ break;
+ case LOG_LINE_DELETE:
+ memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
+ TRACE(sp, "%lu: %s: DELETE: %lu\n", rno, msg, lno);
+ break;
+ case LOG_LINE_RESET_F:
+ memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
+ TRACE(sp, "%lu: %s: RESET_F: %lu\n", rno, msg, lno);
+ break;
+ case LOG_LINE_RESET_B:
+ memmove(&lno, p + sizeof(u_char), sizeof(recno_t));
+ TRACE(sp, "%lu: %s: RESET_B: %lu\n", rno, msg, lno);
+ break;
+ case LOG_MARK:
+ memmove(&lm, p + sizeof(u_char), sizeof(LMARK));
+ TRACE(sp,
+ "%lu: %s: MARK: %u/%u\n", rno, msg, lm.lno, lm.cno);
+ break;
+ default:
+ abort();
+ }
+}
+#endif
diff --git a/common/log.h b/common/log.h
new file mode 100644
index 000000000000..df307319b1d3
--- /dev/null
+++ b/common/log.h
@@ -0,0 +1,20 @@
+/*-
+ * 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.
+ *
+ * @(#)log.h 10.2 (Berkeley) 3/6/96
+ */
+
+#define LOG_NOTYPE 0
+#define LOG_CURSOR_INIT 1
+#define LOG_CURSOR_END 2
+#define LOG_LINE_APPEND 3
+#define LOG_LINE_DELETE 4
+#define LOG_LINE_INSERT 5
+#define LOG_LINE_RESET_F 6
+#define LOG_LINE_RESET_B 7
+#define LOG_MARK 8
diff --git a/common/main.c b/common/main.c
new file mode 100644
index 000000000000..6fb2ed1fe2f0
--- /dev/null
+++ b/common/main.c
@@ -0,0 +1,617 @@
+/*-
+ * 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
+static const char copyright[] =
+"@(#) Copyright (c) 1992, 1993, 1994\n\
+ The Regents of the University of California. All rights reserved.\n\
+@(#) Copyright (c) 1992, 1993, 1994, 1995, 1996\n\
+ Keith Bostic. All rights reserved.\n";
+#endif /* not lint */
+
+#ifndef lint
+static const char sccsid[] = "@(#)main.c 10.48 (Berkeley) 10/11/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+
+#include <bitstring.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "common.h"
+#include "../vi/vi.h"
+#include "pathnames.h"
+
+static void attach __P((GS *));
+static void v_estr __P((char *, int, char *));
+static int v_obsolete __P((char *, char *[]));
+
+/*
+ * editor --
+ * Main editor routine.
+ *
+ * PUBLIC: int editor __P((GS *, int, char *[]));
+ */
+int
+editor(gp, argc, argv)
+ GS *gp;
+ int argc;
+ char *argv[];
+{
+ extern int optind;
+ extern char *optarg;
+ const char *p;
+ EVENT ev;
+ FREF *frp;
+ SCR *sp;
+ size_t len;
+ u_int flags;
+ int ch, flagchk, lflag, secure, startup, readonly, rval, silent;
+ char *tag_f, *wsizearg, path[256];
+
+ /* Initialize the busy routine, if not defined by the screen. */
+ if (gp->scr_busy == NULL)
+ gp->scr_busy = vs_busy;
+ /* Initialize the message routine, if not defined by the screen. */
+ if (gp->scr_msg == NULL)
+ gp->scr_msg = vs_msg;
+
+ /* Common global structure initialization. */
+ CIRCLEQ_INIT(&gp->dq);
+ CIRCLEQ_INIT(&gp->hq);
+ LIST_INIT(&gp->ecq);
+ LIST_INSERT_HEAD(&gp->ecq, &gp->excmd, q);
+ gp->noprint = DEFAULT_NOPRINT;
+
+ /* Structures shared by screens so stored in the GS structure. */
+ CIRCLEQ_INIT(&gp->frefq);
+ CIRCLEQ_INIT(&gp->dcb_store.textq);
+ LIST_INIT(&gp->cutq);
+ LIST_INIT(&gp->seqq);
+
+ /* Set initial screen type and mode based on the program name. */
+ readonly = 0;
+ if (!strcmp(gp->progname, "ex") || !strcmp(gp->progname, "nex"))
+ LF_INIT(SC_EX);
+ else {
+ /* Nview, view are readonly. */
+ if (!strcmp(gp->progname, "nview") ||
+ !strcmp(gp->progname, "view"))
+ readonly = 1;
+
+ /* Vi is the default. */
+ LF_INIT(SC_VI);
+ }
+
+ /* Convert old-style arguments into new-style ones. */
+ if (v_obsolete(gp->progname, argv))
+ return (1);
+
+ /* Parse the arguments. */
+ flagchk = '\0';
+ tag_f = wsizearg = NULL;
+ lflag = secure = silent = 0;
+ startup = 1;
+
+ /* Set the file snapshot flag. */
+ F_SET(gp, G_SNAPSHOT);
+
+#ifdef DEBUG
+ while ((ch = getopt(argc, argv, "c:D:eFlRrSsT:t:vw:")) != EOF)
+#else
+ while ((ch = getopt(argc, argv, "c:eFlRrSst:vw:")) != EOF)
+#endif
+ switch (ch) {
+ case 'c': /* Run the command. */
+ /*
+ * XXX
+ * We should support multiple -c options.
+ */
+ if (gp->c_option != NULL) {
+ v_estr(gp->progname, 0,
+ "only one -c command may be specified.");
+ return (1);
+ }
+ gp->c_option = optarg;
+ break;
+#ifdef DEBUG
+ case 'D':
+ switch (optarg[0]) {
+ case 's':
+ startup = 0;
+ break;
+ case 'w':
+ attach(gp);
+ break;
+ default:
+ v_estr(gp->progname, 0,
+ "usage: -D requires s or w argument.");
+ return (1);
+ }
+ break;
+#endif
+ case 'e': /* Ex mode. */
+ LF_CLR(SC_VI);
+ LF_SET(SC_EX);
+ break;
+ case 'F': /* No snapshot. */
+ F_CLR(gp, G_SNAPSHOT);
+ break;
+ case 'l': /* Set lisp, showmatch options. */
+ lflag = 1;
+ break;
+ case 'R': /* Readonly. */
+ readonly = 1;
+ break;
+ case 'r': /* Recover. */
+ if (flagchk == 't') {
+ v_estr(gp->progname, 0,
+ "only one of -r and -t may be specified.");
+ return (1);
+ }
+ flagchk = 'r';
+ break;
+ case 'S':
+ secure = 1;
+ break;
+ case 's':
+ silent = 1;
+ break;
+#ifdef DEBUG
+ case 'T': /* Trace. */
+ if ((gp->tracefp = fopen(optarg, "w")) == NULL) {
+ v_estr(gp->progname, errno, optarg);
+ goto err;
+ }
+ (void)fprintf(gp->tracefp,
+ "\n===\ntrace: open %s\n", optarg);
+ break;
+#endif
+ case 't': /* Tag. */
+ if (flagchk == 'r') {
+ v_estr(gp->progname, 0,
+ "only one of -r and -t may be specified.");
+ return (1);
+ }
+ if (flagchk == 't') {
+ v_estr(gp->progname, 0,
+ "only one tag file may be specified.");
+ return (1);
+ }
+ flagchk = 't';
+ tag_f = optarg;
+ break;
+ case 'v': /* Vi mode. */
+ LF_CLR(SC_EX);
+ LF_SET(SC_VI);
+ break;
+ case 'w':
+ wsizearg = optarg;
+ break;
+ case '?':
+ default:
+ (void)gp->scr_usage();
+ return (1);
+ }
+ argc -= optind;
+ argv += optind;
+
+ /*
+ * -s option is only meaningful to ex.
+ *
+ * If not reading from a terminal, it's like -s was specified.
+ */
+ if (silent && !LF_ISSET(SC_EX)) {
+ v_estr(gp->progname, 0, "-s option is only applicable to ex.");
+ goto err;
+ }
+ if (LF_ISSET(SC_EX) && F_ISSET(gp, G_SCRIPTED))
+ silent = 1;
+
+ /*
+ * Build and initialize the first/current screen. This is a bit
+ * tricky. If an error is returned, we may or may not have a
+ * screen structure. If we have a screen structure, put it on a
+ * display queue so that the error messages get displayed.
+ *
+ * !!!
+ * Everything we do until we go interactive is done in ex mode.
+ */
+ if (screen_init(gp, NULL, &sp)) {
+ if (sp != NULL)
+ CIRCLEQ_INSERT_HEAD(&gp->dq, sp, q);
+ goto err;
+ }
+ F_SET(sp, SC_EX);
+ CIRCLEQ_INSERT_HEAD(&gp->dq, sp, q);
+
+ if (v_key_init(sp)) /* Special key initialization. */
+ goto err;
+
+ { int oargs[5], *oargp = oargs;
+ if (lflag) { /* Command-line options. */
+ *oargp++ = O_LISP;
+ *oargp++ = O_SHOWMATCH;
+ }
+ if (readonly)
+ *oargp++ = O_READONLY;
+ if (secure)
+ *oargp++ = O_SECURE;
+ *oargp = -1; /* Options initialization. */
+ if (opts_init(sp, oargs))
+ goto err;
+ }
+ if (wsizearg != NULL) {
+ ARGS *av[2], a, b;
+ (void)snprintf(path, sizeof(path), "window=%s", wsizearg);
+ a.bp = (CHAR_T *)path;
+ a.len = strlen(path);
+ b.bp = NULL;
+ b.len = 0;
+ av[0] = &a;
+ av[1] = &b;
+ (void)opts_set(sp, av, NULL);
+ }
+ if (silent) { /* Ex batch mode option values. */
+ O_CLR(sp, O_AUTOPRINT);
+ O_CLR(sp, O_PROMPT);
+ O_CLR(sp, O_VERBOSE);
+ O_CLR(sp, O_WARN);
+ F_SET(sp, SC_EX_SILENT);
+ }
+
+ sp->rows = O_VAL(sp, O_LINES); /* Make ex formatting work. */
+ sp->cols = O_VAL(sp, O_COLUMNS);
+
+ if (!silent && startup) { /* Read EXINIT, exrc files. */
+ if (ex_exrc(sp))
+ goto err;
+ if (F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE)) {
+ if (screen_end(sp))
+ goto err;
+ goto done;
+ }
+ }
+
+ /*
+ * List recovery files if -r specified without file arguments.
+ * Note, options must be initialized and startup information
+ * read before doing this.
+ */
+ if (flagchk == 'r' && argv[0] == NULL) {
+ if (rcv_list(sp))
+ goto err;
+ if (screen_end(sp))
+ goto err;
+ goto done;
+ }
+
+ /*
+ * !!!
+ * Initialize the default ^D, ^U scrolling value here, after the
+ * user has had every opportunity to set the window option.
+ *
+ * It's historic practice that changing the value of the window
+ * option did not alter the default scrolling value, only giving
+ * a count to ^D/^U did that.
+ */
+ sp->defscroll = (O_VAL(sp, O_WINDOW) + 1) / 2;
+
+ /*
+ * If we don't have a command-line option, switch into the right
+ * editor now, so that we position default files correctly, and
+ * so that any tags file file-already-locked messages are in the
+ * vi screen, not the ex screen.
+ *
+ * XXX
+ * If we have a command-line option, the error message can end
+ * up in the wrong place, but I think that the combination is
+ * unlikely.
+ */
+ if (gp->c_option == NULL) {
+ F_CLR(sp, SC_EX | SC_VI);
+ F_SET(sp, LF_ISSET(SC_EX | SC_VI));
+ }
+
+ /* Open a tag file if specified. */
+ if (tag_f != NULL && ex_tag_first(sp, tag_f))
+ goto err;
+
+ /*
+ * Append any remaining arguments as file names. Files are recovery
+ * files if -r specified. If the tag option or ex startup commands
+ * loaded a file, then any file arguments are going to come after it.
+ */
+ if (*argv != NULL) {
+ if (sp->frp != NULL) {
+ /* Cheat -- we know we have an extra argv slot. */
+ MALLOC_NOMSG(sp,
+ *--argv, char *, strlen(sp->frp->name) + 1);
+ if (*argv == NULL) {
+ v_estr(gp->progname, errno, NULL);
+ goto err;
+ }
+ (void)strcpy(*argv, sp->frp->name);
+ }
+ sp->argv = sp->cargv = argv;
+ F_SET(sp, SC_ARGNOFREE);
+ if (flagchk == 'r')
+ F_SET(sp, SC_ARGRECOVER);
+ }
+
+ /*
+ * If the ex startup commands and or/the tag option haven't already
+ * created a file, create one. If no command-line files were given,
+ * use a temporary file.
+ */
+ if (sp->frp == NULL) {
+ if (sp->argv == NULL) {
+ if ((frp = file_add(sp, NULL)) == NULL)
+ goto err;
+ } else {
+ if ((frp = file_add(sp, (CHAR_T *)sp->argv[0])) == NULL)
+ goto err;
+ if (F_ISSET(sp, SC_ARGRECOVER))
+ F_SET(frp, FR_RECOVER);
+ }
+
+ if (file_init(sp, frp, NULL, 0))
+ goto err;
+ if (EXCMD_RUNNING(gp)) {
+ (void)ex_cmd(sp);
+ if (F_ISSET(sp, SC_EXIT | SC_EXIT_FORCE)) {
+ if (screen_end(sp))
+ goto err;
+ goto done;
+ }
+ }
+ }
+
+ /*
+ * Check to see if we need to wait for ex. If SC_SCR_EX is set, ex
+ * was forced to initialize the screen during startup. We'd like to
+ * wait for a single character from the user, but we can't because
+ * we're not in raw mode. We can't switch to raw mode because the
+ * vi initialization will switch to xterm's alternate screen, causing
+ * us to lose the messages we're pausing to make sure the user read.
+ * So, wait for a complete line.
+ */
+ if (F_ISSET(sp, SC_SCR_EX)) {
+ p = msg_cmsg(sp, CMSG_CONT_R, &len);
+ (void)write(STDOUT_FILENO, p, len);
+ for (;;) {
+ if (v_event_get(sp, &ev, 0, 0))
+ goto err;
+ if (ev.e_event == E_INTERRUPT ||
+ ev.e_event == E_CHARACTER &&
+ (ev.e_value == K_CR || ev.e_value == K_NL))
+ break;
+ (void)gp->scr_bell(sp);
+ }
+ }
+
+ /* Switch into the right editor, regardless. */
+ F_CLR(sp, SC_EX | SC_VI);
+ F_SET(sp, LF_ISSET(SC_EX | SC_VI) | SC_STATUS_CNT);
+
+ /*
+ * Main edit loop. Vi handles split screens itself, we only return
+ * here when switching editor modes or restarting the screen.
+ */
+ while (sp != NULL)
+ if (F_ISSET(sp, SC_EX) ? ex(&sp) : vi(&sp))
+ goto err;
+
+done: rval = 0;
+ if (0)
+err: rval = 1;
+
+ /* Clean out the global structure. */
+ v_end(gp);
+
+ return (rval);
+}
+
+/*
+ * v_end --
+ * End the program, discarding screens and most of the global area.
+ *
+ * PUBLIC: void v_end __P((GS *));
+ */
+void
+v_end(gp)
+ GS *gp;
+{
+ MSGS *mp;
+ SCR *sp;
+
+ /* If there are any remaining screens, kill them off. */
+ if (gp->ccl_sp != NULL) {
+ (void)file_end(gp->ccl_sp, NULL, 1);
+ (void)screen_end(gp->ccl_sp);
+ }
+ while ((sp = gp->dq.cqh_first) != (void *)&gp->dq)
+ (void)screen_end(sp);
+ while ((sp = gp->hq.cqh_first) != (void *)&gp->hq)
+ (void)screen_end(sp);
+
+#ifdef HAVE_PERL_INTERP
+ perl_end(gp);
+#endif
+
+#if defined(DEBUG) || defined(PURIFY) || defined(LIBRARY)
+ { FREF *frp;
+ /* Free FREF's. */
+ while ((frp = gp->frefq.cqh_first) != (FREF *)&gp->frefq) {
+ CIRCLEQ_REMOVE(&gp->frefq, frp, q);
+ if (frp->name != NULL)
+ free(frp->name);
+ if (frp->tname != NULL)
+ free(frp->tname);
+ free(frp);
+ }
+ }
+
+ /* Free key input queue. */
+ if (gp->i_event != NULL)
+ free(gp->i_event);
+
+ /* Free cut buffers. */
+ cut_close(gp);
+
+ /* Free map sequences. */
+ seq_close(gp);
+
+ /* Free default buffer storage. */
+ (void)text_lfree(&gp->dcb_store.textq);
+
+ /* Close message catalogs. */
+ msg_close(gp);
+#endif
+
+ /* Ring the bell if scheduled. */
+ if (F_ISSET(gp, G_BELLSCHED))
+ (void)fprintf(stderr, "\07"); /* \a */
+
+ /*
+ * Flush any remaining messages. If a message is here, it's almost
+ * certainly the message about the event that killed us (although
+ * it's possible that the user is sourcing a file that exits from the
+ * editor).
+ */
+ while ((mp = gp->msgq.lh_first) != NULL) {
+ (void)fprintf(stderr, "%s%.*s",
+ mp->mtype == M_ERR ? "ex/vi: " : "", (int)mp->len, mp->buf);
+ LIST_REMOVE(mp, q);
+#if defined(DEBUG) || defined(PURIFY) || defined(LIBRARY)
+ free(mp->buf);
+ free(mp);
+#endif
+ }
+
+#if defined(DEBUG) || defined(PURIFY) || defined(LIBRARY)
+ /* Free any temporary space. */
+ if (gp->tmp_bp != NULL)
+ free(gp->tmp_bp);
+
+#if defined(DEBUG)
+ /* Close debugging file descriptor. */
+ if (gp->tracefp != NULL)
+ (void)fclose(gp->tracefp);
+#endif
+#endif
+}
+
+/*
+ * v_obsolete --
+ * Convert historic arguments into something getopt(3) will like.
+ */
+static int
+v_obsolete(name, argv)
+ char *name, *argv[];
+{
+ size_t len;
+ char *p;
+
+ /*
+ * Translate old style arguments into something getopt will like.
+ * Make sure it's not text space memory, because ex modifies the
+ * strings.
+ * Change "+" into "-c$".
+ * Change "+<anything else>" into "-c<anything else>".
+ * Change "-" into "-s"
+ * The c, T, t and w options take arguments so they can't be
+ * special arguments.
+ *
+ * Stop if we find "--" as an argument, the user may want to edit
+ * a file named "+foo".
+ */
+ while (*++argv && strcmp(argv[0], "--"))
+ if (argv[0][0] == '+') {
+ if (argv[0][1] == '\0') {
+ MALLOC_NOMSG(NULL, argv[0], char *, 4);
+ if (argv[0] == NULL)
+ goto nomem;
+ (void)strcpy(argv[0], "-c$");
+ } else {
+ p = argv[0];
+ len = strlen(argv[0]);
+ MALLOC_NOMSG(NULL, argv[0], char *, len + 2);
+ if (argv[0] == NULL)
+ goto nomem;
+ argv[0][0] = '-';
+ argv[0][1] = 'c';
+ (void)strcpy(argv[0] + 2, p + 1);
+ }
+ } else if (argv[0][0] == '-')
+ if (argv[0][1] == '\0') {
+ MALLOC_NOMSG(NULL, argv[0], char *, 3);
+ if (argv[0] == NULL) {
+nomem: v_estr(name, errno, NULL);
+ return (1);
+ }
+ (void)strcpy(argv[0], "-s");
+ } else
+ if ((argv[0][1] == 'c' || argv[0][1] == 'T' ||
+ argv[0][1] == 't' || argv[0][1] == 'w') &&
+ argv[0][2] == '\0')
+ ++argv;
+ return (0);
+}
+
+#ifdef DEBUG
+static void
+attach(gp)
+ GS *gp;
+{
+ int fd;
+ char ch;
+
+ if ((fd = open(_PATH_TTY, O_RDONLY, 0)) < 0) {
+ v_estr(gp->progname, errno, _PATH_TTY);
+ return;
+ }
+
+ (void)printf("process %lu waiting, enter <CR> to continue: ",
+ (u_long)getpid());
+ (void)fflush(stdout);
+
+ do {
+ if (read(fd, &ch, 1) != 1) {
+ (void)close(fd);
+ return;
+ }
+ } while (ch != '\n' && ch != '\r');
+ (void)close(fd);
+}
+#endif
+
+static void
+v_estr(name, eno, msg)
+ char *name, *msg;
+ int eno;
+{
+ (void)fprintf(stderr, "%s", name);
+ if (msg != NULL)
+ (void)fprintf(stderr, ": %s", msg);
+ if (eno)
+ (void)fprintf(stderr, ": %s", strerror(errno));
+ (void)fprintf(stderr, "\n");
+}
diff --git a/common/mark.c b/common/mark.c
new file mode 100644
index 000000000000..0ac1fc28bf9c
--- /dev/null
+++ b/common/mark.c
@@ -0,0 +1,277 @@
+/*-
+ * 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
+static const char sccsid[] = "@(#)mark.c 10.13 (Berkeley) 7/19/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+
+#include <bitstring.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "common.h"
+
+static LMARK *mark_find __P((SCR *, ARG_CHAR_T));
+
+/*
+ * Marks are maintained in a key sorted doubly linked list. We can't
+ * use arrays because we have no idea how big an index key could be.
+ * The underlying assumption is that users don't have more than, say,
+ * 10 marks at any one time, so this will be is fast enough.
+ *
+ * Marks are fixed, and modifications to the line don't update the mark's
+ * position in the line. This can be hard. If you add text to the line,
+ * place a mark in that text, undo the addition and use ` to move to the
+ * mark, the location will have disappeared. It's tempting to try to adjust
+ * the mark with the changes in the line, but this is hard to do, especially
+ * if we've given the line to v_ntext.c:v_ntext() for editing. Historic vi
+ * would move to the first non-blank on the line when the mark location was
+ * past the end of the line. This can be complicated by deleting to a mark
+ * that has disappeared using the ` command. Historic vi treated this as
+ * a line-mode motion and deleted the line. This implementation complains to
+ * the user.
+ *
+ * In historic vi, marks returned if the operation was undone, unless the
+ * mark had been subsequently reset. Tricky. This is hard to start with,
+ * but in the presence of repeated undo it gets nasty. When a line is
+ * deleted, we delete (and log) any marks on that line. An undo will create
+ * the mark. Any mark creations are noted as to whether the user created
+ * it or if it was created by an undo. The former cannot be reset by another
+ * undo, but the latter may.
+ *
+ * All of these routines translate ABSMARK2 to ABSMARK1. Setting either of
+ * the absolute mark locations sets both, so that "m'" and "m`" work like
+ * they, ah, for lack of a better word, "should".
+ */
+
+/*
+ * mark_init --
+ * Set up the marks.
+ *
+ * PUBLIC: int mark_init __P((SCR *, EXF *));
+ */
+int
+mark_init(sp, ep)
+ SCR *sp;
+ EXF *ep;
+{
+ /*
+ * !!!
+ * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER.
+ *
+ * Set up the marks.
+ */
+ LIST_INIT(&ep->marks);
+ return (0);
+}
+
+/*
+ * mark_end --
+ * Free up the marks.
+ *
+ * PUBLIC: int mark_end __P((SCR *, EXF *));
+ */
+int
+mark_end(sp, ep)
+ SCR *sp;
+ EXF *ep;
+{
+ LMARK *lmp;
+
+ /*
+ * !!!
+ * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER.
+ */
+ while ((lmp = ep->marks.lh_first) != NULL) {
+ LIST_REMOVE(lmp, q);
+ free(lmp);
+ }
+ return (0);
+}
+
+/*
+ * mark_get --
+ * Get the location referenced by a mark.
+ *
+ * PUBLIC: int mark_get __P((SCR *, ARG_CHAR_T, MARK *, mtype_t));
+ */
+int
+mark_get(sp, key, mp, mtype)
+ SCR *sp;
+ ARG_CHAR_T key;
+ MARK *mp;
+ mtype_t mtype;
+{
+ LMARK *lmp;
+
+ if (key == ABSMARK2)
+ key = ABSMARK1;
+
+ lmp = mark_find(sp, key);
+ if (lmp == NULL || lmp->name != key) {
+ msgq(sp, mtype, "017|Mark %s: not set", KEY_NAME(sp, key));
+ return (1);
+ }
+ if (F_ISSET(lmp, MARK_DELETED)) {
+ msgq(sp, mtype,
+ "018|Mark %s: the line was deleted", KEY_NAME(sp, key));
+ return (1);
+ }
+
+ /*
+ * !!!
+ * The absolute mark is initialized to lno 1/cno 0, and historically
+ * you could use it in an empty file. Make such a mark always work.
+ */
+ if ((lmp->lno != 1 || lmp->cno != 0) && !db_exist(sp, lmp->lno)) {
+ msgq(sp, mtype,
+ "019|Mark %s: cursor position no longer exists",
+ KEY_NAME(sp, key));
+ return (1);
+ }
+ mp->lno = lmp->lno;
+ mp->cno = lmp->cno;
+ return (0);
+}
+
+/*
+ * mark_set --
+ * Set the location referenced by a mark.
+ *
+ * PUBLIC: int mark_set __P((SCR *, ARG_CHAR_T, MARK *, int));
+ */
+int
+mark_set(sp, key, value, userset)
+ SCR *sp;
+ ARG_CHAR_T key;
+ MARK *value;
+ int userset;
+{
+ LMARK *lmp, *lmt;
+
+ if (key == ABSMARK2)
+ key = ABSMARK1;
+
+ /*
+ * The rules are simple. If the user is setting a mark (if it's a
+ * new mark this is always true), it always happens. If not, it's
+ * an undo, and we set it if it's not already set or if it was set
+ * by a previous undo.
+ */
+ lmp = mark_find(sp, key);
+ if (lmp == NULL || lmp->name != key) {
+ MALLOC_RET(sp, lmt, LMARK *, sizeof(LMARK));
+ if (lmp == NULL) {
+ LIST_INSERT_HEAD(&sp->ep->marks, lmt, q);
+ } else
+ LIST_INSERT_AFTER(lmp, lmt, q);
+ lmp = lmt;
+ } else if (!userset &&
+ !F_ISSET(lmp, MARK_DELETED) && F_ISSET(lmp, MARK_USERSET))
+ return (0);
+
+ lmp->lno = value->lno;
+ lmp->cno = value->cno;
+ lmp->name = key;
+ lmp->flags = userset ? MARK_USERSET : 0;
+ return (0);
+}
+
+/*
+ * mark_find --
+ * Find the requested mark, or, the slot immediately before
+ * where it would go.
+ */
+static LMARK *
+mark_find(sp, key)
+ SCR *sp;
+ ARG_CHAR_T key;
+{
+ LMARK *lmp, *lastlmp;
+
+ /*
+ * Return the requested mark or the slot immediately before
+ * where it should go.
+ */
+ for (lastlmp = NULL, lmp = sp->ep->marks.lh_first;
+ lmp != NULL; lastlmp = lmp, lmp = lmp->q.le_next)
+ if (lmp->name >= key)
+ return (lmp->name == key ? lmp : lastlmp);
+ return (lastlmp);
+}
+
+/*
+ * mark_insdel --
+ * Update the marks based on an insertion or deletion.
+ *
+ * PUBLIC: int mark_insdel __P((SCR *, lnop_t, recno_t));
+ */
+int
+mark_insdel(sp, op, lno)
+ SCR *sp;
+ lnop_t op;
+ recno_t lno;
+{
+ LMARK *lmp;
+ recno_t lline;
+
+ switch (op) {
+ case LINE_APPEND:
+ /* All insert/append operations are done as inserts. */
+ abort();
+ case LINE_DELETE:
+ for (lmp = sp->ep->marks.lh_first;
+ lmp != NULL; lmp = lmp->q.le_next)
+ if (lmp->lno >= lno)
+ if (lmp->lno == lno) {
+ F_SET(lmp, MARK_DELETED);
+ (void)log_mark(sp, lmp);
+ } else
+ --lmp->lno;
+ break;
+ case LINE_INSERT:
+ /*
+ * XXX
+ * Very nasty special case. If the file was empty, then we're
+ * adding the first line, which is a replacement. So, we don't
+ * modify the marks. This is a hack to make:
+ *
+ * mz:r!echo foo<carriage-return>'z
+ *
+ * work, i.e. historically you could mark the "line" in an empty
+ * file and replace it, and continue to use the mark. Insane,
+ * well, yes, I know, but someone complained.
+ *
+ * Check for line #2 before going to the end of the file.
+ */
+ if (!db_exist(sp, 2)) {
+ if (db_last(sp, &lline))
+ return (1);
+ if (lline == 1)
+ return (0);
+ }
+
+ for (lmp = sp->ep->marks.lh_first;
+ lmp != NULL; lmp = lmp->q.le_next)
+ if (lmp->lno >= lno)
+ ++lmp->lno;
+ break;
+ case LINE_RESET:
+ break;
+ }
+ return (0);
+}
diff --git a/common/mark.h b/common/mark.h
new file mode 100644
index 000000000000..9c63e183e83f
--- /dev/null
+++ b/common/mark.h
@@ -0,0 +1,42 @@
+/*-
+ * 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.
+ *
+ * @(#)mark.h 10.3 (Berkeley) 3/6/96
+ */
+
+/*
+ * The MARK and LMARK structures define positions in the file. There are
+ * two structures because the mark subroutines are the only places where
+ * anything cares about something other than line and column.
+ *
+ * Because of the different interfaces used by the db(3) package, curses,
+ * and users, the line number is 1 based and the column number is 0 based.
+ * Additionally, it is known that the out-of-band line number is less than
+ * any legal line number. The line number is of type recno_t, as that's
+ * the underlying type of the database. The column number is of type size_t,
+ * guaranteeing that we can malloc a line.
+ */
+struct _mark {
+#define OOBLNO 0 /* Out-of-band line number. */
+ recno_t lno; /* Line number. */
+ size_t cno; /* Column number. */
+};
+
+struct _lmark {
+ LIST_ENTRY(_lmark) q; /* Linked list of marks. */
+ recno_t lno; /* Line number. */
+ size_t cno; /* Column number. */
+ CHAR_T name; /* Mark name. */
+
+#define MARK_DELETED 0x01 /* Mark was deleted. */
+#define MARK_USERSET 0x02 /* User set this mark. */
+ u_int8_t flags;
+};
+
+#define ABSMARK1 '\'' /* Absolute mark name. */
+#define ABSMARK2 '`' /* Absolute mark name. */
diff --git a/common/mem.h b/common/mem.h
new file mode 100644
index 000000000000..af42e6bcd1de
--- /dev/null
+++ b/common/mem.h
@@ -0,0 +1,168 @@
+/*-
+ * Copyright (c) 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 1993, 1994, 1995, 1996
+ * Keith Bostic. All rights reserved.
+ *
+ * See the LICENSE file for redistribution information.
+ *
+ * @(#)mem.h 10.7 (Berkeley) 3/30/96
+ */
+
+/* Increase the size of a malloc'd buffer. Two versions, one that
+ * returns, one that jumps to an error label.
+ */
+#define BINC_GOTO(sp, lp, llen, nlen) { \
+ void *L__bincp; \
+ if ((nlen) > llen) { \
+ if ((L__bincp = binc(sp, lp, &(llen), nlen)) == NULL) \
+ goto alloc_err; \
+ /* \
+ * !!! \
+ * Possible pointer conversion. \
+ */ \
+ lp = L__bincp; \
+ } \
+}
+#define BINC_RET(sp, lp, llen, nlen) { \
+ void *L__bincp; \
+ if ((nlen) > llen) { \
+ if ((L__bincp = binc(sp, lp, &(llen), nlen)) == NULL) \
+ return (1); \
+ /* \
+ * !!! \
+ * Possible pointer conversion. \
+ */ \
+ lp = L__bincp; \
+ } \
+}
+
+/*
+ * Get some temporary space, preferably from the global temporary buffer,
+ * from a malloc'd buffer otherwise. Two versions, one that returns, one
+ * that jumps to an error label.
+ */
+#define GET_SPACE_GOTO(sp, bp, blen, nlen) { \
+ GS *L__gp = (sp) == NULL ? NULL : (sp)->gp; \
+ if (L__gp == NULL || F_ISSET(L__gp, G_TMP_INUSE)) { \
+ bp = NULL; \
+ blen = 0; \
+ BINC_GOTO(sp, bp, blen, nlen); \
+ } else { \
+ BINC_GOTO(sp, L__gp->tmp_bp, L__gp->tmp_blen, nlen); \
+ bp = L__gp->tmp_bp; \
+ blen = L__gp->tmp_blen; \
+ F_SET(L__gp, G_TMP_INUSE); \
+ } \
+}
+#define GET_SPACE_RET(sp, bp, blen, nlen) { \
+ GS *L__gp = (sp) == NULL ? NULL : (sp)->gp; \
+ if (L__gp == NULL || F_ISSET(L__gp, G_TMP_INUSE)) { \
+ bp = NULL; \
+ blen = 0; \
+ BINC_RET(sp, bp, blen, nlen); \
+ } else { \
+ BINC_RET(sp, L__gp->tmp_bp, L__gp->tmp_blen, nlen); \
+ bp = L__gp->tmp_bp; \
+ blen = L__gp->tmp_blen; \
+ F_SET(L__gp, G_TMP_INUSE); \
+ } \
+}
+
+/*
+ * Add space to a GET_SPACE returned buffer. Two versions, one that
+ * returns, one that jumps to an error label.
+ */
+#define ADD_SPACE_GOTO(sp, bp, blen, nlen) { \
+ GS *L__gp = (sp) == NULL ? NULL : (sp)->gp; \
+ if (L__gp == NULL || bp == L__gp->tmp_bp) { \
+ F_CLR(L__gp, G_TMP_INUSE); \
+ BINC_GOTO(sp, L__gp->tmp_bp, L__gp->tmp_blen, nlen); \
+ bp = L__gp->tmp_bp; \
+ blen = L__gp->tmp_blen; \
+ F_SET(L__gp, G_TMP_INUSE); \
+ } else \
+ BINC_GOTO(sp, bp, blen, nlen); \
+}
+#define ADD_SPACE_RET(sp, bp, blen, nlen) { \
+ GS *L__gp = (sp) == NULL ? NULL : (sp)->gp; \
+ if (L__gp == NULL || bp == L__gp->tmp_bp) { \
+ F_CLR(L__gp, G_TMP_INUSE); \
+ BINC_RET(sp, L__gp->tmp_bp, L__gp->tmp_blen, nlen); \
+ bp = L__gp->tmp_bp; \
+ blen = L__gp->tmp_blen; \
+ F_SET(L__gp, G_TMP_INUSE); \
+ } else \
+ BINC_RET(sp, bp, blen, nlen); \
+}
+
+/* Free a GET_SPACE returned buffer. */
+#define FREE_SPACE(sp, bp, blen) { \
+ GS *L__gp = (sp) == NULL ? NULL : (sp)->gp; \
+ if (L__gp != NULL && bp == L__gp->tmp_bp) \
+ F_CLR(L__gp, G_TMP_INUSE); \
+ else \
+ free(bp); \
+}
+
+/*
+ * Malloc a buffer, casting the return pointer. Various versions.
+ *
+ * !!!
+ * The cast should be unnecessary, malloc(3) and friends return void *'s,
+ * which is all we need. However, some systems that nvi needs to run on
+ * don't do it right yet, resulting in the compiler printing out roughly
+ * a million warnings. After awhile, it seemed easier to put the casts
+ * in instead of explaining it all the time.
+ */
+#define CALLOC(sp, p, cast, nmemb, size) { \
+ if ((p = (cast)calloc(nmemb, size)) == NULL) \
+ msgq(sp, M_SYSERR, NULL); \
+}
+#define CALLOC_GOTO(sp, p, cast, nmemb, size) { \
+ if ((p = (cast)calloc(nmemb, size)) == NULL) \
+ goto alloc_err; \
+}
+#define CALLOC_NOMSG(sp, p, cast, nmemb, size) { \
+ p = (cast)calloc(nmemb, size); \
+}
+#define CALLOC_RET(sp, p, cast, nmemb, size) { \
+ if ((p = (cast)calloc(nmemb, size)) == NULL) { \
+ msgq(sp, M_SYSERR, NULL); \
+ return (1); \
+ } \
+}
+
+#define MALLOC(sp, p, cast, size) { \
+ if ((p = (cast)malloc(size)) == NULL) \
+ msgq(sp, M_SYSERR, NULL); \
+}
+#define MALLOC_GOTO(sp, p, cast, size) { \
+ if ((p = (cast)malloc(size)) == NULL) \
+ goto alloc_err; \
+}
+#define MALLOC_NOMSG(sp, p, cast, size) { \
+ p = (cast)malloc(size); \
+}
+#define MALLOC_RET(sp, p, cast, size) { \
+ if ((p = (cast)malloc(size)) == NULL) { \
+ msgq(sp, M_SYSERR, NULL); \
+ return (1); \
+ } \
+}
+/*
+ * XXX
+ * Don't depend on realloc(NULL, size) working.
+ */
+#define REALLOC(sp, p, cast, size) { \
+ if ((p = (cast)(p == NULL ? \
+ malloc(size) : realloc(p, size))) == NULL) \
+ msgq(sp, M_SYSERR, NULL); \
+}
+
+/*
+ * Versions of memmove(3) and memset(3) that use the size of the
+ * initial pointer to figure out how much memory to manipulate.
+ */
+#define MEMMOVE(p, t, len) memmove(p, t, (len) * sizeof(*(p)))
+#define MEMSET(p, value, len) memset(p, value, (len) * sizeof(*(p)))
diff --git a/common/msg.c b/common/msg.c
new file mode 100644
index 000000000000..2b18082c7ab8
--- /dev/null
+++ b/common/msg.c
@@ -0,0 +1,895 @@
+/*-
+ * 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[] = "@(#)msg.c 10.48 (Berkeley) 9/15/96";
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/types.h> /* XXX: param.h may not have included types.h */
+#include <sys/queue.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+
+#include <bitstring.h>
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#ifdef __STDC__
+#include <stdarg.h>
+#else
+#include <varargs.h>
+#endif
+
+#include "common.h"
+#include "../vi/vi.h"
+
+/*
+ * msgq --
+ * Display a message.
+ *
+ * PUBLIC: void msgq __P((SCR *, mtype_t, const char *, ...));
+ */
+void
+#ifdef __STDC__
+msgq(SCR *sp, mtype_t mt, const char *fmt, ...)
+#else
+msgq(sp, mt, fmt, va_alist)
+ SCR *sp;
+ mtype_t mt;
+ const char *fmt;
+ va_dcl
+#endif
+{
+#ifndef NL_ARGMAX
+#define __NL_ARGMAX 20 /* Set to 9 by System V. */
+ struct {
+ const char *str; /* String pointer. */
+ size_t arg; /* Argument number. */
+ size_t prefix; /* Prefix string length. */
+ size_t skip; /* Skipped string length. */
+ size_t suffix; /* Suffix string length. */
+ } str[__NL_ARGMAX];
+#endif
+ static int reenter; /* STATIC: Re-entrancy check. */
+ CHAR_T ch;
+ GS *gp;
+ size_t blen, cnt1, cnt2, len, mlen, nlen, soff;
+ const char *p, *t, *u;
+ char *bp, *mp, *rbp, *s_rbp;
+ va_list ap;
+
+ /*
+ * !!!
+ * It's possible to enter msg when there's no screen to hold the
+ * message. If sp is NULL, ignore the special cases and put the
+ * message out to stderr.
+ */
+ if (sp == NULL) {
+ gp = NULL;
+ if (mt == M_BERR)
+ mt = M_ERR;
+ else if (mt == M_VINFO)
+ mt = M_INFO;
+ } else {
+ gp = sp->gp;
+ switch (mt) {
+ case M_BERR:
+ if (F_ISSET(sp, SC_VI) && !O_ISSET(sp, O_VERBOSE)) {
+ F_SET(gp, G_BELLSCHED);
+ return;
+ }
+ mt = M_ERR;
+ break;
+ case M_VINFO:
+ if (!O_ISSET(sp, O_VERBOSE))
+ return;
+ mt = M_INFO;
+ /* FALLTHROUGH */
+ case M_INFO:
+ if (F_ISSET(sp, SC_EX_SILENT))
+ return;
+ break;
+ case M_ERR:
+ case M_SYSERR:
+ break;
+ default:
+ abort();
+ }
+ }
+
+ /*
+ * It's possible to reenter msg when it allocates space. We're
+ * probably dead anyway, but there's no reason to drop core.
+ *
+ * XXX
+ * Yes, there's a race, but it should only be two instructions.
+ */
+ if (reenter++)
+ return;
+
+ /* Get space for the message. */
+ nlen = 1024;
+ if (0) {
+retry: FREE_SPACE(sp, bp, blen);
+ nlen *= 2;
+ }
+ bp = NULL;
+ blen = 0;
+ GET_SPACE_GOTO(sp, bp, blen, nlen);
+
+ /*
+ * Error prefix.
+ *
+ * mp: pointer to the current next character to be written
+ * mlen: length of the already written characters
+ * blen: total length of the buffer
+ */
+#define REM (blen - mlen)
+ mp = bp;
+ mlen = 0;
+ if (mt == M_SYSERR) {
+ p = msg_cat(sp, "020|Error: ", &len);
+ if (REM < len)
+ goto retry;
+ memcpy(mp, p, len);
+ mp += len;
+ mlen += len;
+ }
+
+ /*
+ * If we're running an ex command that the user didn't enter, display
+ * the file name and line number prefix.
+ */
+ if ((mt == M_ERR || mt == M_SYSERR) &&
+ sp != NULL && gp != NULL && gp->if_name != NULL) {
+ for (p = gp->if_name; *p != '\0'; ++p) {
+ len = snprintf(mp, REM, "%s", KEY_NAME(sp, *p));
+ mp += len;
+ if ((mlen += len) > blen)
+ goto retry;
+ }
+ len = snprintf(mp, REM, ", %d: ", gp->if_lno);
+ mp += len;
+ if ((mlen += len) > blen)
+ goto retry;
+ }
+
+ /* If nothing to format, we're done. */
+ if (fmt == NULL)
+ goto nofmt;
+ fmt = msg_cat(sp, fmt, NULL);
+
+#ifndef NL_ARGMAX
+ /*
+ * Nvi should run on machines that don't support the numbered argument
+ * specifications (%[digit]*$). We do this by reformatting the string
+ * so that we can hand it to vsprintf(3) and it will use the arguments
+ * in the right order. When vsprintf returns, we put the string back
+ * into the right order. It's undefined, according to SVID III, to mix
+ * numbered argument specifications with the standard style arguments,
+ * so this should be safe.
+ *
+ * In addition, we also need a character that is known to not occur in
+ * any vi message, for separating the parts of the string. As callers
+ * of msgq are responsible for making sure that all the non-printable
+ * characters are formatted for printing before calling msgq, we use a
+ * random non-printable character selected at terminal initialization
+ * time. This code isn't fast by any means, but as messages should be
+ * relatively short and normally have only a few arguments, it won't be
+ * too bad. Regardless, nobody has come up with any other solution.
+ *
+ * The result of this loop is an array of pointers into the message
+ * string, with associated lengths and argument numbers. The array
+ * is in the "correct" order, and the arg field contains the argument
+ * order.
+ */
+ for (p = fmt, soff = 0; soff < __NL_ARGMAX;) {
+ for (t = p; *p != '\0' && *p != '%'; ++p);
+ if (*p == '\0')
+ break;
+ ++p;
+ if (!isdigit(*p)) {
+ if (*p == '%')
+ ++p;
+ continue;
+ }
+ for (u = p; *++p != '\0' && isdigit(*p););
+ if (*p != '$')
+ continue;
+
+ /* Up to, and including the % character. */
+ str[soff].str = t;
+ str[soff].prefix = u - t;
+
+ /* Up to, and including the $ character. */
+ str[soff].arg = atoi(u);
+ str[soff].skip = (p - u) + 1;
+ if (str[soff].arg >= __NL_ARGMAX)
+ goto ret;
+
+ /* Up to, and including the conversion character. */
+ for (u = p; (ch = *++p) != '\0';)
+ if (isalpha(ch) &&
+ strchr("diouxXfeEgGcspn", ch) != NULL)
+ break;
+ str[soff].suffix = p - u;
+ if (ch != '\0')
+ ++p;
+ ++soff;
+ }
+
+ /* If no magic strings, we're done. */
+ if (soff == 0)
+ goto format;
+
+ /* Get space for the reordered strings. */
+ if ((rbp = malloc(nlen)) == NULL)
+ goto ret;
+ s_rbp = rbp;
+
+ /*
+ * Reorder the strings into the message string based on argument
+ * order.
+ *
+ * !!!
+ * We ignore arguments that are out of order, i.e. if we don't find
+ * an argument, we continue. Assume (almost certainly incorrectly)
+ * that whoever created the string knew what they were doing.
+ *
+ * !!!
+ * Brute force "sort", but since we don't expect more than one or two
+ * arguments in a string, the setup cost of a fast sort will be more
+ * expensive than the loop.
+ */
+ for (cnt1 = 1; cnt1 <= soff; ++cnt1)
+ for (cnt2 = 0; cnt2 < soff; ++cnt2)
+ if (cnt1 == str[cnt2].arg) {
+ memmove(s_rbp, str[cnt2].str, str[cnt2].prefix);
+ memmove(s_rbp + str[cnt2].prefix,
+ str[cnt2].str + str[cnt2].prefix +
+ str[cnt2].skip, str[cnt2].suffix);
+ s_rbp += str[cnt2].prefix + str[cnt2].suffix;
+ *s_rbp++ =
+ gp == NULL ? DEFAULT_NOPRINT : gp->noprint;
+ break;
+ }
+ *s_rbp = '\0';
+ fmt = rbp;
+#endif
+
+format: /* Format the arguments into the string. */
+#ifdef __STDC__
+ va_start(ap, fmt);
+#else
+ va_start(ap);
+#endif
+ len = vsnprintf(mp, REM, fmt, ap);
+ va_end(ap);
+ if (len >= nlen)
+ goto retry;
+
+#ifndef NL_ARGMAX
+ if (soff == 0)
+ goto nofmt;
+
+ /*
+ * Go through the resulting string, and, for each separator character
+ * separated string, enter its new starting position and length in the
+ * array.
+ */
+ for (p = t = mp, cnt1 = 1,
+ ch = gp == NULL ? DEFAULT_NOPRINT : gp->noprint; *p != '\0'; ++p)
+ if (*p == ch) {
+ for (cnt2 = 0; cnt2 < soff; ++cnt2)
+ if (str[cnt2].arg == cnt1)
+ break;
+ str[cnt2].str = t;
+ str[cnt2].prefix = p - t;
+ t = p + 1;
+ ++cnt1;
+ }
+
+ /*
+ * Reorder the strings once again, putting them back into the
+ * message buffer.
+ *
+ * !!!
+ * Note, the length of the message gets decremented once for
+ * each substring, when we discard the separator character.
+ */
+ for (s_rbp = rbp, cnt1 = 0; cnt1 < soff; ++cnt1) {
+ memmove(rbp, str[cnt1].str, str[cnt1].prefix);
+ rbp += str[cnt1].prefix;
+ --len;
+ }
+ memmove(mp, s_rbp, rbp - s_rbp);
+
+ /* Free the reordered string memory. */
+ free(s_rbp);
+#endif
+
+nofmt: mp += len;
+ if ((mlen += len) > blen)
+ goto retry;
+ if (mt == M_SYSERR) {
+ len = snprintf(mp, REM, ": %s", strerror(errno));
+ mp += len;
+ if ((mlen += len) > blen)
+ goto retry;
+ mt = M_ERR;
+ }
+
+ /* Add trailing newline. */
+ if ((mlen += 1) > blen)
+ goto retry;
+ *mp = '\n';
+
+ if (sp != NULL)
+ (void)ex_fflush(sp);
+ if (gp != NULL)
+ gp->scr_msg(sp, mt, bp, mlen);
+ else
+ (void)fprintf(stderr, "%.*s", (int)mlen, bp);
+
+ /* Cleanup. */
+ret: FREE_SPACE(sp, bp, blen);
+alloc_err:
+ reenter = 0;
+}
+
+/*
+ * msgq_str --
+ * Display a message with an embedded string.
+ *
+ * PUBLIC: void msgq_str __P((SCR *, mtype_t, char *, char *));
+ */
+void
+msgq_str(sp, mtype, str, fmt)
+ SCR *sp;
+ mtype_t mtype;
+ char *str, *fmt;
+{
+ int nf, sv_errno;
+ char *p;
+
+ if (str == NULL) {
+ msgq(sp, mtype, fmt);
+ return;
+ }
+
+ sv_errno = errno;
+ p = msg_print(sp, str, &nf);
+ errno = sv_errno;
+ msgq(sp, mtype, fmt, p);
+ if (nf)
+ FREE_SPACE(sp, p, 0);
+}
+
+/*
+ * mod_rpt --
+ * Report on the lines that changed.
+ *
+ * !!!
+ * Historic vi documentation (USD:15-8) claimed that "The editor will also
+ * always tell you when a change you make affects text which you cannot see."
+ * This wasn't true -- edit a large file and do "100d|1". We don't implement
+ * this semantic since it requires tracking each line that changes during a
+ * command instead of just keeping count.
+ *
+ * Line counts weren't right in historic vi, either. For example, given the
+ * file:
+ * abc
+ * def
+ * the command 2d}, from the 'b' would report that two lines were deleted,
+ * not one.
+ *
+ * PUBLIC: void mod_rpt __P((SCR *));
+ */
+void
+mod_rpt(sp)
+ SCR *sp;
+{
+ static char * const action[] = {
+ "293|added",
+ "294|changed",
+ "295|deleted",
+ "296|joined",
+ "297|moved",
+ "298|shifted",
+ "299|yanked",
+ };
+ static char * const lines[] = {
+ "300|line",
+ "301|lines",
+ };
+ recno_t total;
+ u_long rptval;
+ int first, cnt;
+ size_t blen, len, tlen;
+ const char *t;
+ char * const *ap;
+ char *bp, *p;
+
+ /* Change reports are turned off in batch mode. */
+ if (F_ISSET(sp, SC_EX_SILENT))
+ return;
+
+ /* Reset changing line number. */
+ sp->rptlchange = OOBLNO;
+
+ /*
+ * Don't build a message if not enough changed.
+ *
+ * !!!
+ * And now, a vi clone test. Historically, vi reported if the number
+ * of changed lines was > than the value, not >=, unless it was a yank
+ * command, which used >=. No lie. Furthermore, an action was never
+ * reported for a single line action. This is consistent for actions
+ * other than yank, but yank didn't report single line actions even if
+ * the report edit option was set to 1. In addition, setting report to
+ * 0 in the 4BSD historic vi was equivalent to setting it to 1, for an
+ * unknown reason (this bug was fixed in System III/V at some point).
+ * I got complaints, so nvi conforms to System III/V historic practice
+ * except that we report a yank of 1 line if report is set to 1.
+ */
+#define ARSIZE(a) sizeof(a) / sizeof (*a)
+#define MAXNUM 25
+ rptval = O_VAL(sp, O_REPORT);
+ for (cnt = 0, total = 0; cnt < ARSIZE(action); ++cnt)
+ total += sp->rptlines[cnt];
+ if (total == 0)
+ return;
+ if (total <= rptval && sp->rptlines[L_YANKED] < rptval) {
+ for (cnt = 0; cnt < ARSIZE(action); ++cnt)
+ sp->rptlines[cnt] = 0;
+ return;
+ }
+
+ /* Build and display the message. */
+ GET_SPACE_GOTO(sp, bp, blen, sizeof(action) * MAXNUM + 1);
+ for (p = bp, first = 1, tlen = 0,
+ ap = action, cnt = 0; cnt < ARSIZE(action); ++ap, ++cnt)
+ if (sp->rptlines[cnt] != 0) {
+ if (first)
+ first = 0;
+ else {
+ *p++ = ';';
+ *p++ = ' ';
+ tlen += 2;
+ }
+ len = snprintf(p, MAXNUM, "%lu ", sp->rptlines[cnt]);
+ p += len;
+ tlen += len;
+ t = msg_cat(sp,
+ lines[sp->rptlines[cnt] == 1 ? 0 : 1], &len);
+ memcpy(p, t, len);
+ p += len;
+ tlen += len;
+ *p++ = ' ';
+ ++tlen;
+ t = msg_cat(sp, *ap, &len);
+ memcpy(p, t, len);
+ p += len;
+ tlen += len;
+ sp->rptlines[cnt] = 0;
+ }
+
+ /* Add trailing newline. */
+ *p = '\n';
+ ++tlen;
+
+ (void)ex_fflush(sp);
+ sp->gp->scr_msg(sp, M_INFO, bp, tlen);
+
+ FREE_SPACE(sp, bp, blen);
+alloc_err:
+ return;
+
+#undef ARSIZE
+#undef MAXNUM
+}
+
+/*
+ * msgq_status --
+ * Report on the file's status.
+ *
+ * PUBLIC: void msgq_status __P((SCR *, recno_t, u_int));
+ */
+void
+msgq_status(sp, lno, flags)
+ SCR *sp;
+ recno_t lno;
+ u_int flags;
+{
+ static int poisoned;
+ recno_t last;
+ size_t blen, len;
+ int cnt, needsep;
+ const char *t;
+ char **ap, *bp, *np, *p, *s;
+
+ /* Get sufficient memory. */
+ len = strlen(sp->frp->name);
+ GET_SPACE_GOTO(sp, bp, blen, len * MAX_CHARACTER_COLUMNS + 128);
+ p = bp;
+
+ /* Copy in the filename. */
+ for (p = bp, t = sp->frp->name; *t != '\0'; ++t) {
+ len = KEY_LEN(sp, *t);
+ memcpy(p, KEY_NAME(sp, *t), len);
+ p += len;
+ }
+ np = p;
+ *p++ = ':';
+ *p++ = ' ';
+
+ /* Copy in the argument count. */
+ if (F_ISSET(sp, SC_STATUS_CNT) && sp->argv != NULL) {
+ for (cnt = 0, ap = sp->argv; *ap != NULL; ++ap, ++cnt);
+ if (cnt > 1) {
+ (void)sprintf(p,
+ msg_cat(sp, "317|%d files to edit", NULL), cnt);
+ p += strlen(p);
+ *p++ = ':';
+ *p++ = ' ';
+ }
+ F_CLR(sp, SC_STATUS_CNT);
+ }
+
+ /*
+ * See nvi/exf.c:file_init() for a description of how and when the
+ * read-only bit is set.
+ *
+ * !!!
+ * The historic display for "name changed" was "[Not edited]".
+ */
+ needsep = 0;
+ if (F_ISSET(sp->frp, FR_NEWFILE)) {
+ F_CLR(sp->frp, FR_NEWFILE);
+ t = msg_cat(sp, "021|new file", &len);
+ memcpy(p, t, len);
+ p += len;
+ needsep = 1;
+ } else {
+ if (F_ISSET(sp->frp, FR_NAMECHANGE)) {
+ t = msg_cat(sp, "022|name changed", &len);
+ memcpy(p, t, len);
+ p += len;
+ needsep = 1;
+ }
+ if (needsep) {
+ *p++ = ',';
+ *p++ = ' ';
+ }
+ if (F_ISSET(sp->ep, F_MODIFIED))
+ t = msg_cat(sp, "023|modified", &len);
+ else
+ t = msg_cat(sp, "024|unmodified", &len);
+ memcpy(p, t, len);
+ p += len;
+ needsep = 1;
+ }
+ if (F_ISSET(sp->frp, FR_UNLOCKED)) {
+ if (needsep) {
+ *p++ = ',';
+ *p++ = ' ';
+ }
+ t = msg_cat(sp, "025|UNLOCKED", &len);
+ memcpy(p, t, len);
+ p += len;
+ needsep = 1;
+ }
+ if (O_ISSET(sp, O_READONLY)) {
+ if (needsep) {
+ *p++ = ',';
+ *p++ = ' ';
+ }
+ t = msg_cat(sp, "026|readonly", &len);
+ memcpy(p, t, len);
+ p += len;
+ needsep = 1;
+ }
+ if (needsep) {
+ *p++ = ':';
+ *p++ = ' ';
+ }
+ if (LF_ISSET(MSTAT_SHOWLAST)) {
+ if (db_last(sp, &last))
+ return;
+ if (last == 0) {
+ t = msg_cat(sp, "028|empty file", &len);
+ memcpy(p, t, len);
+ p += len;
+ } else {
+ t = msg_cat(sp, "027|line %lu of %lu [%ld%%]", &len);
+ (void)sprintf(p, t, lno, last, (lno * 100) / last);
+ p += strlen(p);
+ }
+ } else {
+ t = msg_cat(sp, "029|line %lu", &len);
+ (void)sprintf(p, t, lno);
+ p += strlen(p);
+ }
+#ifdef DEBUG
+ (void)sprintf(p, " (pid %lu)", (u_long)getpid());
+ p += strlen(p);
+#endif
+ *p++ = '\n';
+ len = p - bp;
+
+ /*
+ * 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 as soon as it starts the screen. Unfortunately, the user
+ * has already typed ahead, and chaos results. If we assume that the
+ * characters in the filenames and informational messages only take a
+ * single screen column each, we can trim the filename.
+ *
+ * XXX
+ * Status lines get put up at fairly awkward times. For example, when
+ * you do a filter read (e.g., :read ! echo foo) in the top screen of a
+ * split screen, we have to repaint the status lines for all the screens
+ * below the top screen. We don't want users having to enter continue
+ * characters for those screens. Make it really hard to screw this up.
+ */
+ s = bp;
+ if (LF_ISSET(MSTAT_TRUNCATE) && len > sp->cols) {
+ for (; s < np && (*s != '/' || (p - s) > sp->cols - 3); ++s);
+ if (s == np) {
+ s = p - (sp->cols - 5);
+ *--s = ' ';
+ }
+ *--s = '.';
+ *--s = '.';
+ *--s = '.';
+ len = p - s;
+ }
+
+ /* Flush any waiting ex messages. */
+ (void)ex_fflush(sp);
+
+ sp->gp->scr_msg(sp, M_INFO, s, len);
+
+ FREE_SPACE(sp, bp, blen);
+alloc_err:
+ return;
+}
+
+/*
+ * msg_open --
+ * Open the message catalogs.
+ *
+ * PUBLIC: int msg_open __P((SCR *, char *));
+ */
+int
+msg_open(sp, file)
+ SCR *sp;
+ char *file;
+{
+ /*
+ * !!!
+ * Assume that the first file opened is the system default, and that
+ * all subsequent ones user defined. Only display error messages
+ * if we can't open the user defined ones -- it's useful to know if
+ * the system one wasn't there, but if nvi is being shipped with an
+ * installed system, the file will be there, if it's not, then the
+ * message will be repeated every time nvi is started up.
+ */
+ static int first = 1;
+ DB *db;
+ DBT data, key;
+ recno_t msgno;
+ char *p, *t, buf[MAXPATHLEN];
+
+ if ((p = strrchr(file, '/')) != NULL && p[1] == '\0' &&
+ ((t = getenv("LC_MESSAGES")) != NULL && t[0] != '\0' ||
+ (t = getenv("LANG")) != NULL && t[0] != '\0')) {
+ (void)snprintf(buf, sizeof(buf), "%s%s", file, t);
+ p = buf;
+ } else
+ p = file;
+ if ((db = dbopen(p,
+ O_NONBLOCK | O_RDONLY, 0, DB_RECNO, NULL)) == NULL) {
+ if (first) {
+ first = 0;
+ return (1);
+ }
+ msgq_str(sp, M_SYSERR, p, "%s");
+ return (1);
+ }
+
+ /*
+ * Test record 1 for the magic string. The msgq call is here so
+ * the message catalog build finds it.
+ */
+#define VMC "VI_MESSAGE_CATALOG"
+ key.data = &msgno;
+ key.size = sizeof(recno_t);
+ msgno = 1;
+ if (db->get(db, &key, &data, 0) != 0 ||
+ data.size != sizeof(VMC) - 1 ||
+ memcmp(data.data, VMC, sizeof(VMC) - 1)) {
+ (void)db->close(db);
+ if (first) {
+ first = 0;
+ return (1);
+ }
+ msgq_str(sp, M_ERR, p,
+ "030|The file %s is not a message catalog");
+ return (1);
+ }
+ first = 0;
+
+ if (sp->gp->msg != NULL)
+ (void)sp->gp->msg->close(sp->gp->msg);
+ sp->gp->msg = db;
+ return (0);
+}
+
+/*
+ * msg_close --
+ * Close the message catalogs.
+ *
+ * PUBLIC: void msg_close __P((GS *));
+ */
+void
+msg_close(gp)
+ GS *gp;
+{
+ if (gp->msg != NULL)
+ (void)gp->msg->close(gp->msg);
+}
+
+/*
+ * msg_cont --
+ * Return common continuation messages.
+ *
+ * PUBLIC: const char *msg_cmsg __P((SCR *, cmsg_t, size_t *));
+ */
+const char *
+msg_cmsg(sp, which, lenp)
+ SCR *sp;
+ cmsg_t which;
+ size_t *lenp;
+{
+ switch (which) {
+ case CMSG_CONF:
+ return (msg_cat(sp, "268|confirm? [ynq]", lenp));
+ case CMSG_CONT:
+ return (msg_cat(sp, "269|Press any key to continue: ", lenp));
+ case CMSG_CONT_EX:
+ return (msg_cat(sp,
+ "270|Press any key to continue [: to enter more ex commands]: ",
+ lenp));
+ case CMSG_CONT_R:
+ return (msg_cat(sp, "161|Press Enter to continue: ", lenp));
+ case CMSG_CONT_S:
+ return (msg_cat(sp, "275| cont?", lenp));
+ case CMSG_CONT_Q:
+ return (msg_cat(sp,
+ "271|Press any key to continue [q to quit]: ", lenp));
+ default:
+ abort();
+ }
+ /* NOTREACHED */
+}
+
+/*
+ * msg_cat --
+ * Return a single message from the catalog, plus its length.
+ *
+ * !!!
+ * Only a single catalog message can be accessed at a time, if multiple
+ * ones are needed, they must be copied into local memory.
+ *
+ * PUBLIC: const char *msg_cat __P((SCR *, const char *, size_t *));
+ */
+const char *
+msg_cat(sp, str, lenp)
+ SCR *sp;
+ const char *str;
+ size_t *lenp;
+{
+ GS *gp;
+ DBT data, key;
+ recno_t msgno;
+
+ /*
+ * If it's not a catalog message, i.e. has doesn't have a leading
+ * number and '|' symbol, we're done.
+ */
+ if (isdigit(str[0]) &&
+ isdigit(str[1]) && isdigit(str[2]) && str[3] == '|') {
+ key.data = &msgno;
+ key.size = sizeof(recno_t);
+ msgno = atoi(str);
+
+ /*
+ * XXX
+ * Really sleazy hack -- we put an extra character on the
+ * end of the format string, and then we change it to be
+ * the nul termination of the string. There ought to be
+ * a better way. Once we can allocate multiple temporary
+ * memory buffers, maybe we can use one of them instead.
+ */
+ gp = sp == NULL ? NULL : sp->gp;
+ if (gp != NULL && gp->msg != NULL &&
+ gp->msg->get(gp->msg, &key, &data, 0) == 0 &&
+ data.size != 0) {
+ if (lenp != NULL)
+ *lenp = data.size - 1;
+ ((char *)data.data)[data.size - 1] = '\0';
+ return (data.data);
+ }
+ str = &str[4];
+ }
+ if (lenp != NULL)
+ *lenp = strlen(str);
+ return (str);
+}
+
+/*
+ * msg_print --
+ * Return a printable version of a string, in allocated memory.
+ *
+ * PUBLIC: char *msg_print __P((SCR *, const char *, int *));
+ */
+char *
+msg_print(sp, s, needfree)
+ SCR *sp;
+ const char *s;
+ int *needfree;
+{
+ size_t blen, nlen;
+ const char *cp;
+ char *bp, *ep, *p, *t;
+
+ *needfree = 0;
+
+ for (cp = s; *cp != '\0'; ++cp)
+ if (!isprint(*cp))
+ break;
+ if (*cp == '\0')
+ return ((char *)s); /* SAFE: needfree set to 0. */
+
+ nlen = 0;
+ if (0) {
+retry: if (sp == NULL)
+ free(bp);
+ else
+ FREE_SPACE(sp, bp, blen);
+ needfree = 0;
+ }
+ nlen += 256;
+ if (sp == NULL) {
+ if ((bp = malloc(nlen)) == NULL)
+ goto alloc_err;
+ } else
+ GET_SPACE_GOTO(sp, bp, blen, nlen);
+ if (0) {
+alloc_err: return ("");
+ }
+ *needfree = 1;
+
+ for (p = bp, ep = (bp + blen) - 1, cp = s; *cp != '\0' && p < ep; ++cp)
+ for (t = KEY_NAME(sp, *cp); *t != '\0' && p < ep; *p++ = *t++);
+ if (p == ep)
+ goto retry;
+ *p = '\0';
+ return (bp);
+}
diff --git a/common/msg.h b/common/msg.h
new file mode 100644
index 000000000000..b10f4ccae5c6
--- /dev/null
+++ b/common/msg.h
@@ -0,0 +1,65 @@
+/*-
+ * Copyright (c) 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 1993, 1994, 1995, 1996
+ * Keith Bostic. All rights reserved.
+ *
+ * See the LICENSE file for redistribution information.
+ *
+ * @(#)msg.h 10.10 (Berkeley) 5/10/96
+ */
+
+/*
+ * Common messages (continuation or confirmation).
+ */
+typedef enum {
+ CMSG_CONF, CMSG_CONT, CMSG_CONT_EX,
+ CMSG_CONT_R, CMSG_CONT_S, CMSG_CONT_Q } cmsg_t;
+
+/*
+ * Message types.
+ *
+ * !!!
+ * In historical vi, O_VERBOSE didn't exist, and O_TERSE made the error
+ * messages shorter. In this implementation, O_TERSE has no effect and
+ * O_VERBOSE results in informational displays about common errors, for
+ * naive users.
+ *
+ * M_NONE Display to the user, no reformatting, no nothing.
+ *
+ * M_BERR Error: M_ERR if O_VERBOSE, else bell.
+ * M_ERR Error: Display in inverse video.
+ * M_INFO Info: Display in normal video.
+ * M_SYSERR Error: M_ERR, using strerror(3) message.
+ * M_VINFO Info: M_INFO if O_VERBOSE, else ignore.
+ *
+ * The underlying message display routines only need to know about M_NONE,
+ * M_ERR and M_INFO -- all the other message types are converted into one
+ * of them by the message routines.
+ */
+typedef enum {
+ M_NONE = 1, M_BERR, M_ERR, M_INFO, M_SYSERR, M_VINFO } mtype_t;
+
+/*
+ * There are major problems with error messages being generated by routines
+ * preparing the screen to display error messages. It's possible for the
+ * editor to generate messages before we have a screen in which to display
+ * them, or during the transition between ex (and vi startup) and a true vi.
+ * There's a queue in the global area to hold them.
+ *
+ * If SC_EX/SC_VI is set, that's the mode that the editor is in. If the flag
+ * S_SCREEN_READY is set, that means that the screen is prepared to display
+ * messages.
+ */
+typedef struct _msgh MSGH; /* MSGS list head structure. */
+LIST_HEAD(_msgh, _msg);
+struct _msg {
+ LIST_ENTRY(_msg) q; /* Linked list of messages. */
+ mtype_t mtype; /* Message type: M_NONE, M_ERR, M_INFO. */
+ char *buf; /* Message buffer. */
+ size_t len; /* Message length. */
+};
+
+/* Flags to msgq_status(). */
+#define MSTAT_SHOWLAST 0x01 /* Show the line number of the last line. */
+#define MSTAT_TRUNCATE 0x02 /* Truncate the file name if it's too long. */
diff --git a/common/options.awk b/common/options.awk
new file mode 100644
index 000000000000..0c91f0718f07
--- /dev/null
+++ b/common/options.awk
@@ -0,0 +1,9 @@
+# @(#)options.awk 10.1 (Berkeley) 6/8/95
+
+/^\/\* O_[0-9A-Z_]*/ {
+ printf("#define %s %d\n", $2, cnt++);
+ next;
+}
+END {
+ printf("#define O_OPTIONCOUNT %d\n", cnt);
+}
diff --git a/common/options.c b/common/options.c
new file mode 100644
index 000000000000..973778c78bbd
--- /dev/null
+++ b/common/options.c
@@ -0,0 +1,1141 @@
+/*-
+ * 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 <sys/types.h>
+#include <sys/queue.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+
+#include <bitstring.h>
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#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 */
+ {"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},
+/* 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 */
+ {"eb", O_ERRORBELLS}, /* 4BSD */
+ {"ed", O_EDCOMPATIBLE}, /* 4BSD */
+ {"ex", O_EXRC}, /* System V (undocumented) */
+ {"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_ESCAPETIME, "escapetime=1");
+ 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));
+ }
+}
diff --git a/common/options.h b/common/options.h
new file mode 100644
index 000000000000..2646dc301b5a
--- /dev/null
+++ b/common/options.h
@@ -0,0 +1,101 @@
+/*-
+ * 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.
+ *
+ * @(#)options.h 10.19 (Berkeley) 10/10/96
+ */
+
+/*
+ * Edit option information. Historically, if you set a boolean or numeric
+ * edit option value to its "default" value, it didn't show up in the :set
+ * display, i.e. it wasn't considered "changed". String edit options would
+ * show up as changed, regardless. We maintain a parallel set of values
+ * which are the default values and never consider an edit option changed
+ * if it was reset to the default value.
+ *
+ * Macros to retrieve boolean, integral and string option values, and to
+ * set, clear and test boolean option values. Some options (secure, lines,
+ * columns, terminal type) are global in scope, and are therefore stored
+ * in the global area. The offset in the global options array is stored
+ * in the screen's value field. This is set up when the options are first
+ * initialized.
+ */
+#define O_V(sp, o, fld) \
+ (F_ISSET(&(sp)->opts[(o)], OPT_GLOBAL) ? \
+ (sp)->gp->opts[(sp)->opts[(o)].o_cur.val].fld : \
+ (sp)->opts[(o)].fld)
+
+/* Global option macros. */
+#define OG_CLR(gp, o) ((gp)->opts[(o)].o_cur.val) = 0
+#define OG_SET(gp, o) ((gp)->opts[(o)].o_cur.val) = 1
+#define OG_STR(gp, o) ((gp)->opts[(o)].o_cur.str)
+#define OG_VAL(gp, o) ((gp)->opts[(o)].o_cur.val)
+#define OG_ISSET(gp, o) OG_VAL(gp, o)
+
+#define OG_D_STR(gp, o) ((gp)->opts[(o)].o_def.str)
+#define OG_D_VAL(gp, o) ((gp)->opts[(o)].o_def.val)
+
+/*
+ * Flags to o_set(); need explicit OS_STR as can be setting the value to
+ * NULL.
+ */
+#define OS_DEF 0x01 /* Set the default value. */
+#define OS_NOFREE 0x02 /* Don't free the old string. */
+#define OS_STR 0x04 /* Set to string argument. */
+#define OS_STRDUP 0x08 /* Copy then set to string argument. */
+
+struct _option {
+ union {
+ u_long val; /* Value or boolean. */
+ char *str; /* String. */
+ } o_cur;
+#define O_CLR(sp, o) o_set(sp, o, 0, NULL, 0)
+#define O_SET(sp, o) o_set(sp, o, 0, NULL, 1)
+#define O_STR(sp, o) O_V(sp, o, o_cur.str)
+#define O_VAL(sp, o) O_V(sp, o, o_cur.val)
+#define O_ISSET(sp, o) O_VAL(sp, o)
+
+ union {
+ u_long val; /* Value or boolean. */
+ char *str; /* String. */
+ } o_def;
+#define O_D_CLR(sp, o) o_set(sp, o, OS_DEF, NULL, 0)
+#define O_D_SET(sp, o) o_set(sp, o, OS_DEF, NULL, 1)
+#define O_D_STR(sp, o) O_V(sp, o, o_def.str)
+#define O_D_VAL(sp, o) O_V(sp, o, o_def.val)
+#define O_D_ISSET(sp, o) O_D_VAL(sp, o)
+
+#define OPT_GLOBAL 0x01 /* Option is global. */
+#define OPT_SELECTED 0x02 /* Selected for display. */
+ u_int8_t flags;
+};
+
+/* List of option names, associated update functions and information. */
+struct _optlist {
+ char *name; /* Name. */
+ /* Change function. */
+ int (*func) __P((SCR *, OPTION *, char *, u_long *));
+ /* Type of object. */
+ enum { OPT_0BOOL, OPT_1BOOL, OPT_NUM, OPT_STR } type;
+
+#define OPT_ADISP 0x001 /* Always display the option. */
+#define OPT_ALWAYS 0x002 /* Always call the support function. */
+#define OPT_NDISP 0x004 /* Never display the option. */
+#define OPT_NOSAVE 0x008 /* Mkexrc command doesn't save. */
+#define OPT_NOSET 0x010 /* Option may not be set. */
+#define OPT_NOUNSET 0x020 /* Option may not be unset. */
+#define OPT_NOZERO 0x040 /* Option may not be set to 0. */
+ u_int8_t flags;
+};
+
+/* Option argument to opts_dump(). */
+enum optdisp { NO_DISPLAY, ALL_DISPLAY, CHANGED_DISPLAY, SELECT_DISPLAY };
+
+/* Options array. */
+extern OPTLIST const optlist[];
+
+#include "options_def.h"
diff --git a/common/options_f.c b/common/options_f.c
new file mode 100644
index 000000000000..ea3c61160cf5
--- /dev/null
+++ b/common/options_f.c
@@ -0,0 +1,367 @@
+/*-
+ * Copyright (c) 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 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_f.c 10.25 (Berkeley) 7/12/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/stat.h>
+
+#include <bitstring.h>
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "common.h"
+
+/*
+ * PUBLIC: int f_altwerase __P((SCR *, OPTION *, char *, u_long *));
+ */
+int
+f_altwerase(sp, op, str, valp)
+ SCR *sp;
+ OPTION *op;
+ char *str;
+ u_long *valp;
+{
+ if (!*valp)
+ O_CLR(sp, O_TTYWERASE);
+ return (0);
+}
+
+/*
+ * PUBLIC: int f_columns __P((SCR *, OPTION *, char *, u_long *));
+ */
+int
+f_columns(sp, op, str, valp)
+ SCR *sp;
+ OPTION *op;
+ char *str;
+ u_long *valp;
+{
+ /* Validate the number. */
+ if (*valp < MINIMUM_SCREEN_COLS) {
+ msgq(sp, M_ERR, "040|Screen columns too small, less than %d",
+ MINIMUM_SCREEN_COLS);
+ return (1);
+ }
+
+ /*
+ * !!!
+ * It's not uncommon for allocation of huge chunks of memory to cause
+ * core dumps on various systems. So, we prune out numbers that are
+ * "obviously" wrong. Vi will not work correctly if it has the wrong
+ * number of lines/columns for the screen, but at least we don't drop
+ * core.
+ */
+#define MAXIMUM_SCREEN_COLS 500
+ if (*valp > MAXIMUM_SCREEN_COLS) {
+ msgq(sp, M_ERR, "041|Screen columns too large, greater than %d",
+ MAXIMUM_SCREEN_COLS);
+ return (1);
+ }
+ return (0);
+}
+
+/*
+ * PUBLIC: int f_lines __P((SCR *, OPTION *, char *, u_long *));
+ */
+int
+f_lines(sp, op, str, valp)
+ SCR *sp;
+ OPTION *op;
+ char *str;
+ u_long *valp;
+{
+ /* Validate the number. */
+ if (*valp < MINIMUM_SCREEN_ROWS) {
+ msgq(sp, M_ERR, "042|Screen lines too small, less than %d",
+ MINIMUM_SCREEN_ROWS);
+ return (1);
+ }
+
+ /*
+ * !!!
+ * It's not uncommon for allocation of huge chunks of memory to cause
+ * core dumps on various systems. So, we prune out numbers that are
+ * "obviously" wrong. Vi will not work correctly if it has the wrong
+ * number of lines/columns for the screen, but at least we don't drop
+ * core.
+ */
+#define MAXIMUM_SCREEN_ROWS 500
+ if (*valp > MAXIMUM_SCREEN_ROWS) {
+ msgq(sp, M_ERR, "043|Screen lines too large, greater than %d",
+ MAXIMUM_SCREEN_ROWS);
+ return (1);
+ }
+
+ /*
+ * Set the value, and the related scroll value. If no window
+ * value set, set a new default window.
+ */
+ o_set(sp, O_LINES, 0, NULL, *valp);
+ if (*valp == 1) {
+ sp->defscroll = 1;
+
+ if (O_VAL(sp, O_WINDOW) == O_D_VAL(sp, O_WINDOW) ||
+ O_VAL(sp, O_WINDOW) > *valp) {
+ o_set(sp, O_WINDOW, 0, NULL, 1);
+ o_set(sp, O_WINDOW, OS_DEF, NULL, 1);
+ }
+ } else {
+ sp->defscroll = (*valp - 1) / 2;
+
+ if (O_VAL(sp, O_WINDOW) == O_D_VAL(sp, O_WINDOW) ||
+ O_VAL(sp, O_WINDOW) > *valp) {
+ o_set(sp, O_WINDOW, 0, NULL, *valp - 1);
+ o_set(sp, O_WINDOW, OS_DEF, NULL, *valp - 1);
+ }
+ }
+ return (0);
+}
+
+/*
+ * PUBLIC: int f_lisp __P((SCR *, OPTION *, char *, u_long *));
+ */
+int
+f_lisp(sp, op, str, valp)
+ SCR *sp;
+ OPTION *op;
+ char *str;
+ u_long *valp;
+{
+ msgq(sp, M_ERR, "044|The lisp option is not implemented");
+ return (0);
+}
+
+/*
+ * PUBLIC: int f_msgcat __P((SCR *, OPTION *, char *, u_long *));
+ */
+int
+f_msgcat(sp, op, str, valp)
+ SCR *sp;
+ OPTION *op;
+ char *str;
+ u_long *valp;
+{
+ (void)msg_open(sp, str);
+ return (0);
+}
+
+/*
+ * PUBLIC: int f_paragraph __P((SCR *, OPTION *, char *, u_long *));
+ */
+int
+f_paragraph(sp, op, str, valp)
+ SCR *sp;
+ OPTION *op;
+ char *str;
+ u_long *valp;
+{
+ if (strlen(str) & 1) {
+ msgq(sp, M_ERR,
+ "048|The paragraph option must be in two character groups");
+ return (1);
+ }
+ return (0);
+}
+
+/*
+ * PUBLIC: int f_print __P((SCR *, OPTION *, char *, u_long *));
+ */
+int
+f_print(sp, op, str, valp)
+ SCR *sp;
+ OPTION *op;
+ char *str;
+ u_long *valp;
+{
+ /* Reinitialize the key fast lookup table. */
+ v_key_ilookup(sp);
+
+ /* Reformat the screen. */
+ F_SET(sp, SC_SCR_REFORMAT);
+ return (0);
+}
+
+/*
+ * PUBLIC: int f_readonly __P((SCR *, OPTION *, char *, u_long *));
+ */
+int
+f_readonly(sp, op, str, valp)
+ SCR *sp;
+ OPTION *op;
+ char *str;
+ u_long *valp;
+{
+ /*
+ * !!!
+ * See the comment in exf.c.
+ */
+ if (*valp)
+ F_CLR(sp, SC_READONLY);
+ else
+ F_SET(sp, SC_READONLY);
+ return (0);
+}
+
+/*
+ * PUBLIC: int f_recompile __P((SCR *, OPTION *, char *, u_long *));
+ */
+int
+f_recompile(sp, op, str, valp)
+ SCR *sp;
+ OPTION *op;
+ char *str;
+ u_long *valp;
+{
+ if (F_ISSET(sp, SC_RE_SEARCH)) {
+ regfree(&sp->re_c);
+ F_CLR(sp, SC_RE_SEARCH);
+ }
+ if (F_ISSET(sp, SC_RE_SUBST)) {
+ regfree(&sp->subre_c);
+ F_CLR(sp, SC_RE_SUBST);
+ }
+ return (0);
+}
+
+/*
+ * PUBLIC: int f_reformat __P((SCR *, OPTION *, char *, u_long *));
+ */
+int
+f_reformat(sp, op, str, valp)
+ SCR *sp;
+ OPTION *op;
+ char *str;
+ u_long *valp;
+{
+ F_SET(sp, SC_SCR_REFORMAT);
+ return (0);
+}
+
+/*
+ * PUBLIC: int f_section __P((SCR *, OPTION *, char *, u_long *));
+ */
+int
+f_section(sp, op, str, valp)
+ SCR *sp;
+ OPTION *op;
+ char *str;
+ u_long *valp;
+{
+ if (strlen(str) & 1) {
+ msgq(sp, M_ERR,
+ "049|The section option must be in two character groups");
+ return (1);
+ }
+ return (0);
+}
+
+/*
+ * PUBLIC: int f_ttywerase __P((SCR *, OPTION *, char *, u_long *));
+ */
+int
+f_ttywerase(sp, op, str, valp)
+ SCR *sp;
+ OPTION *op;
+ char *str;
+ u_long *valp;
+{
+ if (!*valp)
+ O_CLR(sp, O_ALTWERASE);
+ return (0);
+}
+
+/*
+ * PUBLIC: int f_w300 __P((SCR *, OPTION *, char *, u_long *));
+ */
+int
+f_w300(sp, op, str, valp)
+ SCR *sp;
+ OPTION *op;
+ char *str;
+ u_long *valp;
+{
+ u_long v;
+
+ /* Historical behavior for w300 was < 1200. */
+ if (sp->gp->scr_baud(sp, &v))
+ return (1);
+ if (v >= 1200)
+ return (0);
+
+ return (f_window(sp, op, str, valp));
+}
+
+/*
+ * PUBLIC: int f_w1200 __P((SCR *, OPTION *, char *, u_long *));
+ */
+int
+f_w1200(sp, op, str, valp)
+ SCR *sp;
+ OPTION *op;
+ char *str;
+ u_long *valp;
+{
+ u_long v;
+
+ /* Historical behavior for w1200 was == 1200. */
+ if (sp->gp->scr_baud(sp, &v))
+ return (1);
+ if (v < 1200 || v > 4800)
+ return (0);
+
+ return (f_window(sp, op, str, valp));
+}
+
+/*
+ * PUBLIC: int f_w9600 __P((SCR *, OPTION *, char *, u_long *));
+ */
+int
+f_w9600(sp, op, str, valp)
+ SCR *sp;
+ OPTION *op;
+ char *str;
+ u_long *valp;
+{
+ u_long v;
+
+ /* Historical behavior for w9600 was > 1200. */
+ if (sp->gp->scr_baud(sp, &v))
+ return (1);
+ if (v <= 4800)
+ return (0);
+
+ return (f_window(sp, op, str, valp));
+}
+
+/*
+ * PUBLIC: int f_window __P((SCR *, OPTION *, char *, u_long *));
+ */
+int
+f_window(sp, op, str, valp)
+ SCR *sp;
+ OPTION *op;
+ char *str;
+ u_long *valp;
+{
+ if (*valp >= O_VAL(sp, O_LINES) - 1 &&
+ (*valp = O_VAL(sp, O_LINES) - 1) == 0)
+ *valp = 1;
+ return (0);
+}
diff --git a/common/put.c b/common/put.c
new file mode 100644
index 000000000000..8c0ca4b7c14f
--- /dev/null
+++ b/common/put.c
@@ -0,0 +1,231 @@
+/*-
+ * 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
+static const char sccsid[] = "@(#)put.c 10.11 (Berkeley) 9/23/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+
+#include <bitstring.h>
+#include <ctype.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "common.h"
+
+/*
+ * put --
+ * Put text buffer contents into the file.
+ *
+ * PUBLIC: int put __P((SCR *, CB *, CHAR_T *, MARK *, MARK *, int));
+ */
+int
+put(sp, cbp, namep, cp, rp, append)
+ SCR *sp;
+ CB *cbp;
+ CHAR_T *namep;
+ MARK *cp, *rp;
+ int append;
+{
+ CHAR_T name;
+ TEXT *ltp, *tp;
+ recno_t lno;
+ size_t blen, clen, len;
+ int rval;
+ char *bp, *p, *t;
+
+ if (cbp == NULL)
+ if (namep == NULL) {
+ cbp = sp->gp->dcbp;
+ if (cbp == NULL) {
+ msgq(sp, M_ERR,
+ "053|The default buffer is empty");
+ return (1);
+ }
+ } else {
+ name = *namep;
+ CBNAME(sp, cbp, name);
+ if (cbp == NULL) {
+ msgq(sp, M_ERR, "054|Buffer %s is empty",
+ KEY_NAME(sp, name));
+ return (1);
+ }
+ }
+ tp = cbp->textq.cqh_first;
+
+ /*
+ * It's possible to do a put into an empty file, meaning that the cut
+ * buffer simply becomes the file. It's a special case so that we can
+ * ignore it in general.
+ *
+ * !!!
+ * Historically, pasting into a file with no lines in vi would preserve
+ * the single blank line. This is surely a result of the fact that the
+ * historic vi couldn't deal with a file that had no lines in it. This
+ * implementation treats that as a bug, and does not retain the blank
+ * line.
+ *
+ * Historical practice is that the cursor ends at the first character
+ * in the file.
+ */
+ if (cp->lno == 1) {
+ if (db_last(sp, &lno))
+ return (1);
+ if (lno == 0) {
+ for (; tp != (void *)&cbp->textq;
+ ++lno, ++sp->rptlines[L_ADDED], tp = tp->q.cqe_next)
+ if (db_append(sp, 1, lno, tp->lb, tp->len))
+ return (1);
+ rp->lno = 1;
+ rp->cno = 0;
+ return (0);
+ }
+ }
+
+ /* If a line mode buffer, append each new line into the file. */
+ if (F_ISSET(cbp, CB_LMODE)) {
+ lno = append ? cp->lno : cp->lno - 1;
+ rp->lno = lno + 1;
+ for (; tp != (void *)&cbp->textq;
+ ++lno, ++sp->rptlines[L_ADDED], tp = tp->q.cqe_next)
+ if (db_append(sp, 1, lno, tp->lb, tp->len))
+ return (1);
+ rp->cno = 0;
+ (void)nonblank(sp, rp->lno, &rp->cno);
+ return (0);
+ }
+
+ /*
+ * If buffer was cut in character mode, replace the current line with
+ * one built from the portion of the first line to the left of the
+ * split plus the first line in the CB. Append each intermediate line
+ * in the CB. Append a line built from the portion of the first line
+ * to the right of the split plus the last line in the CB.
+ *
+ * Get the first line.
+ */
+ lno = cp->lno;
+ if (db_get(sp, lno, DBG_FATAL, &p, &len))
+ return (1);
+
+ GET_SPACE_RET(sp, bp, blen, tp->len + len + 1);
+ t = bp;
+
+ /* Original line, left of the split. */
+ if (len > 0 && (clen = cp->cno + (append ? 1 : 0)) > 0) {
+ memcpy(bp, p, clen);
+ p += clen;
+ t += clen;
+ }
+
+ /* First line from the CB. */
+ if (tp->len != 0) {
+ memcpy(t, tp->lb, tp->len);
+ t += tp->len;
+ }
+
+ /* Calculate length left in the original line. */
+ clen = len == 0 ? 0 : len - (cp->cno + (append ? 1 : 0));
+
+ /*
+ * !!!
+ * In the historical 4BSD version of vi, character mode puts within
+ * a single line have two cursor behaviors: if the put is from the
+ * unnamed buffer, the cursor moves to the character inserted which
+ * appears last in the file. If the put is from a named buffer,
+ * the cursor moves to the character inserted which appears first
+ * in the file. In System III/V, it was changed at some point and
+ * the cursor always moves to the first character. In both versions
+ * of vi, character mode puts that cross line boundaries leave the
+ * cursor on the first character. Nvi implements the System III/V
+ * behavior, and expect POSIX.2 to do so as well.
+ */
+ rp->lno = lno;
+ rp->cno = len == 0 ? 0 : sp->cno + (append && tp->len ? 1 : 0);
+
+ /*
+ * If no more lines in the CB, append the rest of the original
+ * line and quit. Otherwise, build the last line before doing
+ * the intermediate lines, because the line changes will lose
+ * the cached line.
+ */
+ if (tp->q.cqe_next == (void *)&cbp->textq) {
+ if (clen > 0) {
+ memcpy(t, p, clen);
+ t += clen;
+ }
+ if (db_set(sp, lno, bp, t - bp))
+ goto err;
+ if (sp->rptlchange != lno) {
+ sp->rptlchange = lno;
+ ++sp->rptlines[L_CHANGED];
+ }
+ } else {
+ /*
+ * Have to build both the first and last lines of the
+ * put before doing any sets or we'll lose the cached
+ * line. Build both the first and last lines in the
+ * same buffer, so we don't have to have another buffer
+ * floating around.
+ *
+ * Last part of original line; check for space, reset
+ * the pointer into the buffer.
+ */
+ ltp = cbp->textq.cqh_last;
+ len = t - bp;
+ ADD_SPACE_RET(sp, bp, blen, ltp->len + clen);
+ t = bp + len;
+
+ /* Add in last part of the CB. */
+ memcpy(t, ltp->lb, ltp->len);
+ if (clen)
+ memcpy(t + ltp->len, p, clen);
+ clen += ltp->len;
+
+ /*
+ * Now: bp points to the first character of the first
+ * line, t points to the last character of the last
+ * line, t - bp is the length of the first line, and
+ * clen is the length of the last. Just figured you'd
+ * want to know.
+ *
+ * Output the line replacing the original line.
+ */
+ if (db_set(sp, lno, bp, t - bp))
+ goto err;
+ if (sp->rptlchange != lno) {
+ sp->rptlchange = lno;
+ ++sp->rptlines[L_CHANGED];
+ }
+
+ /* Output any intermediate lines in the CB. */
+ for (tp = tp->q.cqe_next;
+ tp->q.cqe_next != (void *)&cbp->textq;
+ ++lno, ++sp->rptlines[L_ADDED], tp = tp->q.cqe_next)
+ if (db_append(sp, 1, lno, tp->lb, tp->len))
+ goto err;
+
+ if (db_append(sp, 1, lno, t, clen))
+ goto err;
+ ++sp->rptlines[L_ADDED];
+ }
+ rval = 0;
+
+ if (0)
+err: rval = 1;
+
+ FREE_SPACE(sp, bp, blen);
+ return (rval);
+}
diff --git a/common/recover.c b/common/recover.c
new file mode 100644
index 000000000000..f3abaab5a536
--- /dev/null
+++ b/common/recover.c
@@ -0,0 +1,878 @@
+/*-
+ * Copyright (c) 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 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[] = "@(#)recover.c 10.21 (Berkeley) 9/15/96";
+#endif /* not lint */
+
+#include <sys/param.h>
+#include <sys/types.h> /* XXX: param.h may not have included types.h */
+#include <sys/queue.h>
+#include <sys/stat.h>
+
+/*
+ * We include <sys/file.h>, because the open #defines were found there
+ * on historical systems. We also include <fcntl.h> because the open(2)
+ * #defines are found there on newer systems.
+ */
+#include <sys/file.h>
+
+#include <bitstring.h>
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+
+#include "common.h"
+#include "pathnames.h"
+
+/*
+ * Recovery code.
+ *
+ * The basic scheme is as follows. In the EXF structure, we maintain full
+ * paths of a b+tree file and a mail recovery file. The former is the file
+ * used as backing store by the DB package. The latter is the file that
+ * contains an email message to be sent to the user if we crash. The two
+ * simple states of recovery are:
+ *
+ * + first starting the edit session:
+ * the b+tree file exists and is mode 700, the mail recovery
+ * file doesn't exist.
+ * + after the file has been modified:
+ * the b+tree file exists and is mode 600, the mail recovery
+ * file exists, and is exclusively locked.
+ *
+ * In the EXF structure we maintain a file descriptor that is the locked
+ * file descriptor for the mail recovery file. NOTE: we sometimes have to
+ * do locking with fcntl(2). This is a problem because if you close(2) any
+ * file descriptor associated with the file, ALL of the locks go away. Be
+ * sure to remember that if you have to modify the recovery code. (It has
+ * been rhetorically asked of what the designers could have been thinking
+ * when they did that interface. The answer is simple: they weren't.)
+ *
+ * To find out if a recovery file/backing file pair are in use, try to get
+ * a lock on the recovery file.
+ *
+ * To find out if a backing file can be deleted at boot time, check for an
+ * owner execute bit. (Yes, I know it's ugly, but it's either that or put
+ * special stuff into the backing file itself, or correlate the files at
+ * boot time, neither of which looks like fun.) Note also that there's a
+ * window between when the file is created and the X bit is set. It's small,
+ * but it's there. To fix the window, check for 0 length files as well.
+ *
+ * To find out if a file can be recovered, check the F_RCV_ON bit. Note,
+ * this DOES NOT mean that any initialization has been done, only that we
+ * haven't yet failed at setting up or doing recovery.
+ *
+ * To preserve a recovery file/backing file pair, set the F_RCV_NORM bit.
+ * If that bit is not set when ending a file session:
+ * If the EXF structure paths (rcv_path and rcv_mpath) are not NULL,
+ * they are unlink(2)'d, and free(3)'d.
+ * If the EXF file descriptor (rcv_fd) is not -1, it is closed.
+ *
+ * The backing b+tree file is set up when a file is first edited, so that
+ * the DB package can use it for on-disk caching and/or to snapshot the
+ * file. When the file is first modified, the mail recovery file is created,
+ * the backing file permissions are updated, the file is sync(2)'d to disk,
+ * and the timer is started. Then, at RCV_PERIOD second intervals, the
+ * b+tree file is synced to disk. RCV_PERIOD is measured using SIGALRM, which
+ * means that the data structures (SCR, EXF, the underlying tree structures)
+ * must be consistent when the signal arrives.
+ *
+ * The recovery mail file contains normal mail headers, with two additions,
+ * which occur in THIS order, as the FIRST TWO headers:
+ *
+ * X-vi-recover-file: file_name
+ * X-vi-recover-path: recover_path
+ *
+ * Since newlines delimit the headers, this means that file names cannot have
+ * newlines in them, but that's probably okay. As these files aren't intended
+ * to be long-lived, changing their format won't be too painful.
+ *
+ * Btree files are named "vi.XXXX" and recovery files are named "recover.XXXX".
+ */
+
+#define VI_FHEADER "X-vi-recover-file: "
+#define VI_PHEADER "X-vi-recover-path: "
+
+static int rcv_copy __P((SCR *, int, char *));
+static void rcv_email __P((SCR *, char *));
+static char *rcv_gets __P((char *, size_t, int));
+static int rcv_mailfile __P((SCR *, int, char *));
+static int rcv_mktemp __P((SCR *, char *, char *, int));
+
+/*
+ * rcv_tmp --
+ * Build a file name that will be used as the recovery file.
+ *
+ * PUBLIC: int rcv_tmp __P((SCR *, EXF *, char *));
+ */
+int
+rcv_tmp(sp, ep, name)
+ SCR *sp;
+ EXF *ep;
+ char *name;
+{
+ struct stat sb;
+ int fd;
+ char *dp, *p, path[MAXPATHLEN];
+
+ /*
+ * !!!
+ * ep MAY NOT BE THE SAME AS sp->ep, DON'T USE THE LATTER.
+ *
+ *
+ * If the recovery directory doesn't exist, try and create it. As
+ * the recovery files are themselves protected from reading/writing
+ * by other than the owner, the worst that can happen is that a user
+ * would have permission to remove other user's recovery files. If
+ * the sticky bit has the BSD semantics, that too will be impossible.
+ */
+ if (opts_empty(sp, O_RECDIR, 0))
+ goto err;
+ dp = O_STR(sp, O_RECDIR);
+ if (stat(dp, &sb)) {
+ if (errno != ENOENT || mkdir(dp, 0)) {
+ msgq(sp, M_SYSERR, "%s", dp);
+ goto err;
+ }
+ (void)chmod(dp, S_IRWXU | S_IRWXG | S_IRWXO | S_ISVTX);
+ }
+
+ /* Newlines delimit the mail messages. */
+ for (p = name; *p; ++p)
+ if (*p == '\n') {
+ msgq(sp, M_ERR,
+ "055|Files with newlines in the name are unrecoverable");
+ goto err;
+ }
+
+ (void)snprintf(path, sizeof(path), "%s/vi.XXXXXX", dp);
+ if ((fd = rcv_mktemp(sp, path, dp, S_IRWXU)) == -1)
+ goto err;
+ (void)close(fd);
+
+ if ((ep->rcv_path = strdup(path)) == NULL) {
+ msgq(sp, M_SYSERR, NULL);
+ (void)unlink(path);
+err: msgq(sp, M_ERR,
+ "056|Modifications not recoverable if the session fails");
+ return (1);
+ }
+
+ /* We believe the file is recoverable. */
+ F_SET(ep, F_RCV_ON);
+ return (0);
+}
+
+/*
+ * rcv_init --
+ * Force the file to be snapshotted for recovery.
+ *
+ * PUBLIC: int rcv_init __P((SCR *));
+ */
+int
+rcv_init(sp)
+ SCR *sp;
+{
+ EXF *ep;
+ recno_t lno;
+
+ ep = sp->ep;
+
+ /* Only do this once. */
+ F_CLR(ep, F_FIRSTMODIFY);
+
+ /* If we already know the file isn't recoverable, we're done. */
+ if (!F_ISSET(ep, F_RCV_ON))
+ return (0);
+
+ /* Turn off recoverability until we figure out if this will work. */
+ F_CLR(ep, F_RCV_ON);
+
+ /* Test if we're recovering a file, not editing one. */
+ if (ep->rcv_mpath == NULL) {
+ /* Build a file to mail to the user. */
+ if (rcv_mailfile(sp, 0, NULL))
+ goto err;
+
+ /* Force a read of the entire file. */
+ if (db_last(sp, &lno))
+ goto err;
+
+ /* Turn on a busy message, and sync it to backing store. */
+ sp->gp->scr_busy(sp,
+ "057|Copying file for recovery...", BUSY_ON);
+ if (ep->db->sync(ep->db, R_RECNOSYNC)) {
+ msgq_str(sp, M_SYSERR, ep->rcv_path,
+ "058|Preservation failed: %s");
+ sp->gp->scr_busy(sp, NULL, BUSY_OFF);
+ goto err;
+ }
+ sp->gp->scr_busy(sp, NULL, BUSY_OFF);
+ }
+
+ /* Turn off the owner execute bit. */
+ (void)chmod(ep->rcv_path, S_IRUSR | S_IWUSR);
+
+ /* We believe the file is recoverable. */
+ F_SET(ep, F_RCV_ON);
+ return (0);
+
+err: msgq(sp, M_ERR,
+ "059|Modifications not recoverable if the session fails");
+ return (1);
+}
+
+/*
+ * rcv_sync --
+ * Sync the file, optionally:
+ * flagging the backup file to be preserved
+ * snapshotting the backup file and send email to the user
+ * sending email to the user if the file was modified
+ * ending the file session
+ *
+ * PUBLIC: int rcv_sync __P((SCR *, u_int));
+ */
+int
+rcv_sync(sp, flags)
+ SCR *sp;
+ u_int flags;
+{
+ EXF *ep;
+ int fd, rval;
+ char *dp, buf[1024];
+
+ /* Make sure that there's something to recover/sync. */
+ ep = sp->ep;
+ if (ep == NULL || !F_ISSET(ep, F_RCV_ON))
+ return (0);
+
+ /* Sync the file if it's been modified. */
+ if (F_ISSET(ep, F_MODIFIED)) {
+ SIGBLOCK;
+ if (ep->db->sync(ep->db, R_RECNOSYNC)) {
+ F_CLR(ep, F_RCV_ON | F_RCV_NORM);
+ msgq_str(sp, M_SYSERR,
+ ep->rcv_path, "060|File backup failed: %s");
+ SIGUNBLOCK;
+ return (1);
+ }
+ SIGUNBLOCK;
+
+ /* REQUEST: don't remove backing file on exit. */
+ if (LF_ISSET(RCV_PRESERVE))
+ F_SET(ep, F_RCV_NORM);
+
+ /* REQUEST: send email. */
+ if (LF_ISSET(RCV_EMAIL))
+ rcv_email(sp, ep->rcv_mpath);
+ }
+
+ /*
+ * !!!
+ * Each time the user exec's :preserve, we have to snapshot all of
+ * the recovery information, i.e. it's like the user re-edited the
+ * file. We copy the DB(3) backing file, and then create a new mail
+ * recovery file, it's simpler than exiting and reopening all of the
+ * underlying files.
+ *
+ * REQUEST: snapshot the file.
+ */
+ rval = 0;
+ if (LF_ISSET(RCV_SNAPSHOT)) {
+ if (opts_empty(sp, O_RECDIR, 0))
+ goto err;
+ dp = O_STR(sp, O_RECDIR);
+ (void)snprintf(buf, sizeof(buf), "%s/vi.XXXXXX", dp);
+ if ((fd = rcv_mktemp(sp, buf, dp, S_IRUSR | S_IWUSR)) == -1)
+ goto err;
+ sp->gp->scr_busy(sp,
+ "061|Copying file for recovery...", BUSY_ON);
+ if (rcv_copy(sp, fd, ep->rcv_path) ||
+ close(fd) || rcv_mailfile(sp, 1, buf)) {
+ (void)unlink(buf);
+ (void)close(fd);
+ rval = 1;
+ }
+ sp->gp->scr_busy(sp, NULL, BUSY_OFF);
+ }
+ if (0) {
+err: rval = 1;
+ }
+
+ /* REQUEST: end the file session. */
+ if (LF_ISSET(RCV_ENDSESSION) && file_end(sp, NULL, 1))
+ rval = 1;
+
+ return (rval);
+}
+
+/*
+ * rcv_mailfile --
+ * Build the file to mail to the user.
+ */
+static int
+rcv_mailfile(sp, issync, cp_path)
+ SCR *sp;
+ int issync;
+ char *cp_path;
+{
+ EXF *ep;
+ GS *gp;
+ struct passwd *pw;
+ size_t len;
+ time_t now;
+ uid_t uid;
+ int fd;
+ char *dp, *p, *t, buf[4096], mpath[MAXPATHLEN];
+ char *t1, *t2, *t3;
+
+ /*
+ * XXX
+ * MAXHOSTNAMELEN is in various places on various systems, including
+ * <netdb.h> and <sys/socket.h>. If not found, use a large default.
+ */
+#ifndef MAXHOSTNAMELEN
+#define MAXHOSTNAMELEN 1024
+#endif
+ char host[MAXHOSTNAMELEN];
+
+ gp = sp->gp;
+ if ((pw = getpwuid(uid = getuid())) == NULL) {
+ msgq(sp, M_ERR,
+ "062|Information on user id %u not found", uid);
+ return (1);
+ }
+
+ if (opts_empty(sp, O_RECDIR, 0))
+ return (1);
+ dp = O_STR(sp, O_RECDIR);
+ (void)snprintf(mpath, sizeof(mpath), "%s/recover.XXXXXX", dp);
+ if ((fd = rcv_mktemp(sp, mpath, dp, S_IRUSR | S_IWUSR)) == -1)
+ return (1);
+
+ /*
+ * XXX
+ * We keep an open lock on the file so that the recover option can
+ * distinguish between files that are live and those that need to
+ * be recovered. There's an obvious window between the mkstemp call
+ * and the lock, but it's pretty small.
+ */
+ ep = sp->ep;
+ if (file_lock(sp, NULL, NULL, fd, 1) != LOCK_SUCCESS)
+ msgq(sp, M_SYSERR, "063|Unable to lock recovery file");
+ if (!issync) {
+ /* Save the recover file descriptor, and mail path. */
+ ep->rcv_fd = fd;
+ if ((ep->rcv_mpath = strdup(mpath)) == NULL) {
+ msgq(sp, M_SYSERR, NULL);
+ goto err;
+ }
+ cp_path = ep->rcv_path;
+ }
+
+ /*
+ * XXX
+ * We can't use stdio(3) here. The problem is that we may be using
+ * fcntl(2), so if ANY file descriptor into the file is closed, the
+ * lock is lost. So, we could never close the FILE *, even if we
+ * dup'd the fd first.
+ */
+ t = sp->frp->name;
+ if ((p = strrchr(t, '/')) == NULL)
+ p = t;
+ else
+ ++p;
+ (void)time(&now);
+ (void)gethostname(host, sizeof(host));
+ len = snprintf(buf, sizeof(buf),
+ "%s%s\n%s%s\n%s\n%s\n%s%s\n%s%s\n%s\n\n",
+ VI_FHEADER, t, /* Non-standard. */
+ VI_PHEADER, cp_path, /* Non-standard. */
+ "Reply-To: root",
+ "From: root (Nvi recovery program)",
+ "To: ", pw->pw_name,
+ "Subject: Nvi saved the file ", p,
+ "Precedence: bulk"); /* For vacation(1). */
+ if (len > sizeof(buf) - 1)
+ goto lerr;
+ if (write(fd, buf, len) != len)
+ goto werr;
+
+ len = snprintf(buf, sizeof(buf),
+ "%s%.24s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n\n",
+ "On ", ctime(&now), ", the user ", pw->pw_name,
+ " was editing a file named ", t, " on the machine ",
+ host, ", when it was saved for recovery. ",
+ "You can recover most, if not all, of the changes ",
+ "to this file using the -r option to ", gp->progname, ":\n\n\t",
+ gp->progname, " -r ", t);
+ if (len > sizeof(buf) - 1) {
+lerr: msgq(sp, M_ERR, "064|Recovery file buffer overrun");
+ goto err;
+ }
+
+ /*
+ * Format the message. (Yes, I know it's silly.)
+ * Requires that the message end in a <newline>.
+ */
+#define FMTCOLS 60
+ for (t1 = buf; len > 0; len -= t2 - t1, t1 = t2) {
+ /* Check for a short length. */
+ if (len <= FMTCOLS) {
+ t2 = t1 + (len - 1);
+ goto wout;
+ }
+
+ /* Check for a required <newline>. */
+ t2 = strchr(t1, '\n');
+ if (t2 - t1 <= FMTCOLS)
+ goto wout;
+
+ /* Find the closest space, if any. */
+ for (t3 = t2; t2 > t1; --t2)
+ if (*t2 == ' ') {
+ if (t2 - t1 <= FMTCOLS)
+ goto wout;
+ t3 = t2;
+ }
+ t2 = t3;
+
+ /* t2 points to the last character to display. */
+wout: *t2++ = '\n';
+
+ /* t2 points one after the last character to display. */
+ if (write(fd, t1, t2 - t1) != t2 - t1)
+ goto werr;
+ }
+
+ if (issync) {
+ rcv_email(sp, mpath);
+ if (close(fd)) {
+werr: msgq(sp, M_SYSERR, "065|Recovery file");
+ goto err;
+ }
+ }
+ return (0);
+
+err: if (!issync)
+ ep->rcv_fd = -1;
+ if (fd != -1)
+ (void)close(fd);
+ return (1);
+}
+
+/*
+ * people making love
+ * never exactly the same
+ * just like a snowflake
+ *
+ * rcv_list --
+ * List the files that can be recovered by this user.
+ *
+ * PUBLIC: int rcv_list __P((SCR *));
+ */
+int
+rcv_list(sp)
+ SCR *sp;
+{
+ struct dirent *dp;
+ struct stat sb;
+ DIR *dirp;
+ FILE *fp;
+ int found;
+ char *p, *t, file[MAXPATHLEN], path[MAXPATHLEN];
+
+ /* Open the recovery directory for reading. */
+ if (opts_empty(sp, O_RECDIR, 0))
+ return (1);
+ p = O_STR(sp, O_RECDIR);
+ if (chdir(p) || (dirp = opendir(".")) == NULL) {
+ msgq_str(sp, M_SYSERR, p, "recdir: %s");
+ return (1);
+ }
+
+ /* Read the directory. */
+ for (found = 0; (dp = readdir(dirp)) != NULL;) {
+ if (strncmp(dp->d_name, "recover.", 8))
+ continue;
+
+ /*
+ * If it's readable, it's recoverable.
+ *
+ * XXX
+ * Should be "r", we don't want to write the file. However,
+ * if we're using fcntl(2), there's no way to lock a file
+ * descriptor that's not open for writing.
+ */
+ if ((fp = fopen(dp->d_name, "r+")) == NULL)
+ continue;
+
+ switch (file_lock(sp, NULL, NULL, fileno(fp), 1)) {
+ case LOCK_FAILED:
+ /*
+ * XXX
+ * Assume that a lock can't be acquired, but that we
+ * should permit recovery anyway. If this is wrong,
+ * and someone else is using the file, we're going to
+ * die horribly.
+ */
+ break;
+ case LOCK_SUCCESS:
+ break;
+ case LOCK_UNAVAIL:
+ /* If it's locked, it's live. */
+ (void)fclose(fp);
+ continue;
+ }
+
+ /* Check the headers. */
+ if (fgets(file, sizeof(file), fp) == NULL ||
+ strncmp(file, VI_FHEADER, sizeof(VI_FHEADER) - 1) ||
+ (p = strchr(file, '\n')) == NULL ||
+ fgets(path, sizeof(path), fp) == NULL ||
+ strncmp(path, VI_PHEADER, sizeof(VI_PHEADER) - 1) ||
+ (t = strchr(path, '\n')) == NULL) {
+ msgq_str(sp, M_ERR, dp->d_name,
+ "066|%s: malformed recovery file");
+ goto next;
+ }
+ *p = *t = '\0';
+
+ /*
+ * If the file doesn't exist, it's an orphaned recovery file,
+ * toss it.
+ *
+ * XXX
+ * This can occur if the backup file was deleted and we crashed
+ * before deleting the email file.
+ */
+ errno = 0;
+ if (stat(path + sizeof(VI_PHEADER) - 1, &sb) &&
+ errno == ENOENT) {
+ (void)unlink(dp->d_name);
+ goto next;
+ }
+
+ /* Get the last modification time and display. */
+ (void)fstat(fileno(fp), &sb);
+ (void)printf("%.24s: %s\n",
+ ctime(&sb.st_mtime), file + sizeof(VI_FHEADER) - 1);
+ found = 1;
+
+ /* Close, discarding lock. */
+next: (void)fclose(fp);
+ }
+ if (found == 0)
+ (void)printf("vi: no files to recover.\n");
+ (void)closedir(dirp);
+ return (0);
+}
+
+/*
+ * rcv_read --
+ * Start a recovered file as the file to edit.
+ *
+ * PUBLIC: int rcv_read __P((SCR *, FREF *));
+ */
+int
+rcv_read(sp, frp)
+ SCR *sp;
+ FREF *frp;
+{
+ struct dirent *dp;
+ struct stat sb;
+ DIR *dirp;
+ EXF *ep;
+ time_t rec_mtime;
+ int fd, found, locked, requested, sv_fd;
+ char *name, *p, *t, *rp, *recp, *pathp;
+ char file[MAXPATHLEN], path[MAXPATHLEN], recpath[MAXPATHLEN];
+
+ if (opts_empty(sp, O_RECDIR, 0))
+ return (1);
+ rp = O_STR(sp, O_RECDIR);
+ if ((dirp = opendir(rp)) == NULL) {
+ msgq_str(sp, M_ERR, rp, "%s");
+ return (1);
+ }
+
+ name = frp->name;
+ sv_fd = -1;
+ rec_mtime = 0;
+ recp = pathp = NULL;
+ for (found = requested = 0; (dp = readdir(dirp)) != NULL;) {
+ if (strncmp(dp->d_name, "recover.", 8))
+ continue;
+ (void)snprintf(recpath,
+ sizeof(recpath), "%s/%s", rp, dp->d_name);
+
+ /*
+ * If it's readable, it's recoverable. It would be very
+ * nice to use stdio(3), but, we can't because that would
+ * require closing and then reopening the file so that we
+ * could have a lock and still close the FP. Another tip
+ * of the hat to fcntl(2).
+ *
+ * XXX
+ * Should be O_RDONLY, we don't want to write it. However,
+ * if we're using fcntl(2), there's no way to lock a file
+ * descriptor that's not open for writing.
+ */
+ if ((fd = open(recpath, O_RDWR, 0)) == -1)
+ continue;
+
+ switch (file_lock(sp, NULL, NULL, fd, 1)) {
+ case LOCK_FAILED:
+ /*
+ * XXX
+ * Assume that a lock can't be acquired, but that we
+ * should permit recovery anyway. If this is wrong,
+ * and someone else is using the file, we're going to
+ * die horribly.
+ */
+ locked = 0;
+ break;
+ case LOCK_SUCCESS:
+ locked = 1;
+ break;
+ case LOCK_UNAVAIL:
+ /* If it's locked, it's live. */
+ (void)close(fd);
+ continue;
+ }
+
+ /* Check the headers. */
+ if (rcv_gets(file, sizeof(file), fd) == NULL ||
+ strncmp(file, VI_FHEADER, sizeof(VI_FHEADER) - 1) ||
+ (p = strchr(file, '\n')) == NULL ||
+ rcv_gets(path, sizeof(path), fd) == NULL ||
+ strncmp(path, VI_PHEADER, sizeof(VI_PHEADER) - 1) ||
+ (t = strchr(path, '\n')) == NULL) {
+ msgq_str(sp, M_ERR, recpath,
+ "067|%s: malformed recovery file");
+ goto next;
+ }
+ *p = *t = '\0';
+ ++found;
+
+ /*
+ * If the file doesn't exist, it's an orphaned recovery file,
+ * toss it.
+ *
+ * XXX
+ * This can occur if the backup file was deleted and we crashed
+ * before deleting the email file.
+ */
+ errno = 0;
+ if (stat(path + sizeof(VI_PHEADER) - 1, &sb) &&
+ errno == ENOENT) {
+ (void)unlink(dp->d_name);
+ goto next;
+ }
+
+ /* Check the file name. */
+ if (strcmp(file + sizeof(VI_FHEADER) - 1, name))
+ goto next;
+
+ ++requested;
+
+ /*
+ * If we've found more than one, take the most recent.
+ *
+ * XXX
+ * Since we're using st_mtime, for portability reasons,
+ * we only get a single second granularity, instead of
+ * getting it right.
+ */
+ (void)fstat(fd, &sb);
+ if (recp == NULL || rec_mtime < sb.st_mtime) {
+ p = recp;
+ t = pathp;
+ if ((recp = strdup(recpath)) == NULL) {
+ msgq(sp, M_SYSERR, NULL);
+ recp = p;
+ goto next;
+ }
+ if ((pathp = strdup(path)) == NULL) {
+ msgq(sp, M_SYSERR, NULL);
+ free(recp);
+ recp = p;
+ pathp = t;
+ goto next;
+ }
+ if (p != NULL) {
+ free(p);
+ free(t);
+ }
+ rec_mtime = sb.st_mtime;
+ if (sv_fd != -1)
+ (void)close(sv_fd);
+ sv_fd = fd;
+ } else
+next: (void)close(fd);
+ }
+ (void)closedir(dirp);
+
+ if (recp == NULL) {
+ msgq_str(sp, M_INFO, name,
+ "068|No files named %s, readable by you, to recover");
+ return (1);
+ }
+ if (found) {
+ if (requested > 1)
+ msgq(sp, M_INFO,
+ "069|There are older versions of this file for you to recover");
+ if (found > requested)
+ msgq(sp, M_INFO,
+ "070|There are other files for you to recover");
+ }
+
+ /*
+ * Create the FREF structure, start the btree file.
+ *
+ * XXX
+ * file_init() is going to set ep->rcv_path.
+ */
+ if (file_init(sp, frp, pathp + sizeof(VI_PHEADER) - 1, 0)) {
+ free(recp);
+ free(pathp);
+ (void)close(sv_fd);
+ return (1);
+ }
+
+ /*
+ * We keep an open lock on the file so that the recover option can
+ * distinguish between files that are live and those that need to
+ * be recovered. The lock is already acquired, just copy it.
+ */
+ ep = sp->ep;
+ ep->rcv_mpath = recp;
+ ep->rcv_fd = sv_fd;
+ if (!locked)
+ F_SET(frp, FR_UNLOCKED);
+
+ /* We believe the file is recoverable. */
+ F_SET(ep, F_RCV_ON);
+ return (0);
+}
+
+/*
+ * rcv_copy --
+ * Copy a recovery file.
+ */
+static int
+rcv_copy(sp, wfd, fname)
+ SCR *sp;
+ int wfd;
+ char *fname;
+{
+ int nr, nw, off, rfd;
+ char buf[8 * 1024];
+
+ if ((rfd = open(fname, O_RDONLY, 0)) == -1)
+ goto err;
+ while ((nr = read(rfd, buf, sizeof(buf))) > 0)
+ for (off = 0; nr; nr -= nw, off += nw)
+ if ((nw = write(wfd, buf + off, nr)) < 0)
+ goto err;
+ if (nr == 0)
+ return (0);
+
+err: msgq_str(sp, M_SYSERR, fname, "%s");
+ return (1);
+}
+
+/*
+ * rcv_gets --
+ * Fgets(3) for a file descriptor.
+ */
+static char *
+rcv_gets(buf, len, fd)
+ char *buf;
+ size_t len;
+ int fd;
+{
+ int nr;
+ char *p;
+
+ if ((nr = read(fd, buf, len - 1)) == -1)
+ return (NULL);
+ if ((p = strchr(buf, '\n')) == NULL)
+ return (NULL);
+ (void)lseek(fd, (off_t)((p - buf) + 1), SEEK_SET);
+ return (buf);
+}
+
+/*
+ * rcv_mktemp --
+ * Paranoid make temporary file routine.
+ */
+static int
+rcv_mktemp(sp, path, dname, perms)
+ SCR *sp;
+ char *path, *dname;
+ int perms;
+{
+ int fd;
+
+ /*
+ * !!!
+ * We expect mkstemp(3) to set the permissions correctly. On
+ * historic System V systems, mkstemp didn't. Do it here, on
+ * GP's.
+ *
+ * XXX
+ * The variable perms should really be a mode_t, and it would
+ * be nice to use fchmod(2) instead of chmod(2), here.
+ */
+ if ((fd = mkstemp(path)) == -1)
+ msgq_str(sp, M_SYSERR, dname, "%s");
+ else
+ (void)chmod(path, perms);
+ return (fd);
+}
+
+/*
+ * rcv_email --
+ * Send email.
+ */
+static void
+rcv_email(sp, fname)
+ SCR *sp;
+ char *fname;
+{
+ struct stat sb;
+ char buf[MAXPATHLEN * 2 + 20];
+
+ if (_PATH_SENDMAIL[0] != '/' || stat(_PATH_SENDMAIL, &sb))
+ msgq_str(sp, M_SYSERR,
+ _PATH_SENDMAIL, "071|not sending email: %s");
+ else {
+ /*
+ * !!!
+ * If you need to port this to a system that doesn't have
+ * sendmail, the -t flag causes sendmail to read the message
+ * for the recipients instead of specifying them some other
+ * way.
+ */
+ (void)snprintf(buf, sizeof(buf),
+ "%s -t < %s", _PATH_SENDMAIL, fname);
+ (void)system(buf);
+ }
+}
diff --git a/common/screen.c b/common/screen.c
new file mode 100644
index 000000000000..ba9e287b648b
--- /dev/null
+++ b/common/screen.c
@@ -0,0 +1,233 @@
+/*-
+ * Copyright (c) 1993, 1994
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 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[] = "@(#)screen.c 10.15 (Berkeley) 9/15/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+#include <sys/time.h>
+
+#include <bitstring.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "common.h"
+#include "../vi/vi.h"
+
+/*
+ * screen_init --
+ * Do the default initialization of an SCR structure.
+ *
+ * PUBLIC: int screen_init __P((GS *, SCR *, SCR **));
+ */
+int
+screen_init(gp, orig, spp)
+ GS *gp;
+ SCR *orig, **spp;
+{
+ SCR *sp;
+ size_t len;
+
+ *spp = NULL;
+ CALLOC_RET(orig, sp, SCR *, 1, sizeof(SCR));
+ *spp = sp;
+
+/* INITIALIZED AT SCREEN CREATE. */
+ sp->id = ++gp->id;
+ sp->refcnt = 1;
+
+ sp->gp = gp; /* All ref the GS structure. */
+
+ sp->ccnt = 2; /* Anything > 1 */
+
+ /*
+ * XXX
+ * sp->defscroll is initialized by the opts_init() code because
+ * we don't have the option information yet.
+ */
+
+ CIRCLEQ_INIT(&sp->tiq);
+
+/* PARTIALLY OR COMPLETELY COPIED FROM PREVIOUS SCREEN. */
+ if (orig == NULL) {
+ sp->searchdir = NOTSET;
+ } else {
+ /* Alternate file name. */
+ if (orig->alt_name != NULL &&
+ (sp->alt_name = strdup(orig->alt_name)) == NULL)
+ goto mem;
+
+ /* Last executed at buffer. */
+ if (F_ISSET(orig, SC_AT_SET)) {
+ F_SET(sp, SC_AT_SET);
+ sp->at_lbuf = orig->at_lbuf;
+ }
+
+ /* Retain searching/substitution information. */
+ sp->searchdir = orig->searchdir == NOTSET ? NOTSET : FORWARD;
+ if (orig->re != NULL && (sp->re =
+ v_strdup(sp, orig->re, orig->re_len)) == NULL)
+ goto mem;
+ sp->re_len = orig->re_len;
+ if (orig->subre != NULL && (sp->subre =
+ v_strdup(sp, orig->subre, orig->subre_len)) == NULL)
+ goto mem;
+ sp->subre_len = orig->subre_len;
+ if (orig->repl != NULL && (sp->repl =
+ v_strdup(sp, orig->repl, orig->repl_len)) == NULL)
+ goto mem;
+ sp->repl_len = orig->repl_len;
+ if (orig->newl_len) {
+ len = orig->newl_len * sizeof(size_t);
+ MALLOC(sp, sp->newl, size_t *, len);
+ if (sp->newl == NULL) {
+mem: msgq(orig, M_SYSERR, NULL);
+ goto err;
+ }
+ sp->newl_len = orig->newl_len;
+ sp->newl_cnt = orig->newl_cnt;
+ memcpy(sp->newl, orig->newl, len);
+ }
+
+ if (opts_copy(orig, sp))
+ goto err;
+
+ F_SET(sp, F_ISSET(orig, SC_EX | SC_VI));
+ }
+
+ if (ex_screen_copy(orig, sp)) /* Ex. */
+ goto err;
+ if (v_screen_copy(orig, sp)) /* Vi. */
+ goto err;
+
+ *spp = sp;
+ return (0);
+
+err: screen_end(sp);
+ return (1);
+}
+
+/*
+ * screen_end --
+ * Release a screen, no matter what had (and had not) been
+ * initialized.
+ *
+ * PUBLIC: int screen_end __P((SCR *));
+ */
+int
+screen_end(sp)
+ SCR *sp;
+{
+ int rval;
+
+ /* If multiply referenced, just decrement the count and return. */
+ if (--sp->refcnt != 0)
+ return (0);
+
+ /*
+ * Remove the screen from the displayed queue.
+ *
+ * If a created screen failed during initialization, it may not
+ * be linked into the chain.
+ */
+ if (sp->q.cqe_next != NULL)
+ CIRCLEQ_REMOVE(&sp->gp->dq, sp, q);
+
+ /* The screen is no longer real. */
+ F_CLR(sp, SC_SCR_EX | SC_SCR_VI);
+
+ rval = 0;
+#ifdef HAVE_PERL_INTERP
+ if (perl_screen_end(sp)) /* End perl. */
+ rval = 1;
+#endif
+ if (v_screen_end(sp)) /* End vi. */
+ rval = 1;
+ if (ex_screen_end(sp)) /* End ex. */
+ rval = 1;
+
+ /* Free file names. */
+ { char **ap;
+ if (!F_ISSET(sp, SC_ARGNOFREE) && sp->argv != NULL) {
+ for (ap = sp->argv; *ap != NULL; ++ap)
+ free(*ap);
+ free(sp->argv);
+ }
+ }
+
+ /* Free any text input. */
+ if (sp->tiq.cqh_first != NULL)
+ text_lfree(&sp->tiq);
+
+ /* Free alternate file name. */
+ if (sp->alt_name != NULL)
+ free(sp->alt_name);
+
+ /* Free up search information. */
+ if (sp->re != NULL)
+ free(sp->re);
+ if (F_ISSET(sp, SC_RE_SEARCH))
+ regfree(&sp->re_c);
+ if (sp->subre != NULL)
+ free(sp->subre);
+ if (F_ISSET(sp, SC_RE_SUBST))
+ regfree(&sp->subre_c);
+ if (sp->repl != NULL)
+ free(sp->repl);
+ if (sp->newl != NULL)
+ free(sp->newl);
+
+ /* Free all the options */
+ opts_free(sp);
+
+ /* Free the screen itself. */
+ free(sp);
+
+ return (rval);
+}
+
+/*
+ * screen_next --
+ * Return the next screen in the queue.
+ *
+ * PUBLIC: SCR *screen_next __P((SCR *));
+ */
+SCR *
+screen_next(sp)
+ SCR *sp;
+{
+ GS *gp;
+ SCR *next;
+
+ /* Try the display queue, without returning the current screen. */
+ gp = sp->gp;
+ for (next = gp->dq.cqh_first;
+ next != (void *)&gp->dq; next = next->q.cqe_next)
+ if (next != sp)
+ break;
+ if (next != (void *)&gp->dq)
+ return (next);
+
+ /* Try the hidden queue; if found, move screen to the display queue. */
+ if (gp->hq.cqh_first != (void *)&gp->hq) {
+ next = gp->hq.cqh_first;
+ CIRCLEQ_REMOVE(&gp->hq, next, q);
+ CIRCLEQ_INSERT_HEAD(&gp->dq, next, q);
+ return (next);
+ }
+ return (NULL);
+}
diff --git a/common/screen.h b/common/screen.h
new file mode 100644
index 000000000000..bb7254f62a21
--- /dev/null
+++ b/common/screen.h
@@ -0,0 +1,203 @@
+/*-
+ * 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.
+ *
+ * @(#)screen.h 10.24 (Berkeley) 7/19/96
+ */
+
+/*
+ * There are minimum values that vi has to have to display a screen. The row
+ * minimum is fixed at 1 (the svi code can share a line between the text line
+ * and the colon command/message line). Column calculation is a lot trickier.
+ * For example, you have to have enough columns to display the line number,
+ * not to mention guaranteeing that tabstop and shiftwidth values are smaller
+ * than the current column value. It's simpler to have a fixed value and not
+ * worry about it.
+ *
+ * XXX
+ * MINIMUM_SCREEN_COLS is almost certainly wrong.
+ */
+#define MINIMUM_SCREEN_ROWS 1
+#define MINIMUM_SCREEN_COLS 20
+
+/*
+ * SCR --
+ * The screen structure. To the extent possible, all screen information
+ * is stored in the various private areas. The only information here
+ * is used by global routines or is shared by too many screens.
+ */
+struct _scr {
+/* INITIALIZED AT SCREEN CREATE. */
+ CIRCLEQ_ENTRY(_scr) q; /* Screens. */
+
+ int id; /* Screen id #. */
+ int refcnt; /* Reference count. */
+
+ GS *gp; /* Pointer to global area. */
+ SCR *nextdisp; /* Next display screen. */
+ SCR *ccl_parent; /* Colon command-line parent screen. */
+ EXF *ep; /* Screen's current EXF structure. */
+
+ FREF *frp; /* FREF being edited. */
+ char **argv; /* NULL terminated file name array. */
+ char **cargv; /* Current file name. */
+
+ u_long ccnt; /* Command count. */
+ u_long q_ccnt; /* Quit or ZZ command count. */
+
+ /* Screen's: */
+ size_t rows; /* 1-N: number of rows. */
+ size_t cols; /* 1-N: number of columns. */
+ size_t t_rows; /* 1-N: cur number of text rows. */
+ size_t t_maxrows; /* 1-N: max number of text rows. */
+ size_t t_minrows; /* 1-N: min number of text rows. */
+ size_t woff; /* 0-N: screen offset in frame. */
+
+ /* Cursor's: */
+ recno_t lno; /* 1-N: file line. */
+ size_t cno; /* 0-N: file character in line. */
+
+ size_t rcm; /* Vi: 0-N: Most attractive column. */
+
+#define L_ADDED 0 /* Added lines. */
+#define L_CHANGED 1 /* Changed lines. */
+#define L_DELETED 2 /* Deleted lines. */
+#define L_JOINED 3 /* Joined lines. */
+#define L_MOVED 4 /* Moved lines. */
+#define L_SHIFT 5 /* Shift lines. */
+#define L_YANKED 6 /* Yanked lines. */
+ recno_t rptlchange; /* Ex/vi: last L_CHANGED lno. */
+ recno_t rptlines[L_YANKED + 1];/* Ex/vi: lines changed by last op. */
+
+ TEXTH tiq; /* Ex/vi: text input queue. */
+
+ SCRIPT *script; /* Vi: script mode information .*/
+
+ recno_t defscroll; /* Vi: ^D, ^U scroll information. */
+
+ /* Display character. */
+ CHAR_T cname[MAX_CHARACTER_COLUMNS + 1];
+ size_t clen; /* Length of display character. */
+
+ enum { /* Vi editor mode. */
+ SM_APPEND = 0, SM_CHANGE, SM_COMMAND, SM_INSERT,
+ SM_REPLACE } showmode;
+
+ void *ex_private; /* Ex private area. */
+ void *vi_private; /* Vi private area. */
+ void *perl_private; /* Perl private area. */
+
+/* PARTIALLY OR COMPLETELY COPIED FROM PREVIOUS SCREEN. */
+ char *alt_name; /* Ex/vi: alternate file name. */
+
+ CHAR_T at_lbuf; /* Ex/vi: Last executed at buffer. */
+
+ /* Ex/vi: re_compile flags. */
+#define RE_C_CSCOPE 0x0001 /* Compile cscope pattern. */
+#define RE_C_SEARCH 0x0002 /* Compile search replacement. */
+#define RE_C_SILENT 0x0004 /* No error messages. */
+#define RE_C_SUBST 0x0008 /* Compile substitute replacement. */
+#define RE_C_TAG 0x0010 /* Compile ctag pattern. */
+
+#define RE_WSTART "[[:<:]]" /* Ex/vi: not-in-word search pattern. */
+#define RE_WSTOP "[[:>:]]"
+ /* Ex/vi: flags to search routines. */
+#define SEARCH_CSCOPE 0x0001 /* Search for a cscope pattern. */
+#define SEARCH_EOL 0x0002 /* Offset past EOL is okay. */
+#define SEARCH_FILE 0x0004 /* Search the entire file. */
+#define SEARCH_INCR 0x0008 /* Search incrementally. */
+#define SEARCH_MSG 0x0010 /* Display search messages. */
+#define SEARCH_PARSE 0x0020 /* Parse the search pattern. */
+#define SEARCH_SET 0x0040 /* Set search direction. */
+#define SEARCH_TAG 0x0080 /* Search for a tag pattern. */
+#define SEARCH_WMSG 0x0100 /* Display search-wrapped messages. */
+
+ /* Ex/vi: RE information. */
+ dir_t searchdir; /* Last file search direction. */
+ regex_t re_c; /* Search RE: compiled form. */
+ char *re; /* Search RE: uncompiled form. */
+ size_t re_len; /* Search RE: uncompiled length. */
+ regex_t subre_c; /* Substitute RE: compiled form. */
+ char *subre; /* Substitute RE: uncompiled form. */
+ size_t subre_len; /* Substitute RE: uncompiled length). */
+ char *repl; /* Substitute replacement. */
+ size_t repl_len; /* Substitute replacement length.*/
+ size_t *newl; /* Newline offset array. */
+ size_t newl_len; /* Newline array size. */
+ size_t newl_cnt; /* Newlines in replacement. */
+ u_int8_t c_suffix; /* Edcompatible 'c' suffix value. */
+ u_int8_t g_suffix; /* Edcompatible 'g' suffix value. */
+
+ OPTION opts[O_OPTIONCOUNT]; /* Ex/vi: Options. */
+
+/*
+ * Screen flags.
+ *
+ * Editor screens.
+ */
+#define SC_EX 0x00000001 /* Ex editor. */
+#define SC_VI 0x00000002 /* Vi editor. */
+
+/*
+ * Screen formatting flags, first major, then minor.
+ *
+ * SC_SCR_EX
+ * Ex screen, i.e. cooked mode.
+ * SC_SCR_VI
+ * Vi screen, i.e. raw mode.
+ * SC_SCR_EXWROTE
+ * The editor had to write on the screen behind curses' back, and we can't
+ * let curses change anything until the user agrees, e.g. entering the
+ * commands :!utility followed by :set. We have to switch back into the
+ * vi "editor" to read the user's command input, but we can't touch the
+ * rest of the screen because it's known to be wrong.
+ * SC_SCR_REFORMAT
+ * The expected presentation of the lines on the screen have changed,
+ * requiring that the intended screen lines be recalculated. Implies
+ * SC_SCR_REDRAW.
+ * SC_SCR_REDRAW
+ * The screen doesn't correctly represent the file; repaint it. Note,
+ * setting SC_SCR_REDRAW in the current window causes *all* windows to
+ * be repainted.
+ * SC_SCR_CENTER
+ * If the current line isn't already on the screen, center it.
+ * SC_SCR_TOP
+ * If the current line isn't already on the screen, put it at the to@.
+ */
+#define SC_SCR_EX 0x00000004 /* Screen is in ex mode. */
+#define SC_SCR_VI 0x00000008 /* Screen is in vi mode. */
+#define SC_SCR_EXWROTE 0x00000010 /* Ex overwrite: see comment above. */
+#define SC_SCR_REFORMAT 0x00000020 /* Reformat (refresh). */
+#define SC_SCR_REDRAW 0x00000040 /* Refresh. */
+
+#define SC_SCR_CENTER 0x00000080 /* Center the line if not visible. */
+#define SC_SCR_TOP 0x00000100 /* Top the line if not visible. */
+
+/* Screen/file changes. */
+#define SC_EXIT 0x00000200 /* Exiting (not forced). */
+#define SC_EXIT_FORCE 0x00000400 /* Exiting (forced). */
+#define SC_FSWITCH 0x00000800 /* Switch underlying files. */
+#define SC_SSWITCH 0x00001000 /* Switch screens. */
+
+#define SC_ARGNOFREE 0x00002000 /* Argument list wasn't allocated. */
+#define SC_ARGRECOVER 0x00004000 /* Argument list is recovery files. */
+#define SC_AT_SET 0x00008000 /* Last at buffer set. */
+#define SC_COMEDIT 0x00010000 /* Colon command-line edit window. */
+#define SC_EX_GLOBAL 0x00020000 /* Ex: executing a global command. */
+#define SC_EX_SILENT 0x00040000 /* Ex: batch script. */
+#define SC_EX_WAIT_NO 0x00080000 /* Ex: don't wait for the user. */
+#define SC_EX_WAIT_YES 0x00100000 /* Ex: do wait for the user. */
+#define SC_READONLY 0x00200000 /* Persistent readonly state. */
+#define SC_RE_SEARCH 0x00400000 /* Search RE has been compiled. */
+#define SC_RE_SUBST 0x00800000 /* Substitute RE has been compiled. */
+#define SC_SCRIPT 0x01000000 /* Shell script window. */
+#define SC_STATUS 0x02000000 /* Welcome message. */
+#define SC_STATUS_CNT 0x04000000 /* Welcome message plus file count. */
+#define SC_TINPUT 0x08000000 /* Doing text input. */
+#define SC_TINPUT_INFO 0x10000000 /* Doing text input on info line. */
+ u_int32_t flags;
+};
diff --git a/common/search.c b/common/search.c
new file mode 100644
index 000000000000..3fd2719778fa
--- /dev/null
+++ b/common/search.c
@@ -0,0 +1,492 @@
+/*-
+ * 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
+static const char sccsid[] = "@(#)search.c 10.25 (Berkeley) 6/30/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+
+#include <bitstring.h>
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "common.h"
+
+typedef enum { S_EMPTY, S_EOF, S_NOPREV, S_NOTFOUND, S_SOF, S_WRAP } smsg_t;
+
+static void search_msg __P((SCR *, smsg_t));
+static int search_init __P((SCR *, dir_t, char *, size_t, char **, u_int));
+
+/*
+ * search_init --
+ * Set up a search.
+ */
+static int
+search_init(sp, dir, ptrn, plen, epp, flags)
+ SCR *sp;
+ dir_t dir;
+ char *ptrn, **epp;
+ size_t plen;
+ u_int flags;
+{
+ recno_t lno;
+ int delim;
+ char *p, *t;
+
+ /* If the file is empty, it's a fast search. */
+ if (sp->lno <= 1) {
+ if (db_last(sp, &lno))
+ return (1);
+ if (lno == 0) {
+ if (LF_ISSET(SEARCH_MSG))
+ search_msg(sp, S_EMPTY);
+ return (1);
+ }
+ }
+
+ if (LF_ISSET(SEARCH_PARSE)) { /* Parse the string. */
+ /*
+ * Use the saved pattern if no pattern specified, or if only
+ * one or two delimiter characters specified.
+ *
+ * !!!
+ * Historically, only the pattern itself was saved, vi didn't
+ * preserve addressing or delta information.
+ */
+ if (ptrn == NULL)
+ goto prev;
+ if (plen == 1) {
+ if (epp != NULL)
+ *epp = ptrn + 1;
+ goto prev;
+ }
+ if (ptrn[0] == ptrn[1]) {
+ if (epp != NULL)
+ *epp = ptrn + 2;
+
+ /* Complain if we don't have a previous pattern. */
+prev: if (sp->re == NULL) {
+ search_msg(sp, S_NOPREV);
+ return (1);
+ }
+ /* Re-compile the search pattern if necessary. */
+ if (!F_ISSET(sp, SC_RE_SEARCH) && re_compile(sp,
+ sp->re, sp->re_len, NULL, NULL, &sp->re_c,
+ RE_C_SEARCH |
+ (LF_ISSET(SEARCH_MSG) ? 0 : RE_C_SILENT)))
+ return (1);
+
+ /* Set the search direction. */
+ if (LF_ISSET(SEARCH_SET))
+ sp->searchdir = dir;
+ return (0);
+ }
+
+ /*
+ * Set the delimiter, and move forward to the terminating
+ * delimiter, handling escaped delimiters.
+ *
+ * QUOTING NOTE:
+ * Only discard an escape character if it escapes a delimiter.
+ */
+ for (delim = *ptrn, p = t = ++ptrn;; *t++ = *p++) {
+ if (--plen == 0 || p[0] == delim) {
+ if (plen != 0)
+ ++p;
+ break;
+ }
+ if (plen > 1 && p[0] == '\\' && p[1] == delim) {
+ ++p;
+ --plen;
+ }
+ }
+ if (epp != NULL)
+ *epp = p;
+
+ plen = t - ptrn;
+ }
+
+ /* Compile the RE. */
+ if (re_compile(sp, ptrn, plen, &sp->re, &sp->re_len, &sp->re_c,
+ RE_C_SEARCH |
+ (LF_ISSET(SEARCH_MSG) ? 0 : RE_C_SILENT) |
+ (LF_ISSET(SEARCH_TAG) ? RE_C_TAG : 0) |
+ (LF_ISSET(SEARCH_CSCOPE) ? RE_C_CSCOPE : 0)))
+ return (1);
+
+ /* Set the search direction. */
+ if (LF_ISSET(SEARCH_SET))
+ sp->searchdir = dir;
+
+ return (0);
+}
+
+/*
+ * f_search --
+ * Do a forward search.
+ *
+ * PUBLIC: int f_search __P((SCR *,
+ * PUBLIC: MARK *, MARK *, char *, size_t, char **, u_int));
+ */
+int
+f_search(sp, fm, rm, ptrn, plen, eptrn, flags)
+ SCR *sp;
+ MARK *fm, *rm;
+ char *ptrn, **eptrn;
+ size_t plen;
+ u_int flags;
+{
+ busy_t btype;
+ recno_t lno;
+ regmatch_t match[1];
+ size_t coff, len;
+ int cnt, eval, rval, wrapped;
+ char *l;
+
+ if (search_init(sp, FORWARD, ptrn, plen, eptrn, flags))
+ return (1);
+
+ if (LF_ISSET(SEARCH_FILE)) {
+ lno = 1;
+ coff = 0;
+ } else {
+ if (db_get(sp, fm->lno, DBG_FATAL, &l, &len))
+ return (1);
+ lno = fm->lno;
+
+ /*
+ * If doing incremental search, start searching at the previous
+ * column, so that we search a minimal distance and still match
+ * special patterns, e.g., \< for beginning of a word.
+ *
+ * Otherwise, start searching immediately after the cursor. If
+ * at the end of the line, start searching on the next line.
+ * This is incompatible (read bug fix) with the historic vi --
+ * searches for the '$' pattern never moved forward, and the
+ * "-t foo" didn't work if the 'f' was the first character in
+ * the file.
+ */
+ if (LF_ISSET(SEARCH_INCR)) {
+ if ((coff = fm->cno) != 0)
+ --coff;
+ } else if (fm->cno + 1 >= len) {
+ coff = 0;
+ lno = fm->lno + 1;
+ if (db_get(sp, lno, 0, &l, &len)) {
+ if (!O_ISSET(sp, O_WRAPSCAN)) {
+ if (LF_ISSET(SEARCH_MSG))
+ search_msg(sp, S_EOF);
+ return (1);
+ }
+ lno = 1;
+ }
+ } else
+ coff = fm->cno + 1;
+ }
+
+ btype = BUSY_ON;
+ for (cnt = INTERRUPT_CHECK, rval = 1, wrapped = 0;; ++lno, coff = 0) {
+ if (cnt-- == 0) {
+ if (INTERRUPTED(sp))
+ break;
+ if (LF_ISSET(SEARCH_MSG)) {
+ search_busy(sp, btype);
+ btype = BUSY_UPDATE;
+ }
+ cnt = INTERRUPT_CHECK;
+ }
+ if (wrapped && lno > fm->lno || db_get(sp, lno, 0, &l, &len)) {
+ if (wrapped) {
+ if (LF_ISSET(SEARCH_MSG))
+ search_msg(sp, S_NOTFOUND);
+ break;
+ }
+ if (!O_ISSET(sp, O_WRAPSCAN)) {
+ if (LF_ISSET(SEARCH_MSG))
+ search_msg(sp, S_EOF);
+ break;
+ }
+ lno = 0;
+ wrapped = 1;
+ continue;
+ }
+
+ /* If already at EOL, just keep going. */
+ if (len != 0 && coff == len)
+ continue;
+
+ /* Set the termination. */
+ match[0].rm_so = coff;
+ match[0].rm_eo = len;
+
+#if defined(DEBUG) && 0
+ TRACE(sp, "F search: %lu from %u to %u\n",
+ lno, coff, len != 0 ? len - 1 : len);
+#endif
+ /* Search the line. */
+ eval = regexec(&sp->re_c, l, 1, match,
+ (match[0].rm_so == 0 ? 0 : REG_NOTBOL) | REG_STARTEND);
+ if (eval == REG_NOMATCH)
+ continue;
+ if (eval != 0) {
+ if (LF_ISSET(SEARCH_MSG))
+ re_error(sp, eval, &sp->re_c);
+ else
+ (void)sp->gp->scr_bell(sp);
+ break;
+ }
+
+ /* Warn if the search wrapped. */
+ if (wrapped && LF_ISSET(SEARCH_WMSG))
+ search_msg(sp, S_WRAP);
+
+#if defined(DEBUG) && 0
+ TRACE(sp, "F search: %qu to %qu\n",
+ match[0].rm_so, match[0].rm_eo);
+#endif
+ rm->lno = lno;
+ rm->cno = match[0].rm_so;
+
+ /*
+ * If a change command, it's possible to move beyond the end
+ * of a line. Historic vi generally got this wrong (e.g. try
+ * "c?$<cr>"). Not all that sure this gets it right, there
+ * are lots of strange cases.
+ */
+ if (!LF_ISSET(SEARCH_EOL) && rm->cno >= len)
+ rm->cno = len != 0 ? len - 1 : 0;
+
+ rval = 0;
+ break;
+ }
+
+ if (LF_ISSET(SEARCH_MSG))
+ search_busy(sp, BUSY_OFF);
+ return (rval);
+}
+
+/*
+ * b_search --
+ * Do a backward search.
+ *
+ * PUBLIC: int b_search __P((SCR *,
+ * PUBLIC: MARK *, MARK *, char *, size_t, char **, u_int));
+ */
+int
+b_search(sp, fm, rm, ptrn, plen, eptrn, flags)
+ SCR *sp;
+ MARK *fm, *rm;
+ char *ptrn, **eptrn;
+ size_t plen;
+ u_int flags;
+{
+ busy_t btype;
+ recno_t lno;
+ regmatch_t match[1];
+ size_t coff, last, len;
+ int cnt, eval, rval, wrapped;
+ char *l;
+
+ if (search_init(sp, BACKWARD, ptrn, plen, eptrn, flags))
+ return (1);
+
+ /*
+ * If doing incremental search, set the "starting" position past the
+ * current column, so that we search a minimal distance and still
+ * match special patterns, e.g., \> for the end of a word. This is
+ * safe when the cursor is at the end of a line because we only use
+ * it for comparison with the location of the match.
+ *
+ * Otherwise, start searching immediately before the cursor. If in
+ * the first column, start search on the previous line.
+ */
+ if (LF_ISSET(SEARCH_INCR)) {
+ lno = fm->lno;
+ coff = fm->cno + 1;
+ } else {
+ if (fm->cno == 0) {
+ if (fm->lno == 1 && !O_ISSET(sp, O_WRAPSCAN)) {
+ if (LF_ISSET(SEARCH_MSG))
+ search_msg(sp, S_SOF);
+ return (1);
+ }
+ lno = fm->lno - 1;
+ } else
+ lno = fm->lno;
+ coff = fm->cno;
+ }
+
+ btype = BUSY_ON;
+ for (cnt = INTERRUPT_CHECK, rval = 1, wrapped = 0;; --lno, coff = 0) {
+ if (cnt-- == 0) {
+ if (INTERRUPTED(sp))
+ break;
+ if (LF_ISSET(SEARCH_MSG)) {
+ search_busy(sp, btype);
+ btype = BUSY_UPDATE;
+ }
+ cnt = INTERRUPT_CHECK;
+ }
+ if (wrapped && lno < fm->lno || lno == 0) {
+ if (wrapped) {
+ if (LF_ISSET(SEARCH_MSG))
+ search_msg(sp, S_NOTFOUND);
+ break;
+ }
+ if (!O_ISSET(sp, O_WRAPSCAN)) {
+ if (LF_ISSET(SEARCH_MSG))
+ search_msg(sp, S_SOF);
+ break;
+ }
+ if (db_last(sp, &lno))
+ break;
+ if (lno == 0) {
+ if (LF_ISSET(SEARCH_MSG))
+ search_msg(sp, S_EMPTY);
+ break;
+ }
+ ++lno;
+ wrapped = 1;
+ continue;
+ }
+
+ if (db_get(sp, lno, 0, &l, &len))
+ break;
+
+ /* Set the termination. */
+ match[0].rm_so = 0;
+ match[0].rm_eo = len;
+
+#if defined(DEBUG) && 0
+ TRACE(sp, "B search: %lu from 0 to %qu\n", lno, match[0].rm_eo);
+#endif
+ /* Search the line. */
+ eval = regexec(&sp->re_c, l, 1, match,
+ (match[0].rm_eo == len ? 0 : REG_NOTEOL) | REG_STARTEND);
+ if (eval == REG_NOMATCH)
+ continue;
+ if (eval != 0) {
+ if (LF_ISSET(SEARCH_MSG))
+ re_error(sp, eval, &sp->re_c);
+ else
+ (void)sp->gp->scr_bell(sp);
+ break;
+ }
+
+ /* Check for a match starting past the cursor. */
+ if (coff != 0 && match[0].rm_so >= coff)
+ continue;
+
+ /* Warn if the search wrapped. */
+ if (wrapped && LF_ISSET(SEARCH_WMSG))
+ search_msg(sp, S_WRAP);
+
+#if defined(DEBUG) && 0
+ TRACE(sp, "B found: %qu to %qu\n",
+ match[0].rm_so, match[0].rm_eo);
+#endif
+ /*
+ * We now have the first match on the line. Step through the
+ * line character by character until find the last acceptable
+ * match. This is painful, we need a better interface to regex
+ * to make this work.
+ */
+ for (;;) {
+ last = match[0].rm_so++;
+ if (match[0].rm_so >= len)
+ break;
+ match[0].rm_eo = len;
+ eval = regexec(&sp->re_c, l, 1, match,
+ (match[0].rm_so == 0 ? 0 : REG_NOTBOL) |
+ REG_STARTEND);
+ if (eval == REG_NOMATCH)
+ break;
+ if (eval != 0) {
+ if (LF_ISSET(SEARCH_MSG))
+ re_error(sp, eval, &sp->re_c);
+ else
+ (void)sp->gp->scr_bell(sp);
+ goto err;
+ }
+ if (coff && match[0].rm_so >= coff)
+ break;
+ }
+ rm->lno = lno;
+
+ /* See comment in f_search(). */
+ if (!LF_ISSET(SEARCH_EOL) && last >= len)
+ rm->cno = len != 0 ? len - 1 : 0;
+ else
+ rm->cno = last;
+ rval = 0;
+ break;
+ }
+
+err: if (LF_ISSET(SEARCH_MSG))
+ search_busy(sp, BUSY_OFF);
+ return (rval);
+}
+
+/*
+ * search_msg --
+ * Display one of the search messages.
+ */
+static void
+search_msg(sp, msg)
+ SCR *sp;
+ smsg_t msg;
+{
+ switch (msg) {
+ case S_EMPTY:
+ msgq(sp, M_ERR, "072|File empty; nothing to search");
+ break;
+ case S_EOF:
+ msgq(sp, M_ERR,
+ "073|Reached end-of-file without finding the pattern");
+ break;
+ case S_NOPREV:
+ msgq(sp, M_ERR, "074|No previous search pattern");
+ break;
+ case S_NOTFOUND:
+ msgq(sp, M_ERR, "075|Pattern not found");
+ break;
+ case S_SOF:
+ msgq(sp, M_ERR,
+ "076|Reached top-of-file without finding the pattern");
+ break;
+ case S_WRAP:
+ msgq(sp, M_ERR, "077|Search wrapped");
+ break;
+ default:
+ abort();
+ }
+}
+
+/*
+ * search_busy --
+ * Put up the busy searching message.
+ *
+ * PUBLIC: void search_busy __P((SCR *, busy_t));
+ */
+void
+search_busy(sp, btype)
+ SCR *sp;
+ busy_t btype;
+{
+ sp->gp->scr_busy(sp, "078|Searching...", btype);
+}
diff --git a/common/seq.c b/common/seq.c
new file mode 100644
index 000000000000..e2be879ab686
--- /dev/null
+++ b/common/seq.c
@@ -0,0 +1,395 @@
+/*-
+ * 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
+static const char sccsid[] = "@(#)seq.c 10.10 (Berkeley) 3/30/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+
+#include <bitstring.h>
+#include <ctype.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "common.h"
+
+/*
+ * seq_set --
+ * Internal version to enter a sequence.
+ *
+ * PUBLIC: int seq_set __P((SCR *, CHAR_T *,
+ * PUBLIC: size_t, CHAR_T *, size_t, CHAR_T *, size_t, seq_t, int));
+ */
+int
+seq_set(sp, name, nlen, input, ilen, output, olen, stype, flags)
+ SCR *sp;
+ CHAR_T *name, *input, *output;
+ size_t nlen, ilen, olen;
+ seq_t stype;
+ int flags;
+{
+ CHAR_T *p;
+ SEQ *lastqp, *qp;
+ int sv_errno;
+
+ /*
+ * An input string must always be present. The output string
+ * can be NULL, when set internally, that's how we throw away
+ * input.
+ *
+ * Just replace the output field if the string already set.
+ */
+ if ((qp =
+ seq_find(sp, &lastqp, NULL, input, ilen, stype, NULL)) != NULL) {
+ if (LF_ISSET(SEQ_NOOVERWRITE))
+ return (0);
+ if (output == NULL || olen == 0) {
+ p = NULL;
+ olen = 0;
+ } else if ((p = v_strdup(sp, output, olen)) == NULL) {
+ sv_errno = errno;
+ goto mem1;
+ }
+ if (qp->output != NULL)
+ free(qp->output);
+ qp->olen = olen;
+ qp->output = p;
+ return (0);
+ }
+
+ /* Allocate and initialize SEQ structure. */
+ CALLOC(sp, qp, SEQ *, 1, sizeof(SEQ));
+ if (qp == NULL) {
+ sv_errno = errno;
+ goto mem1;
+ }
+
+ /* Name. */
+ if (name == NULL || nlen == 0)
+ qp->name = NULL;
+ else if ((qp->name = v_strdup(sp, name, nlen)) == NULL) {
+ sv_errno = errno;
+ goto mem2;
+ }
+ qp->nlen = nlen;
+
+ /* Input. */
+ if ((qp->input = v_strdup(sp, input, ilen)) == NULL) {
+ sv_errno = errno;
+ goto mem3;
+ }
+ qp->ilen = ilen;
+
+ /* Output. */
+ if (output == NULL) {
+ qp->output = NULL;
+ olen = 0;
+ } else if ((qp->output = v_strdup(sp, output, olen)) == NULL) {
+ sv_errno = errno;
+ free(qp->input);
+mem3: if (qp->name != NULL)
+ free(qp->name);
+mem2: free(qp);
+mem1: errno = sv_errno;
+ msgq(sp, M_SYSERR, NULL);
+ return (1);
+ }
+ qp->olen = olen;
+
+ /* Type, flags. */
+ qp->stype = stype;
+ qp->flags = flags;
+
+ /* Link into the chain. */
+ if (lastqp == NULL) {
+ LIST_INSERT_HEAD(&sp->gp->seqq, qp, q);
+ } else {
+ LIST_INSERT_AFTER(lastqp, qp, q);
+ }
+
+ /* Set the fast lookup bit. */
+ if (qp->input[0] < MAX_BIT_SEQ)
+ bit_set(sp->gp->seqb, qp->input[0]);
+
+ return (0);
+}
+
+/*
+ * seq_delete --
+ * Delete a sequence.
+ *
+ * PUBLIC: int seq_delete __P((SCR *, CHAR_T *, size_t, seq_t));
+ */
+int
+seq_delete(sp, input, ilen, stype)
+ SCR *sp;
+ CHAR_T *input;
+ size_t ilen;
+ seq_t stype;
+{
+ SEQ *qp;
+
+ if ((qp = seq_find(sp, NULL, NULL, input, ilen, stype, NULL)) == NULL)
+ return (1);
+ return (seq_mdel(qp));
+}
+
+/*
+ * seq_mdel --
+ * Delete a map entry, without lookup.
+ *
+ * PUBLIC: int seq_mdel __P((SEQ *));
+ */
+int
+seq_mdel(qp)
+ SEQ *qp;
+{
+ LIST_REMOVE(qp, q);
+ if (qp->name != NULL)
+ free(qp->name);
+ free(qp->input);
+ if (qp->output != NULL)
+ free(qp->output);
+ free(qp);
+ return (0);
+}
+
+/*
+ * seq_find --
+ * Search the sequence list for a match to a buffer, if ispartial
+ * isn't NULL, partial matches count.
+ *
+ * PUBLIC: SEQ *seq_find
+ * PUBLIC: __P((SCR *, SEQ **, EVENT *, CHAR_T *, size_t, seq_t, int *));
+ */
+SEQ *
+seq_find(sp, lastqp, e_input, c_input, ilen, stype, ispartialp)
+ SCR *sp;
+ SEQ **lastqp;
+ EVENT *e_input;
+ CHAR_T *c_input;
+ size_t ilen;
+ seq_t stype;
+ int *ispartialp;
+{
+ SEQ *lqp, *qp;
+ int diff;
+
+ /*
+ * Ispartialp is a location where we return if there was a
+ * partial match, i.e. if the string were extended it might
+ * match something.
+ *
+ * XXX
+ * Overload the meaning of ispartialp; only the terminal key
+ * search doesn't want the search limited to complete matches,
+ * i.e. ilen may be longer than the match.
+ */
+ if (ispartialp != NULL)
+ *ispartialp = 0;
+ for (lqp = NULL, qp = sp->gp->seqq.lh_first;
+ qp != NULL; lqp = qp, qp = qp->q.le_next) {
+ /*
+ * Fast checks on the first character and type, and then
+ * a real comparison.
+ */
+ if (e_input == NULL) {
+ if (qp->input[0] > c_input[0])
+ break;
+ if (qp->input[0] < c_input[0] ||
+ qp->stype != stype || F_ISSET(qp, SEQ_FUNCMAP))
+ continue;
+ diff = memcmp(qp->input, c_input, MIN(qp->ilen, ilen));
+ } else {
+ if (qp->input[0] > e_input->e_c)
+ break;
+ if (qp->input[0] < e_input->e_c ||
+ qp->stype != stype || F_ISSET(qp, SEQ_FUNCMAP))
+ continue;
+ diff =
+ e_memcmp(qp->input, e_input, MIN(qp->ilen, ilen));
+ }
+ if (diff > 0)
+ break;
+ if (diff < 0)
+ continue;
+ /*
+ * If the entry is the same length as the string, return a
+ * match. If the entry is shorter than the string, return a
+ * match if called from the terminal key routine. Otherwise,
+ * keep searching for a complete match.
+ */
+ if (qp->ilen <= ilen) {
+ if (qp->ilen == ilen || ispartialp != NULL) {
+ if (lastqp != NULL)
+ *lastqp = lqp;
+ return (qp);
+ }
+ continue;
+ }
+ /*
+ * If the entry longer than the string, return partial match
+ * if called from the terminal key routine. Otherwise, no
+ * match.
+ */
+ if (ispartialp != NULL)
+ *ispartialp = 1;
+ break;
+ }
+ if (lastqp != NULL)
+ *lastqp = lqp;
+ return (NULL);
+}
+
+/*
+ * seq_close --
+ * Discard all sequences.
+ *
+ * PUBLIC: void seq_close __P((GS *));
+ */
+void
+seq_close(gp)
+ GS *gp;
+{
+ SEQ *qp;
+
+ while ((qp = gp->seqq.lh_first) != NULL) {
+ if (qp->name != NULL)
+ free(qp->name);
+ if (qp->input != NULL)
+ free(qp->input);
+ if (qp->output != NULL)
+ free(qp->output);
+ LIST_REMOVE(qp, q);
+ free(qp);
+ }
+}
+
+/*
+ * seq_dump --
+ * Display the sequence entries of a specified type.
+ *
+ * PUBLIC: int seq_dump __P((SCR *, seq_t, int));
+ */
+int
+seq_dump(sp, stype, isname)
+ SCR *sp;
+ seq_t stype;
+ int isname;
+{
+ CHAR_T *p;
+ GS *gp;
+ SEQ *qp;
+ int cnt, len, olen;
+
+ cnt = 0;
+ gp = sp->gp;
+ for (qp = gp->seqq.lh_first; qp != NULL; qp = qp->q.le_next) {
+ if (stype != qp->stype || F_ISSET(qp, SEQ_FUNCMAP))
+ continue;
+ ++cnt;
+ for (p = qp->input,
+ olen = qp->ilen, len = 0; olen > 0; --olen, ++p)
+ len += ex_puts(sp, KEY_NAME(sp, *p));
+ for (len = STANDARD_TAB - len % STANDARD_TAB; len > 0;)
+ len -= ex_puts(sp, " ");
+
+ if (qp->output != NULL)
+ for (p = qp->output,
+ olen = qp->olen, len = 0; olen > 0; --olen, ++p)
+ len += ex_puts(sp, KEY_NAME(sp, *p));
+ else
+ len = 0;
+
+ if (isname && qp->name != NULL) {
+ for (len = STANDARD_TAB - len % STANDARD_TAB; len > 0;)
+ len -= ex_puts(sp, " ");
+ for (p = qp->name,
+ olen = qp->nlen; olen > 0; --olen, ++p)
+ (void)ex_puts(sp, KEY_NAME(sp, *p));
+ }
+ (void)ex_puts(sp, "\n");
+ }
+ return (cnt);
+}
+
+/*
+ * seq_save --
+ * Save the sequence entries to a file.
+ *
+ * PUBLIC: int seq_save __P((SCR *, FILE *, char *, seq_t));
+ */
+int
+seq_save(sp, fp, prefix, stype)
+ SCR *sp;
+ FILE *fp;
+ char *prefix;
+ seq_t stype;
+{
+ CHAR_T *p;
+ SEQ *qp;
+ size_t olen;
+ int ch;
+
+ /* Write a sequence command for all keys the user defined. */
+ for (qp = sp->gp->seqq.lh_first; qp != NULL; qp = qp->q.le_next) {
+ if (stype != qp->stype || !F_ISSET(qp, SEQ_USERDEF))
+ continue;
+ if (prefix)
+ (void)fprintf(fp, "%s", prefix);
+ for (p = qp->input, olen = qp->ilen; olen > 0; --olen) {
+ ch = *p++;
+ if (ch == CH_LITERAL || ch == '|' ||
+ isblank(ch) || KEY_VAL(sp, ch) == K_NL)
+ (void)putc(CH_LITERAL, fp);
+ (void)putc(ch, fp);
+ }
+ (void)putc(' ', fp);
+ if (qp->output != NULL)
+ for (p = qp->output,
+ olen = qp->olen; olen > 0; --olen) {
+ ch = *p++;
+ if (ch == CH_LITERAL || ch == '|' ||
+ KEY_VAL(sp, ch) == K_NL)
+ (void)putc(CH_LITERAL, fp);
+ (void)putc(ch, fp);
+ }
+ (void)putc('\n', fp);
+ }
+ return (0);
+}
+
+/*
+ * e_memcmp --
+ * Compare a string of EVENT's to a string of CHAR_T's.
+ *
+ * PUBLIC: int e_memcmp __P((CHAR_T *, EVENT *, size_t));
+ */
+int
+e_memcmp(p1, ep, n)
+ CHAR_T *p1;
+ EVENT *ep;
+ size_t n;
+{
+ if (n != 0) {
+ do {
+ if (*p1++ != ep->e_c)
+ return (*--p1 - ep->e_c);
+ ++ep;
+ } while (--n != 0);
+ }
+ return (0);
+}
diff --git a/common/seq.h b/common/seq.h
new file mode 100644
index 000000000000..984bb6c0bd18
--- /dev/null
+++ b/common/seq.h
@@ -0,0 +1,44 @@
+/*-
+ * 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.
+ *
+ * @(#)seq.h 10.3 (Berkeley) 3/6/96
+ */
+
+/*
+ * Map and abbreviation structures.
+ *
+ * The map structure is doubly linked list, sorted by input string and by
+ * input length within the string. (The latter is necessary so that short
+ * matches will happen before long matches when the list is searched.)
+ * Additionally, there is a bitmap which has bits set if there are entries
+ * starting with the corresponding character. This keeps us from walking
+ * the list unless it's necessary.
+ *
+ * The name and the output fields of a SEQ can be empty, i.e. NULL.
+ * Only the input field is required.
+ *
+ * XXX
+ * The fast-lookup bits are never turned off -- users don't usually unmap
+ * things, though, so it's probably not a big deal.
+ */
+struct _seq {
+ LIST_ENTRY(_seq) q; /* Linked list of all sequences. */
+ seq_t stype; /* Sequence type. */
+ CHAR_T *name; /* Sequence name (if any). */
+ size_t nlen; /* Name length. */
+ CHAR_T *input; /* Sequence input keys. */
+ size_t ilen; /* Input keys length. */
+ CHAR_T *output; /* Sequence output keys. */
+ size_t olen; /* Output keys length. */
+
+#define SEQ_FUNCMAP 0x01 /* If unresolved function key.*/
+#define SEQ_NOOVERWRITE 0x02 /* Don't replace existing entry. */
+#define SEQ_SCREEN 0x04 /* If screen specific. */
+#define SEQ_USERDEF 0x08 /* If user defined. */
+ u_int8_t flags;
+};
diff --git a/common/util.c b/common/util.c
new file mode 100644
index 000000000000..5a4422a2c422
--- /dev/null
+++ b/common/util.c
@@ -0,0 +1,230 @@
+/*-
+ * 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[] = "@(#)util.c 10.11 (Berkeley) 9/15/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+
+#include <bitstring.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "common.h"
+
+/*
+ * binc --
+ * Increase the size of a buffer.
+ *
+ * PUBLIC: void *binc __P((SCR *, void *, size_t *, size_t));
+ */
+void *
+binc(sp, bp, bsizep, min)
+ SCR *sp; /* sp MAY BE NULL!!! */
+ void *bp;
+ size_t *bsizep, min;
+{
+ size_t csize;
+
+ /* If already larger than the minimum, just return. */
+ if (min && *bsizep >= min)
+ return (bp);
+
+ csize = *bsizep + MAX(min, 256);
+ REALLOC(sp, bp, void *, csize);
+
+ if (bp == NULL) {
+ /*
+ * Theoretically, realloc is supposed to leave any already
+ * held memory alone if it can't get more. Don't trust it.
+ */
+ *bsizep = 0;
+ return (NULL);
+ }
+ /*
+ * Memory is guaranteed to be zero-filled, various parts of
+ * nvi depend on this.
+ */
+ memset((char *)bp + *bsizep, 0, csize - *bsizep);
+ *bsizep = csize;
+ return (bp);
+}
+
+/*
+ * nonblank --
+ * Set the column number of the first non-blank character
+ * including or after the starting column. On error, set
+ * the column to 0, it's safest.
+ *
+ * PUBLIC: int nonblank __P((SCR *, recno_t, size_t *));
+ */
+int
+nonblank(sp, lno, cnop)
+ SCR *sp;
+ recno_t lno;
+ size_t *cnop;
+{
+ char *p;
+ size_t cnt, len, off;
+ int isempty;
+
+ /* Default. */
+ off = *cnop;
+ *cnop = 0;
+
+ /* Get the line, succeeding in an empty file. */
+ if (db_eget(sp, lno, &p, &len, &isempty))
+ return (!isempty);
+
+ /* Set the offset. */
+ if (len == 0 || off >= len)
+ return (0);
+
+ for (cnt = off, p = &p[off],
+ len -= off; len && isblank(*p); ++cnt, ++p, --len);
+
+ /* Set the return. */
+ *cnop = len ? cnt : cnt - 1;
+ return (0);
+}
+
+/*
+ * tail --
+ * Return tail of a path.
+ *
+ * PUBLIC: char *tail __P((char *));
+ */
+char *
+tail(path)
+ char *path;
+{
+ char *p;
+
+ if ((p = strrchr(path, '/')) == NULL)
+ return (path);
+ return (p + 1);
+}
+
+/*
+ * v_strdup --
+ * Strdup for wide character strings with an associated length.
+ *
+ * PUBLIC: CHAR_T *v_strdup __P((SCR *, const CHAR_T *, size_t));
+ */
+CHAR_T *
+v_strdup(sp, str, len)
+ SCR *sp;
+ const CHAR_T *str;
+ size_t len;
+{
+ CHAR_T *copy;
+
+ MALLOC(sp, copy, CHAR_T *, len + 1);
+ if (copy == NULL)
+ return (NULL);
+ memcpy(copy, str, len * sizeof(CHAR_T));
+ copy[len] = '\0';
+ return (copy);
+}
+
+/*
+ * nget_uslong --
+ * Get an unsigned long, checking for overflow.
+ *
+ * PUBLIC: enum nresult nget_uslong __P((u_long *, const char *, char **, int));
+ */
+enum nresult
+nget_uslong(valp, p, endp, base)
+ u_long *valp;
+ const char *p;
+ char **endp;
+ int base;
+{
+ errno = 0;
+ *valp = strtoul(p, endp, base);
+ if (errno == 0)
+ return (NUM_OK);
+ if (errno == ERANGE && *valp == ULONG_MAX)
+ return (NUM_OVER);
+ return (NUM_ERR);
+}
+
+/*
+ * nget_slong --
+ * Convert a signed long, checking for overflow and underflow.
+ *
+ * PUBLIC: enum nresult nget_slong __P((long *, const char *, char **, int));
+ */
+enum nresult
+nget_slong(valp, p, endp, base)
+ long *valp;
+ const char *p;
+ char **endp;
+ int base;
+{
+ errno = 0;
+ *valp = strtol(p, endp, base);
+ if (errno == 0)
+ return (NUM_OK);
+ if (errno == ERANGE) {
+ if (*valp == LONG_MAX)
+ return (NUM_OVER);
+ if (*valp == LONG_MIN)
+ return (NUM_UNDER);
+ }
+ return (NUM_ERR);
+}
+
+#ifdef DEBUG
+#ifdef __STDC__
+#include <stdarg.h>
+#else
+#include <varargs.h>
+#endif
+
+/*
+ * TRACE --
+ * debugging trace routine.
+ *
+ * PUBLIC: void TRACE __P((SCR *, const char *, ...));
+ */
+void
+#ifdef __STDC__
+TRACE(SCR *sp, const char *fmt, ...)
+#else
+TRACE(sp, fmt, va_alist)
+ SCR *sp;
+ char *fmt;
+ va_dcl
+#endif
+{
+ FILE *tfp;
+ va_list ap;
+
+ if ((tfp = sp->gp->tracefp) == NULL)
+ return;
+#ifdef __STDC__
+ va_start(ap, fmt);
+#else
+ va_start(ap);
+#endif
+ (void)vfprintf(tfp, fmt, ap);
+ va_end(ap);
+
+ (void)fflush(tfp);
+}
+#endif
diff --git a/common/util.h b/common/util.h
new file mode 100644
index 000000000000..46edb4aae5a8
--- /dev/null
+++ b/common/util.h
@@ -0,0 +1,56 @@
+/*-
+ * Copyright (c) 1994
+ * The Regents of the University of California. All rights reserved.
+ * Copyright (c) 1994, 1995, 1996
+ * Keith Bostic. All rights reserved.
+ *
+ * See the LICENSE file for redistribution information.
+ *
+ * @(#)util.h 10.5 (Berkeley) 3/16/96
+ */
+
+/* Macros to init/set/clear/test flags. */
+#define FL_INIT(l, f) (l) = (f) /* Specific flags location. */
+#define FL_SET(l, f) ((l) |= (f))
+#define FL_CLR(l, f) ((l) &= ~(f))
+#define FL_ISSET(l, f) ((l) & (f))
+
+#define LF_INIT(f) FL_INIT(flags, f) /* Local variable flags. */
+#define LF_SET(f) FL_SET(flags, f)
+#define LF_CLR(f) FL_CLR(flags, f)
+#define LF_ISSET(f) FL_ISSET(flags, f)
+
+#define F_INIT(p, f) FL_INIT((p)->flags, f) /* Structure element flags. */
+#define F_SET(p, f) FL_SET((p)->flags, f)
+#define F_CLR(p, f) FL_CLR((p)->flags, f)
+#define F_ISSET(p, f) FL_ISSET((p)->flags, f)
+
+/* Offset to next column of stop size, e.g. tab offsets. */
+#define COL_OFF(c, stop) ((stop) - ((c) % (stop)))
+
+/* Busy message types. */
+typedef enum { B_NONE, B_OFF, B_READ, B_RECOVER, B_SEARCH, B_WRITE } bmsg_t;
+
+/*
+ * Number handling defines and protoypes.
+ *
+ * NNFITS: test for addition of two negative numbers under a limit
+ * NPFITS: test for addition of two positive numbers under a limit
+ * NADD_SLONG: test for addition of two signed longs
+ * NADD_USLONG: test for addition of two unsigned longs
+ */
+enum nresult { NUM_ERR, NUM_OK, NUM_OVER, NUM_UNDER };
+#define NNFITS(min, cur, add) \
+ (((long)(min)) - (cur) <= (add))
+#define NPFITS(max, cur, add) \
+ (((unsigned long)(max)) - (cur) >= (add))
+#define NADD_SLONG(sp, v1, v2) \
+ ((v1) < 0 ? \
+ ((v2) < 0 && \
+ NNFITS(LONG_MIN, (v1), (v2))) ? NUM_UNDER : NUM_OK : \
+ (v1) > 0 ? \
+ (v2) > 0 && \
+ NPFITS(LONG_MAX, (v1), (v2)) ? NUM_OK : NUM_OVER : \
+ NUM_OK)
+#define NADD_USLONG(sp, v1, v2) \
+ (NPFITS(ULONG_MAX, (v1), (v2)) ? NUM_OK : NUM_OVER)