diff options
Diffstat (limited to 'contrib/nvi/common')
33 files changed, 11889 insertions, 0 deletions
diff --git a/contrib/nvi/common/api.c b/contrib/nvi/common/api.c new file mode 100644 index 000000000000..35d9f0c8f66e --- /dev/null +++ b/contrib/nvi/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/contrib/nvi/common/args.h b/contrib/nvi/common/args.h new file mode 100644 index 000000000000..e84dc2ca04c1 --- /dev/null +++ b/contrib/nvi/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/contrib/nvi/common/common.h b/contrib/nvi/common/common.h new file mode 100644 index 000000000000..0e13fc80b844 --- /dev/null +++ b/contrib/nvi/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/contrib/nvi/common/cut.c b/contrib/nvi/common/cut.c new file mode 100644 index 000000000000..faceecd11166 --- /dev/null +++ b/contrib/nvi/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/contrib/nvi/common/cut.h b/contrib/nvi/common/cut.h new file mode 100644 index 000000000000..43f3ca817efd --- /dev/null +++ b/contrib/nvi/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/contrib/nvi/common/delete.c b/contrib/nvi/common/delete.c new file mode 100644 index 000000000000..001788f9bb38 --- /dev/null +++ b/contrib/nvi/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/contrib/nvi/common/exf.c b/contrib/nvi/common/exf.c new file mode 100644 index 000000000000..2993b0f4a8a5 --- /dev/null +++ b/contrib/nvi/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/contrib/nvi/common/exf.h b/contrib/nvi/common/exf.h new file mode 100644 index 000000000000..cdfaa8294485 --- /dev/null +++ b/contrib/nvi/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/contrib/nvi/common/gs.h b/contrib/nvi/common/gs.h new file mode 100644 index 000000000000..e5a43a656ac2 --- /dev/null +++ b/contrib/nvi/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/contrib/nvi/common/key.c b/contrib/nvi/common/key.c new file mode 100644 index 000000000000..e1311ab571b0 --- /dev/null +++ b/contrib/nvi/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/contrib/nvi/common/key.h b/contrib/nvi/common/key.h new file mode 100644 index 000000000000..76fb64f8e1ec --- /dev/null +++ b/contrib/nvi/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/contrib/nvi/common/line.c b/contrib/nvi/common/line.c new file mode 100644 index 000000000000..bcb9e0c86bcb --- /dev/null +++ b/contrib/nvi/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/contrib/nvi/common/log.c b/contrib/nvi/common/log.c new file mode 100644 index 000000000000..9a9fe793ffb8 --- /dev/null +++ b/contrib/nvi/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/contrib/nvi/common/log.h b/contrib/nvi/common/log.h new file mode 100644 index 000000000000..df307319b1d3 --- /dev/null +++ b/contrib/nvi/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/contrib/nvi/common/main.c b/contrib/nvi/common/main.c new file mode 100644 index 000000000000..6fb2ed1fe2f0 --- /dev/null +++ b/contrib/nvi/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/contrib/nvi/common/mark.c b/contrib/nvi/common/mark.c new file mode 100644 index 000000000000..0ac1fc28bf9c --- /dev/null +++ b/contrib/nvi/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/contrib/nvi/common/mark.h b/contrib/nvi/common/mark.h new file mode 100644 index 000000000000..9c63e183e83f --- /dev/null +++ b/contrib/nvi/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/contrib/nvi/common/mem.h b/contrib/nvi/common/mem.h new file mode 100644 index 000000000000..af42e6bcd1de --- /dev/null +++ b/contrib/nvi/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/contrib/nvi/common/msg.c b/contrib/nvi/common/msg.c new file mode 100644 index 000000000000..2b18082c7ab8 --- /dev/null +++ b/contrib/nvi/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/contrib/nvi/common/msg.h b/contrib/nvi/common/msg.h new file mode 100644 index 000000000000..b10f4ccae5c6 --- /dev/null +++ b/contrib/nvi/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/contrib/nvi/common/options.awk b/contrib/nvi/common/options.awk new file mode 100644 index 000000000000..0c91f0718f07 --- /dev/null +++ b/contrib/nvi/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/contrib/nvi/common/options.c b/contrib/nvi/common/options.c new file mode 100644 index 000000000000..973778c78bbd --- /dev/null +++ b/contrib/nvi/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/contrib/nvi/common/options.h b/contrib/nvi/common/options.h new file mode 100644 index 000000000000..2646dc301b5a --- /dev/null +++ b/contrib/nvi/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/contrib/nvi/common/options_f.c b/contrib/nvi/common/options_f.c new file mode 100644 index 000000000000..ea3c61160cf5 --- /dev/null +++ b/contrib/nvi/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/contrib/nvi/common/put.c b/contrib/nvi/common/put.c new file mode 100644 index 000000000000..8c0ca4b7c14f --- /dev/null +++ b/contrib/nvi/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/contrib/nvi/common/recover.c b/contrib/nvi/common/recover.c new file mode 100644 index 000000000000..f3abaab5a536 --- /dev/null +++ b/contrib/nvi/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/contrib/nvi/common/screen.c b/contrib/nvi/common/screen.c new file mode 100644 index 000000000000..ba9e287b648b --- /dev/null +++ b/contrib/nvi/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/contrib/nvi/common/screen.h b/contrib/nvi/common/screen.h new file mode 100644 index 000000000000..bb7254f62a21 --- /dev/null +++ b/contrib/nvi/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/contrib/nvi/common/search.c b/contrib/nvi/common/search.c new file mode 100644 index 000000000000..3fd2719778fa --- /dev/null +++ b/contrib/nvi/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/contrib/nvi/common/seq.c b/contrib/nvi/common/seq.c new file mode 100644 index 000000000000..e2be879ab686 --- /dev/null +++ b/contrib/nvi/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/contrib/nvi/common/seq.h b/contrib/nvi/common/seq.h new file mode 100644 index 000000000000..984bb6c0bd18 --- /dev/null +++ b/contrib/nvi/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/contrib/nvi/common/util.c b/contrib/nvi/common/util.c new file mode 100644 index 000000000000..5a4422a2c422 --- /dev/null +++ b/contrib/nvi/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/contrib/nvi/common/util.h b/contrib/nvi/common/util.h new file mode 100644 index 000000000000..46edb4aae5a8 --- /dev/null +++ b/contrib/nvi/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) |