make # and % work in wide char version
[nvi.git] / ex / ex_argv.c
blob36307f67bc63fbda17598f6b09b3bd456aabfd2f
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.
7 * See the LICENSE file for redistribution information.
8 */
10 #include "config.h"
12 #ifndef lint
13 static const char sccsid[] = "$Id: ex_argv.c,v 10.33 2000/07/21 17:33:58 skimo Exp $ (Berkeley) $Date: 2000/07/21 17:33:58 $";
14 #endif /* not lint */
16 #include <sys/types.h>
17 #include <sys/queue.h>
19 #include <bitstring.h>
20 #include <ctype.h>
21 #include <dirent.h>
22 #include <errno.h>
23 #include <limits.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <unistd.h>
29 #include "../common/common.h"
31 static int argv_alloc __P((SCR *, size_t));
32 static int argv_comp __P((const void *, const void *));
33 static int argv_fexp __P((SCR *, EXCMD *,
34 CHAR_T *, size_t, CHAR_T *, size_t *, CHAR_T **, size_t *, int));
35 static int argv_lexp __P((SCR *, EXCMD *, char *));
36 static int argv_sexp __P((SCR *, CHAR_T **, size_t *, size_t *));
39 * argv_init --
40 * Build a prototype arguments list.
42 * PUBLIC: int argv_init __P((SCR *, EXCMD *));
44 int
45 argv_init(sp, excp)
46 SCR *sp;
47 EXCMD *excp;
49 EX_PRIVATE *exp;
51 exp = EXP(sp);
52 exp->argsoff = 0;
53 argv_alloc(sp, 1);
55 excp->argv = exp->args;
56 excp->argc = exp->argsoff;
57 return (0);
61 * argv_exp0 --
62 * Append a string to the argument list.
64 * PUBLIC: int argv_exp0 __P((SCR *, EXCMD *, CHAR_T *, size_t));
66 int
67 argv_exp0(sp, excp, cmd, cmdlen)
68 SCR *sp;
69 EXCMD *excp;
70 CHAR_T *cmd;
71 size_t cmdlen;
73 EX_PRIVATE *exp;
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);
87 * argv_exp1 --
88 * Do file name expansion on a string, and append it to the
89 * argument list.
91 * PUBLIC: int argv_exp1 __P((SCR *, EXCMD *, CHAR_T *, size_t, int));
93 int
94 argv_exp1(sp, excp, cmd, cmdlen, is_bang)
95 SCR *sp;
96 EXCMD *excp;
97 CHAR_T *cmd;
98 size_t cmdlen;
99 int is_bang;
101 EX_PRIVATE *exp;
102 size_t blen, len;
103 CHAR_T *p, *t, *bp;
104 size_t wlen;
105 CHAR_T *wp;
107 GET_SPACE_RETW(sp, bp, blen, 512);
109 len = 0;
110 exp = EXP(sp);
111 if (argv_fexp(sp, excp, cmd, cmdlen, bp, &len, &bp, &blen, is_bang)) {
112 FREE_SPACEW(sp, bp, blen);
113 return (1);
116 /* If it's empty, we're done. */
117 if (len != 0) {
118 for (p = bp, t = bp + len; p < t; ++p)
119 if (!isblank(*p))
120 break;
121 if (p == t)
122 goto ret;
123 } else
124 goto ret;
126 (void)argv_exp0(sp, excp, bp, len);
128 ret: FREE_SPACEW(sp, bp, blen);
129 return (0);
133 * argv_exp2 --
134 * Do file name and shell expansion on a string, and append it to
135 * the argument list.
137 * PUBLIC: int argv_exp2 __P((SCR *, EXCMD *, CHAR_T *, size_t));
140 argv_exp2(sp, excp, cmd, cmdlen)
141 SCR *sp;
142 EXCMD *excp;
143 CHAR_T *cmd;
144 size_t cmdlen;
146 size_t blen, len, n;
147 int rval;
148 CHAR_T *bp, *p;
149 char *mp, *np;
151 GET_SPACE_RETW(sp, bp, blen, 512);
153 #define SHELLECHO "echo "
154 #define SHELLOFFSET (sizeof(SHELLECHO) - 1)
155 p = bp;
156 *p++ = 'e';
157 *p++ = 'c';
158 *p++ = 'h';
159 *p++ = 'o';
160 *p++ = ' ';
161 len = SHELLOFFSET;
163 #if defined(DEBUG) && 0
164 vtrace(sp, "file_argv: {%.*s}\n", (int)cmdlen, cmd);
165 #endif
167 if (argv_fexp(sp, excp, cmd, cmdlen, p, &len, &bp, &blen, 0)) {
168 rval = 1;
169 goto err;
172 #if defined(DEBUG) && 0
173 vtrace(sp, "before shell: %d: {%s}\n", len, bp);
174 #endif
177 * Do shell word expansion -- it's very, very hard to figure out what
178 * magic characters the user's shell expects. Historically, it was a
179 * union of v7 shell and csh meta characters. We match that practice
180 * by default, so ":read \%" tries to read a file named '%'. It would
181 * make more sense to pass any special characters through the shell,
182 * but then, if your shell was csh, the above example will behave
183 * differently in nvi than in vi. If you want to get other characters
184 * passed through to your shell, change the "meta" option.
186 * To avoid a function call per character, we do a first pass through
187 * the meta characters looking for characters that aren't expected
188 * to be there, and then we can ignore them in the user's argument.
190 if (opts_empty(sp, O_SHELL, 1) || opts_empty(sp, O_SHELLMETA, 1))
191 n = 0;
192 else {
193 for (np = mp = O_STR(sp, O_SHELLMETA); *np != '\0'; ++np)
194 if (isblank(*np) || isalnum(*np))
195 break;
196 p = bp + SHELLOFFSET;
197 n = len - SHELLOFFSET;
198 if (*p != '\0') {
199 for (; n > 0; --n, ++p)
200 if (strchr(mp, *p) != NULL)
201 break;
202 } else
203 for (; n > 0; --n, ++p)
204 if (!isblank(*p) &&
205 !isalnum(*p) && strchr(mp, *p) != NULL)
206 break;
210 * If we found a meta character in the string, fork a shell to expand
211 * it. Unfortunately, this is comparatively slow. Historically, it
212 * didn't matter much, since users don't enter meta characters as part
213 * of pathnames that frequently. The addition of filename completion
214 * broke that assumption because it's easy to use. As a result, lots
215 * folks have complained that the expansion code is too slow. So, we
216 * detect filename completion as a special case, and do it internally.
217 * Note that this code assumes that the <asterisk> character is the
218 * match-anything meta character. That feels safe -- if anyone writes
219 * a shell that doesn't follow that convention, I'd suggest giving them
220 * a festive hot-lead enema.
222 switch (n) {
223 case 0:
224 p = bp + SHELLOFFSET;
225 len -= SHELLOFFSET;
226 rval = argv_exp3(sp, excp, p, len);
227 break;
228 case 1:
229 if (*p == '*') {
230 char *np, *d;
231 size_t nlen;
233 *p = '\0';
234 INT2CHAR(sp, bp + SHELLOFFSET,
235 v_strlen(bp + SHELLOFFSET) + 1, np, nlen);
236 d = strdup(np);
237 rval = argv_lexp(sp, excp, d);
238 free (d);
239 break;
241 /* FALLTHROUGH */
242 default:
243 if (argv_sexp(sp, &bp, &blen, &len)) {
244 rval = 1;
245 goto err;
247 p = bp;
248 rval = argv_exp3(sp, excp, p, len);
249 break;
252 err: FREE_SPACEW(sp, bp, blen);
253 return (rval);
257 * argv_exp3 --
258 * Take a string and break it up into an argv, which is appended
259 * to the argument list.
261 * PUBLIC: int argv_exp3 __P((SCR *, EXCMD *, CHAR_T *, size_t));
264 argv_exp3(sp, excp, cmd, cmdlen)
265 SCR *sp;
266 EXCMD *excp;
267 CHAR_T *cmd;
268 size_t cmdlen;
270 EX_PRIVATE *exp;
271 size_t len;
272 int ch, off;
273 CHAR_T *ap, *p;
275 for (exp = EXP(sp); cmdlen > 0; ++exp->argsoff) {
276 /* Skip any leading whitespace. */
277 for (; cmdlen > 0; --cmdlen, ++cmd) {
278 ch = *cmd;
279 if (!isblank(ch))
280 break;
282 if (cmdlen == 0)
283 break;
286 * Determine the length of this whitespace delimited
287 * argument.
289 * QUOTING NOTE:
291 * Skip any character preceded by the user's quoting
292 * character.
294 for (ap = cmd, len = 0; cmdlen > 0; ++cmd, --cmdlen, ++len) {
295 ch = *cmd;
296 if (IS_ESCAPE(sp, excp, ch) && cmdlen > 1) {
297 ++cmd;
298 --cmdlen;
299 } else if (isblank(ch))
300 break;
304 * Copy the argument into place.
306 * QUOTING NOTE:
308 * Lose quote chars.
310 argv_alloc(sp, len);
311 off = exp->argsoff;
312 exp->args[off]->len = len;
313 for (p = exp->args[off]->bp; len > 0; --len, *p++ = *ap++)
314 if (IS_ESCAPE(sp, excp, *ap))
315 ++ap;
316 *p = '\0';
318 excp->argv = exp->args;
319 excp->argc = exp->argsoff;
321 #if defined(DEBUG) && 0
322 for (cnt = 0; cnt < exp->argsoff; ++cnt)
323 vtrace(sp, "arg %d: {%s}\n", cnt, exp->argv[cnt]);
324 #endif
325 return (0);
329 * argv_fexp --
330 * Do file name and bang command expansion.
332 static int
333 argv_fexp(sp, excp, cmd, cmdlen, p, lenp, bpp, blenp, is_bang)
334 SCR *sp;
335 EXCMD *excp;
336 CHAR_T *cmd, *p, **bpp;
337 size_t cmdlen, *lenp, *blenp;
338 int is_bang;
340 EX_PRIVATE *exp;
341 char *t;
342 size_t blen, len, off, tlen;
343 CHAR_T *bp;
344 CHAR_T *wp;
345 size_t wlen;
347 /* Replace file name characters. */
348 for (bp = *bpp, blen = *blenp, len = *lenp; cmdlen > 0; --cmdlen, ++cmd)
349 switch (*cmd) {
350 case '!':
351 if (!is_bang)
352 goto ins_ch;
353 exp = EXP(sp);
354 if (exp->lastbcomm == NULL) {
355 msgq(sp, M_ERR,
356 "115|No previous command to replace \"!\"");
357 return (1);
359 len += tlen = v_strlen(exp->lastbcomm);
360 off = p - bp;
361 ADD_SPACE_RETW(sp, bp, blen, len);
362 p = bp + off;
363 MEMCPY(p, exp->lastbcomm, tlen);
364 p += tlen;
365 F_SET(excp, E_MODIFY);
366 break;
367 case '%':
368 if ((t = sp->frp->name) == NULL) {
369 msgq(sp, M_ERR,
370 "116|No filename to substitute for %%");
371 return (1);
373 tlen = strlen(t);
374 len += tlen;
375 off = p - bp;
376 ADD_SPACE_RETW(sp, bp, blen, len);
377 p = bp + off;
378 CHAR2INT(sp, t, tlen, wp, wlen);
379 MEMCPY(p, wp, wlen);
380 p += tlen;
381 F_SET(excp, E_MODIFY);
382 break;
383 case '#':
384 if ((t = sp->alt_name) == NULL) {
385 msgq(sp, M_ERR,
386 "117|No filename to substitute for #");
387 return (1);
389 len += tlen = strlen(t);
390 off = p - bp;
391 ADD_SPACE_RETW(sp, bp, blen, len);
392 p = bp + off;
393 CHAR2INT(sp, t, tlen, wp, wlen);
394 MEMCPY(p, wp, wlen);
395 p += tlen;
396 F_SET(excp, E_MODIFY);
397 break;
398 case '\\':
400 * QUOTING NOTE:
402 * Strip any backslashes that protected the file
403 * expansion characters.
405 if (cmdlen > 1 &&
406 (cmd[1] == '%' || cmd[1] == '#' || cmd[1] == '!')) {
407 ++cmd;
408 --cmdlen;
410 /* FALLTHROUGH */
411 default:
412 ins_ch: ++len;
413 off = p - bp;
414 ADD_SPACE_RETW(sp, bp, blen, len);
415 p = bp + off;
416 *p++ = *cmd;
419 /* Nul termination. */
420 ++len;
421 off = p - bp;
422 ADD_SPACE_RETW(sp, bp, blen, len);
423 p = bp + off;
424 *p = '\0';
426 /* Return the new string length, buffer, buffer length. */
427 *lenp = len - 1;
428 *bpp = bp;
429 *blenp = blen;
430 return (0);
434 * argv_alloc --
435 * Make more space for arguments.
437 static int
438 argv_alloc(sp, len)
439 SCR *sp;
440 size_t len;
442 ARGS *ap;
443 EX_PRIVATE *exp;
444 int cnt, off;
447 * Allocate room for another argument, always leaving
448 * enough room for an ARGS structure with a length of 0.
450 #define INCREMENT 20
451 exp = EXP(sp);
452 off = exp->argsoff;
453 if (exp->argscnt == 0 || off + 2 >= exp->argscnt - 1) {
454 cnt = exp->argscnt + INCREMENT;
455 REALLOC(sp, exp->args, ARGS **, cnt * sizeof(ARGS *));
456 if (exp->args == NULL) {
457 (void)argv_free(sp);
458 goto mem;
460 memset(&exp->args[exp->argscnt], 0, INCREMENT * sizeof(ARGS *));
461 exp->argscnt = cnt;
464 /* First argument. */
465 if (exp->args[off] == NULL) {
466 CALLOC(sp, exp->args[off], ARGS *, 1, sizeof(ARGS));
467 if (exp->args[off] == NULL)
468 goto mem;
471 /* First argument buffer. */
472 ap = exp->args[off];
473 ap->len = 0;
474 if (ap->blen < len + 1) {
475 ap->blen = len + 1;
476 REALLOC(sp, ap->bp, CHAR_T *, ap->blen * sizeof(CHAR_T));
477 if (ap->bp == NULL) {
478 ap->bp = NULL;
479 ap->blen = 0;
480 F_CLR(ap, A_ALLOCATED);
481 mem: msgq(sp, M_SYSERR, NULL);
482 return (1);
484 F_SET(ap, A_ALLOCATED);
487 /* Second argument. */
488 if (exp->args[++off] == NULL) {
489 CALLOC(sp, exp->args[off], ARGS *, 1, sizeof(ARGS));
490 if (exp->args[off] == NULL)
491 goto mem;
493 /* 0 length serves as end-of-argument marker. */
494 exp->args[off]->len = 0;
495 return (0);
499 * argv_free --
500 * Free up argument structures.
502 * PUBLIC: int argv_free __P((SCR *));
505 argv_free(sp)
506 SCR *sp;
508 EX_PRIVATE *exp;
509 int off;
511 exp = EXP(sp);
512 if (exp->args != NULL) {
513 for (off = 0; off < exp->argscnt; ++off) {
514 if (exp->args[off] == NULL)
515 continue;
516 if (F_ISSET(exp->args[off], A_ALLOCATED))
517 free(exp->args[off]->bp);
518 free(exp->args[off]);
520 free(exp->args);
522 exp->args = NULL;
523 exp->argscnt = 0;
524 exp->argsoff = 0;
525 return (0);
529 * argv_lexp --
530 * Find all file names matching the prefix and append them to the
531 * buffer.
533 static int
534 argv_lexp(SCR *sp, EXCMD *excp, char *path)
536 struct dirent *dp;
537 DIR *dirp;
538 EX_PRIVATE *exp;
539 int off;
540 size_t dlen, len, nlen;
541 char *dname, *name;
542 char *p;
543 size_t wlen;
544 CHAR_T *wp;
545 CHAR_T *n;
547 exp = EXP(sp);
549 /* Set up the name and length for comparison. */
550 if ((p = strrchr(path, '/')) == NULL) {
551 dname = ".";
552 dlen = 0;
553 name = path;
554 } else {
555 if (p == path) {
556 dname = "/";
557 dlen = 1;
558 } else {
559 *p = '\0';
560 dname = path;
561 dlen = strlen(path);
563 name = p + 1;
565 nlen = strlen(name);
568 * XXX
569 * We don't use the d_namlen field, it's not portable enough; we
570 * assume that d_name is nul terminated, instead.
572 if ((dirp = opendir(dname)) == NULL) {
573 msgq_str(sp, M_SYSERR, dname, "%s");
574 return (1);
576 for (off = exp->argsoff; (dp = readdir(dirp)) != NULL;) {
577 if (nlen == 0) {
578 if (dp->d_name[0] == '.')
579 continue;
580 len = strlen(dp->d_name);
581 } else {
582 len = strlen(dp->d_name);
583 if (len < nlen || memcmp(dp->d_name, name, nlen))
584 continue;
587 /* Directory + name + slash + null. */
588 argv_alloc(sp, dlen + len + 2);
589 n = exp->args[exp->argsoff]->bp;
590 if (dlen != 0) {
591 CHAR2INT(sp, dname, dlen, wp, wlen);
592 MEMCPY(n, wp, wlen);
593 n += dlen;
594 if (dlen > 1 || dname[0] != '/')
595 *n++ = '/';
597 CHAR2INT(sp, dp->d_name, len + 1, wp, wlen);
598 MEMCPY(n, wp, wlen);
599 exp->args[exp->argsoff]->len = dlen + len + 1;
600 ++exp->argsoff;
601 excp->argv = exp->args;
602 excp->argc = exp->argsoff;
604 closedir(dirp);
606 if (off == exp->argsoff) {
608 * If we didn't find a match, complain that the expansion
609 * failed. We can't know for certain that's the error, but
610 * it's a good guess, and it matches historic practice.
612 msgq(sp, M_ERR, "304|Shell expansion failed");
613 return (1);
615 qsort(exp->args + off, exp->argsoff - off, sizeof(ARGS *), argv_comp);
616 return (0);
620 * argv_comp --
621 * Alphabetic comparison.
623 static int
624 argv_comp(a, b)
625 const void *a, *b;
627 return (v_strcmp((*(ARGS **)a)->bp, (*(ARGS **)b)->bp));
631 * argv_sexp --
632 * Fork a shell, pipe a command through it, and read the output into
633 * a buffer.
635 static int
636 argv_sexp(sp, bpp, blenp, lenp)
637 SCR *sp;
638 CHAR_T **bpp;
639 size_t *blenp, *lenp;
641 enum { SEXP_ERR, SEXP_EXPANSION_ERR, SEXP_OK } rval;
642 FILE *ifp;
643 pid_t pid;
644 size_t blen, len;
645 int ch, std_output[2];
646 CHAR_T *bp, *p;
647 char *sh, *sh_path;
649 /* Secure means no shell access. */
650 if (O_ISSET(sp, O_SECURE)) {
651 msgq(sp, M_ERR,
652 "289|Shell expansions not supported when the secure edit option is set");
653 return (1);
656 sh_path = O_STR(sp, O_SHELL);
657 if ((sh = strrchr(sh_path, '/')) == NULL)
658 sh = sh_path;
659 else
660 ++sh;
662 /* Local copies of the buffer variables. */
663 bp = *bpp;
664 blen = *blenp;
667 * There are two different processes running through this code, named
668 * the utility (the shell) and the parent. The utility reads standard
669 * input and writes standard output and standard error output. The
670 * parent writes to the utility, reads its standard output and ignores
671 * its standard error output. Historically, the standard error output
672 * was discarded by vi, as it produces a lot of noise when file patterns
673 * don't match.
675 * The parent reads std_output[0], and the utility writes std_output[1].
677 ifp = NULL;
678 std_output[0] = std_output[1] = -1;
679 if (pipe(std_output) < 0) {
680 msgq(sp, M_SYSERR, "pipe");
681 return (1);
683 if ((ifp = fdopen(std_output[0], "r")) == NULL) {
684 msgq(sp, M_SYSERR, "fdopen");
685 goto err;
689 * Do the minimal amount of work possible, the shell is going to run
690 * briefly and then exit. We sincerely hope.
692 switch (pid = vfork()) {
693 case -1: /* Error. */
694 msgq(sp, M_SYSERR, "vfork");
695 err: if (ifp != NULL)
696 (void)fclose(ifp);
697 else if (std_output[0] != -1)
698 close(std_output[0]);
699 if (std_output[1] != -1)
700 close(std_output[0]);
701 return (1);
702 case 0: /* Utility. */
703 /* Redirect stdout to the write end of the pipe. */
704 (void)dup2(std_output[1], STDOUT_FILENO);
706 /* Close the utility's file descriptors. */
707 (void)close(std_output[0]);
708 (void)close(std_output[1]);
709 (void)close(STDERR_FILENO);
712 * XXX
713 * Assume that all shells have -c.
715 execl(sh_path, sh, "-c", bp, NULL);
716 msgq_str(sp, M_SYSERR, sh_path, "118|Error: execl: %s");
717 _exit(127);
718 default: /* Parent. */
719 /* Close the pipe ends the parent won't use. */
720 (void)close(std_output[1]);
721 break;
725 * Copy process standard output into a buffer.
727 * !!!
728 * Historic vi apparently discarded leading \n and \r's from
729 * the shell output stream. We don't on the grounds that any
730 * shell that does that is broken.
732 for (p = bp, len = 0, ch = EOF;
733 (ch = getc(ifp)) != EOF; *p++ = ch, --blen, ++len)
734 if (blen < 5) {
735 ADD_SPACE_GOTOW(sp, bp, *blenp, *blenp * 2);
736 p = bp + len;
737 blen = *blenp - len;
740 /* Delete the final newline, nul terminate the string. */
741 if (p > bp && (p[-1] == '\n' || p[-1] == '\r')) {
742 --p;
743 --len;
745 *p = '\0';
746 *lenp = len;
747 *bpp = bp; /* *blenp is already updated. */
749 if (ferror(ifp))
750 goto ioerr;
751 if (fclose(ifp)) {
752 ioerr: msgq_str(sp, M_ERR, sh, "119|I/O error: %s");
753 alloc_err: rval = SEXP_ERR;
754 } else
755 rval = SEXP_OK;
758 * Wait for the process. If the shell process fails (e.g., "echo $q"
759 * where q wasn't a defined variable) or if the returned string has
760 * no characters or only blank characters, (e.g., "echo $5"), complain
761 * that the shell expansion failed. We can't know for certain that's
762 * the error, but it's a good guess, and it matches historic practice.
763 * This won't catch "echo foo_$5", but that's not a common error and
764 * historic vi didn't catch it either.
766 if (proc_wait(sp, (long)pid, sh, 1, 0))
767 rval = SEXP_EXPANSION_ERR;
769 for (p = bp; len; ++p, --len)
770 if (!isblank(*p))
771 break;
772 if (len == 0)
773 rval = SEXP_EXPANSION_ERR;
775 if (rval == SEXP_EXPANSION_ERR)
776 msgq(sp, M_ERR, "304|Shell expansion failed");
778 return (rval == SEXP_OK ? 0 : 1);