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