xref: /freebsd/contrib/nvi/ex/ex_argv.c (revision 0fcececbac9880b092aeb56a41a16f1ec8ac1ae6)
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 
29 static int argv_alloc(SCR *, size_t);
30 static int argv_comp(const void *, const void *);
31 static int argv_fexp(SCR *, EXCMD *,
32 	CHAR_T *, size_t, CHAR_T *, size_t *, CHAR_T **, size_t *, int);
33 static int argv_sexp(SCR *, CHAR_T **, size_t *, size_t *);
34 static 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  */
42 int
argv_init(SCR * sp,EXCMD * excp)43 argv_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  */
62 int
argv_exp0(SCR * sp,EXCMD * excp,CHAR_T * cmd,size_t cmdlen)63 argv_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  */
85 int
argv_exp1(SCR * sp,EXCMD * excp,CHAR_T * cmd,size_t cmdlen,int is_bang)86 argv_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 
113 ret:	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  */
124 int
argv_exp2(SCR * sp,EXCMD * excp,CHAR_T * cmd,size_t cmdlen)125 argv_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 
199 err:	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  */
210 int
argv_exp3(SCR * sp,EXCMD * excp,CHAR_T * cmd,size_t cmdlen)211 argv_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  */
278 int
argv_flt_ex(SCR * sp,EXCMD * excp,CHAR_T * cmd,size_t cmdlen)279 argv_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  */
311 static int
argv_flt_user(SCR * sp,EXCMD * excp,CHAR_T * uname,size_t ulen)312 argv_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  */
356 static int
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)357 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)
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:
431 ins_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  */
456 static int
argv_alloc(SCR * sp,size_t len)457 argv_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);
498 mem:			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  */
521 int
argv_free(SCR * sp)522 argv_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  */
551 int
argv_flt_path(SCR * sp,EXCMD * excp,CHAR_T * path,size_t plen)552 argv_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  */
658 static int
argv_comp(const void * a,const void * b)659 argv_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  */
669 static int
argv_sexp(SCR * sp,CHAR_T ** bpp,size_t * blenp,size_t * lenp)670 argv_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");
728 err:		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)) {
786 ioerr:		msgq_str(sp, M_ERR, sh, "119|I/O error: %s");
787 alloc_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  */
821 CHAR_T *
argv_esc(SCR * sp,EXCMD * excp,CHAR_T * str,size_t len)822 argv_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 
874 alloc_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  */
884 CHAR_T *
argv_uesc(SCR * sp,EXCMD * excp,CHAR_T * str,size_t len)885 argv_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 
916 alloc_err:
917 	return NULL;
918 }
919