clean up naming, add select functions
[nvi.git] / ex / ex_argv.c
blob9bd25ad06d5b26a864c592f14ed7cb571fbd2986
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.28 1996/12/17 14:50:33 bostic Exp $ (Berkeley) $Date: 1996/12/17 14:50:33 $";
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 *, size_t, char *, size_t *, char **, size_t *, int));
35 static int argv_lexp __P((SCR *, EXCMD *, char *));
36 static int argv_sexp __P((SCR *, char **, 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 *, size_t));
66 int
67 argv_exp0(sp, excp, cmd, cmdlen)
68 SCR *sp;
69 EXCMD *excp;
70 char *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 *, size_t, int));
93 int
94 argv_exp1(sp, excp, cmd, cmdlen, is_bang)
95 SCR *sp;
96 EXCMD *excp;
97 char *cmd;
98 size_t cmdlen;
99 int is_bang;
101 EX_PRIVATE *exp;
102 size_t blen, len;
103 char *bp, *p, *t;
105 GET_SPACE_RET(sp, bp, blen, 512);
107 len = 0;
108 exp = EXP(sp);
109 if (argv_fexp(sp, excp, cmd, cmdlen, bp, &len, &bp, &blen, is_bang)) {
110 FREE_SPACE(sp, bp, blen);
111 return (1);
114 /* If it's empty, we're done. */
115 if (len != 0) {
116 for (p = bp, t = bp + len; p < t; ++p)
117 if (!isblank(*p))
118 break;
119 if (p == t)
120 goto ret;
121 } else
122 goto ret;
124 (void)argv_exp0(sp, excp, bp, len);
126 ret: FREE_SPACE(sp, bp, blen);
127 return (0);
131 * argv_exp2 --
132 * Do file name and shell expansion on a string, and append it to
133 * the argument list.
135 * PUBLIC: int argv_exp2 __P((SCR *, EXCMD *, char *, size_t));
138 argv_exp2(sp, excp, cmd, cmdlen)
139 SCR *sp;
140 EXCMD *excp;
141 char *cmd;
142 size_t cmdlen;
144 size_t blen, len, n;
145 int rval;
146 char *bp, *mp, *p;
148 GET_SPACE_RET(sp, bp, blen, 512);
150 #define SHELLECHO "echo "
151 #define SHELLOFFSET (sizeof(SHELLECHO) - 1)
152 memcpy(bp, SHELLECHO, SHELLOFFSET);
153 p = bp + SHELLOFFSET;
154 len = SHELLOFFSET;
156 #if defined(DEBUG) && 0
157 vtrace(sp, "file_argv: {%.*s}\n", (int)cmdlen, cmd);
158 #endif
160 if (argv_fexp(sp, excp, cmd, cmdlen, p, &len, &bp, &blen, 0)) {
161 rval = 1;
162 goto err;
165 #if defined(DEBUG) && 0
166 vtrace(sp, "before shell: %d: {%s}\n", len, bp);
167 #endif
170 * Do shell word expansion -- it's very, very hard to figure out what
171 * magic characters the user's shell expects. Historically, it was a
172 * union of v7 shell and csh meta characters. We match that practice
173 * by default, so ":read \%" tries to read a file named '%'. It would
174 * make more sense to pass any special characters through the shell,
175 * but then, if your shell was csh, the above example will behave
176 * differently in nvi than in vi. If you want to get other characters
177 * passed through to your shell, change the "meta" option.
179 * To avoid a function call per character, we do a first pass through
180 * the meta characters looking for characters that aren't expected
181 * to be there, and then we can ignore them in the user's argument.
183 if (opts_empty(sp, O_SHELL, 1) || opts_empty(sp, O_SHELLMETA, 1))
184 n = 0;
185 else {
186 for (p = mp = O_STR(sp, O_SHELLMETA); *p != '\0'; ++p)
187 if (isblank(*p) || isalnum(*p))
188 break;
189 p = bp + SHELLOFFSET;
190 n = len - SHELLOFFSET;
191 if (*p != '\0') {
192 for (; n > 0; --n, ++p)
193 if (strchr(mp, *p) != NULL)
194 break;
195 } else
196 for (; n > 0; --n, ++p)
197 if (!isblank(*p) &&
198 !isalnum(*p) && strchr(mp, *p) != NULL)
199 break;
203 * If we found a meta character in the string, fork a shell to expand
204 * it. Unfortunately, this is comparatively slow. Historically, it
205 * didn't matter much, since users don't enter meta characters as part
206 * of pathnames that frequently. The addition of filename completion
207 * broke that assumption because it's easy to use. As a result, lots
208 * folks have complained that the expansion code is too slow. So, we
209 * detect filename completion as a special case, and do it internally.
210 * Note that this code assumes that the <asterisk> character is the
211 * match-anything meta character. That feels safe -- if anyone writes
212 * a shell that doesn't follow that convention, I'd suggest giving them
213 * a festive hot-lead enema.
215 switch (n) {
216 case 0:
217 p = bp + SHELLOFFSET;
218 len -= SHELLOFFSET;
219 rval = argv_exp3(sp, excp, p, len);
220 break;
221 case 1:
222 if (*p == '*') {
223 *p = '\0';
224 rval = argv_lexp(sp, excp, bp + SHELLOFFSET);
225 break;
227 /* FALLTHROUGH */
228 default:
229 if (argv_sexp(sp, &bp, &blen, &len)) {
230 rval = 1;
231 goto err;
233 p = bp;
234 rval = argv_exp3(sp, excp, p, len);
235 break;
238 err: FREE_SPACE(sp, bp, blen);
239 return (rval);
243 * argv_exp3 --
244 * Take a string and break it up into an argv, which is appended
245 * to the argument list.
247 * PUBLIC: int argv_exp3 __P((SCR *, EXCMD *, char *, size_t));
250 argv_exp3(sp, excp, cmd, cmdlen)
251 SCR *sp;
252 EXCMD *excp;
253 char *cmd;
254 size_t cmdlen;
256 EX_PRIVATE *exp;
257 size_t len;
258 int ch, off;
259 char *ap, *p;
261 for (exp = EXP(sp); cmdlen > 0; ++exp->argsoff) {
262 /* Skip any leading whitespace. */
263 for (; cmdlen > 0; --cmdlen, ++cmd) {
264 ch = *cmd;
265 if (!isblank(ch))
266 break;
268 if (cmdlen == 0)
269 break;
272 * Determine the length of this whitespace delimited
273 * argument.
275 * QUOTING NOTE:
277 * Skip any character preceded by the user's quoting
278 * character.
280 for (ap = cmd, len = 0; cmdlen > 0; ++cmd, --cmdlen, ++len) {
281 ch = *cmd;
282 if (IS_ESCAPE(sp, excp, ch) && cmdlen > 1) {
283 ++cmd;
284 --cmdlen;
285 } else if (isblank(ch))
286 break;
290 * Copy the argument into place.
292 * QUOTING NOTE:
294 * Lose quote chars.
296 argv_alloc(sp, len);
297 off = exp->argsoff;
298 exp->args[off]->len = len;
299 for (p = exp->args[off]->bp; len > 0; --len, *p++ = *ap++)
300 if (IS_ESCAPE(sp, excp, *ap))
301 ++ap;
302 *p = '\0';
304 excp->argv = exp->args;
305 excp->argc = exp->argsoff;
307 #if defined(DEBUG) && 0
308 for (cnt = 0; cnt < exp->argsoff; ++cnt)
309 vtrace(sp, "arg %d: {%s}\n", cnt, exp->argv[cnt]);
310 #endif
311 return (0);
315 * argv_fexp --
316 * Do file name and bang command expansion.
318 static int
319 argv_fexp(sp, excp, cmd, cmdlen, p, lenp, bpp, blenp, is_bang)
320 SCR *sp;
321 EXCMD *excp;
322 char *cmd, *p, **bpp;
323 size_t cmdlen, *lenp, *blenp;
324 int is_bang;
326 EX_PRIVATE *exp;
327 char *bp, *t;
328 size_t blen, len, off, tlen;
330 /* Replace file name characters. */
331 for (bp = *bpp, blen = *blenp, len = *lenp; cmdlen > 0; --cmdlen, ++cmd)
332 switch (*cmd) {
333 case '!':
334 if (!is_bang)
335 goto ins_ch;
336 exp = EXP(sp);
337 if (exp->lastbcomm == NULL) {
338 msgq(sp, M_ERR,
339 "115|No previous command to replace \"!\"");
340 return (1);
342 len += tlen = strlen(exp->lastbcomm);
343 off = p - bp;
344 ADD_SPACE_RET(sp, bp, blen, len);
345 p = bp + off;
346 memcpy(p, exp->lastbcomm, tlen);
347 p += tlen;
348 F_SET(excp, E_MODIFY);
349 break;
350 case '%':
351 if ((t = sp->frp->name) == NULL) {
352 msgq(sp, M_ERR,
353 "116|No filename to substitute for %%");
354 return (1);
356 tlen = strlen(t);
357 len += tlen;
358 off = p - bp;
359 ADD_SPACE_RET(sp, bp, blen, len);
360 p = bp + off;
361 memcpy(p, t, tlen);
362 p += tlen;
363 F_SET(excp, E_MODIFY);
364 break;
365 case '#':
366 if ((t = sp->alt_name) == NULL) {
367 msgq(sp, M_ERR,
368 "117|No filename to substitute for #");
369 return (1);
371 len += tlen = strlen(t);
372 off = p - bp;
373 ADD_SPACE_RET(sp, bp, blen, len);
374 p = bp + off;
375 memcpy(p, t, tlen);
376 p += tlen;
377 F_SET(excp, E_MODIFY);
378 break;
379 case '\\':
381 * QUOTING NOTE:
383 * Strip any backslashes that protected the file
384 * expansion characters.
386 if (cmdlen > 1 &&
387 (cmd[1] == '%' || cmd[1] == '#' || cmd[1] == '!')) {
388 ++cmd;
389 --cmdlen;
391 /* FALLTHROUGH */
392 default:
393 ins_ch: ++len;
394 off = p - bp;
395 ADD_SPACE_RET(sp, bp, blen, len);
396 p = bp + off;
397 *p++ = *cmd;
400 /* Nul termination. */
401 ++len;
402 off = p - bp;
403 ADD_SPACE_RET(sp, bp, blen, len);
404 p = bp + off;
405 *p = '\0';
407 /* Return the new string length, buffer, buffer length. */
408 *lenp = len - 1;
409 *bpp = bp;
410 *blenp = blen;
411 return (0);
415 * argv_alloc --
416 * Make more space for arguments.
418 static int
419 argv_alloc(sp, len)
420 SCR *sp;
421 size_t len;
423 ARGS *ap;
424 EX_PRIVATE *exp;
425 int cnt, off;
428 * Allocate room for another argument, always leaving
429 * enough room for an ARGS structure with a length of 0.
431 #define INCREMENT 20
432 exp = EXP(sp);
433 off = exp->argsoff;
434 if (exp->argscnt == 0 || off + 2 >= exp->argscnt - 1) {
435 cnt = exp->argscnt + INCREMENT;
436 REALLOC(sp, exp->args, ARGS **, cnt * sizeof(ARGS *));
437 if (exp->args == NULL) {
438 (void)argv_free(sp);
439 goto mem;
441 memset(&exp->args[exp->argscnt], 0, INCREMENT * sizeof(ARGS *));
442 exp->argscnt = cnt;
445 /* First argument. */
446 if (exp->args[off] == NULL) {
447 CALLOC(sp, exp->args[off], ARGS *, 1, sizeof(ARGS));
448 if (exp->args[off] == NULL)
449 goto mem;
452 /* First argument buffer. */
453 ap = exp->args[off];
454 ap->len = 0;
455 if (ap->blen < len + 1) {
456 ap->blen = len + 1;
457 REALLOC(sp, ap->bp, CHAR_T *, ap->blen * sizeof(CHAR_T));
458 if (ap->bp == NULL) {
459 ap->bp = NULL;
460 ap->blen = 0;
461 F_CLR(ap, A_ALLOCATED);
462 mem: msgq(sp, M_SYSERR, NULL);
463 return (1);
465 F_SET(ap, A_ALLOCATED);
468 /* Second argument. */
469 if (exp->args[++off] == NULL) {
470 CALLOC(sp, exp->args[off], ARGS *, 1, sizeof(ARGS));
471 if (exp->args[off] == NULL)
472 goto mem;
474 /* 0 length serves as end-of-argument marker. */
475 exp->args[off]->len = 0;
476 return (0);
480 * argv_free --
481 * Free up argument structures.
483 * PUBLIC: int argv_free __P((SCR *));
486 argv_free(sp)
487 SCR *sp;
489 EX_PRIVATE *exp;
490 int off;
492 exp = EXP(sp);
493 if (exp->args != NULL) {
494 for (off = 0; off < exp->argscnt; ++off) {
495 if (exp->args[off] == NULL)
496 continue;
497 if (F_ISSET(exp->args[off], A_ALLOCATED))
498 free(exp->args[off]->bp);
499 free(exp->args[off]);
501 free(exp->args);
503 exp->args = NULL;
504 exp->argscnt = 0;
505 exp->argsoff = 0;
506 return (0);
510 * argv_lexp --
511 * Find all file names matching the prefix and append them to the
512 * buffer.
514 static int
515 argv_lexp(sp, excp, path)
516 SCR *sp;
517 EXCMD *excp;
518 char *path;
520 struct dirent *dp;
521 DIR *dirp;
522 EX_PRIVATE *exp;
523 int off;
524 size_t dlen, len, nlen;
525 char *dname, *name, *p;
527 exp = EXP(sp);
529 /* Set up the name and length for comparison. */
530 if ((p = strrchr(path, '/')) == NULL) {
531 dname = ".";
532 dlen = 0;
533 name = path;
534 } else {
535 if (p == path) {
536 dname = "/";
537 dlen = 1;
538 } else {
539 *p = '\0';
540 dname = path;
541 dlen = strlen(path);
543 name = p + 1;
545 nlen = strlen(name);
548 * XXX
549 * We don't use the d_namlen field, it's not portable enough; we
550 * assume that d_name is nul terminated, instead.
552 if ((dirp = opendir(dname)) == NULL) {
553 msgq_str(sp, M_SYSERR, dname, "%s");
554 return (1);
556 for (off = exp->argsoff; (dp = readdir(dirp)) != NULL;) {
557 if (nlen == 0) {
558 if (dp->d_name[0] == '.')
559 continue;
560 len = strlen(dp->d_name);
561 } else {
562 len = strlen(dp->d_name);
563 if (len < nlen || memcmp(dp->d_name, name, nlen))
564 continue;
567 /* Directory + name + slash + null. */
568 argv_alloc(sp, dlen + len + 2);
569 p = exp->args[exp->argsoff]->bp;
570 if (dlen != 0) {
571 memcpy(p, dname, dlen);
572 p += dlen;
573 if (dlen > 1 || dname[0] != '/')
574 *p++ = '/';
576 memcpy(p, dp->d_name, len + 1);
577 exp->args[exp->argsoff]->len = dlen + len + 1;
578 ++exp->argsoff;
579 excp->argv = exp->args;
580 excp->argc = exp->argsoff;
582 closedir(dirp);
584 if (off == exp->argsoff) {
586 * If we didn't find a match, complain that the expansion
587 * failed. We can't know for certain that's the error, but
588 * it's a good guess, and it matches historic practice.
590 msgq(sp, M_ERR, "304|Shell expansion failed");
591 return (1);
593 qsort(exp->args + off, exp->argsoff - off, sizeof(ARGS *), argv_comp);
594 return (0);
598 * argv_comp --
599 * Alphabetic comparison.
601 static int
602 argv_comp(a, b)
603 const void *a, *b;
605 return (strcmp((char *)(*(ARGS **)a)->bp, (char *)(*(ARGS **)b)->bp));
609 * argv_sexp --
610 * Fork a shell, pipe a command through it, and read the output into
611 * a buffer.
613 static int
614 argv_sexp(sp, bpp, blenp, lenp)
615 SCR *sp;
616 char **bpp;
617 size_t *blenp, *lenp;
619 enum { SEXP_ERR, SEXP_EXPANSION_ERR, SEXP_OK } rval;
620 FILE *ifp;
621 pid_t pid;
622 size_t blen, len;
623 int ch, std_output[2];
624 char *bp, *p, *sh, *sh_path;
626 /* Secure means no shell access. */
627 if (O_ISSET(sp, O_SECURE)) {
628 msgq(sp, M_ERR,
629 "289|Shell expansions not supported when the secure edit option is set");
630 return (1);
633 sh_path = O_STR(sp, O_SHELL);
634 if ((sh = strrchr(sh_path, '/')) == NULL)
635 sh = sh_path;
636 else
637 ++sh;
639 /* Local copies of the buffer variables. */
640 bp = *bpp;
641 blen = *blenp;
644 * There are two different processes running through this code, named
645 * the utility (the shell) and the parent. The utility reads standard
646 * input and writes standard output and standard error output. The
647 * parent writes to the utility, reads its standard output and ignores
648 * its standard error output. Historically, the standard error output
649 * was discarded by vi, as it produces a lot of noise when file patterns
650 * don't match.
652 * The parent reads std_output[0], and the utility writes std_output[1].
654 ifp = NULL;
655 std_output[0] = std_output[1] = -1;
656 if (pipe(std_output) < 0) {
657 msgq(sp, M_SYSERR, "pipe");
658 return (1);
660 if ((ifp = fdopen(std_output[0], "r")) == NULL) {
661 msgq(sp, M_SYSERR, "fdopen");
662 goto err;
666 * Do the minimal amount of work possible, the shell is going to run
667 * briefly and then exit. We sincerely hope.
669 switch (pid = vfork()) {
670 case -1: /* Error. */
671 msgq(sp, M_SYSERR, "vfork");
672 err: if (ifp != NULL)
673 (void)fclose(ifp);
674 else if (std_output[0] != -1)
675 close(std_output[0]);
676 if (std_output[1] != -1)
677 close(std_output[0]);
678 return (1);
679 case 0: /* Utility. */
680 /* Redirect stdout to the write end of the pipe. */
681 (void)dup2(std_output[1], STDOUT_FILENO);
683 /* Close the utility's file descriptors. */
684 (void)close(std_output[0]);
685 (void)close(std_output[1]);
686 (void)close(STDERR_FILENO);
689 * XXX
690 * Assume that all shells have -c.
692 execl(sh_path, sh, "-c", bp, NULL);
693 msgq_str(sp, M_SYSERR, sh_path, "118|Error: execl: %s");
694 _exit(127);
695 default: /* Parent. */
696 /* Close the pipe ends the parent won't use. */
697 (void)close(std_output[1]);
698 break;
702 * Copy process standard output into a buffer.
704 * !!!
705 * Historic vi apparently discarded leading \n and \r's from
706 * the shell output stream. We don't on the grounds that any
707 * shell that does that is broken.
709 for (p = bp, len = 0, ch = EOF;
710 (ch = getc(ifp)) != EOF; *p++ = ch, --blen, ++len)
711 if (blen < 5) {
712 ADD_SPACE_GOTO(sp, bp, *blenp, *blenp * 2);
713 p = bp + len;
714 blen = *blenp - len;
717 /* Delete the final newline, nul terminate the string. */
718 if (p > bp && (p[-1] == '\n' || p[-1] == '\r')) {
719 --p;
720 --len;
722 *p = '\0';
723 *lenp = len;
724 *bpp = bp; /* *blenp is already updated. */
726 if (ferror(ifp))
727 goto ioerr;
728 if (fclose(ifp)) {
729 ioerr: msgq_str(sp, M_ERR, sh, "119|I/O error: %s");
730 alloc_err: rval = SEXP_ERR;
731 } else
732 rval = SEXP_OK;
735 * Wait for the process. If the shell process fails (e.g., "echo $q"
736 * where q wasn't a defined variable) or if the returned string has
737 * no characters or only blank characters, (e.g., "echo $5"), complain
738 * that the shell expansion failed. We can't know for certain that's
739 * the error, but it's a good guess, and it matches historic practice.
740 * This won't catch "echo foo_$5", but that's not a common error and
741 * historic vi didn't catch it either.
743 if (proc_wait(sp, (long)pid, sh, 1, 0))
744 rval = SEXP_EXPANSION_ERR;
746 for (p = bp; len; ++p, --len)
747 if (!isblank(*p))
748 break;
749 if (len == 0)
750 rval = SEXP_EXPANSION_ERR;
752 if (rval == SEXP_EXPANSION_ERR)
753 msgq(sp, M_ERR, "304|Shell expansion failed");
755 return (rval == SEXP_OK ? 0 : 1);