diff options
Diffstat (limited to 'usr.bin/vi/ex/ex_argv.c')
-rw-r--r-- | usr.bin/vi/ex/ex_argv.c | 169 |
1 files changed, 158 insertions, 11 deletions
diff --git a/usr.bin/vi/ex/ex_argv.c b/usr.bin/vi/ex/ex_argv.c index f62b081eb40..e2cbfd61246 100644 --- a/usr.bin/vi/ex/ex_argv.c +++ b/usr.bin/vi/ex/ex_argv.c @@ -10,7 +10,7 @@ #include "config.h" #ifndef lint -static const char sccsid[] = "@(#)ex_argv.c 10.19 (Berkeley) 3/30/96"; +static const char sccsid[] = "@(#)ex_argv.c 10.23 (Berkeley) 8/11/96"; #endif /* not lint */ #include <sys/types.h> @@ -18,6 +18,7 @@ static const char sccsid[] = "@(#)ex_argv.c 10.19 (Berkeley) 3/30/96"; #include <bitstring.h> #include <ctype.h> +#include <dirent.h> #include <errno.h> #include <limits.h> #include <stdio.h> @@ -29,7 +30,9 @@ static const char sccsid[] = "@(#)ex_argv.c 10.19 (Berkeley) 3/30/96"; static int argv_alloc __P((SCR *, size_t)); static int argv_fexp __P((SCR *, EXCMD *, - char *, size_t, char *, size_t *, char **, size_t *, int)); + char *, size_t, char *, size_t *, char **, size_t *, int)); +static int argv_prefix __P((SCR *, + char *, char *, char **, size_t *, size_t *)); static int argv_sexp __P((SCR *, char **, size_t *, size_t *)); /* @@ -183,25 +186,58 @@ argv_exp2(sp, excp, cmd, cmdlen) 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 (p = bp, n = len; n > 0; --n, ++p) + for (; n > 0; --n, ++p) if (strchr(mp, *p) != NULL) break; } else - for (p = bp, n = len; n > 0; --n, ++p) + for (; n > 0; --n, ++p) if (!isblank(*p) && !isalnum(*p) && strchr(mp, *p) != NULL) break; } - if (n > 0) { + + /* + * 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; + break; + case 1: + if (*p == '*') { + *p++ = '\0'; + n = p - bp; + if (argv_prefix(sp, + bp + SHELLOFFSET, p, &bp, &blen, &len)) { + rval = 1; + goto err; + } + p = bp + n; + len -= n; + break; + } + /* FALLTHROUGH */ + default: if (argv_sexp(sp, &bp, &blen, &len)) { rval = 1; goto err; } p = bp; - } else { - p = bp + SHELLOFFSET; - len -= SHELLOFFSET; + break; } #if defined(DEBUG) && 0 @@ -472,6 +508,116 @@ argv_free(sp) } /* + * argv_prefix -- + * Find all file names matching the prefix and append them to the + * buffer. + */ +static int +argv_prefix(sp, path, wp, bpp, blenp, lenp) + SCR *sp; + char *path, *wp, **bpp; + size_t *blenp, *lenp; +{ + DIR *dirp; + struct dirent *dp; + size_t blen, clen, dlen, doffset, len, nlen; + char *bp, *dname, *name, *p; + + /* + * Open the directory, set up the name and length for comparison, + * the prepended directory and length. + */ + if ((p = strrchr(path, '/')) == NULL) { + dlen = 0; + dname = "."; + name = path; + } else { + if (p == path) { + dname = "/"; + dlen = 0; + } else { + *p = '\0'; + dname = path; + dlen = strlen(path); + } + name = p + 1; + } + nlen = strlen(name); + + if ((dirp = opendir(dname)) == NULL) { + msgq_str(sp, M_SYSERR, dname, "%s"); + return (1); + } + + /* Local copies of the buffer variables. */ + bp = *bpp; + blen = *blenp; + + /* + * We're passed a pointer to the name (after the echo command at + * the start of the buffer) and a pointer to the place to start + * writing. Set a pointer to the start of the write area, and a + * value for the amount of space we have to write. + */ + p = wp; + len = wp - bp; + blen -= len; + + /* + * Read the directory, checking for files with a matching prefix. + * + * XXX + * We don't use the d_namlen field, it's not portable enough; we + * assume that d_name is nul terminated, instead. + */ + while ((dp = readdir(dirp)) != NULL) { + clen = strlen(dp->d_name); + if (nlen == 0 || + (clen >= nlen && !memcmp(dp->d_name, name, nlen))) { + if (blen < clen + dlen + 5) { + doffset = dname - bp; + ADD_SPACE_GOTO(sp, bp, *blenp, + *blenp * 2 + clen + dlen + 5); + p = bp + len; + blen = *blenp - len; + if (dname == path) + dname = bp + doffset; + } + if (dlen != 0) { + memcpy(p, dname, dlen); + p += dlen; + *p++ = '/'; + len += dlen + 1; + blen -= dlen + 1; + } + memcpy(p, dp->d_name, clen); + p += clen; + *p++ = ' '; + len += clen + 1; + blen -= clen + 1; + } + } + (void)closedir(dirp); + + /* + * 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. + */ + if (p == wp) { + msgq(sp, M_ERR, "304|Shell expansion failed"); +alloc_err: return (1); + } + + /* Delete the final <space>, nul terminate the string. */ + *--p = '\0'; + *lenp = len - 1; + *bpp = bp; /* *blenp is already updated. */ + + return (0); +} + +/* * argv_sexp -- * Fork a shell, pipe a command through it, and read the output into * a buffer. @@ -502,6 +648,7 @@ argv_sexp(sp, bpp, blenp, lenp) else ++sh; + /* Local copies of the buffer variables. */ bp = *bpp; blen = *blenp; @@ -581,10 +728,10 @@ err: if (ifp != NULL) /* Delete the final newline, nul terminate the string. */ if (p > bp && (p[-1] == '\n' || p[-1] == '\r')) { + --p; --len; - *--p = '\0'; - } else - *p = '\0'; + } + *p = '\0'; *lenp = len; *bpp = bp; /* *blenp is already updated. */ |