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