aboutsummaryrefslogtreecommitdiff
path: root/contrib/nvi/ex/ex_argv.c
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/nvi/ex/ex_argv.c')
-rw-r--r--contrib/nvi/ex/ex_argv.c756
1 files changed, 756 insertions, 0 deletions
diff --git a/contrib/nvi/ex/ex_argv.c b/contrib/nvi/ex/ex_argv.c
new file mode 100644
index 000000000000..cc5a201bea26
--- /dev/null
+++ b/contrib/nvi/ex/ex_argv.c
@@ -0,0 +1,756 @@
+/*-
+ * 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[] = "@(#)ex_argv.c 10.26 (Berkeley) 9/20/96";
+#endif /* not lint */
+
+#include <sys/types.h>
+#include <sys/queue.h>
+
+#include <bitstring.h>
+#include <ctype.h>
+#include <dirent.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "../common/common.h"
+
+static int argv_alloc __P((SCR *, size_t));
+static int argv_comp __P((const void *, const void *));
+static int argv_fexp __P((SCR *, EXCMD *,
+ char *, size_t, char *, size_t *, char **, size_t *, int));
+static int argv_lexp __P((SCR *, EXCMD *, char *));
+static int argv_sexp __P((SCR *, char **, size_t *, size_t *));
+
+/*
+ * argv_init --
+ * Build a prototype arguments list.
+ *
+ * PUBLIC: int argv_init __P((SCR *, EXCMD *));
+ */
+int
+argv_init(sp, excp)
+ SCR *sp;
+ EXCMD *excp;
+{
+ EX_PRIVATE *exp;
+
+ exp = EXP(sp);
+ exp->argsoff = 0;
+ argv_alloc(sp, 1);
+
+ excp->argv = exp->args;
+ excp->argc = exp->argsoff;
+ return (0);
+}
+
+/*
+ * argv_exp0 --
+ * Append a string to the argument list.
+ *
+ * PUBLIC: int argv_exp0 __P((SCR *, EXCMD *, char *, size_t));
+ */
+int
+argv_exp0(sp, excp, cmd, cmdlen)
+ SCR *sp;
+ EXCMD *excp;
+ char *cmd;
+ size_t cmdlen;
+{
+ EX_PRIVATE *exp;
+
+ exp = EXP(sp);
+ argv_alloc(sp, cmdlen);
+ memcpy(exp->args[exp->argsoff]->bp, cmd, cmdlen);
+ exp->args[exp->argsoff]->bp[cmdlen] = '\0';
+ exp->args[exp->argsoff]->len = cmdlen;
+ ++exp->argsoff;
+ excp->argv = exp->args;
+ excp->argc = exp->argsoff;
+ return (0);
+}
+
+/*
+ * argv_exp1 --
+ * Do file name expansion on a string, and append it to the
+ * argument list.
+ *
+ * PUBLIC: int argv_exp1 __P((SCR *, EXCMD *, char *, size_t, int));
+ */
+int
+argv_exp1(sp, excp, cmd, cmdlen, is_bang)
+ SCR *sp;
+ EXCMD *excp;
+ char *cmd;
+ size_t cmdlen;
+ int is_bang;
+{
+ EX_PRIVATE *exp;
+ size_t blen, len;
+ char *bp, *p, *t;
+
+ GET_SPACE_RET(sp, bp, blen, 512);
+
+ len = 0;
+ exp = EXP(sp);
+ if (argv_fexp(sp, excp, cmd, cmdlen, bp, &len, &bp, &blen, is_bang)) {
+ FREE_SPACE(sp, bp, blen);
+ return (1);
+ }
+
+ /* If it's empty, we're done. */
+ if (len != 0) {
+ for (p = bp, t = bp + len; p < t; ++p)
+ if (!isblank(*p))
+ break;
+ if (p == t)
+ goto ret;
+ } else
+ goto ret;
+
+ (void)argv_exp0(sp, excp, bp, len);
+
+ret: FREE_SPACE(sp, bp, blen);
+ return (0);
+}
+
+/*
+ * argv_exp2 --
+ * Do file name and shell expansion on a string, and append it to
+ * the argument list.
+ *
+ * PUBLIC: int argv_exp2 __P((SCR *, EXCMD *, char *, size_t));
+ */
+int
+argv_exp2(sp, excp, cmd, cmdlen)
+ SCR *sp;
+ EXCMD *excp;
+ char *cmd;
+ size_t cmdlen;
+{
+ size_t blen, len, n;
+ int rval;
+ char *bp, *mp, *p;
+
+ GET_SPACE_RET(sp, bp, blen, 512);
+
+#define SHELLECHO "echo "
+#define SHELLOFFSET (sizeof(SHELLECHO) - 1)
+ memcpy(bp, SHELLECHO, SHELLOFFSET);
+ p = bp + SHELLOFFSET;
+ len = SHELLOFFSET;
+
+#if defined(DEBUG) && 0
+ TRACE(sp, "file_argv: {%.*s}\n", (int)cmdlen, cmd);
+#endif
+
+ if (argv_fexp(sp, excp, cmd, cmdlen, p, &len, &bp, &blen, 0)) {
+ rval = 1;
+ goto err;
+ }
+
+#if defined(DEBUG) && 0
+ TRACE(sp, "before shell: %d: {%s}\n", len, bp);
+#endif
+
+ /*
+ * Do shell word expansion -- it's very, very hard to figure out what
+ * magic characters the user's shell expects. Historically, it was a
+ * union of v7 shell and csh meta characters. We match that practice
+ * by default, so ":read \%" tries to read a file named '%'. It would
+ * make more sense to pass any special characters through the shell,
+ * but then, if your shell was csh, the above example will behave
+ * differently in nvi than in vi. If you want to get other characters
+ * passed through to your shell, change the "meta" option.
+ *
+ * To avoid a function call per character, we do a first pass through
+ * the meta characters looking for characters that aren't expected
+ * to be there, and then we can ignore them in the user's argument.
+ */
+ if (opts_empty(sp, O_SHELL, 1) || opts_empty(sp, O_SHELLMETA, 1))
+ n = 0;
+ else {
+ for (p = mp = O_STR(sp, O_SHELLMETA); *p != '\0'; ++p)
+ if (isblank(*p) || isalnum(*p))
+ break;
+ p = bp + SHELLOFFSET;
+ n = len - SHELLOFFSET;
+ if (*p != '\0') {
+ for (; n > 0; --n, ++p)
+ if (strchr(mp, *p) != NULL)
+ break;
+ } else
+ for (; n > 0; --n, ++p)
+ if (!isblank(*p) &&
+ !isalnum(*p) && strchr(mp, *p) != NULL)
+ break;
+ }
+
+ /*
+ * If we found a meta character in the string, fork a shell to expand
+ * it. Unfortunately, this is comparatively slow. Historically, it
+ * didn't matter much, since users don't enter meta characters as part
+ * of pathnames that frequently. The addition of filename completion
+ * broke that assumption because it's easy to use. As a result, lots
+ * folks have complained that the expansion code is too slow. So, we
+ * detect filename completion as a special case, and do it internally.
+ * Note that this code assumes that the <asterisk> character is the
+ * match-anything meta character. That feels safe -- if anyone writes
+ * a shell that doesn't follow that convention, I'd suggest giving them
+ * a festive hot-lead enema.
+ */
+ switch (n) {
+ case 0:
+ p = bp + SHELLOFFSET;
+ len -= SHELLOFFSET;
+ rval = argv_exp3(sp, excp, p, len);
+ break;
+ case 1:
+ if (*p == '*') {
+ *p = '\0';
+ rval = argv_lexp(sp, excp, bp + SHELLOFFSET);
+ break;
+ }
+ /* FALLTHROUGH */
+ default:
+ if (argv_sexp(sp, &bp, &blen, &len)) {
+ rval = 1;
+ goto err;
+ }
+ p = bp;
+ rval = argv_exp3(sp, excp, p, len);
+ break;
+ }
+
+err: FREE_SPACE(sp, bp, blen);
+ return (rval);
+}
+
+/*
+ * argv_exp3 --
+ * Take a string and break it up into an argv, which is appended
+ * to the argument list.
+ *
+ * PUBLIC: int argv_exp3 __P((SCR *, EXCMD *, char *, size_t));
+ */
+int
+argv_exp3(sp, excp, cmd, cmdlen)
+ SCR *sp;
+ EXCMD *excp;
+ char *cmd;
+ size_t cmdlen;
+{
+ EX_PRIVATE *exp;
+ size_t len;
+ int ch, off;
+ char *ap, *p;
+
+ for (exp = EXP(sp); cmdlen > 0; ++exp->argsoff) {
+ /* Skip any leading whitespace. */
+ for (; cmdlen > 0; --cmdlen, ++cmd) {
+ ch = *cmd;
+ if (!isblank(ch))
+ break;
+ }
+ if (cmdlen == 0)
+ break;
+
+ /*
+ * Determine the length of this whitespace delimited
+ * argument.
+ *
+ * QUOTING NOTE:
+ *
+ * Skip any character preceded by the user's quoting
+ * character.
+ */
+ for (ap = cmd, len = 0; cmdlen > 0; ++cmd, --cmdlen, ++len) {
+ ch = *cmd;
+ if (IS_ESCAPE(sp, excp, ch) && cmdlen > 1) {
+ ++cmd;
+ --cmdlen;
+ } else if (isblank(ch))
+ break;
+ }
+
+ /*
+ * Copy the argument into place.
+ *
+ * QUOTING NOTE:
+ *
+ * Lose quote chars.
+ */
+ argv_alloc(sp, len);
+ off = exp->argsoff;
+ exp->args[off]->len = len;
+ for (p = exp->args[off]->bp; len > 0; --len, *p++ = *ap++)
+ if (IS_ESCAPE(sp, excp, *ap))
+ ++ap;
+ *p = '\0';
+ }
+ excp->argv = exp->args;
+ excp->argc = exp->argsoff;
+
+#if defined(DEBUG) && 0
+ for (cnt = 0; cnt < exp->argsoff; ++cnt)
+ TRACE(sp, "arg %d: {%s}\n", cnt, exp->argv[cnt]);
+#endif
+ return (0);
+}
+
+/*
+ * argv_fexp --
+ * Do file name and bang command expansion.
+ */
+static int
+argv_fexp(sp, excp, cmd, cmdlen, p, lenp, bpp, blenp, is_bang)
+ SCR *sp;
+ EXCMD *excp;
+ char *cmd, *p, **bpp;
+ size_t cmdlen, *lenp, *blenp;
+ int is_bang;
+{
+ EX_PRIVATE *exp;
+ char *bp, *t;
+ size_t blen, len, off, tlen;
+
+ /* Replace file name characters. */
+ for (bp = *bpp, blen = *blenp, len = *lenp; cmdlen > 0; --cmdlen, ++cmd)
+ switch (*cmd) {
+ case '!':
+ if (!is_bang)
+ goto ins_ch;
+ exp = EXP(sp);
+ if (exp->lastbcomm == NULL) {
+ msgq(sp, M_ERR,
+ "115|No previous command to replace \"!\"");
+ return (1);
+ }
+ len += tlen = strlen(exp->lastbcomm);
+ off = p - bp;
+ ADD_SPACE_RET(sp, bp, blen, len);
+ p = bp + off;
+ memcpy(p, exp->lastbcomm, tlen);
+ p += tlen;
+ F_SET(excp, E_MODIFY);
+ break;
+ case '%':
+ if ((t = sp->frp->name) == NULL) {
+ msgq(sp, M_ERR,
+ "116|No filename to substitute for %%");
+ return (1);
+ }
+ tlen = strlen(t);
+ len += tlen;
+ off = p - bp;
+ ADD_SPACE_RET(sp, bp, blen, len);
+ p = bp + off;
+ memcpy(p, t, tlen);
+ p += tlen;
+ F_SET(excp, E_MODIFY);
+ break;
+ case '#':
+ if ((t = sp->alt_name) == NULL) {
+ msgq(sp, M_ERR,
+ "117|No filename to substitute for #");
+ return (1);
+ }
+ len += tlen = strlen(t);
+ off = p - bp;
+ ADD_SPACE_RET(sp, bp, blen, len);
+ p = bp + off;
+ memcpy(p, t, tlen);
+ p += tlen;
+ F_SET(excp, E_MODIFY);
+ break;
+ case '\\':
+ /*
+ * QUOTING NOTE:
+ *
+ * Strip any backslashes that protected the file
+ * expansion characters.
+ */
+ if (cmdlen > 1 &&
+ (cmd[1] == '%' || cmd[1] == '#' || cmd[1] == '!')) {
+ ++cmd;
+ --cmdlen;
+ }
+ /* FALLTHROUGH */
+ default:
+ins_ch: ++len;
+ off = p - bp;
+ ADD_SPACE_RET(sp, bp, blen, len);
+ p = bp + off;
+ *p++ = *cmd;
+ }
+
+ /* Nul termination. */
+ ++len;
+ off = p - bp;
+ ADD_SPACE_RET(sp, bp, blen, len);
+ p = bp + off;
+ *p = '\0';
+
+ /* Return the new string length, buffer, buffer length. */
+ *lenp = len - 1;
+ *bpp = bp;
+ *blenp = blen;
+ return (0);
+}
+
+/*
+ * argv_alloc --
+ * Make more space for arguments.
+ */
+static int
+argv_alloc(sp, len)
+ SCR *sp;
+ size_t len;
+{
+ ARGS *ap;
+ EX_PRIVATE *exp;
+ int cnt, off;
+
+ /*
+ * Allocate room for another argument, always leaving
+ * enough room for an ARGS structure with a length of 0.
+ */
+#define INCREMENT 20
+ exp = EXP(sp);
+ off = exp->argsoff;
+ if (exp->argscnt == 0 || off + 2 >= exp->argscnt - 1) {
+ cnt = exp->argscnt + INCREMENT;
+ REALLOC(sp, exp->args, ARGS **, cnt * sizeof(ARGS *));
+ if (exp->args == NULL) {
+ (void)argv_free(sp);
+ goto mem;
+ }
+ memset(&exp->args[exp->argscnt], 0, INCREMENT * sizeof(ARGS *));
+ exp->argscnt = cnt;
+ }
+
+ /* First argument. */
+ if (exp->args[off] == NULL) {
+ CALLOC(sp, exp->args[off], ARGS *, 1, sizeof(ARGS));
+ if (exp->args[off] == NULL)
+ goto mem;
+ }
+
+ /* First argument buffer. */
+ ap = exp->args[off];
+ ap->len = 0;
+ if (ap->blen < len + 1) {
+ ap->blen = len + 1;
+ REALLOC(sp, ap->bp, CHAR_T *, ap->blen * sizeof(CHAR_T));
+ if (ap->bp == NULL) {
+ ap->bp = NULL;
+ ap->blen = 0;
+ F_CLR(ap, A_ALLOCATED);
+mem: msgq(sp, M_SYSERR, NULL);
+ return (1);
+ }
+ F_SET(ap, A_ALLOCATED);
+ }
+
+ /* Second argument. */
+ if (exp->args[++off] == NULL) {
+ CALLOC(sp, exp->args[off], ARGS *, 1, sizeof(ARGS));
+ if (exp->args[off] == NULL)
+ goto mem;
+ }
+ /* 0 length serves as end-of-argument marker. */
+ exp->args[off]->len = 0;
+ return (0);
+}
+
+/*
+ * argv_free --
+ * Free up argument structures.
+ *
+ * PUBLIC: int argv_free __P((SCR *));
+ */
+int
+argv_free(sp)
+ SCR *sp;
+{
+ EX_PRIVATE *exp;
+ int off;
+
+ exp = EXP(sp);
+ if (exp->args != NULL) {
+ for (off = 0; off < exp->argscnt; ++off) {
+ if (exp->args[off] == NULL)
+ continue;
+ if (F_ISSET(exp->args[off], A_ALLOCATED))
+ free(exp->args[off]->bp);
+ free(exp->args[off]);
+ }
+ free(exp->args);
+ }
+ exp->args = NULL;
+ exp->argscnt = 0;
+ exp->argsoff = 0;
+ return (0);
+}
+
+/*
+ * argv_lexp --
+ * Find all file names matching the prefix and append them to the
+ * buffer.
+ */
+static int
+argv_lexp(sp, excp, path)
+ SCR *sp;
+ EXCMD *excp;
+ char *path;
+{
+ struct dirent *dp;
+ DIR *dirp;
+ EX_PRIVATE *exp;
+ int off;
+ size_t dlen, len, nlen;
+ char *dname, *name, *p;
+
+ exp = EXP(sp);
+
+ /* Set up the name and length for comparison. */
+ if ((p = strrchr(path, '/')) == NULL) {
+ dname = ".";
+ dlen = 0;
+ name = path;
+ } else {
+ if (p == path) {
+ dname = "/";
+ dlen = 1;
+ } else {
+ *p = '\0';
+ dname = path;
+ dlen = strlen(path);
+ }
+ name = p + 1;
+ }
+ nlen = strlen(name);
+
+ /*
+ * XXX
+ * We don't use the d_namlen field, it's not portable enough; we
+ * assume that d_name is nul terminated, instead.
+ */
+ if ((dirp = opendir(dname)) == NULL) {
+ msgq_str(sp, M_SYSERR, dname, "%s");
+ return (1);
+ }
+ for (off = exp->argsoff; (dp = readdir(dirp)) != NULL;) {
+ if (nlen == 0) {
+ if (dp->d_name[0] == '.')
+ continue;
+ len = strlen(dp->d_name);
+ } else {
+ len = strlen(dp->d_name);
+ if (len < nlen || memcmp(dp->d_name, name, nlen))
+ continue;
+ }
+
+ /* Directory + name + slash + null. */
+ argv_alloc(sp, dlen + len + 2);
+ p = exp->args[exp->argsoff]->bp;
+ if (dlen != 0) {
+ memcpy(p, dname, dlen);
+ p += dlen;
+ if (dlen > 1 || dname[0] != '/')
+ *p++ = '/';
+ }
+ memcpy(p, dp->d_name, len + 1);
+ exp->args[exp->argsoff]->len = dlen + len + 1;
+ ++exp->argsoff;
+ excp->argv = exp->args;
+ excp->argc = exp->argsoff;
+ }
+ closedir(dirp);
+
+ if (off == exp->argsoff) {
+ /*
+ * If we didn't find a match, complain that the expansion
+ * failed. We can't know for certain that's the error, but
+ * it's a good guess, and it matches historic practice.
+ */
+ msgq(sp, M_ERR, "304|Shell expansion failed");
+ return (1);
+ }
+ qsort(exp->args + off, exp->argsoff - off, sizeof(ARGS *), argv_comp);
+ return (0);
+}
+
+/*
+ * argv_comp --
+ * Alphabetic comparison.
+ */
+static int
+argv_comp(a, b)
+ const void *a, *b;
+{
+ return (strcmp((char *)(*(ARGS **)a)->bp, (char *)(*(ARGS **)b)->bp));
+}
+
+/*
+ * argv_sexp --
+ * Fork a shell, pipe a command through it, and read the output into
+ * a buffer.
+ */
+static int
+argv_sexp(sp, bpp, blenp, lenp)
+ SCR *sp;
+ char **bpp;
+ size_t *blenp, *lenp;
+{
+ enum { SEXP_ERR, SEXP_EXPANSION_ERR, SEXP_OK } rval;
+ FILE *ifp;
+ pid_t pid;
+ size_t blen, len;
+ int ch, std_output[2];
+ char *bp, *p, *sh, *sh_path;
+
+ /* Secure means no shell access. */
+ if (O_ISSET(sp, O_SECURE)) {
+ msgq(sp, M_ERR,
+"289|Shell expansions not supported when the secure edit option is set");
+ return (1);
+ }
+
+ sh_path = O_STR(sp, O_SHELL);
+ if ((sh = strrchr(sh_path, '/')) == NULL)
+ sh = sh_path;
+ else
+ ++sh;
+
+ /* Local copies of the buffer variables. */
+ bp = *bpp;
+ blen = *blenp;
+
+ /*
+ * There are two different processes running through this code, named
+ * the utility (the shell) and the parent. The utility reads standard
+ * input and writes standard output and standard error output. The
+ * parent writes to the utility, reads its standard output and ignores
+ * its standard error output. Historically, the standard error output
+ * was discarded by vi, as it produces a lot of noise when file patterns
+ * don't match.
+ *
+ * The parent reads std_output[0], and the utility writes std_output[1].
+ */
+ ifp = NULL;
+ std_output[0] = std_output[1] = -1;
+ if (pipe(std_output) < 0) {
+ msgq(sp, M_SYSERR, "pipe");
+ return (1);
+ }
+ if ((ifp = fdopen(std_output[0], "r")) == NULL) {
+ msgq(sp, M_SYSERR, "fdopen");
+ goto err;
+ }
+
+ /*
+ * Do the minimal amount of work possible, the shell is going to run
+ * briefly and then exit. We sincerely hope.
+ */
+ switch (pid = vfork()) {
+ case -1: /* Error. */
+ msgq(sp, M_SYSERR, "vfork");
+err: if (ifp != NULL)
+ (void)fclose(ifp);
+ else if (std_output[0] != -1)
+ close(std_output[0]);
+ if (std_output[1] != -1)
+ close(std_output[0]);
+ return (1);
+ case 0: /* Utility. */
+ /* Redirect stdout to the write end of the pipe. */
+ (void)dup2(std_output[1], STDOUT_FILENO);
+
+ /* Close the utility's file descriptors. */
+ (void)close(std_output[0]);
+ (void)close(std_output[1]);
+ (void)close(STDERR_FILENO);
+
+ /*
+ * XXX
+ * Assume that all shells have -c.
+ */
+ execl(sh_path, sh, "-c", bp, NULL);
+ msgq_str(sp, M_SYSERR, sh_path, "118|Error: execl: %s");
+ _exit(127);
+ default: /* Parent. */
+ /* Close the pipe ends the parent won't use. */
+ (void)close(std_output[1]);
+ break;
+ }
+
+ /*
+ * Copy process standard output into a buffer.
+ *
+ * !!!
+ * Historic vi apparently discarded leading \n and \r's from
+ * the shell output stream. We don't on the grounds that any
+ * shell that does that is broken.
+ */
+ for (p = bp, len = 0, ch = EOF;
+ (ch = getc(ifp)) != EOF; *p++ = ch, --blen, ++len)
+ if (blen < 5) {
+ ADD_SPACE_GOTO(sp, bp, *blenp, *blenp * 2);
+ p = bp + len;
+ blen = *blenp - len;
+ }
+
+ /* Delete the final newline, nul terminate the string. */
+ if (p > bp && (p[-1] == '\n' || p[-1] == '\r')) {
+ --p;
+ --len;
+ }
+ *p = '\0';
+ *lenp = len;
+ *bpp = bp; /* *blenp is already updated. */
+
+ if (ferror(ifp))
+ goto ioerr;
+ if (fclose(ifp)) {
+ioerr: msgq_str(sp, M_ERR, sh, "119|I/O error: %s");
+alloc_err: rval = SEXP_ERR;
+ } else
+ rval = SEXP_OK;
+
+ /*
+ * Wait for the process. If the shell process fails (e.g., "echo $q"
+ * where q wasn't a defined variable) or if the returned string has
+ * no characters or only blank characters, (e.g., "echo $5"), complain
+ * that the shell expansion failed. We can't know for certain that's
+ * the error, but it's a good guess, and it matches historic practice.
+ * This won't catch "echo foo_$5", but that's not a common error and
+ * historic vi didn't catch it either.
+ */
+ if (proc_wait(sp, (long)pid, sh, 1, 0))
+ rval = SEXP_EXPANSION_ERR;
+
+ for (p = bp; len; ++p, --len)
+ if (!isblank(*p))
+ break;
+ if (len == 0)
+ rval = SEXP_EXPANSION_ERR;
+
+ if (rval == SEXP_EXPANSION_ERR)
+ msgq(sp, M_ERR, "304|Shell expansion failed");
+
+ return (rval == SEXP_OK ? 0 : 1);
+}