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