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