Home | History | Annotate | Line # | Download | only in ex
      1 /*	$NetBSD: ex_argv.c,v 1.3 2014/01/26 21:43:45 christos Exp $ */
      2 /*-
      3  * Copyright (c) 1993, 1994
      4  *	The Regents of the University of California.  All rights reserved.
      5  * Copyright (c) 1993, 1994, 1995, 1996
      6  *	Keith Bostic.  All rights reserved.
      7  *
      8  * See the LICENSE file for redistribution information.
      9  */
     10 
     11 #include "config.h"
     12 
     13 #include <sys/cdefs.h>
     14 #if 0
     15 #ifndef lint
     16 static const char sccsid[] = "Id: ex_argv.c,v 10.39 2003/11/05 17:11:54 skimo Exp  (Berkeley) Date: 2003/11/05 17:11:54 ";
     17 #endif /* not lint */
     18 #else
     19 __RCSID("$NetBSD: ex_argv.c,v 1.3 2014/01/26 21:43:45 christos Exp $");
     20 #endif
     21 
     22 #include <sys/types.h>
     23 #include <sys/queue.h>
     24 
     25 #include <bitstring.h>
     26 #include <ctype.h>
     27 #include <dirent.h>
     28 #include <errno.h>
     29 #include <limits.h>
     30 #include <stdio.h>
     31 #include <stdlib.h>
     32 #include <string.h>
     33 #include <unistd.h>
     34 
     35 #include "../common/common.h"
     36 
     37 static int argv_alloc __P((SCR *, size_t));
     38 static int argv_comp __P((const void *, const void *));
     39 static int argv_fexp __P((SCR *, EXCMD *,
     40 	const CHAR_T *, size_t, CHAR_T *, size_t *, CHAR_T **, size_t *, int));
     41 static int argv_lexp __P((SCR *, EXCMD *, const char *));
     42 static int argv_sexp __P((SCR *, CHAR_T **, size_t *, size_t *));
     43 
     44 /*
     45  * argv_init --
     46  *	Build  a prototype arguments list.
     47  *
     48  * PUBLIC: int argv_init __P((SCR *, EXCMD *));
     49  */
     50 int
     51 argv_init(SCR *sp, EXCMD *excp)
     52 {
     53 	EX_PRIVATE *exp;
     54 
     55 	exp = EXP(sp);
     56 	exp->argsoff = 0;
     57 	argv_alloc(sp, 1);
     58 
     59 	excp->argv = exp->args;
     60 	excp->argc = exp->argsoff;
     61 	return (0);
     62 }
     63 
     64 /*
     65  * argv_exp0 --
     66  *	Append a string to the argument list.
     67  *
     68  * PUBLIC: int argv_exp0 __P((SCR *, EXCMD *, const CHAR_T *, size_t));
     69  */
     70 int
     71 argv_exp0(SCR *sp, EXCMD *excp, const CHAR_T *cmd, size_t cmdlen)
     72 {
     73 	EX_PRIVATE *exp;
     74 
     75 	exp = EXP(sp);
     76 	argv_alloc(sp, cmdlen);
     77 	MEMCPY(exp->args[exp->argsoff]->bp, cmd, cmdlen);
     78 	exp->args[exp->argsoff]->bp[cmdlen] = '\0';
     79 	exp->args[exp->argsoff]->len = cmdlen;
     80 	++exp->argsoff;
     81 	excp->argv = exp->args;
     82 	excp->argc = exp->argsoff;
     83 	return (0);
     84 }
     85 
     86 /*
     87  * argv_exp1 --
     88  *	Do file name expansion on a string, and append it to the
     89  *	argument list.
     90  *
     91  * PUBLIC: int argv_exp1 __P((SCR *, EXCMD *, const CHAR_T *, size_t, int));
     92  */
     93 int
     94 argv_exp1(SCR *sp, EXCMD *excp, const CHAR_T *cmd, size_t cmdlen, int is_bang)
     95 {
     96 	size_t blen, len;
     97 	CHAR_T *p, *t, *bp;
     98 
     99 	GET_SPACE_RETW(sp, bp, blen, 512);
    100 
    101 	len = 0;
    102 	if (argv_fexp(sp, excp, cmd, cmdlen, bp, &len, &bp, &blen, is_bang)) {
    103 		FREE_SPACEW(sp, bp, blen);
    104 		return (1);
    105 	}
    106 
    107 	/* If it's empty, we're done. */
    108 	if (len != 0) {
    109 		for (p = bp, t = bp + len; p < t; ++p)
    110 			if (!ISBLANK(*p))
    111 				break;
    112 		if (p == t)
    113 			goto ret;
    114 	} else
    115 		goto ret;
    116 
    117 	(void)argv_exp0(sp, excp, bp, len);
    118 
    119 ret:	FREE_SPACEW(sp, bp, blen);
    120 	return (0);
    121 }
    122 
    123 /*
    124  * argv_exp2 --
    125  *	Do file name and shell expansion on a string, and append it to
    126  *	the argument list.
    127  *
    128  * PUBLIC: int argv_exp2 __P((SCR *, EXCMD *, const CHAR_T *, size_t));
    129  */
    130 int
    131 argv_exp2(SCR *sp, EXCMD *excp, const CHAR_T *cmd, size_t cmdlen)
    132 {
    133 	size_t blen, len, n;
    134 	int rval;
    135 	CHAR_T *bp, *p;
    136 	const char *mp, *np;
    137 
    138 	GET_SPACE_RETW(sp, bp, blen, 512);
    139 
    140 #define	SHELLECHO	"echo "
    141 #define	SHELLOFFSET	(sizeof(SHELLECHO) - 1)
    142 	p = bp;
    143 	*p++ = 'e';
    144 	*p++ = 'c';
    145 	*p++ = 'h';
    146 	*p++ = 'o';
    147 	*p++ = ' ';
    148 	len = SHELLOFFSET;
    149 
    150 #if defined(DEBUG) && 0
    151 	vtrace(sp, "file_argv: {%.*s}\n", (int)cmdlen, cmd);
    152 #endif
    153 
    154 	if (argv_fexp(sp, excp, cmd, cmdlen, p, &len, &bp, &blen, 0)) {
    155 		rval = 1;
    156 		goto err;
    157 	}
    158 
    159 #if defined(DEBUG) && 0
    160 	vtrace(sp, "before shell: %d: {%s}\n", len, bp);
    161 #endif
    162 
    163 	/*
    164 	 * Do shell word expansion -- it's very, very hard to figure out what
    165 	 * magic characters the user's shell expects.  Historically, it was a
    166 	 * union of v7 shell and csh meta characters.  We match that practice
    167 	 * by default, so ":read \%" tries to read a file named '%'.  It would
    168 	 * make more sense to pass any special characters through the shell,
    169 	 * but then, if your shell was csh, the above example will behave
    170 	 * differently in nvi than in vi.  If you want to get other characters
    171 	 * passed through to your shell, change the "meta" option.
    172 	 *
    173 	 * To avoid a function call per character, we do a first pass through
    174 	 * the meta characters looking for characters that aren't expected
    175 	 * to be there, and then we can ignore them in the user's argument.
    176 	 */
    177 	if (opts_empty(sp, O_SHELL, 1) || opts_empty(sp, O_SHELLMETA, 1))
    178 		n = 0;
    179 	else {
    180 		for (np = mp = O_STR(sp, O_SHELLMETA); *np != '\0'; ++np)
    181 			if (ISBLANK((UCHAR_T)*np) ||
    182 			    ISALNUM((UCHAR_T)*np))
    183 				break;
    184 		p = bp + SHELLOFFSET;
    185 		n = len - SHELLOFFSET;
    186 		if (*p != '\0') {
    187 			for (; n > 0; --n, ++p)
    188 				if (strchr(mp, *p) != NULL)
    189 					break;
    190 		} else
    191 			for (; n > 0; --n, ++p)
    192 				if (!ISBLANK((UCHAR_T)*p) &&
    193 				    !ISALNUM((UCHAR_T)*p) &&
    194 				    strchr(mp, *p) != NULL)
    195 					break;
    196 	}
    197 
    198 	/*
    199 	 * If we found a meta character in the string, fork a shell to expand
    200 	 * it.  Unfortunately, this is comparatively slow.  Historically, it
    201 	 * didn't matter much, since users don't enter meta characters as part
    202 	 * of pathnames that frequently.  The addition of filename completion
    203 	 * broke that assumption because it's easy to use.  As a result, lots
    204 	 * folks have complained that the expansion code is too slow.  So, we
    205 	 * detect filename completion as a special case, and do it internally.
    206 	 * Note that this code assumes that the <asterisk> character is the
    207 	 * match-anything meta character.  That feels safe -- if anyone writes
    208 	 * a shell that doesn't follow that convention, I'd suggest giving them
    209 	 * a festive hot-lead enema.
    210 	 */
    211 	switch (n) {
    212 	case 0:
    213 		p = bp + SHELLOFFSET;
    214 		len -= SHELLOFFSET;
    215 		rval = argv_exp3(sp, excp, p, len);
    216 		break;
    217 	case 1:
    218 		if (*p == '*') {
    219 			const char *np1;
    220 			char *d;
    221 			size_t nlen;
    222 
    223 			*p = '\0';
    224 			INT2CHAR(sp, bp + SHELLOFFSET,
    225 				 STRLEN(bp + SHELLOFFSET) + 1, np1, nlen);
    226 			d = strdup(np1);
    227 			rval = argv_lexp(sp, excp, d);
    228 			free (d);
    229 			break;
    230 		}
    231 		/* FALLTHROUGH */
    232 	default:
    233 		if (argv_sexp(sp, &bp, &blen, &len)) {
    234 			rval = 1;
    235 			goto err;
    236 		}
    237 		p = bp;
    238 		rval = argv_exp3(sp, excp, p, len);
    239 		break;
    240 	}
    241 
    242 err:	FREE_SPACEW(sp, bp, blen);
    243 	return (rval);
    244 }
    245 
    246 /*
    247  * argv_exp3 --
    248  *	Take a string and break it up into an argv, which is appended
    249  *	to the argument list.
    250  *
    251  * PUBLIC: int argv_exp3 __P((SCR *, EXCMD *, const CHAR_T *, size_t));
    252  */
    253 int
    254 argv_exp3(SCR *sp, EXCMD *excp, const CHAR_T *cmd, size_t cmdlen)
    255 {
    256 	EX_PRIVATE *exp;
    257 	size_t len;
    258 	ARG_CHAR_T ch;
    259 	int off;
    260 	const CHAR_T *ap;
    261 	CHAR_T *p;
    262 
    263 	for (exp = EXP(sp); cmdlen > 0; ++exp->argsoff) {
    264 		/* Skip any leading whitespace. */
    265 		for (; cmdlen > 0; --cmdlen, ++cmd) {
    266 			ch = (UCHAR_T)*cmd;
    267 			if (!ISBLANK(ch))
    268 				break;
    269 		}
    270 		if (cmdlen == 0)
    271 			break;
    272 
    273 		/*
    274 		 * Determine the length of this whitespace delimited
    275 		 * argument.
    276 		 *
    277 		 * QUOTING NOTE:
    278 		 *
    279 		 * Skip any character preceded by the user's quoting
    280 		 * character.
    281 		 */
    282 		for (ap = cmd, len = 0; cmdlen > 0; ++cmd, --cmdlen, ++len) {
    283 			ch = (UCHAR_T)*cmd;
    284 			if (IS_ESCAPE(sp, excp, ch) && cmdlen > 1) {
    285 				++cmd;
    286 				--cmdlen;
    287 			} else if (ISBLANK(ch))
    288 				break;
    289 		}
    290 
    291 		/*
    292 		 * Copy the argument into place.
    293 		 *
    294 		 * QUOTING NOTE:
    295 		 *
    296 		 * Lose quote chars.
    297 		 */
    298 		argv_alloc(sp, len);
    299 		off = exp->argsoff;
    300 		exp->args[off]->len = len;
    301 		for (p = exp->args[off]->bp; len > 0; --len, *p++ = *ap++)
    302 			if (IS_ESCAPE(sp, excp, *ap))
    303 				++ap;
    304 		*p = '\0';
    305 	}
    306 	excp->argv = exp->args;
    307 	excp->argc = exp->argsoff;
    308 
    309 #if defined(DEBUG) && 0
    310 	for (cnt = 0; cnt < exp->argsoff; ++cnt)
    311 		vtrace(sp, "arg %d: {%s}\n", cnt, exp->argv[cnt]);
    312 #endif
    313 	return (0);
    314 }
    315 
    316 /*
    317  * argv_fexp --
    318  *	Do file name and bang command expansion.
    319  */
    320 static int
    321 argv_fexp(SCR *sp, EXCMD *excp, const CHAR_T *cmd, size_t cmdlen, CHAR_T *p, size_t *lenp, CHAR_T **bpp, size_t *blenp, int is_bang)
    322 {
    323 	EX_PRIVATE *exp;
    324 	char *t;
    325 	size_t blen, len, off, tlen;
    326 	CHAR_T *bp;
    327 	const CHAR_T *wp;
    328 	size_t wlen;
    329 
    330 	/* Replace file name characters. */
    331 	for (bp = *bpp, blen = *blenp, len = *lenp; cmdlen > 0; --cmdlen, ++cmd)
    332 		switch (*cmd) {
    333 		case '!':
    334 			if (!is_bang)
    335 				goto ins_ch;
    336 			exp = EXP(sp);
    337 			if (exp->lastbcomm == NULL) {
    338 				msgq(sp, M_ERR,
    339 				    "115|No previous command to replace \"!\"");
    340 				return (1);
    341 			}
    342 			len += tlen = STRLEN(exp->lastbcomm);
    343 			off = p - bp;
    344 			ADD_SPACE_RETW(sp, bp, blen, len);
    345 			p = bp + off;
    346 			MEMCPY(p, exp->lastbcomm, tlen);
    347 			p += tlen;
    348 			F_SET(excp, E_MODIFY);
    349 			break;
    350 		case '%':
    351 			if ((t = sp->frp->name) == NULL) {
    352 				msgq(sp, M_ERR,
    353 				    "116|No filename to substitute for %%");
    354 				return (1);
    355 			}
    356 			tlen = strlen(t);
    357 			len += tlen;
    358 			off = p - bp;
    359 			ADD_SPACE_RETW(sp, bp, blen, len);
    360 			p = bp + off;
    361 			CHAR2INT(sp, t, tlen, wp, wlen);
    362 			MEMCPY(p, wp, wlen);
    363 			p += wlen;
    364 			F_SET(excp, E_MODIFY);
    365 			break;
    366 		case '#':
    367 			if ((t = sp->alt_name) == NULL) {
    368 				msgq(sp, M_ERR,
    369 				    "117|No filename to substitute for #");
    370 				return (1);
    371 			}
    372 			len += tlen = strlen(t);
    373 			off = p - bp;
    374 			ADD_SPACE_RETW(sp, bp, blen, len);
    375 			p = bp + off;
    376 			CHAR2INT(sp, t, tlen, wp, wlen);
    377 			MEMCPY(p, wp, wlen);
    378 			p += tlen;
    379 			F_SET(excp, E_MODIFY);
    380 			break;
    381 		case '\\':
    382 			/*
    383 			 * QUOTING NOTE:
    384 			 *
    385 			 * Strip any backslashes that protected the file
    386 			 * expansion characters.
    387 			 */
    388 			if (cmdlen > 1 &&
    389 			    (cmd[1] == '%' || cmd[1] == '#' || cmd[1] == '!')) {
    390 				++cmd;
    391 				--cmdlen;
    392 			}
    393 			/* FALLTHROUGH */
    394 		default:
    395 ins_ch:			++len;
    396 			off = p - bp;
    397 			ADD_SPACE_RETW(sp, bp, blen, len);
    398 			p = bp + off;
    399 			*p++ = *cmd;
    400 		}
    401 
    402 	/* Nul termination. */
    403 	++len;
    404 	off = p - bp;
    405 	ADD_SPACE_RETW(sp, bp, blen, len);
    406 	p = bp + off;
    407 	*p = '\0';
    408 
    409 	/* Return the new string length, buffer, buffer length. */
    410 	*lenp = len - 1;
    411 	*bpp = bp;
    412 	*blenp = blen;
    413 	return (0);
    414 }
    415 
    416 /*
    417  * argv_alloc --
    418  *	Make more space for arguments.
    419  */
    420 static int
    421 argv_alloc(SCR *sp, size_t len)
    422 {
    423 	ARGS *ap;
    424 	EX_PRIVATE *exp;
    425 	int cnt, off;
    426 
    427 	/*
    428 	 * Allocate room for another argument, always leaving
    429 	 * enough room for an ARGS structure with a length of 0.
    430 	 */
    431 #define	INCREMENT	20
    432 	exp = EXP(sp);
    433 	off = exp->argsoff;
    434 	if (exp->argscnt == 0 || off + 2 >= exp->argscnt - 1) {
    435 		cnt = exp->argscnt + INCREMENT;
    436 		REALLOC(sp, exp->args, ARGS **, cnt * sizeof(ARGS *));
    437 		if (exp->args == NULL) {
    438 			(void)argv_free(sp);
    439 			goto mem;
    440 		}
    441 		memset(&exp->args[exp->argscnt], 0, INCREMENT * sizeof(ARGS *));
    442 		exp->argscnt = cnt;
    443 	}
    444 
    445 	/* First argument. */
    446 	if (exp->args[off] == NULL) {
    447 		CALLOC(sp, exp->args[off], ARGS *, 1, sizeof(ARGS));
    448 		if (exp->args[off] == NULL)
    449 			goto mem;
    450 	}
    451 
    452 	/* First argument buffer. */
    453 	ap = exp->args[off];
    454 	ap->len = 0;
    455 	if (ap->blen < len + 1) {
    456 		ap->blen = len + 1;
    457 		REALLOC(sp, ap->bp, CHAR_T *, ap->blen * sizeof(CHAR_T));
    458 		if (ap->bp == NULL) {
    459 			ap->bp = NULL;
    460 			ap->blen = 0;
    461 			F_CLR(ap, A_ALLOCATED);
    462 mem:			msgq(sp, M_SYSERR, NULL);
    463 			return (1);
    464 		}
    465 		F_SET(ap, A_ALLOCATED);
    466 	}
    467 
    468 	/* Second argument. */
    469 	if (exp->args[++off] == NULL) {
    470 		CALLOC(sp, exp->args[off], ARGS *, 1, sizeof(ARGS));
    471 		if (exp->args[off] == NULL)
    472 			goto mem;
    473 	}
    474 	/* 0 length serves as end-of-argument marker. */
    475 	exp->args[off]->len = 0;
    476 	return (0);
    477 }
    478 
    479 /*
    480  * argv_free --
    481  *	Free up argument structures.
    482  *
    483  * PUBLIC: int argv_free __P((SCR *));
    484  */
    485 int
    486 argv_free(SCR *sp)
    487 {
    488 	EX_PRIVATE *exp;
    489 	int off;
    490 
    491 	exp = EXP(sp);
    492 	if (exp->args != NULL) {
    493 		for (off = 0; off < exp->argscnt; ++off) {
    494 			if (exp->args[off] == NULL)
    495 				continue;
    496 			if (F_ISSET(exp->args[off], A_ALLOCATED))
    497 				free(exp->args[off]->bp);
    498 			free(exp->args[off]);
    499 		}
    500 		free(exp->args);
    501 	}
    502 	exp->args = NULL;
    503 	exp->argscnt = 0;
    504 	exp->argsoff = 0;
    505 	return (0);
    506 }
    507 
    508 /*
    509  * argv_lexp --
    510  *	Find all file names matching the prefix and append them to the
    511  *	buffer.
    512  */
    513 static int
    514 argv_lexp(SCR *sp, EXCMD *excp, const char *path)
    515 {
    516 	struct dirent *dp;
    517 	DIR *dirp;
    518 	EX_PRIVATE *exp;
    519 	int off;
    520 	size_t dlen, len, nlen;
    521 	const char *dname, *name;
    522 	char *p;
    523 	size_t wlen;
    524 	const CHAR_T *wp;
    525 	CHAR_T *n;
    526 
    527 	exp = EXP(sp);
    528 
    529 	/* Set up the name and length for comparison. */
    530 	if ((p = strrchr(path, '/')) == NULL) {
    531 		dname = ".";
    532 		dlen = 0;
    533 		name = path;
    534 	} else {
    535 		if (p == path) {
    536 			dname = "/";
    537 			dlen = 1;
    538 		} else {
    539 			*p = '\0';
    540 			dname = path;
    541 			dlen = strlen(path);
    542 		}
    543 		name = p + 1;
    544 	}
    545 	nlen = strlen(name);
    546 
    547 	/*
    548 	 * XXX
    549 	 * We don't use the d_namlen field, it's not portable enough; we
    550 	 * assume that d_name is nul terminated, instead.
    551 	 */
    552 	if ((dirp = opendir(dname)) == NULL) {
    553 		msgq_str(sp, M_SYSERR, dname, "%s");
    554 		return (1);
    555 	}
    556 	for (off = exp->argsoff; (dp = readdir(dirp)) != NULL;) {
    557 		if (nlen == 0) {
    558 			if (dp->d_name[0] == '.')
    559 				continue;
    560 			len = strlen(dp->d_name);
    561 		} else {
    562 			len = strlen(dp->d_name);
    563 			if (len < nlen || memcmp(dp->d_name, name, nlen))
    564 				continue;
    565 		}
    566 
    567 		/* Directory + name + slash + null. */
    568 		argv_alloc(sp, dlen + len + 2);
    569 		n = exp->args[exp->argsoff]->bp;
    570 		if (dlen != 0) {
    571 			CHAR2INT(sp, dname, dlen, wp, wlen);
    572 			MEMCPY(n, wp, wlen);
    573 			n += dlen;
    574 			if (dlen > 1 || dname[0] != '/')
    575 				*n++ = '/';
    576 		}
    577 		CHAR2INT(sp, dp->d_name, len + 1, wp, wlen);
    578 		MEMCPY(n, wp, wlen);
    579 		exp->args[exp->argsoff]->len = dlen + len + 1;
    580 		++exp->argsoff;
    581 		excp->argv = exp->args;
    582 		excp->argc = exp->argsoff;
    583 	}
    584 	closedir(dirp);
    585 
    586 	if (off == exp->argsoff) {
    587 		/*
    588 		 * If we didn't find a match, complain that the expansion
    589 		 * failed.  We can't know for certain that's the error, but
    590 		 * it's a good guess, and it matches historic practice.
    591 		 */
    592 		msgq(sp, M_ERR, "304|Shell expansion failed");
    593 		return (1);
    594 	}
    595 	qsort(exp->args + off, exp->argsoff - off, sizeof(ARGS *), argv_comp);
    596 	return (0);
    597 }
    598 
    599 /*
    600  * argv_comp --
    601  *	Alphabetic comparison.
    602  */
    603 static int
    604 argv_comp(const void *a, const void *b)
    605 {
    606 	return (STRCMP((*(const ARGS * const*)a)->bp, (*(const ARGS * const*)b)->bp));
    607 }
    608 
    609 static pid_t
    610 runcmd(SCR *sp, const char *sh_path, const char *sh, const char *np,
    611     int *std_output)
    612 {
    613 	pid_t pid;
    614 	/*
    615 	 * Do the minimal amount of work possible, the shell is going to run
    616 	 * briefly and then exit.  We sincerely hope.
    617 	 */
    618 	switch (pid = vfork()) {
    619 	case -1:			/* Error. */
    620 		msgq(sp, M_SYSERR, "vfork");
    621 		return (pid_t)-1;
    622 	case 0:				/* Utility. */
    623 		/* Redirect stdout to the write end of the pipe. */
    624 		(void)dup2(std_output[1], STDOUT_FILENO);
    625 
    626 		/* Close the utility's file descriptors. */
    627 		(void)close(std_output[0]);
    628 		(void)close(std_output[1]);
    629 		(void)close(STDERR_FILENO);
    630 
    631 		/*
    632 		 * XXX
    633 		 * Assume that all shells have -c.
    634 		 */
    635 		execl(sh_path, sh, "-c", np, (char *)NULL);
    636 		msgq_str(sp, M_SYSERR, sh_path, "118|Error: execl: %s");
    637 		_exit(127);
    638 	default:			/* Parent. */
    639 		/* Close the pipe ends the parent won't use. */
    640 		(void)close(std_output[1]);
    641 		return pid;
    642 	}
    643 }
    644 
    645 /*
    646  * argv_sexp --
    647  *	Fork a shell, pipe a command through it, and read the output into
    648  *	a buffer.
    649  */
    650 static int
    651 argv_sexp(SCR *sp, CHAR_T **bpp, size_t *blenp, size_t *lenp)
    652 {
    653 	enum { SEXP_ERR, SEXP_EXPANSION_ERR, SEXP_OK } rval;
    654 	FILE *ifp;
    655 	pid_t pid;
    656 	size_t blen, len;
    657 	int ch, std_output[2];
    658 	CHAR_T *bp, *p;
    659 	const char *sh, *sh_path;
    660 	const char *np;
    661 	size_t nlen;
    662 
    663 	/* Secure means no shell access. */
    664 	if (O_ISSET(sp, O_SECURE)) {
    665 		msgq(sp, M_ERR,
    666 "289|Shell expansions not supported when the secure edit option is set");
    667 		return (1);
    668 	}
    669 
    670 	sh_path = O_STR(sp, O_SHELL);
    671 	if ((sh = strrchr(sh_path, '/')) == NULL)
    672 		sh = sh_path;
    673 	else
    674 		++sh;
    675 
    676 	/* Local copies of the buffer variables. */
    677 	bp = *bpp;
    678 	blen = *blenp;
    679 
    680 	/*
    681 	 * There are two different processes running through this code, named
    682 	 * the utility (the shell) and the parent. The utility reads standard
    683 	 * input and writes standard output and standard error output.  The
    684 	 * parent writes to the utility, reads its standard output and ignores
    685 	 * its standard error output.  Historically, the standard error output
    686 	 * was discarded by vi, as it produces a lot of noise when file patterns
    687 	 * don't match.
    688 	 *
    689 	 * The parent reads std_output[0], and the utility writes std_output[1].
    690 	 */
    691 	ifp = NULL;
    692 	std_output[0] = std_output[1] = -1;
    693 	if (pipe(std_output) < 0) {
    694 		msgq(sp, M_SYSERR, "pipe");
    695 		return (1);
    696 	}
    697 	if ((ifp = fdopen(std_output[0], "r")) == NULL) {
    698 		msgq(sp, M_SYSERR, "fdopen");
    699 		goto err;
    700 	}
    701 	INT2CHAR(sp, bp, STRLEN(bp)+1, np, nlen);
    702 	pid = runcmd(sp, sh_path, sh, np, std_output);
    703 	if (pid == -1)
    704 		goto err;
    705 
    706 	/*
    707 	 * Copy process standard output into a buffer.
    708 	 *
    709 	 * !!!
    710 	 * Historic vi apparently discarded leading \n and \r's from
    711 	 * the shell output stream.  We don't on the grounds that any
    712 	 * shell that does that is broken.
    713 	 */
    714 	for (p = bp, len = 0, ch = EOF;
    715 	    (ch = getc(ifp)) != EOF; *p++ = ch, blen-=sizeof(CHAR_T), ++len)
    716 		if (blen < 5) {
    717 			ADD_SPACE_GOTOW(sp, bp, *blenp, *blenp * 2);
    718 			p = bp + len;
    719 			blen = *blenp - len;
    720 		}
    721 
    722 	/* Delete the final newline, nul terminate the string. */
    723 	if (p > bp && (p[-1] == '\n' || p[-1] == '\r')) {
    724 		--p;
    725 		--len;
    726 	}
    727 	*p = '\0';
    728 	*lenp = len;
    729 	*bpp = bp;		/* *blenp is already updated. */
    730 
    731 	if (ferror(ifp))
    732 		goto ioerr;
    733 	if (fclose(ifp)) {
    734 ioerr:		msgq_str(sp, M_ERR, sh, "119|I/O error: %s");
    735 alloc_err:	rval = SEXP_ERR;
    736 	} else
    737 		rval = SEXP_OK;
    738 
    739 	/*
    740 	 * Wait for the process.  If the shell process fails (e.g., "echo $q"
    741 	 * where q wasn't a defined variable) or if the returned string has
    742 	 * no characters or only blank characters, (e.g., "echo $5"), complain
    743 	 * that the shell expansion failed.  We can't know for certain that's
    744 	 * the error, but it's a good guess, and it matches historic practice.
    745 	 * This won't catch "echo foo_$5", but that's not a common error and
    746 	 * historic vi didn't catch it either.
    747 	 */
    748 	if (proc_wait(sp, (long)pid, sh, 1, 0))
    749 		rval = SEXP_EXPANSION_ERR;
    750 
    751 	for (p = bp; len; ++p, --len)
    752 		if (!ISBLANK(*p))
    753 			break;
    754 	if (len == 0)
    755 		rval = SEXP_EXPANSION_ERR;
    756 
    757 	if (rval == SEXP_EXPANSION_ERR)
    758 		msgq(sp, M_ERR, "304|Shell expansion failed");
    759 
    760 	return (rval == SEXP_OK ? 0 : 1);
    761 err:	if (ifp != NULL)
    762 		(void)fclose(ifp);
    763 	else if (std_output[0] != -1)
    764 		close(std_output[0]);
    765 	if (std_output[1] != -1)
    766 		close(std_output[0]);
    767 	return 1;
    768 }
    769