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
argv_init(SCR * sp,EXCMD * excp)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
argv_exp0(SCR * sp,EXCMD * excp,CHAR_T * cmd,size_t cmdlen)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
argv_exp1(SCR * sp,EXCMD * excp,CHAR_T * cmd,size_t cmdlen,int is_bang)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
argv_exp2(SCR * sp,EXCMD * excp,CHAR_T * cmd,size_t cmdlen)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
argv_exp3(SCR * sp,EXCMD * excp,CHAR_T * cmd,size_t cmdlen)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
argv_flt_ex(SCR * sp,EXCMD * excp,CHAR_T * cmd,size_t cmdlen)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
argv_flt_user(SCR * sp,EXCMD * excp,CHAR_T * uname,size_t ulen)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
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)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
argv_alloc(SCR * sp,size_t len)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
argv_free(SCR * sp)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
argv_flt_path(SCR * sp,EXCMD * excp,CHAR_T * path,size_t plen)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 len = dp->d_namlen;
615 } else {
616 len = dp->d_namlen;
617 if (len < nlen || memcmp(dp->d_name, name, nlen))
618 continue;
619 }
620
621 /* Directory + name + slash + null. */
622 CHAR2INT(sp, dp->d_name, len + 1, wp, wlen);
623 argv_alloc(sp, dlen + wlen + 1);
624 n = exp->args[exp->argsoff]->bp;
625 if (dlen != 0) {
626 MEMCPY(n, dname, dlen);
627 n += dlen;
628 if (dlen > 1 || dname[0] != '/')
629 *n++ = '/';
630 exp->args[exp->argsoff]->len = dlen + 1;
631 }
632 MEMCPY(n, wp, wlen);
633 exp->args[exp->argsoff]->len += wlen - 1;
634 ++exp->argsoff;
635 excp->argv = exp->args;
636 excp->argc = exp->argsoff;
637 }
638 closedir(dirp);
639 free(name);
640 free(path);
641
642 qsort(exp->args + off, exp->argsoff - off, sizeof(ARGS *), argv_comp);
643 return (0);
644 }
645
646 /*
647 * argv_comp --
648 * Alphabetic comparison.
649 */
650 static int
argv_comp(const void * a,const void * b)651 argv_comp(const void *a, const void *b)
652 {
653 return (STRCMP((*(ARGS **)a)->bp, (*(ARGS **)b)->bp));
654 }
655
656 /*
657 * argv_sexp --
658 * Fork a shell, pipe a command through it, and read the output into
659 * a buffer.
660 */
661 static int
argv_sexp(SCR * sp,CHAR_T ** bpp,size_t * blenp,size_t * lenp)662 argv_sexp(SCR *sp, CHAR_T **bpp, size_t *blenp, size_t *lenp)
663 {
664 enum { SEXP_ERR, SEXP_EXPANSION_ERR, SEXP_OK } rval;
665 FILE *ifp;
666 pid_t pid;
667 size_t blen, len;
668 int ch, std_output[2];
669 CHAR_T *bp, *p;
670 char *sh, *sh_path;
671 char *np;
672 size_t nlen;
673
674 /* Secure means no shell access. */
675 if (O_ISSET(sp, O_SECURE)) {
676 msgq(sp, M_ERR,
677 "289|Shell expansions not supported when the secure edit option is set");
678 return (1);
679 }
680
681 sh_path = O_STR(sp, O_SHELL);
682 if ((sh = strrchr(sh_path, '/')) == NULL)
683 sh = sh_path;
684 else
685 ++sh;
686
687 /* Local copies of the buffer variables. */
688 bp = *bpp;
689 blen = *blenp;
690
691 /*
692 * There are two different processes running through this code, named
693 * the utility (the shell) and the parent. The utility reads standard
694 * input and writes standard output and standard error output. The
695 * parent writes to the utility, reads its standard output and ignores
696 * its standard error output. Historically, the standard error output
697 * was discarded by vi, as it produces a lot of noise when file patterns
698 * don't match.
699 *
700 * The parent reads std_output[0], and the utility writes std_output[1].
701 */
702 ifp = NULL;
703 std_output[0] = std_output[1] = -1;
704 if (pipe(std_output) < 0) {
705 msgq(sp, M_SYSERR, "pipe");
706 return (1);
707 }
708 if ((ifp = fdopen(std_output[0], "r")) == NULL) {
709 msgq(sp, M_SYSERR, "fdopen");
710 goto err;
711 }
712
713 /*
714 * Do the minimal amount of work possible, the shell is going to run
715 * briefly and then exit. We sincerely hope.
716 */
717 switch (pid = vfork()) {
718 case -1: /* Error. */
719 msgq(sp, M_SYSERR, "vfork");
720 err: if (ifp != NULL)
721 (void)fclose(ifp);
722 else if (std_output[0] != -1)
723 close(std_output[0]);
724 if (std_output[1] != -1)
725 close(std_output[0]);
726 return (1);
727 case 0: /* Utility. */
728 /* Redirect stdout to the write end of the pipe. */
729 (void)dup2(std_output[1], STDOUT_FILENO);
730
731 /* Close the utility's file descriptors. */
732 (void)close(std_output[0]);
733 (void)close(std_output[1]);
734 (void)close(STDERR_FILENO);
735
736 /*
737 * XXX
738 * Assume that all shells have -c.
739 */
740 INT2CHAR(sp, bp, STRLEN(bp)+1, np, nlen);
741 execl(sh_path, sh, "-c", np, (char *)NULL);
742 msgq_str(sp, M_SYSERR, sh_path, "118|Error: execl: %s");
743 _exit(127);
744 default: /* Parent. */
745 /* Close the pipe ends the parent won't use. */
746 (void)close(std_output[1]);
747 break;
748 }
749
750 /*
751 * Copy process standard output into a buffer.
752 *
753 * !!!
754 * Historic vi apparently discarded leading \n and \r's from
755 * the shell output stream. We don't on the grounds that any
756 * shell that does that is broken.
757 */
758 for (p = bp, len = 0, ch = EOF;
759 (ch = GETC(ifp)) != EOF; *p++ = ch, blen-=sizeof(CHAR_T), ++len)
760 if (blen < 5) {
761 ADD_SPACE_GOTOW(sp, bp, *blenp, *blenp * 2);
762 p = bp + len;
763 blen = *blenp - len;
764 }
765
766 /* Delete the final newline, nul terminate the string. */
767 if (p > bp && (p[-1] == '\n' || p[-1] == '\r')) {
768 --p;
769 --len;
770 }
771 *p = '\0';
772 *lenp = len;
773 *bpp = bp; /* *blenp is already updated. */
774
775 if (ferror(ifp))
776 goto ioerr;
777 if (fclose(ifp)) {
778 ioerr: msgq_str(sp, M_ERR, sh, "119|I/O error: %s");
779 alloc_err: rval = SEXP_ERR;
780 } else
781 rval = SEXP_OK;
782
783 /*
784 * Wait for the process. If the shell process fails (e.g., "echo $q"
785 * where q wasn't a defined variable) or if the returned string has
786 * no characters or only blank characters, (e.g., "echo $5"), complain
787 * that the shell expansion failed. We can't know for certain that's
788 * the error, but it's a good guess, and it matches historic practice.
789 * This won't catch "echo foo_$5", but that's not a common error and
790 * historic vi didn't catch it either.
791 */
792 if (proc_wait(sp, (long)pid, sh, 1, 0))
793 rval = SEXP_EXPANSION_ERR;
794
795 for (p = bp; len; ++p, --len)
796 if (!cmdskip(*p))
797 break;
798 if (len == 0)
799 rval = SEXP_EXPANSION_ERR;
800
801 if (rval == SEXP_EXPANSION_ERR)
802 msgq(sp, M_ERR, "304|Shell expansion failed");
803
804 return (rval == SEXP_OK ? 0 : 1);
805 }
806
807 /*
808 * argv_esc --
809 * Escape a string into an ex and shell argument.
810 *
811 * PUBLIC: CHAR_T *argv_esc(SCR *, EXCMD *, CHAR_T *, size_t);
812 */
813 CHAR_T *
argv_esc(SCR * sp,EXCMD * excp,CHAR_T * str,size_t len)814 argv_esc(SCR *sp, EXCMD *excp, CHAR_T *str, size_t len)
815 {
816 size_t blen, off;
817 CHAR_T *bp, *p;
818 int ch;
819
820 GET_SPACE_GOTOW(sp, bp, blen, len + 1);
821
822 /*
823 * Leaving the first '~' unescaped causes the user to need a
824 * "./" prefix to edit a file which really starts with a '~'.
825 * However, the file completion happens to not work for these
826 * files without the prefix.
827 *
828 * All ex expansion characters, "!%#", are double escaped.
829 */
830 for (p = bp; len > 0; ++str, --len) {
831 ch = *str;
832 off = p - bp;
833 if (blen / sizeof(CHAR_T) - off < 3) {
834 ADD_SPACE_GOTOW(sp, bp, blen, off + 3);
835 p = bp + off;
836 }
837 if (cmdskip(ch) || ch == '\n' ||
838 IS_ESCAPE(sp, excp, ch)) /* Ex. */
839 *p++ = CH_LITERAL;
840 else switch (ch) {
841 case '~': /* ~user. */
842 if (p != bp)
843 *p++ = '\\';
844 break;
845 case '+': /* Ex +cmd. */
846 if (p == bp)
847 *p++ = '\\';
848 break;
849 case '!': case '%': case '#': /* Ex exp. */
850 *p++ = '\\';
851 *p++ = '\\';
852 break;
853 case ',': case '-': case '.': case '/': /* Safe. */
854 case ':': case '=': case '@': case '_':
855 break;
856 default: /* Unsafe. */
857 if (isascii(ch) && !isalnum(ch))
858 *p++ = '\\';
859 }
860 *p++ = ch;
861 }
862 *p = '\0';
863
864 return bp;
865
866 alloc_err:
867 return NULL;
868 }
869
870 /*
871 * argv_uesc --
872 * Unescape an escaped ex and shell argument.
873 *
874 * PUBLIC: CHAR_T *argv_uesc(SCR *, EXCMD *, CHAR_T *, size_t);
875 */
876 CHAR_T *
argv_uesc(SCR * sp,EXCMD * excp,CHAR_T * str,size_t len)877 argv_uesc(SCR *sp, EXCMD *excp, CHAR_T *str, size_t len)
878 {
879 size_t blen;
880 CHAR_T *bp, *p;
881
882 GET_SPACE_GOTOW(sp, bp, blen, len + 1);
883
884 for (p = bp; len > 0; ++str, --len) {
885 if (IS_ESCAPE(sp, excp, *str)) {
886 if (--len < 1)
887 break;
888 ++str;
889 } else if (*str == '\\') {
890 if (--len < 1)
891 break;
892 ++str;
893
894 /* Check for double escaping. */
895 if (*str == '\\' && len > 1)
896 switch (str[1]) {
897 case '!': case '%': case '#':
898 ++str;
899 --len;
900 }
901 }
902 *p++ = *str;
903 }
904 *p = '\0';
905
906 return bp;
907
908 alloc_err:
909 return NULL;
910 }
911