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