diff options
author | peter <peter@FreeBSD.org> | 1996-11-01 06:45:43 +0000 |
---|---|---|
committer | peter <peter@FreeBSD.org> | 1996-11-01 06:45:43 +0000 |
commit | 59cc89c2c2e686da3bdab2d5cfac4f33462d29fe (patch) | |
tree | 88f923c9c0be2e2a225a9b21716fd582de668b42 /contrib/nvi/ex/ex_argv.c | |
download | FreeBSD-src-59cc89c2c2e686da3bdab2d5cfac4f33462d29fe.zip FreeBSD-src-59cc89c2c2e686da3bdab2d5cfac4f33462d29fe.tar.gz |
Import of nvi-1.79, minus a few bits that we dont need (eg: postscript
files, curses, db, regex etc that we already have). The other glue will
follow shortly.
Obtained from: Keith Bostic <bostic@bostic.com>
Diffstat (limited to 'contrib/nvi/ex/ex_argv.c')
-rw-r--r-- | contrib/nvi/ex/ex_argv.c | 756 |
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 0000000..cc5a201 --- /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); +} |