119304Speter/*-
219304Speter * Copyright (c) 1993, 1994
319304Speter *	The Regents of the University of California.  All rights reserved.
419304Speter * Copyright (c) 1993, 1994, 1995, 1996
519304Speter *	Keith Bostic.  All rights reserved.
619304Speter *
719304Speter * See the LICENSE file for redistribution information.
819304Speter */
919304Speter
1019304Speter#include "config.h"
1119304Speter
1219304Speter#ifndef lint
13254225Speterstatic const char sccsid[] = "$Id: ex_argv.c,v 11.2 2012/10/09 23:00:29 zy Exp $";
1419304Speter#endif /* not lint */
1519304Speter
1619304Speter#include <sys/types.h>
1719304Speter#include <sys/queue.h>
18254225Speter#include <sys/time.h>
1919304Speter
2019304Speter#include <bitstring.h>
2119304Speter#include <ctype.h>
2219304Speter#include <dirent.h>
2319304Speter#include <errno.h>
2419304Speter#include <limits.h>
25254225Speter#include <pwd.h>
2619304Speter#include <stdio.h>
2719304Speter#include <stdlib.h>
2819304Speter#include <string.h>
2919304Speter#include <unistd.h>
3019304Speter
3119304Speter#include "../common/common.h"
3219304Speter
3319304Speterstatic int argv_alloc __P((SCR *, size_t));
3419304Speterstatic int argv_comp __P((const void *, const void *));
3519304Speterstatic int argv_fexp __P((SCR *, EXCMD *,
36254225Speter	CHAR_T *, size_t, CHAR_T *, size_t *, CHAR_T **, size_t *, int));
37254225Speterstatic int argv_sexp __P((SCR *, CHAR_T **, size_t *, size_t *));
38254225Speterstatic int argv_flt_user __P((SCR *, EXCMD *, CHAR_T *, size_t));
3919304Speter
4019304Speter/*
4119304Speter * argv_init --
4219304Speter *	Build  a prototype arguments list.
4319304Speter *
4419304Speter * PUBLIC: int argv_init __P((SCR *, EXCMD *));
4519304Speter */
4619304Speterint
47254225Speterargv_init(SCR *sp, EXCMD *excp)
4819304Speter{
4919304Speter	EX_PRIVATE *exp;
5019304Speter
5119304Speter	exp = EXP(sp);
5219304Speter	exp->argsoff = 0;
5319304Speter	argv_alloc(sp, 1);
5419304Speter
5519304Speter	excp->argv = exp->args;
5619304Speter	excp->argc = exp->argsoff;
5719304Speter	return (0);
5819304Speter}
5919304Speter
6019304Speter/*
6119304Speter * argv_exp0 --
6219304Speter *	Append a string to the argument list.
6319304Speter *
64254225Speter * PUBLIC: int argv_exp0 __P((SCR *, EXCMD *, CHAR_T *, size_t));
6519304Speter */
6619304Speterint
67254225Speterargv_exp0(SCR *sp, EXCMD *excp, CHAR_T *cmd, size_t cmdlen)
6819304Speter{
6919304Speter	EX_PRIVATE *exp;
7019304Speter
7119304Speter	exp = EXP(sp);
7219304Speter	argv_alloc(sp, cmdlen);
73254225Speter	MEMCPY(exp->args[exp->argsoff]->bp, cmd, cmdlen);
7419304Speter	exp->args[exp->argsoff]->bp[cmdlen] = '\0';
7519304Speter	exp->args[exp->argsoff]->len = cmdlen;
7619304Speter	++exp->argsoff;
7719304Speter	excp->argv = exp->args;
7819304Speter	excp->argc = exp->argsoff;
7919304Speter	return (0);
8019304Speter}
8119304Speter
8219304Speter/*
8319304Speter * argv_exp1 --
8419304Speter *	Do file name expansion on a string, and append it to the
8519304Speter *	argument list.
8619304Speter *
87254225Speter * PUBLIC: int argv_exp1 __P((SCR *, EXCMD *, CHAR_T *, size_t, int));
8819304Speter */
8919304Speterint
90254225Speterargv_exp1(SCR *sp, EXCMD *excp, CHAR_T *cmd, size_t cmdlen, int is_bang)
9119304Speter{
9219304Speter	EX_PRIVATE *exp;
9319304Speter	size_t blen, len;
94254225Speter	CHAR_T *p, *t, *bp;
9519304Speter
96254225Speter	GET_SPACE_RETW(sp, bp, blen, 512);
9719304Speter
9819304Speter	len = 0;
9919304Speter	exp = EXP(sp);
10019304Speter	if (argv_fexp(sp, excp, cmd, cmdlen, bp, &len, &bp, &blen, is_bang)) {
101254225Speter		FREE_SPACEW(sp, bp, blen);
10219304Speter		return (1);
10319304Speter	}
10419304Speter
10519304Speter	/* If it's empty, we're done. */
10619304Speter	if (len != 0) {
10719304Speter		for (p = bp, t = bp + len; p < t; ++p)
108254225Speter			if (!cmdskip(*p))
10919304Speter				break;
11019304Speter		if (p == t)
11119304Speter			goto ret;
11219304Speter	} else
11319304Speter		goto ret;
11419304Speter
11519304Speter	(void)argv_exp0(sp, excp, bp, len);
11619304Speter
117254225Speterret:	FREE_SPACEW(sp, bp, blen);
11819304Speter	return (0);
11919304Speter}
12019304Speter
12119304Speter/*
12219304Speter * argv_exp2 --
12319304Speter *	Do file name and shell expansion on a string, and append it to
12419304Speter *	the argument list.
12519304Speter *
126254225Speter * PUBLIC: int argv_exp2 __P((SCR *, EXCMD *, CHAR_T *, size_t));
12719304Speter */
12819304Speterint
129254225Speterargv_exp2(SCR *sp, EXCMD *excp, CHAR_T *cmd, size_t cmdlen)
13019304Speter{
13119304Speter	size_t blen, len, n;
13219304Speter	int rval;
133254225Speter	CHAR_T *bp, *p;
13419304Speter
135254225Speter	GET_SPACE_RETW(sp, bp, blen, 512);
13619304Speter
137254225Speter#define	SHELLECHO	L("echo ")
138254225Speter#define	SHELLOFFSET	(SIZE(SHELLECHO) - 1)
139254225Speter	MEMCPY(bp, SHELLECHO, SHELLOFFSET);
14019304Speter	p = bp + SHELLOFFSET;
14119304Speter	len = SHELLOFFSET;
14219304Speter
14319304Speter#if defined(DEBUG) && 0
14419304Speter	TRACE(sp, "file_argv: {%.*s}\n", (int)cmdlen, cmd);
14519304Speter#endif
14619304Speter
14719304Speter	if (argv_fexp(sp, excp, cmd, cmdlen, p, &len, &bp, &blen, 0)) {
14819304Speter		rval = 1;
14919304Speter		goto err;
15019304Speter	}
15119304Speter
15219304Speter#if defined(DEBUG) && 0
15319304Speter	TRACE(sp, "before shell: %d: {%s}\n", len, bp);
15419304Speter#endif
15519304Speter
15619304Speter	/*
15719304Speter	 * Do shell word expansion -- it's very, very hard to figure out what
15819304Speter	 * magic characters the user's shell expects.  Historically, it was a
15919304Speter	 * union of v7 shell and csh meta characters.  We match that practice
16019304Speter	 * by default, so ":read \%" tries to read a file named '%'.  It would
16119304Speter	 * make more sense to pass any special characters through the shell,
16219304Speter	 * but then, if your shell was csh, the above example will behave
16319304Speter	 * differently in nvi than in vi.  If you want to get other characters
16419304Speter	 * passed through to your shell, change the "meta" option.
16519304Speter	 */
16619304Speter	if (opts_empty(sp, O_SHELL, 1) || opts_empty(sp, O_SHELLMETA, 1))
16719304Speter		n = 0;
16819304Speter	else {
16919304Speter		p = bp + SHELLOFFSET;
17019304Speter		n = len - SHELLOFFSET;
171254225Speter		for (; n > 0; --n, ++p)
172254225Speter			if (IS_SHELLMETA(sp, *p))
173254225Speter				break;
17419304Speter	}
17519304Speter
17619304Speter	/*
17719304Speter	 * If we found a meta character in the string, fork a shell to expand
17819304Speter	 * it.  Unfortunately, this is comparatively slow.  Historically, it
17919304Speter	 * didn't matter much, since users don't enter meta characters as part
18019304Speter	 * of pathnames that frequently.  The addition of filename completion
181254225Speter	 * broke that assumption because it's easy to use.  To increase the
182254225Speter	 * completion performance, nvi used to have an internal routine to
183254225Speter	 * handle "filename*".  However, the shell special characters does not
184254225Speter	 * limit to "shellmeta", so such a hack breaks historic practice.
185254225Speter	 * After it all, we split the completion logic out from here.
18619304Speter	 */
18719304Speter	switch (n) {
18819304Speter	case 0:
18919304Speter		p = bp + SHELLOFFSET;
19019304Speter		len -= SHELLOFFSET;
19119304Speter		rval = argv_exp3(sp, excp, p, len);
19219304Speter		break;
19319304Speter	default:
19419304Speter		if (argv_sexp(sp, &bp, &blen, &len)) {
19519304Speter			rval = 1;
19619304Speter			goto err;
19719304Speter		}
19819304Speter		p = bp;
19919304Speter		rval = argv_exp3(sp, excp, p, len);
20019304Speter		break;
20119304Speter	}
20219304Speter
203254225Spetererr:	FREE_SPACEW(sp, bp, blen);
20419304Speter	return (rval);
20519304Speter}
20619304Speter
20719304Speter/*
20819304Speter * argv_exp3 --
20919304Speter *	Take a string and break it up into an argv, which is appended
21019304Speter *	to the argument list.
21119304Speter *
212254225Speter * PUBLIC: int argv_exp3 __P((SCR *, EXCMD *, CHAR_T *, size_t));
21319304Speter */
21419304Speterint
215254225Speterargv_exp3(SCR *sp, EXCMD *excp, CHAR_T *cmd, size_t cmdlen)
21619304Speter{
21719304Speter	EX_PRIVATE *exp;
21819304Speter	size_t len;
21919304Speter	int ch, off;
220254225Speter	CHAR_T *ap, *p;
22119304Speter
22219304Speter	for (exp = EXP(sp); cmdlen > 0; ++exp->argsoff) {
22319304Speter		/* Skip any leading whitespace. */
22419304Speter		for (; cmdlen > 0; --cmdlen, ++cmd) {
22519304Speter			ch = *cmd;
226254225Speter			if (!cmdskip(ch))
22719304Speter				break;
22819304Speter		}
22919304Speter		if (cmdlen == 0)
23019304Speter			break;
23119304Speter
23219304Speter		/*
23319304Speter		 * Determine the length of this whitespace delimited
23419304Speter		 * argument.
23519304Speter		 *
23619304Speter		 * QUOTING NOTE:
23719304Speter		 *
23819304Speter		 * Skip any character preceded by the user's quoting
23919304Speter		 * character.
24019304Speter		 */
24119304Speter		for (ap = cmd, len = 0; cmdlen > 0; ++cmd, --cmdlen, ++len) {
24219304Speter			ch = *cmd;
24319304Speter			if (IS_ESCAPE(sp, excp, ch) && cmdlen > 1) {
24419304Speter				++cmd;
24519304Speter				--cmdlen;
246254225Speter			} else if (cmdskip(ch))
24719304Speter				break;
24819304Speter		}
24919304Speter
25019304Speter		/*
25119304Speter		 * Copy the argument into place.
25219304Speter		 *
25319304Speter		 * QUOTING NOTE:
25419304Speter		 *
25519304Speter		 * Lose quote chars.
25619304Speter		 */
25719304Speter		argv_alloc(sp, len);
25819304Speter		off = exp->argsoff;
25919304Speter		exp->args[off]->len = len;
26019304Speter		for (p = exp->args[off]->bp; len > 0; --len, *p++ = *ap++)
26119304Speter			if (IS_ESCAPE(sp, excp, *ap))
26219304Speter				++ap;
26319304Speter		*p = '\0';
26419304Speter	}
26519304Speter	excp->argv = exp->args;
26619304Speter	excp->argc = exp->argsoff;
26719304Speter
26819304Speter#if defined(DEBUG) && 0
26919304Speter	for (cnt = 0; cnt < exp->argsoff; ++cnt)
27019304Speter		TRACE(sp, "arg %d: {%s}\n", cnt, exp->argv[cnt]);
27119304Speter#endif
27219304Speter	return (0);
27319304Speter}
27419304Speter
27519304Speter/*
276254225Speter * argv_flt_ex --
277254225Speter *	Filter the ex commands with a prefix, and append the results to
278254225Speter *	the argument list.
279254225Speter *
280254225Speter * PUBLIC: int argv_flt_ex __P((SCR *, EXCMD *, CHAR_T *, size_t));
281254225Speter */
282254225Speterint
283254225Speterargv_flt_ex(SCR *sp, EXCMD *excp, CHAR_T *cmd, size_t cmdlen)
284254225Speter{
285254225Speter	EX_PRIVATE *exp;
286254225Speter	EXCMDLIST const *cp;
287254225Speter	int off;
288254225Speter	size_t len;
289254225Speter
290254225Speter	exp = EXP(sp);
291254225Speter
292254225Speter	for (off = exp->argsoff, cp = cmds; cp->name != NULL; ++cp) {
293254225Speter		len = STRLEN(cp->name);
294254225Speter		if (cmdlen > 0 &&
295254225Speter		    (cmdlen > len || MEMCMP(cmd, cp->name, cmdlen)))
296254225Speter			continue;
297254225Speter
298254225Speter		/* Copy the matched ex command name. */
299254225Speter		argv_alloc(sp, len + 1);
300254225Speter		MEMCPY(exp->args[exp->argsoff]->bp, cp->name, len + 1);
301254225Speter		exp->args[exp->argsoff]->len = len;
302254225Speter		++exp->argsoff;
303254225Speter		excp->argv = exp->args;
304254225Speter		excp->argc = exp->argsoff;
305254225Speter	}
306254225Speter
307254225Speter	return (0);
308254225Speter}
309254225Speter
310254225Speter/*
311254225Speter * argv_flt_user --
312254225Speter *	Filter the ~user list on the system with a prefix, and append
313254225Speter *	the results to the argument list.
314254225Speter */
315254225Speterstatic int
316254225Speterargv_flt_user(SCR *sp, EXCMD *excp, CHAR_T *uname, size_t ulen)
317254225Speter{
318254225Speter	EX_PRIVATE *exp;
319254225Speter	struct passwd *pw;
320254225Speter	int off;
321254225Speter	char *np;
322254225Speter	size_t len, nlen;
323254225Speter
324254225Speter	exp = EXP(sp);
325254225Speter	off = exp->argsoff;
326254225Speter
327254225Speter	/* The input must come with a leading '~'. */
328254225Speter	INT2CHAR(sp, uname + 1, ulen - 1, np, nlen);
329254225Speter	if ((np = v_strdup(sp, np, nlen)) == NULL)
330254225Speter		return (1);
331254225Speter
332254225Speter	setpwent();
333254225Speter	while ((pw = getpwent()) != NULL) {
334254225Speter		len = strlen(pw->pw_name);
335254225Speter		if (nlen > 0 &&
336254225Speter		    (nlen > len || memcmp(np, pw->pw_name, nlen)))
337254225Speter			continue;
338254225Speter
339254225Speter		/* Copy '~' + the matched user name. */
340254225Speter		CHAR2INT(sp, pw->pw_name, len + 1, uname, ulen);
341254225Speter		argv_alloc(sp, ulen + 1);
342254225Speter		exp->args[exp->argsoff]->bp[0] = '~';
343254225Speter		MEMCPY(exp->args[exp->argsoff]->bp + 1, uname, ulen);
344254225Speter		exp->args[exp->argsoff]->len = ulen;
345254225Speter		++exp->argsoff;
346254225Speter		excp->argv = exp->args;
347254225Speter		excp->argc = exp->argsoff;
348254225Speter	}
349254225Speter	endpwent();
350254225Speter	free(np);
351254225Speter
352254225Speter	qsort(exp->args + off, exp->argsoff - off, sizeof(ARGS *), argv_comp);
353254225Speter	return (0);
354254225Speter}
355254225Speter
356254225Speter/*
35719304Speter * argv_fexp --
35819304Speter *	Do file name and bang command expansion.
35919304Speter */
36019304Speterstatic int
361254225Speterargv_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)
36219304Speter{
36319304Speter	EX_PRIVATE *exp;
364254225Speter	char *t;
36519304Speter	size_t blen, len, off, tlen;
366254225Speter	CHAR_T *bp;
367254225Speter	CHAR_T *wp;
368254225Speter	size_t wlen;
36919304Speter
37019304Speter	/* Replace file name characters. */
37119304Speter	for (bp = *bpp, blen = *blenp, len = *lenp; cmdlen > 0; --cmdlen, ++cmd)
37219304Speter		switch (*cmd) {
37319304Speter		case '!':
37419304Speter			if (!is_bang)
37519304Speter				goto ins_ch;
37619304Speter			exp = EXP(sp);
37719304Speter			if (exp->lastbcomm == NULL) {
37819304Speter				msgq(sp, M_ERR,
37919304Speter				    "115|No previous command to replace \"!\"");
38019304Speter				return (1);
38119304Speter			}
382254225Speter			len += tlen = STRLEN(exp->lastbcomm);
38319304Speter			off = p - bp;
384254225Speter			ADD_SPACE_RETW(sp, bp, blen, len);
38519304Speter			p = bp + off;
386254225Speter			MEMCPY(p, exp->lastbcomm, tlen);
38719304Speter			p += tlen;
38819304Speter			F_SET(excp, E_MODIFY);
38919304Speter			break;
39019304Speter		case '%':
39119304Speter			if ((t = sp->frp->name) == NULL) {
39219304Speter				msgq(sp, M_ERR,
39319304Speter				    "116|No filename to substitute for %%");
39419304Speter				return (1);
39519304Speter			}
39619304Speter			tlen = strlen(t);
39719304Speter			len += tlen;
39819304Speter			off = p - bp;
399254225Speter			ADD_SPACE_RETW(sp, bp, blen, len);
40019304Speter			p = bp + off;
401254225Speter			CHAR2INT(sp, t, tlen, wp, wlen);
402254225Speter			MEMCPY(p, wp, wlen);
403254225Speter			p += wlen;
40419304Speter			F_SET(excp, E_MODIFY);
40519304Speter			break;
40619304Speter		case '#':
40719304Speter			if ((t = sp->alt_name) == NULL) {
40819304Speter				msgq(sp, M_ERR,
40919304Speter				    "117|No filename to substitute for #");
41019304Speter				return (1);
41119304Speter			}
41219304Speter			len += tlen = strlen(t);
41319304Speter			off = p - bp;
414254225Speter			ADD_SPACE_RETW(sp, bp, blen, len);
41519304Speter			p = bp + off;
416254225Speter			CHAR2INT(sp, t, tlen, wp, wlen);
417254225Speter			MEMCPY(p, wp, wlen);
418254225Speter			p += wlen;
41919304Speter			F_SET(excp, E_MODIFY);
42019304Speter			break;
42119304Speter		case '\\':
42219304Speter			/*
42319304Speter			 * QUOTING NOTE:
42419304Speter			 *
42519304Speter			 * Strip any backslashes that protected the file
42619304Speter			 * expansion characters.
42719304Speter			 */
42819304Speter			if (cmdlen > 1 &&
42919304Speter			    (cmd[1] == '%' || cmd[1] == '#' || cmd[1] == '!')) {
43019304Speter				++cmd;
43119304Speter				--cmdlen;
43219304Speter			}
43319304Speter			/* FALLTHROUGH */
43419304Speter		default:
43519304Speterins_ch:			++len;
43619304Speter			off = p - bp;
437254225Speter			ADD_SPACE_RETW(sp, bp, blen, len);
43819304Speter			p = bp + off;
43919304Speter			*p++ = *cmd;
44019304Speter		}
44119304Speter
44219304Speter	/* Nul termination. */
44319304Speter	++len;
44419304Speter	off = p - bp;
445254225Speter	ADD_SPACE_RETW(sp, bp, blen, len);
44619304Speter	p = bp + off;
44719304Speter	*p = '\0';
44819304Speter
44919304Speter	/* Return the new string length, buffer, buffer length. */
45019304Speter	*lenp = len - 1;
45119304Speter	*bpp = bp;
45219304Speter	*blenp = blen;
45319304Speter	return (0);
45419304Speter}
45519304Speter
45619304Speter/*
45719304Speter * argv_alloc --
45819304Speter *	Make more space for arguments.
45919304Speter */
46019304Speterstatic int
461254225Speterargv_alloc(SCR *sp, size_t len)
46219304Speter{
46319304Speter	ARGS *ap;
46419304Speter	EX_PRIVATE *exp;
46519304Speter	int cnt, off;
46619304Speter
46719304Speter	/*
46819304Speter	 * Allocate room for another argument, always leaving
46919304Speter	 * enough room for an ARGS structure with a length of 0.
47019304Speter	 */
47119304Speter#define	INCREMENT	20
47219304Speter	exp = EXP(sp);
47319304Speter	off = exp->argsoff;
47419304Speter	if (exp->argscnt == 0 || off + 2 >= exp->argscnt - 1) {
47519304Speter		cnt = exp->argscnt + INCREMENT;
47619304Speter		REALLOC(sp, exp->args, ARGS **, cnt * sizeof(ARGS *));
47719304Speter		if (exp->args == NULL) {
47819304Speter			(void)argv_free(sp);
47919304Speter			goto mem;
48019304Speter		}
48119304Speter		memset(&exp->args[exp->argscnt], 0, INCREMENT * sizeof(ARGS *));
48219304Speter		exp->argscnt = cnt;
48319304Speter	}
48419304Speter
48519304Speter	/* First argument. */
48619304Speter	if (exp->args[off] == NULL) {
48719304Speter		CALLOC(sp, exp->args[off], ARGS *, 1, sizeof(ARGS));
48819304Speter		if (exp->args[off] == NULL)
48919304Speter			goto mem;
49019304Speter	}
49119304Speter
49219304Speter	/* First argument buffer. */
49319304Speter	ap = exp->args[off];
49419304Speter	ap->len = 0;
49519304Speter	if (ap->blen < len + 1) {
49619304Speter		ap->blen = len + 1;
49719304Speter		REALLOC(sp, ap->bp, CHAR_T *, ap->blen * sizeof(CHAR_T));
49819304Speter		if (ap->bp == NULL) {
49919304Speter			ap->bp = NULL;
50019304Speter			ap->blen = 0;
50119304Speter			F_CLR(ap, A_ALLOCATED);
50219304Spetermem:			msgq(sp, M_SYSERR, NULL);
50319304Speter			return (1);
50419304Speter		}
50519304Speter		F_SET(ap, A_ALLOCATED);
50619304Speter	}
50719304Speter
50819304Speter	/* Second argument. */
50919304Speter	if (exp->args[++off] == NULL) {
51019304Speter		CALLOC(sp, exp->args[off], ARGS *, 1, sizeof(ARGS));
51119304Speter		if (exp->args[off] == NULL)
51219304Speter			goto mem;
51319304Speter	}
51419304Speter	/* 0 length serves as end-of-argument marker. */
51519304Speter	exp->args[off]->len = 0;
51619304Speter	return (0);
51719304Speter}
51819304Speter
51919304Speter/*
52019304Speter * argv_free --
52119304Speter *	Free up argument structures.
52219304Speter *
52319304Speter * PUBLIC: int argv_free __P((SCR *));
52419304Speter */
52519304Speterint
526254225Speterargv_free(SCR *sp)
52719304Speter{
52819304Speter	EX_PRIVATE *exp;
52919304Speter	int off;
53019304Speter
53119304Speter	exp = EXP(sp);
53219304Speter	if (exp->args != NULL) {
53319304Speter		for (off = 0; off < exp->argscnt; ++off) {
53419304Speter			if (exp->args[off] == NULL)
53519304Speter				continue;
53619304Speter			if (F_ISSET(exp->args[off], A_ALLOCATED))
53719304Speter				free(exp->args[off]->bp);
53819304Speter			free(exp->args[off]);
53919304Speter		}
54019304Speter		free(exp->args);
54119304Speter	}
54219304Speter	exp->args = NULL;
54319304Speter	exp->argscnt = 0;
54419304Speter	exp->argsoff = 0;
54519304Speter	return (0);
54619304Speter}
54719304Speter
54819304Speter/*
549254225Speter * argv_flt_path --
55019304Speter *	Find all file names matching the prefix and append them to the
551254225Speter *	argument list.
552254225Speter *
553254225Speter * PUBLIC: int argv_flt_path __P((SCR *, EXCMD *, CHAR_T *, size_t));
55419304Speter */
555254225Speterint
556254225Speterargv_flt_path(SCR *sp, EXCMD *excp, CHAR_T *path, size_t plen)
55719304Speter{
55819304Speter	struct dirent *dp;
55919304Speter	DIR *dirp;
56019304Speter	EX_PRIVATE *exp;
56119304Speter	int off;
56219304Speter	size_t dlen, len, nlen;
563254225Speter	CHAR_T *dname;
564254225Speter	CHAR_T *p, *np, *n;
565254225Speter	char *name, *tp, *epd = NULL;
566254225Speter	CHAR_T *wp;
567254225Speter	size_t wlen;
56819304Speter
56919304Speter	exp = EXP(sp);
57019304Speter
57119304Speter	/* Set up the name and length for comparison. */
572254225Speter	if ((path = v_wstrdup(sp, path, plen)) == NULL)
573254225Speter		return (1);
574254225Speter	if ((p = STRRCHR(path, '/')) == NULL) {
575254225Speter		if (*path == '~') {
576254225Speter			int rc;
577254225Speter
578254225Speter			/* Filter ~user list instead. */
579254225Speter			rc = argv_flt_user(sp, excp, path, plen);
580254225Speter			free(path);
581254225Speter			return (rc);
582254225Speter		}
583254225Speter		dname = L(".");
58419304Speter		dlen = 0;
585254225Speter		np = path;
586254225Speter	} else {
58719304Speter		if (p == path) {
588254225Speter			dname = L("/");
58919304Speter			dlen = 1;
59019304Speter		} else {
59119304Speter			*p = '\0';
59219304Speter			dname = path;
593254225Speter			dlen = p - path;
59419304Speter		}
595254225Speter		np = p + 1;
59619304Speter	}
59719304Speter
598254225Speter	INT2CHAR(sp, dname, dlen + 1, tp, nlen);
599254225Speter	if ((epd = expanduser(tp)) != NULL)
600254225Speter		tp = epd;
601254225Speter	if ((dirp = opendir(tp)) == NULL) {
602254225Speter		free(epd);
603254225Speter		free(path);
60419304Speter		return (1);
60519304Speter	}
606254225Speter	free(epd);
607254225Speter
608254225Speter	INT2CHAR(sp, np, STRLEN(np), tp, nlen);
609254225Speter	if ((name = v_strdup(sp, tp, nlen)) == NULL) {
610254225Speter		free(path);
611254225Speter		return (1);
612254225Speter	}
613254225Speter
61419304Speter	for (off = exp->argsoff; (dp = readdir(dirp)) != NULL;) {
61519304Speter		if (nlen == 0) {
61619304Speter			if (dp->d_name[0] == '.')
61719304Speter				continue;
618254225Speter			len = dp->d_namlen;
61919304Speter		} else {
620254225Speter			len = dp->d_namlen;
62119304Speter			if (len < nlen || memcmp(dp->d_name, name, nlen))
62219304Speter				continue;
62319304Speter		}
62419304Speter
62519304Speter		/* Directory + name + slash + null. */
626254225Speter		CHAR2INT(sp, dp->d_name, len + 1, wp, wlen);
627254225Speter		argv_alloc(sp, dlen + wlen + 1);
628254225Speter		n = exp->args[exp->argsoff]->bp;
62919304Speter		if (dlen != 0) {
630254225Speter			MEMCPY(n, dname, dlen);
631254225Speter			n += dlen;
63219304Speter			if (dlen > 1 || dname[0] != '/')
633254225Speter				*n++ = '/';
634254225Speter			exp->args[exp->argsoff]->len = dlen + 1;
63519304Speter		}
636254225Speter		MEMCPY(n, wp, wlen);
637254225Speter		exp->args[exp->argsoff]->len += wlen - 1;
63819304Speter		++exp->argsoff;
63919304Speter		excp->argv = exp->args;
64019304Speter		excp->argc = exp->argsoff;
64119304Speter	}
64219304Speter	closedir(dirp);
643254225Speter	free(name);
644254225Speter	free(path);
64519304Speter
64619304Speter	qsort(exp->args + off, exp->argsoff - off, sizeof(ARGS *), argv_comp);
64719304Speter	return (0);
64819304Speter}
64919304Speter
65019304Speter/*
65119304Speter * argv_comp --
65219304Speter *	Alphabetic comparison.
65319304Speter */
65419304Speterstatic int
655254225Speterargv_comp(const void *a, const void *b)
65619304Speter{
657254225Speter	return (STRCMP((*(ARGS **)a)->bp, (*(ARGS **)b)->bp));
65819304Speter}
65919304Speter
66019304Speter/*
66119304Speter * argv_sexp --
66219304Speter *	Fork a shell, pipe a command through it, and read the output into
66319304Speter *	a buffer.
66419304Speter */
66519304Speterstatic int
666254225Speterargv_sexp(SCR *sp, CHAR_T **bpp, size_t *blenp, size_t *lenp)
66719304Speter{
66819304Speter	enum { SEXP_ERR, SEXP_EXPANSION_ERR, SEXP_OK } rval;
66919304Speter	FILE *ifp;
67019304Speter	pid_t pid;
67119304Speter	size_t blen, len;
67219304Speter	int ch, std_output[2];
673254225Speter	CHAR_T *bp, *p;
674254225Speter	char *sh, *sh_path;
675254225Speter	char *np;
676254225Speter	size_t nlen;
67719304Speter
67819304Speter	/* Secure means no shell access. */
67919304Speter	if (O_ISSET(sp, O_SECURE)) {
68019304Speter		msgq(sp, M_ERR,
68119304Speter"289|Shell expansions not supported when the secure edit option is set");
68219304Speter		return (1);
68319304Speter	}
68419304Speter
68519304Speter	sh_path = O_STR(sp, O_SHELL);
68619304Speter	if ((sh = strrchr(sh_path, '/')) == NULL)
68719304Speter		sh = sh_path;
68819304Speter	else
68919304Speter		++sh;
69019304Speter
69119304Speter	/* Local copies of the buffer variables. */
69219304Speter	bp = *bpp;
69319304Speter	blen = *blenp;
69419304Speter
69519304Speter	/*
69619304Speter	 * There are two different processes running through this code, named
69719304Speter	 * the utility (the shell) and the parent. The utility reads standard
69819304Speter	 * input and writes standard output and standard error output.  The
69919304Speter	 * parent writes to the utility, reads its standard output and ignores
70019304Speter	 * its standard error output.  Historically, the standard error output
70119304Speter	 * was discarded by vi, as it produces a lot of noise when file patterns
70219304Speter	 * don't match.
70319304Speter	 *
70419304Speter	 * The parent reads std_output[0], and the utility writes std_output[1].
70519304Speter	 */
70619304Speter	ifp = NULL;
70719304Speter	std_output[0] = std_output[1] = -1;
70819304Speter	if (pipe(std_output) < 0) {
70919304Speter		msgq(sp, M_SYSERR, "pipe");
71019304Speter		return (1);
71119304Speter	}
71219304Speter	if ((ifp = fdopen(std_output[0], "r")) == NULL) {
71319304Speter		msgq(sp, M_SYSERR, "fdopen");
71419304Speter		goto err;
71519304Speter	}
71619304Speter
71719304Speter	/*
71819304Speter	 * Do the minimal amount of work possible, the shell is going to run
71919304Speter	 * briefly and then exit.  We sincerely hope.
72019304Speter	 */
72119304Speter	switch (pid = vfork()) {
72219304Speter	case -1:			/* Error. */
72319304Speter		msgq(sp, M_SYSERR, "vfork");
72419304Spetererr:		if (ifp != NULL)
72519304Speter			(void)fclose(ifp);
72619304Speter		else if (std_output[0] != -1)
72719304Speter			close(std_output[0]);
72819304Speter		if (std_output[1] != -1)
72919304Speter			close(std_output[0]);
73019304Speter		return (1);
73119304Speter	case 0:				/* Utility. */
73219304Speter		/* Redirect stdout to the write end of the pipe. */
73319304Speter		(void)dup2(std_output[1], STDOUT_FILENO);
73419304Speter
73519304Speter		/* Close the utility's file descriptors. */
73619304Speter		(void)close(std_output[0]);
73719304Speter		(void)close(std_output[1]);
73819304Speter		(void)close(STDERR_FILENO);
73919304Speter
74019304Speter		/*
74119304Speter		 * XXX
74219304Speter		 * Assume that all shells have -c.
74319304Speter		 */
744254225Speter		INT2CHAR(sp, bp, STRLEN(bp)+1, np, nlen);
745254225Speter		execl(sh_path, sh, "-c", np, (char *)NULL);
74619304Speter		msgq_str(sp, M_SYSERR, sh_path, "118|Error: execl: %s");
74719304Speter		_exit(127);
74819304Speter	default:			/* Parent. */
74919304Speter		/* Close the pipe ends the parent won't use. */
75019304Speter		(void)close(std_output[1]);
75119304Speter		break;
75219304Speter	}
75319304Speter
75419304Speter	/*
75519304Speter	 * Copy process standard output into a buffer.
75619304Speter	 *
75719304Speter	 * !!!
75819304Speter	 * Historic vi apparently discarded leading \n and \r's from
75919304Speter	 * the shell output stream.  We don't on the grounds that any
76019304Speter	 * shell that does that is broken.
76119304Speter	 */
76219304Speter	for (p = bp, len = 0, ch = EOF;
763254225Speter	    (ch = GETC(ifp)) != EOF; *p++ = ch, blen-=sizeof(CHAR_T), ++len)
76419304Speter		if (blen < 5) {
765254225Speter			ADD_SPACE_GOTOW(sp, bp, *blenp, *blenp * 2);
76619304Speter			p = bp + len;
76719304Speter			blen = *blenp - len;
76819304Speter		}
76919304Speter
77019304Speter	/* Delete the final newline, nul terminate the string. */
77119304Speter	if (p > bp && (p[-1] == '\n' || p[-1] == '\r')) {
77219304Speter		--p;
77319304Speter		--len;
77419304Speter	}
77519304Speter	*p = '\0';
77619304Speter	*lenp = len;
77719304Speter	*bpp = bp;		/* *blenp is already updated. */
77819304Speter
77919304Speter	if (ferror(ifp))
78019304Speter		goto ioerr;
78119304Speter	if (fclose(ifp)) {
78219304Speterioerr:		msgq_str(sp, M_ERR, sh, "119|I/O error: %s");
78319304Speteralloc_err:	rval = SEXP_ERR;
78419304Speter	} else
78519304Speter		rval = SEXP_OK;
78619304Speter
78719304Speter	/*
78819304Speter	 * Wait for the process.  If the shell process fails (e.g., "echo $q"
78919304Speter	 * where q wasn't a defined variable) or if the returned string has
79019304Speter	 * no characters or only blank characters, (e.g., "echo $5"), complain
79119304Speter	 * that the shell expansion failed.  We can't know for certain that's
79219304Speter	 * the error, but it's a good guess, and it matches historic practice.
79319304Speter	 * This won't catch "echo foo_$5", but that's not a common error and
79419304Speter	 * historic vi didn't catch it either.
79519304Speter	 */
79619304Speter	if (proc_wait(sp, (long)pid, sh, 1, 0))
79719304Speter		rval = SEXP_EXPANSION_ERR;
79819304Speter
79919304Speter	for (p = bp; len; ++p, --len)
800254225Speter		if (!cmdskip(*p))
80119304Speter			break;
80219304Speter	if (len == 0)
80319304Speter		rval = SEXP_EXPANSION_ERR;
80419304Speter
80519304Speter	if (rval == SEXP_EXPANSION_ERR)
80619304Speter		msgq(sp, M_ERR, "304|Shell expansion failed");
80719304Speter
80819304Speter	return (rval == SEXP_OK ? 0 : 1);
80919304Speter}
810254225Speter
811254225Speter/*
812254225Speter * argv_esc --
813254225Speter *	Escape a string into an ex and shell argument.
814254225Speter *
815254225Speter * PUBLIC: CHAR_T *argv_esc __P((SCR *, EXCMD *, CHAR_T *, size_t));
816254225Speter */
817254225SpeterCHAR_T *
818254225Speterargv_esc(SCR *sp, EXCMD *excp, CHAR_T *str, size_t len)
819254225Speter{
820254225Speter	size_t blen, off;
821254225Speter	CHAR_T *bp, *p;
822254225Speter	int ch;
823254225Speter
824254225Speter	GET_SPACE_GOTOW(sp, bp, blen, len + 1);
825254225Speter
826254225Speter	/*
827254225Speter	 * Leaving the first '~' unescaped causes the user to need a
828254225Speter	 * "./" prefix to edit a file which really starts with a '~'.
829254225Speter	 * However, the file completion happens to not work for these
830254225Speter	 * files without the prefix.
831254225Speter	 *
832254225Speter	 * All ex expansion characters, "!%#", are double escaped.
833254225Speter	 */
834254225Speter	for (p = bp; len > 0; ++str, --len) {
835254225Speter		ch = *str;
836254225Speter		off = p - bp;
837254225Speter		if (blen / sizeof(CHAR_T) - off < 3) {
838254225Speter			ADD_SPACE_GOTOW(sp, bp, blen, off + 3);
839254225Speter			p = bp + off;
840254225Speter		}
841254225Speter		if (cmdskip(ch) || ch == '\n' ||
842254225Speter		    IS_ESCAPE(sp, excp, ch))			/* Ex. */
843254225Speter			*p++ = CH_LITERAL;
844254225Speter		else switch (ch) {
845254225Speter		case '~':					/* ~user. */
846254225Speter			if (p != bp)
847254225Speter				*p++ = '\\';
848254225Speter			break;
849254225Speter		case '+':					/* Ex +cmd. */
850254225Speter			if (p == bp)
851254225Speter				*p++ = '\\';
852254225Speter			break;
853254225Speter		case '!': case '%': case '#':			/* Ex exp. */
854254225Speter			*p++ = '\\';
855254225Speter			*p++ = '\\';
856254225Speter			break;
857254225Speter		case ',': case '-': case '.': case '/':		/* Safe. */
858254225Speter		case ':': case '=': case '@': case '_':
859254225Speter			break;
860254225Speter		default:					/* Unsafe. */
861254225Speter			if (isascii(ch) && !isalnum(ch))
862254225Speter				*p++ = '\\';
863254225Speter		}
864254225Speter		*p++ = ch;
865254225Speter	}
866254225Speter	*p = '\0';
867254225Speter
868254225Speter	return bp;
869254225Speter
870254225Speteralloc_err:
871254225Speter	return NULL;
872254225Speter}
873254225Speter
874254225Speter/*
875254225Speter * argv_uesc --
876254225Speter *	Unescape an escaped ex and shell argument.
877254225Speter *
878254225Speter * PUBLIC: CHAR_T *argv_uesc __P((SCR *, EXCMD *, CHAR_T *, size_t));
879254225Speter */
880254225SpeterCHAR_T *
881254225Speterargv_uesc(SCR *sp, EXCMD *excp, CHAR_T *str, size_t len)
882254225Speter{
883254225Speter	size_t blen;
884254225Speter	CHAR_T *bp, *p;
885254225Speter
886254225Speter	GET_SPACE_GOTOW(sp, bp, blen, len + 1);
887254225Speter
888254225Speter	for (p = bp; len > 0; ++str, --len) {
889254225Speter		if (IS_ESCAPE(sp, excp, *str)) {
890254225Speter			if (--len < 1)
891254225Speter				break;
892254225Speter			++str;
893254225Speter		} else if (*str == '\\') {
894254225Speter			if (--len < 1)
895254225Speter				break;
896254225Speter			++str;
897254225Speter
898254225Speter			/* Check for double escaping. */
899254225Speter			if (*str == '\\' && len > 1)
900254225Speter				switch (str[1]) {
901254225Speter				case '!': case '%': case '#':
902254225Speter					++str;
903254225Speter					--len;
904254225Speter				}
905254225Speter		}
906254225Speter		*p++ = *str;
907254225Speter	}
908254225Speter	*p = '\0';
909254225Speter
910254225Speter	return bp;
911254225Speter
912254225Speteralloc_err:
913254225Speter	return NULL;
914254225Speter}
915