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