GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: usr.bin/vi/build/../ex/ex_argv.c Lines: 0 280 0.0 %
Date: 2017-11-07 Branches: 0 394 0.0 %

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