vendor/nvi2: upgrade from 2.1.3 to 2.2.0
[dragonfly.git] / contrib / nvi2 / 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 #include <sys/types.h>
13 #include <sys/queue.h>
14 #include <sys/time.h>
15
16 #include <bitstring.h>
17 #include <ctype.h>
18 #include <dirent.h>
19 #include <errno.h>
20 #include <limits.h>
21 #include <pwd.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <unistd.h>
26
27 #include "../common/common.h"
28
29 static int argv_alloc(SCR *, size_t);
30 static int argv_comp(const void *, const void *);
31 static int argv_fexp(SCR *, EXCMD *,
32         CHAR_T *, size_t, CHAR_T *, size_t *, CHAR_T **, size_t *, int);
33 static int argv_sexp(SCR *, CHAR_T **, size_t *, size_t *);
34 static int argv_flt_user(SCR *, EXCMD *, CHAR_T *, size_t);
35
36 /*
37  * argv_init --
38  *      Build  a prototype arguments list.
39  *
40  * PUBLIC: int argv_init(SCR *, EXCMD *);
41  */
42 int
43 argv_init(SCR *sp, EXCMD *excp)
44 {
45         EX_PRIVATE *exp;
46
47         exp = EXP(sp);
48         exp->argsoff = 0;
49         argv_alloc(sp, 1);
50
51         excp->argv = exp->args;
52         excp->argc = exp->argsoff;
53         return (0);
54 }
55
56 /*
57  * argv_exp0 --
58  *      Append a string to the argument list.
59  *
60  * PUBLIC: int argv_exp0(SCR *, EXCMD *, CHAR_T *, size_t);
61  */
62 int
63 argv_exp0(SCR *sp, EXCMD *excp, CHAR_T *cmd, size_t cmdlen)
64 {
65         EX_PRIVATE *exp;
66
67         exp = EXP(sp);
68         argv_alloc(sp, cmdlen);
69         MEMCPY(exp->args[exp->argsoff]->bp, cmd, cmdlen);
70         exp->args[exp->argsoff]->bp[cmdlen] = '\0';
71         exp->args[exp->argsoff]->len = cmdlen;
72         ++exp->argsoff;
73         excp->argv = exp->args;
74         excp->argc = exp->argsoff;
75         return (0);
76 }
77
78 /*
79  * argv_exp1 --
80  *      Do file name expansion on a string, and append it to the
81  *      argument list.
82  *
83  * PUBLIC: int argv_exp1(SCR *, EXCMD *, CHAR_T *, size_t, int);
84  */
85 int
86 argv_exp1(SCR *sp, EXCMD *excp, CHAR_T *cmd, size_t cmdlen, int is_bang)
87 {
88         EX_PRIVATE *exp;
89         size_t blen, len;
90         CHAR_T *p, *t, *bp;
91
92         GET_SPACE_RETW(sp, bp, blen, 512);
93
94         len = 0;
95         exp = EXP(sp);
96         if (argv_fexp(sp, excp, cmd, cmdlen, bp, &len, &bp, &blen, is_bang)) {
97                 FREE_SPACEW(sp, bp, blen);
98                 return (1);
99         }
100
101         /* If it's empty, we're done. */
102         if (len != 0) {
103                 for (p = bp, t = bp + len; p < t; ++p)
104                         if (!cmdskip(*p))
105                                 break;
106                 if (p == t)
107                         goto ret;
108         } else
109                 goto ret;
110
111         (void)argv_exp0(sp, excp, bp, len);
112
113 ret:    FREE_SPACEW(sp, bp, blen);
114         return (0);
115 }
116
117 /*
118  * argv_exp2 --
119  *      Do file name and shell expansion on a string, and append it to
120  *      the argument list.
121  *
122  * PUBLIC: int argv_exp2(SCR *, EXCMD *, CHAR_T *, size_t);
123  */
124 int
125 argv_exp2(SCR *sp, EXCMD *excp, CHAR_T *cmd, size_t cmdlen)
126 {
127         size_t blen, len, n;
128         int rval;
129         CHAR_T *bp, *p;
130
131         GET_SPACE_RETW(sp, bp, blen, 512);
132
133 #define SHELLECHO       L("echo ")
134 #define SHELLOFFSET     (SIZE(SHELLECHO) - 1)
135         MEMCPY(bp, SHELLECHO, SHELLOFFSET);
136         p = bp + SHELLOFFSET;
137         len = SHELLOFFSET;
138
139 #if defined(DEBUG) && 0
140         TRACE(sp, "file_argv: {%.*s}\n", (int)cmdlen, cmd);
141 #endif
142
143         if (argv_fexp(sp, excp, cmd, cmdlen, p, &len, &bp, &blen, 0)) {
144                 rval = 1;
145                 goto err;
146         }
147
148 #if defined(DEBUG) && 0
149         TRACE(sp, "before shell: %d: {%s}\n", len, bp);
150 #endif
151
152         /*
153          * Do shell word expansion -- it's very, very hard to figure out what
154          * magic characters the user's shell expects.  Historically, it was a
155          * union of v7 shell and csh meta characters.  We match that practice
156          * by default, so ":read \%" tries to read a file named '%'.  It would
157          * make more sense to pass any special characters through the shell,
158          * but then, if your shell was csh, the above example will behave
159          * differently in nvi than in vi.  If you want to get other characters
160          * passed through to your shell, change the "meta" option.
161          */
162         if (opts_empty(sp, O_SHELL, 1) || opts_empty(sp, O_SHELLMETA, 1))
163                 n = 0;
164         else {
165                 p = bp + SHELLOFFSET;
166                 n = len - SHELLOFFSET;
167                 for (; n > 0; --n, ++p)
168                         if (IS_SHELLMETA(sp, *p))
169                                 break;
170         }
171
172         /*
173          * If we found a meta character in the string, fork a shell to expand
174          * it.  Unfortunately, this is comparatively slow.  Historically, it
175          * didn't matter much, since users don't enter meta characters as part
176          * of pathnames that frequently.  The addition of filename completion
177          * broke that assumption because it's easy to use.  To increase the
178          * completion performance, nvi used to have an internal routine to
179          * handle "filename*".  However, the shell special characters does not
180          * limit to "shellmeta", so such a hack breaks historic practice.
181          * After it all, we split the completion logic out from here.
182          */
183         switch (n) {
184         case 0:
185                 p = bp + SHELLOFFSET;
186                 len -= SHELLOFFSET;
187                 rval = argv_exp3(sp, excp, p, len);
188                 break;
189         default:
190                 if (argv_sexp(sp, &bp, &blen, &len)) {
191                         rval = 1;
192                         goto err;
193                 }
194                 p = bp;
195                 rval = argv_exp3(sp, excp, p, len);
196                 break;
197         }
198
199 err:    FREE_SPACEW(sp, bp, blen);
200         return (rval);
201 }
202
203 /*
204  * argv_exp3 --
205  *      Take a string and break it up into an argv, which is appended
206  *      to the argument list.
207  *
208  * PUBLIC: int argv_exp3(SCR *, EXCMD *, CHAR_T *, size_t);
209  */
210 int
211 argv_exp3(SCR *sp, EXCMD *excp, CHAR_T *cmd, size_t cmdlen)
212 {
213         EX_PRIVATE *exp;
214         size_t len;
215         int ch, off;
216         CHAR_T *ap, *p;
217
218         for (exp = EXP(sp); cmdlen > 0; ++exp->argsoff) {
219                 /* Skip any leading whitespace. */
220                 for (; cmdlen > 0; --cmdlen, ++cmd) {
221                         ch = *cmd;
222                         if (!cmdskip(ch))
223                                 break;
224                 }
225                 if (cmdlen == 0)
226                         break;
227
228                 /*
229                  * Determine the length of this whitespace delimited
230                  * argument.
231                  *
232                  * QUOTING NOTE:
233                  *
234                  * Skip any character preceded by the user's quoting
235                  * character.
236                  */
237                 for (ap = cmd, len = 0; cmdlen > 0; ++cmd, --cmdlen, ++len) {
238                         ch = *cmd;
239                         if (IS_ESCAPE(sp, excp, ch) && cmdlen > 1) {
240                                 ++cmd;
241                                 --cmdlen;
242                         } else if (cmdskip(ch))
243                                 break;
244                 }
245
246                 /*
247                  * Copy the argument into place.
248                  *
249                  * QUOTING NOTE:
250                  *
251                  * Lose quote chars.
252                  */
253                 argv_alloc(sp, len);
254                 off = exp->argsoff;
255                 exp->args[off]->len = len;
256                 for (p = exp->args[off]->bp; len > 0; --len, *p++ = *ap++)
257                         if (IS_ESCAPE(sp, excp, *ap))
258                                 ++ap;
259                 *p = '\0';
260         }
261         excp->argv = exp->args;
262         excp->argc = exp->argsoff;
263
264 #if defined(DEBUG) && 0
265         for (cnt = 0; cnt < exp->argsoff; ++cnt)
266                 TRACE(sp, "arg %d: {%s}\n", cnt, exp->argv[cnt]);
267 #endif
268         return (0);
269 }
270
271 /*
272  * argv_flt_ex --
273  *      Filter the ex commands with a prefix, and append the results to
274  *      the argument list.
275  *
276  * PUBLIC: int argv_flt_ex(SCR *, EXCMD *, CHAR_T *, size_t);
277  */
278 int
279 argv_flt_ex(SCR *sp, EXCMD *excp, CHAR_T *cmd, size_t cmdlen)
280 {
281         EX_PRIVATE *exp;
282         EXCMDLIST const *cp;
283         int off;
284         size_t len;
285
286         exp = EXP(sp);
287
288         for (off = exp->argsoff, cp = cmds; cp->name != NULL; ++cp) {
289                 len = STRLEN(cp->name);
290                 if (cmdlen > 0 &&
291                     (cmdlen > len || MEMCMP(cmd, cp->name, cmdlen)))
292                         continue;
293
294                 /* Copy the matched ex command name. */
295                 argv_alloc(sp, len + 1);
296                 MEMCPY(exp->args[exp->argsoff]->bp, cp->name, len + 1);
297                 exp->args[exp->argsoff]->len = len;
298                 ++exp->argsoff;
299                 excp->argv = exp->args;
300                 excp->argc = exp->argsoff;
301         }
302
303         return (0);
304 }
305
306 /*
307  * argv_flt_user --
308  *      Filter the ~user list on the system with a prefix, and append
309  *      the results to the argument list.
310  */
311 static int
312 argv_flt_user(SCR *sp, EXCMD *excp, CHAR_T *uname, size_t ulen)
313 {
314         EX_PRIVATE *exp;
315         struct passwd *pw;
316         int off;
317         char *np;
318         size_t len, nlen;
319
320         exp = EXP(sp);
321         off = exp->argsoff;
322
323         /* The input must come with a leading '~'. */
324         INT2CHAR(sp, uname + 1, ulen - 1, np, nlen);
325         if ((np = v_strdup(sp, np, nlen)) == NULL)
326                 return (1);
327
328         setpwent();
329         while ((pw = getpwent()) != NULL) {
330                 len = strlen(pw->pw_name);
331                 if (nlen > 0 &&
332                     (nlen > len || memcmp(np, pw->pw_name, nlen)))
333                         continue;
334
335                 /* Copy '~' + the matched user name. */
336                 CHAR2INT(sp, pw->pw_name, len + 1, uname, ulen);
337                 argv_alloc(sp, ulen + 1);
338                 exp->args[exp->argsoff]->bp[0] = '~';
339                 MEMCPY(exp->args[exp->argsoff]->bp + 1, uname, ulen);
340                 exp->args[exp->argsoff]->len = ulen;
341                 ++exp->argsoff;
342                 excp->argv = exp->args;
343                 excp->argc = exp->argsoff;
344         }
345         endpwent();
346         free(np);
347
348         qsort(exp->args + off, exp->argsoff - off, sizeof(ARGS *), argv_comp);
349         return (0);
350 }
351
352 /*
353  * argv_fexp --
354  *      Do file name and bang command expansion.
355  */
356 static int
357 argv_fexp(SCR *sp, EXCMD *excp, CHAR_T *cmd, size_t cmdlen, CHAR_T *p, size_t *lenp, CHAR_T **bpp, size_t *blenp, int is_bang)
358 {
359         EX_PRIVATE *exp;
360         char *t;
361         size_t blen, len, off, tlen;
362         CHAR_T *bp;
363         CHAR_T *wp;
364         size_t wlen;
365
366         /* Replace file name characters. */
367         for (bp = *bpp, blen = *blenp, len = *lenp; cmdlen > 0; --cmdlen, ++cmd)
368                 switch (*cmd) {
369                 case '!':
370                         if (!is_bang)
371                                 goto ins_ch;
372                         exp = EXP(sp);
373                         if (exp->lastbcomm == NULL) {
374                                 msgq(sp, M_ERR,
375                                     "115|No previous command to replace \"!\"");
376                                 return (1);
377                         }
378                         len += tlen = STRLEN(exp->lastbcomm);
379                         off = p - bp;
380                         ADD_SPACE_RETW(sp, bp, blen, len);
381                         p = bp + off;
382                         MEMCPY(p, exp->lastbcomm, tlen);
383                         p += tlen;
384                         F_SET(excp, E_MODIFY);
385                         break;
386                 case '%':
387                         if ((t = sp->frp->name) == NULL) {
388                                 msgq(sp, M_ERR,
389                                     "116|No filename to substitute for %%");
390                                 return (1);
391                         }
392                         tlen = strlen(t);
393                         len += tlen;
394                         off = p - bp;
395                         ADD_SPACE_RETW(sp, bp, blen, len);
396                         p = bp + off;
397                         CHAR2INT(sp, t, tlen, wp, wlen);
398                         MEMCPY(p, wp, wlen);
399                         p += wlen;
400                         F_SET(excp, E_MODIFY);
401                         break;
402                 case '#':
403                         if ((t = sp->alt_name) == NULL) {
404                                 msgq(sp, M_ERR,
405                                     "117|No filename to substitute for #");
406                                 return (1);
407                         }
408                         len += tlen = strlen(t);
409                         off = p - bp;
410                         ADD_SPACE_RETW(sp, bp, blen, len);
411                         p = bp + off;
412                         CHAR2INT(sp, t, tlen, wp, wlen);
413                         MEMCPY(p, wp, wlen);
414                         p += wlen;
415                         F_SET(excp, E_MODIFY);
416                         break;
417                 case '\\':
418                         /*
419                          * QUOTING NOTE:
420                          *
421                          * Strip any backslashes that protected the file
422                          * expansion characters.
423                          */
424                         if (cmdlen > 1 &&
425                             (cmd[1] == '%' || cmd[1] == '#' || cmd[1] == '!')) {
426                                 ++cmd;
427                                 --cmdlen;
428                         }
429                         /* FALLTHROUGH */
430                 default:
431 ins_ch:                 ++len;
432                         off = p - bp;
433                         ADD_SPACE_RETW(sp, bp, blen, len);
434                         p = bp + off;
435                         *p++ = *cmd;
436                 }
437
438         /* Nul termination. */
439         ++len;
440         off = p - bp;
441         ADD_SPACE_RETW(sp, bp, blen, len);
442         p = bp + off;
443         *p = '\0';
444
445         /* Return the new string length, buffer, buffer length. */
446         *lenp = len - 1;
447         *bpp = bp;
448         *blenp = blen;
449         return (0);
450 }
451
452 /*
453  * argv_alloc --
454  *      Make more space for arguments.
455  */
456 static int
457 argv_alloc(SCR *sp, size_t len)
458 {
459         ARGS *ap;
460         EX_PRIVATE *exp;
461         int cnt, off;
462
463         /*
464          * Allocate room for another argument, always leaving
465          * enough room for an ARGS structure with a length of 0.
466          */
467 #define INCREMENT       20
468         exp = EXP(sp);
469         off = exp->argsoff;
470         if (exp->argscnt == 0 || off + 2 >= exp->argscnt - 1) {
471                 cnt = exp->argscnt + INCREMENT;
472                 REALLOC(sp, exp->args, ARGS **, cnt * sizeof(ARGS *));
473                 if (exp->args == NULL) {
474                         (void)argv_free(sp);
475                         goto mem;
476                 }
477                 memset(&exp->args[exp->argscnt], 0, INCREMENT * sizeof(ARGS *));
478                 exp->argscnt = cnt;
479         }
480
481         /* First argument. */
482         if (exp->args[off] == NULL) {
483                 CALLOC(sp, exp->args[off], 1, sizeof(ARGS));
484                 if (exp->args[off] == NULL)
485                         goto mem;
486         }
487
488         /* First argument buffer. */
489         ap = exp->args[off];
490         ap->len = 0;
491         if (ap->blen < len + 1) {
492                 ap->blen = len + 1;
493                 REALLOC(sp, ap->bp, CHAR_T *, ap->blen * sizeof(CHAR_T));
494                 if (ap->bp == NULL) {
495                         ap->bp = NULL;
496                         ap->blen = 0;
497                         F_CLR(ap, A_ALLOCATED);
498 mem:                    msgq(sp, M_SYSERR, NULL);
499                         return (1);
500                 }
501                 F_SET(ap, A_ALLOCATED);
502         }
503
504         /* Second argument. */
505         if (exp->args[++off] == NULL) {
506                 CALLOC(sp, exp->args[off], 1, sizeof(ARGS));
507                 if (exp->args[off] == NULL)
508                         goto mem;
509         }
510         /* 0 length serves as end-of-argument marker. */
511         exp->args[off]->len = 0;
512         return (0);
513 }
514
515 /*
516  * argv_free --
517  *      Free up argument structures.
518  *
519  * PUBLIC: int argv_free(SCR *);
520  */
521 int
522 argv_free(SCR *sp)
523 {
524         EX_PRIVATE *exp;
525         int off;
526
527         exp = EXP(sp);
528         if (exp->args != NULL) {
529                 for (off = 0; off < exp->argscnt; ++off) {
530                         if (exp->args[off] == NULL)
531                                 continue;
532                         if (F_ISSET(exp->args[off], A_ALLOCATED))
533                                 free(exp->args[off]->bp);
534                         free(exp->args[off]);
535                 }
536                 free(exp->args);
537         }
538         exp->args = NULL;
539         exp->argscnt = 0;
540         exp->argsoff = 0;
541         return (0);
542 }
543
544 /*
545  * argv_flt_path --
546  *      Find all file names matching the prefix and append them to the
547  *      argument list.
548  *
549  * PUBLIC: int argv_flt_path(SCR *, EXCMD *, CHAR_T *, size_t);
550  */
551 int
552 argv_flt_path(SCR *sp, EXCMD *excp, CHAR_T *path, size_t plen)
553 {
554         struct dirent *dp;
555         DIR *dirp;
556         EX_PRIVATE *exp;
557         int off;
558         size_t dlen, len, nlen;
559         CHAR_T *dname;
560         CHAR_T *p, *np, *n;
561         char *name, *tp, *epd = NULL;
562         CHAR_T *wp;
563         size_t wlen;
564
565         exp = EXP(sp);
566
567         /* Set up the name and length for comparison. */
568         if ((path = v_wstrdup(sp, path, plen)) == NULL)
569                 return (1);
570         if ((p = STRRCHR(path, '/')) == NULL) {
571                 if (*path == '~') {
572                         int rc;
573                         
574                         /* Filter ~user list instead. */
575                         rc = argv_flt_user(sp, excp, path, plen);
576                         free(path);
577                         return (rc);
578                 }
579                 dname = L(".");
580                 dlen = 0;
581                 np = path;
582         } else {
583                 if (p == path) {
584                         dname = L("/");
585                         dlen = 1;
586                 } else {
587                         *p = '\0';
588                         dname = path;
589                         dlen = p - path;
590                 }
591                 np = p + 1;
592         }
593
594         INT2CHAR(sp, dname, dlen + 1, tp, nlen);
595         if ((epd = expanduser(tp)) != NULL)
596                 tp = epd;
597         if ((dirp = opendir(tp)) == NULL) {
598                 free(epd);
599                 free(path);
600                 return (1);
601         }
602         free(epd);
603
604         INT2CHAR(sp, np, STRLEN(np), tp, nlen);
605         if ((name = v_strdup(sp, tp, nlen)) == NULL) {
606                 free(path);
607                 return (1);
608         }
609
610         for (off = exp->argsoff; (dp = readdir(dirp)) != NULL;) {
611                 if (nlen == 0) {
612                         if (dp->d_name[0] == '.')
613                                 continue;
614 #ifdef HAVE_DIRENT_D_NAMLEN
615                         len = dp->d_namlen;
616 #else
617                         len = strlen(dp->d_name);
618 #endif
619                 } else {
620 #ifdef HAVE_DIRENT_D_NAMLEN
621                         len = dp->d_namlen;
622 #else
623                         len = strlen(dp->d_name);
624 #endif
625                         if (len < nlen || memcmp(dp->d_name, name, nlen))
626                                 continue;
627                 }
628
629                 /* Directory + name + slash + null. */
630                 CHAR2INT(sp, dp->d_name, len + 1, wp, wlen);
631                 argv_alloc(sp, dlen + wlen + 1);
632                 n = exp->args[exp->argsoff]->bp;
633                 if (dlen != 0) {
634                         MEMCPY(n, dname, dlen);
635                         n += dlen;
636                         if (dlen > 1 || dname[0] != '/')
637                                 *n++ = '/';
638                         exp->args[exp->argsoff]->len = dlen + 1;
639                 }
640                 MEMCPY(n, wp, wlen);
641                 exp->args[exp->argsoff]->len += wlen - 1;
642                 ++exp->argsoff;
643                 excp->argv = exp->args;
644                 excp->argc = exp->argsoff;
645         }
646         closedir(dirp);
647         free(name);
648         free(path);
649
650         qsort(exp->args + off, exp->argsoff - off, sizeof(ARGS *), argv_comp);
651         return (0);
652 }
653
654 /*
655  * argv_comp --
656  *      Alphabetic comparison.
657  */
658 static int
659 argv_comp(const void *a, const void *b)
660 {
661         return (STRCMP((*(ARGS **)a)->bp, (*(ARGS **)b)->bp));
662 }
663
664 /*
665  * argv_sexp --
666  *      Fork a shell, pipe a command through it, and read the output into
667  *      a buffer.
668  */
669 static int
670 argv_sexp(SCR *sp, CHAR_T **bpp, size_t *blenp, size_t *lenp)
671 {
672         enum { SEXP_ERR, SEXP_EXPANSION_ERR, SEXP_OK } rval;
673         FILE *ifp;
674         pid_t pid;
675         size_t blen, len;
676         int ch, std_output[2];
677         CHAR_T *bp, *p;
678         char *sh, *sh_path;
679         char *np;
680         size_t nlen;
681
682         /* Secure means no shell access. */
683         if (O_ISSET(sp, O_SECURE)) {
684                 msgq(sp, M_ERR,
685 "289|Shell expansions not supported when the secure edit option is set");
686                 return (1);
687         }
688
689         sh_path = O_STR(sp, O_SHELL);
690         if ((sh = strrchr(sh_path, '/')) == NULL)
691                 sh = sh_path;
692         else
693                 ++sh;
694
695         /* Local copies of the buffer variables. */
696         bp = *bpp;
697         blen = *blenp;
698
699         /*
700          * There are two different processes running through this code, named
701          * the utility (the shell) and the parent. The utility reads standard
702          * input and writes standard output and standard error output.  The
703          * parent writes to the utility, reads its standard output and ignores
704          * its standard error output.  Historically, the standard error output
705          * was discarded by vi, as it produces a lot of noise when file patterns
706          * don't match.
707          *
708          * The parent reads std_output[0], and the utility writes std_output[1].
709          */
710         ifp = NULL;
711         std_output[0] = std_output[1] = -1;
712         if (pipe(std_output) < 0) {
713                 msgq(sp, M_SYSERR, "pipe");
714                 return (1);
715         }
716         if ((ifp = fdopen(std_output[0], "r")) == NULL) {
717                 msgq(sp, M_SYSERR, "fdopen");
718                 goto err;
719         }
720
721         /*
722          * Do the minimal amount of work possible, the shell is going to run
723          * briefly and then exit.  We sincerely hope.
724          */
725         switch (pid = vfork()) {
726         case -1:                        /* Error. */
727                 msgq(sp, M_SYSERR, "vfork");
728 err:            if (ifp != NULL)
729                         (void)fclose(ifp);
730                 else if (std_output[0] != -1)
731                         close(std_output[0]);
732                 if (std_output[1] != -1)
733                         close(std_output[0]);
734                 return (1);
735         case 0:                         /* Utility. */
736                 /* Redirect stdout to the write end of the pipe. */
737                 (void)dup2(std_output[1], STDOUT_FILENO);
738
739                 /* Close the utility's file descriptors. */
740                 (void)close(std_output[0]);
741                 (void)close(std_output[1]);
742                 (void)close(STDERR_FILENO);
743
744                 /*
745                  * XXX
746                  * Assume that all shells have -c.
747                  */
748                 INT2CHAR(sp, bp, STRLEN(bp)+1, np, nlen);
749                 execl(sh_path, sh, "-c", np, (char *)NULL);
750                 msgq_str(sp, M_SYSERR, sh_path, "118|Error: execl: %s");
751                 _exit(127);
752         default:                        /* Parent. */
753                 /* Close the pipe ends the parent won't use. */
754                 (void)close(std_output[1]);
755                 break;
756         }
757
758         /*
759          * Copy process standard output into a buffer.
760          *
761          * !!!
762          * Historic vi apparently discarded leading \n and \r's from
763          * the shell output stream.  We don't on the grounds that any
764          * shell that does that is broken.
765          */
766         for (p = bp, len = 0, ch = EOF;
767             (ch = GETC(ifp)) != EOF; *p++ = ch, blen-=sizeof(CHAR_T), ++len)
768                 if (blen < 5) {
769                         ADD_SPACE_GOTOW(sp, bp, *blenp, *blenp * 2);
770                         p = bp + len;
771                         blen = *blenp - len;
772                 }
773
774         /* Delete the final newline, nul terminate the string. */
775         if (p > bp && (p[-1] == '\n' || p[-1] == '\r')) {
776                 --p;
777                 --len;
778         }
779         *p = '\0';
780         *lenp = len;
781         *bpp = bp;              /* *blenp is already updated. */
782
783         if (ferror(ifp))
784                 goto ioerr;
785         if (fclose(ifp)) {
786 ioerr:          msgq_str(sp, M_ERR, sh, "119|I/O error: %s");
787 alloc_err:      rval = SEXP_ERR;
788         } else
789                 rval = SEXP_OK;
790
791         /*
792          * Wait for the process.  If the shell process fails (e.g., "echo $q"
793          * where q wasn't a defined variable) or if the returned string has
794          * no characters or only blank characters, (e.g., "echo $5"), complain
795          * that the shell expansion failed.  We can't know for certain that's
796          * the error, but it's a good guess, and it matches historic practice.
797          * This won't catch "echo foo_$5", but that's not a common error and
798          * historic vi didn't catch it either.
799          */
800         if (proc_wait(sp, (long)pid, sh, 1, 0))
801                 rval = SEXP_EXPANSION_ERR;
802
803         for (p = bp; len; ++p, --len)
804                 if (!cmdskip(*p))
805                         break;
806         if (len == 0)
807                 rval = SEXP_EXPANSION_ERR;
808
809         if (rval == SEXP_EXPANSION_ERR)
810                 msgq(sp, M_ERR, "304|Shell expansion failed");
811
812         return (rval == SEXP_OK ? 0 : 1);
813 }
814
815 /*
816  * argv_esc --
817  *      Escape a string into an ex and shell argument.
818  *
819  * PUBLIC: CHAR_T *argv_esc(SCR *, EXCMD *, CHAR_T *, size_t);
820  */
821 CHAR_T *
822 argv_esc(SCR *sp, EXCMD *excp, CHAR_T *str, size_t len)
823 {
824         size_t blen, off;
825         CHAR_T *bp, *p;
826         int ch;
827
828         GET_SPACE_GOTOW(sp, bp, blen, len + 1);
829
830         /*
831          * Leaving the first '~' unescaped causes the user to need a
832          * "./" prefix to edit a file which really starts with a '~'.
833          * However, the file completion happens to not work for these
834          * files without the prefix.
835          * 
836          * All ex expansion characters, "!%#", are double escaped.
837          */
838         for (p = bp; len > 0; ++str, --len) {
839                 ch = *str;
840                 off = p - bp;
841                 if (blen / sizeof(CHAR_T) - off < 3) {
842                         ADD_SPACE_GOTOW(sp, bp, blen, off + 3);
843                         p = bp + off;
844                 }
845                 if (cmdskip(ch) || ch == '\n' ||
846                     IS_ESCAPE(sp, excp, ch))                    /* Ex. */
847                         *p++ = CH_LITERAL;
848                 else switch (ch) {
849                 case '~':                                       /* ~user. */
850                         if (p != bp)
851                                 *p++ = '\\';
852                         break;
853                 case '+':                                       /* Ex +cmd. */
854                         if (p == bp)
855                                 *p++ = '\\';
856                         break;
857                 case '!': case '%': case '#':                   /* Ex exp. */
858                         *p++ = '\\';
859                         *p++ = '\\';
860                         break;
861                 case ',': case '-': case '.': case '/':         /* Safe. */
862                 case ':': case '=': case '@': case '_':
863                         break;
864                 default:                                        /* Unsafe. */
865                         if (isascii(ch) && !isalnum(ch))
866                                 *p++ = '\\';
867                 }
868                 *p++ = ch;
869         }
870         *p = '\0';
871
872         return bp;
873
874 alloc_err:
875         return NULL;
876 }
877
878 /*
879  * argv_uesc --
880  *      Unescape an escaped ex and shell argument.
881  *
882  * PUBLIC: CHAR_T *argv_uesc(SCR *, EXCMD *, CHAR_T *, size_t);
883  */
884 CHAR_T *
885 argv_uesc(SCR *sp, EXCMD *excp, CHAR_T *str, size_t len)
886 {
887         size_t blen;
888         CHAR_T *bp, *p;
889
890         GET_SPACE_GOTOW(sp, bp, blen, len + 1);
891
892         for (p = bp; len > 0; ++str, --len) {
893                 if (IS_ESCAPE(sp, excp, *str)) {
894                         if (--len < 1)
895                                 break;
896                         ++str;
897                 } else if (*str == '\\') {
898                         if (--len < 1)
899                                 break;
900                         ++str;
901
902                         /* Check for double escaping. */
903                         if (*str == '\\' && len > 1)
904                                 switch (str[1]) {
905                                 case '!': case '%': case '#':
906                                         ++str;
907                                         --len;
908                                 }
909                 }
910                 *p++ = *str;
911         }
912         *p = '\0';
913
914         return bp;
915
916 alloc_err:
917         return NULL;
918 }