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
13static 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
33static int argv_alloc(SCR *, size_t);
34static int argv_comp(const void *, const void *);
35static int argv_fexp(SCR *, EXCMD *,
36	CHAR_T *, size_t, CHAR_T *, size_t *, CHAR_T **, size_t *, int);
37static int argv_sexp(SCR *, CHAR_T **, size_t *, size_t *);
38static 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 */
46int
47argv_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 */
66int
67argv_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 */
89int
90argv_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
117ret:	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 */
128int
129argv_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
203err:	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 */
214int
215argv_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 */
282int
283argv_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 */
315static int
316argv_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 */
360static int
361argv_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:
435ins_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 */
460static int
461argv_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);
502mem:			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 */
525int
526argv_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 */
555int
556argv_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 */
654static int
655argv_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 */
665static int
666argv_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");
724err:		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)) {
782ioerr:		msgq_str(sp, M_ERR, sh, "119|I/O error: %s");
783alloc_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 */
817CHAR_T *
818argv_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
870alloc_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 */
880CHAR_T *
881argv_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
912alloc_err:
913	return NULL;
914}
915