perl: fix -Wformat-security warning in ENDMESSAGE()
[nvi.git] / ex / ex_argv.c
blob41074797525bbd1e0b30a87e73df309fdcb96a8d
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.39 2003/11/05 17:11:54 skimo Exp $ (Berkeley) $Date: 2003/11/05 17:11:54 $";
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(SCR *sp, EXCMD *excp)
47 EX_PRIVATE *exp;
49 exp = EXP(sp);
50 exp->argsoff = 0;
51 argv_alloc(sp, 1);
53 excp->argv = exp->args;
54 excp->argc = exp->argsoff;
55 return (0);
59 * argv_exp0 --
60 * Append a string to the argument list.
62 * PUBLIC: int argv_exp0 __P((SCR *, EXCMD *, CHAR_T *, size_t));
64 int
65 argv_exp0(SCR *sp, EXCMD *excp, CHAR_T *cmd, size_t cmdlen)
67 EX_PRIVATE *exp;
69 exp = EXP(sp);
70 argv_alloc(sp, cmdlen);
71 MEMCPY(exp->args[exp->argsoff]->bp, cmd, cmdlen);
72 exp->args[exp->argsoff]->bp[cmdlen] = '\0';
73 exp->args[exp->argsoff]->len = cmdlen;
74 ++exp->argsoff;
75 excp->argv = exp->args;
76 excp->argc = exp->argsoff;
77 return (0);
81 * argv_exp1 --
82 * Do file name expansion on a string, and append it to the
83 * argument list.
85 * PUBLIC: int argv_exp1 __P((SCR *, EXCMD *, CHAR_T *, size_t, int));
87 int
88 argv_exp1(SCR *sp, EXCMD *excp, CHAR_T *cmd, size_t cmdlen, int is_bang)
90 EX_PRIVATE *exp;
91 size_t blen, len;
92 CHAR_T *p, *t, *bp;
93 size_t wlen;
94 CHAR_T *wp;
96 GET_SPACE_RETW(sp, bp, blen, 512);
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);
105 /* If it's empty, we're done. */
106 if (len != 0) {
107 for (p = bp, t = bp + len; p < t; ++p)
108 if (!ISBLANK(*p))
109 break;
110 if (p == t)
111 goto ret;
112 } else
113 goto ret;
115 (void)argv_exp0(sp, excp, bp, len);
117 ret: FREE_SPACEW(sp, bp, blen);
118 return (0);
122 * argv_exp2 --
123 * Do file name and shell expansion on a string, and append it to
124 * the argument list.
126 * PUBLIC: int argv_exp2 __P((SCR *, EXCMD *, CHAR_T *, size_t));
129 argv_exp2(SCR *sp, EXCMD *excp, CHAR_T *cmd, size_t cmdlen)
131 size_t blen, len, n;
132 int rval;
133 CHAR_T *bp, *p;
134 char *mp, *np;
136 GET_SPACE_RETW(sp, bp, blen, 512);
138 #define SHELLECHO "echo "
139 #define SHELLOFFSET (sizeof(SHELLECHO) - 1)
140 p = bp;
141 *p++ = 'e';
142 *p++ = 'c';
143 *p++ = 'h';
144 *p++ = 'o';
145 *p++ = ' ';
146 len = SHELLOFFSET;
148 #if defined(DEBUG) && 0
149 vtrace(sp, "file_argv: {%.*s}\n", (int)cmdlen, cmd);
150 #endif
152 if (argv_fexp(sp, excp, cmd, cmdlen, p, &len, &bp, &blen, 0)) {
153 rval = 1;
154 goto err;
157 #if defined(DEBUG) && 0
158 vtrace(sp, "before shell: %d: {%s}\n", len, bp);
159 #endif
162 * Do shell word expansion -- it's very, very hard to figure out what
163 * magic characters the user's shell expects. Historically, it was a
164 * union of v7 shell and csh meta characters. We match that practice
165 * by default, so ":read \%" tries to read a file named '%'. It would
166 * make more sense to pass any special characters through the shell,
167 * but then, if your shell was csh, the above example will behave
168 * differently in nvi than in vi. If you want to get other characters
169 * passed through to your shell, change the "meta" option.
171 * To avoid a function call per character, we do a first pass through
172 * the meta characters looking for characters that aren't expected
173 * to be there, and then we can ignore them in the user's argument.
175 if (opts_empty(sp, O_SHELL, 1) || opts_empty(sp, O_SHELLMETA, 1))
176 n = 0;
177 else {
178 for (np = mp = O_STR(sp, O_SHELLMETA); *np != '\0'; ++np)
179 if (isblank(*np) || isalnum(*np))
180 break;
181 p = bp + SHELLOFFSET;
182 n = len - SHELLOFFSET;
183 if (*p != '\0') {
184 for (; n > 0; --n, ++p)
185 if (strchr(mp, *p) != NULL)
186 break;
187 } else
188 for (; n > 0; --n, ++p)
189 if (!ISBLANK(*p) &&
190 !isalnum(*p) && strchr(mp, *p) != NULL)
191 break;
195 * If we found a meta character in the string, fork a shell to expand
196 * it. Unfortunately, this is comparatively slow. Historically, it
197 * didn't matter much, since users don't enter meta characters as part
198 * of pathnames that frequently. The addition of filename completion
199 * broke that assumption because it's easy to use. As a result, lots
200 * folks have complained that the expansion code is too slow. So, we
201 * detect filename completion as a special case, and do it internally.
202 * Note that this code assumes that the <asterisk> character is the
203 * match-anything meta character. That feels safe -- if anyone writes
204 * a shell that doesn't follow that convention, I'd suggest giving them
205 * a festive hot-lead enema.
207 switch (n) {
208 case 0:
209 p = bp + SHELLOFFSET;
210 len -= SHELLOFFSET;
211 rval = argv_exp3(sp, excp, p, len);
212 break;
213 case 1:
214 if (*p == '*') {
215 char *np, *d;
216 size_t nlen;
218 *p = '\0';
219 INT2CHAR(sp, bp + SHELLOFFSET,
220 STRLEN(bp + SHELLOFFSET) + 1, np, nlen);
221 d = strdup(np);
222 rval = argv_lexp(sp, excp, d);
223 free (d);
224 break;
226 /* FALLTHROUGH */
227 default:
228 if (argv_sexp(sp, &bp, &blen, &len)) {
229 rval = 1;
230 goto err;
232 p = bp;
233 rval = argv_exp3(sp, excp, p, len);
234 break;
237 err: FREE_SPACEW(sp, bp, blen);
238 return (rval);
242 * argv_exp3 --
243 * Take a string and break it up into an argv, which is appended
244 * to the argument list.
246 * PUBLIC: int argv_exp3 __P((SCR *, EXCMD *, CHAR_T *, size_t));
249 argv_exp3(SCR *sp, EXCMD *excp, CHAR_T *cmd, size_t cmdlen)
251 EX_PRIVATE *exp;
252 size_t len;
253 int ch, off;
254 CHAR_T *ap, *p;
256 for (exp = EXP(sp); cmdlen > 0; ++exp->argsoff) {
257 /* Skip any leading whitespace. */
258 for (; cmdlen > 0; --cmdlen, ++cmd) {
259 ch = *cmd;
260 if (!isblank(ch))
261 break;
263 if (cmdlen == 0)
264 break;
267 * Determine the length of this whitespace delimited
268 * argument.
270 * QUOTING NOTE:
272 * Skip any character preceded by the user's quoting
273 * character.
275 for (ap = cmd, len = 0; cmdlen > 0; ++cmd, --cmdlen, ++len) {
276 ch = *cmd;
277 if (IS_ESCAPE(sp, excp, ch) && cmdlen > 1) {
278 ++cmd;
279 --cmdlen;
280 } else if (isblank(ch))
281 break;
285 * Copy the argument into place.
287 * QUOTING NOTE:
289 * Lose quote chars.
291 argv_alloc(sp, len);
292 off = exp->argsoff;
293 exp->args[off]->len = len;
294 for (p = exp->args[off]->bp; len > 0; --len, *p++ = *ap++)
295 if (IS_ESCAPE(sp, excp, *ap))
296 ++ap;
297 *p = '\0';
299 excp->argv = exp->args;
300 excp->argc = exp->argsoff;
302 #if defined(DEBUG) && 0
303 for (cnt = 0; cnt < exp->argsoff; ++cnt)
304 vtrace(sp, "arg %d: {%s}\n", cnt, exp->argv[cnt]);
305 #endif
306 return (0);
310 * argv_fexp --
311 * Do file name and bang command expansion.
313 static int
314 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)
316 EX_PRIVATE *exp;
317 char *t;
318 size_t blen, len, off, tlen;
319 CHAR_T *bp;
320 CHAR_T *wp;
321 size_t wlen;
323 /* Replace file name characters. */
324 for (bp = *bpp, blen = *blenp, len = *lenp; cmdlen > 0; --cmdlen, ++cmd)
325 switch (*cmd) {
326 case '!':
327 if (!is_bang)
328 goto ins_ch;
329 exp = EXP(sp);
330 if (exp->lastbcomm == NULL) {
331 msgq(sp, M_ERR,
332 "115|No previous command to replace \"!\"");
333 return (1);
335 len += tlen = STRLEN(exp->lastbcomm);
336 off = p - bp;
337 ADD_SPACE_RETW(sp, bp, blen, len);
338 p = bp + off;
339 MEMCPY(p, exp->lastbcomm, tlen);
340 p += tlen;
341 F_SET(excp, E_MODIFY);
342 break;
343 case '%':
344 if ((t = sp->frp->name) == NULL) {
345 msgq(sp, M_ERR,
346 "116|No filename to substitute for %%");
347 return (1);
349 tlen = strlen(t);
350 len += tlen;
351 off = p - bp;
352 ADD_SPACE_RETW(sp, bp, blen, len);
353 p = bp + off;
354 CHAR2INT(sp, t, tlen, wp, wlen);
355 MEMCPY(p, wp, wlen);
356 p += wlen;
357 F_SET(excp, E_MODIFY);
358 break;
359 case '#':
360 if ((t = sp->alt_name) == NULL) {
361 msgq(sp, M_ERR,
362 "117|No filename to substitute for #");
363 return (1);
365 len += tlen = strlen(t);
366 off = p - bp;
367 ADD_SPACE_RETW(sp, bp, blen, len);
368 p = bp + off;
369 CHAR2INT(sp, t, tlen, wp, wlen);
370 MEMCPY(p, wp, wlen);
371 p += tlen;
372 F_SET(excp, E_MODIFY);
373 break;
374 case '\\':
376 * QUOTING NOTE:
378 * Strip any backslashes that protected the file
379 * expansion characters.
381 if (cmdlen > 1 &&
382 (cmd[1] == '%' || cmd[1] == '#' || cmd[1] == '!')) {
383 ++cmd;
384 --cmdlen;
386 /* FALLTHROUGH */
387 default:
388 ins_ch: ++len;
389 off = p - bp;
390 ADD_SPACE_RETW(sp, bp, blen, len);
391 p = bp + off;
392 *p++ = *cmd;
395 /* Nul termination. */
396 ++len;
397 off = p - bp;
398 ADD_SPACE_RETW(sp, bp, blen, len);
399 p = bp + off;
400 *p = '\0';
402 /* Return the new string length, buffer, buffer length. */
403 *lenp = len - 1;
404 *bpp = bp;
405 *blenp = blen;
406 return (0);
410 * argv_alloc --
411 * Make more space for arguments.
413 static int
414 argv_alloc(SCR *sp, size_t len)
416 ARGS *ap;
417 EX_PRIVATE *exp;
418 int cnt, off;
421 * Allocate room for another argument, always leaving
422 * enough room for an ARGS structure with a length of 0.
424 #define INCREMENT 20
425 exp = EXP(sp);
426 off = exp->argsoff;
427 if (exp->argscnt == 0 || off + 2 >= exp->argscnt - 1) {
428 cnt = exp->argscnt + INCREMENT;
429 REALLOC(sp, exp->args, ARGS **, cnt * sizeof(ARGS *));
430 if (exp->args == NULL) {
431 (void)argv_free(sp);
432 goto mem;
434 memset(&exp->args[exp->argscnt], 0, INCREMENT * sizeof(ARGS *));
435 exp->argscnt = cnt;
438 /* First argument. */
439 if (exp->args[off] == NULL) {
440 CALLOC(sp, exp->args[off], ARGS *, 1, sizeof(ARGS));
441 if (exp->args[off] == NULL)
442 goto mem;
445 /* First argument buffer. */
446 ap = exp->args[off];
447 ap->len = 0;
448 if (ap->blen < len + 1) {
449 ap->blen = len + 1;
450 REALLOC(sp, ap->bp, CHAR_T *, ap->blen * sizeof(CHAR_T));
451 if (ap->bp == NULL) {
452 ap->bp = NULL;
453 ap->blen = 0;
454 F_CLR(ap, A_ALLOCATED);
455 mem: msgq(sp, M_SYSERR, NULL);
456 return (1);
458 F_SET(ap, A_ALLOCATED);
461 /* Second argument. */
462 if (exp->args[++off] == NULL) {
463 CALLOC(sp, exp->args[off], ARGS *, 1, sizeof(ARGS));
464 if (exp->args[off] == NULL)
465 goto mem;
467 /* 0 length serves as end-of-argument marker. */
468 exp->args[off]->len = 0;
469 return (0);
473 * argv_free --
474 * Free up argument structures.
476 * PUBLIC: int argv_free __P((SCR *));
479 argv_free(SCR *sp)
481 EX_PRIVATE *exp;
482 int off;
484 exp = EXP(sp);
485 if (exp->args != NULL) {
486 for (off = 0; off < exp->argscnt; ++off) {
487 if (exp->args[off] == NULL)
488 continue;
489 if (F_ISSET(exp->args[off], A_ALLOCATED))
490 free(exp->args[off]->bp);
491 free(exp->args[off]);
493 free(exp->args);
495 exp->args = NULL;
496 exp->argscnt = 0;
497 exp->argsoff = 0;
498 return (0);
502 * argv_lexp --
503 * Find all file names matching the prefix and append them to the
504 * buffer.
506 static int
507 argv_lexp(SCR *sp, EXCMD *excp, char *path)
509 struct dirent *dp;
510 DIR *dirp;
511 EX_PRIVATE *exp;
512 int off;
513 size_t dlen, len, nlen;
514 char *dname, *name;
515 char *p;
516 size_t wlen;
517 CHAR_T *wp;
518 CHAR_T *n;
520 exp = EXP(sp);
522 /* Set up the name and length for comparison. */
523 if ((p = strrchr(path, '/')) == NULL) {
524 dname = ".";
525 dlen = 0;
526 name = path;
527 } else {
528 if (p == path) {
529 dname = "/";
530 dlen = 1;
531 } else {
532 *p = '\0';
533 dname = path;
534 dlen = strlen(path);
536 name = p + 1;
538 nlen = strlen(name);
541 * XXX
542 * We don't use the d_namlen field, it's not portable enough; we
543 * assume that d_name is nul terminated, instead.
545 if ((dirp = opendir(dname)) == NULL) {
546 msgq_str(sp, M_SYSERR, dname, "%s");
547 return (1);
549 for (off = exp->argsoff; (dp = readdir(dirp)) != NULL;) {
550 if (nlen == 0) {
551 if (dp->d_name[0] == '.')
552 continue;
553 len = strlen(dp->d_name);
554 } else {
555 len = strlen(dp->d_name);
556 if (len < nlen || memcmp(dp->d_name, name, nlen))
557 continue;
560 /* Directory + name + slash + null. */
561 argv_alloc(sp, dlen + len + 2);
562 n = exp->args[exp->argsoff]->bp;
563 if (dlen != 0) {
564 CHAR2INT(sp, dname, dlen, wp, wlen);
565 MEMCPY(n, wp, wlen);
566 n += dlen;
567 if (dlen > 1 || dname[0] != '/')
568 *n++ = '/';
570 CHAR2INT(sp, dp->d_name, len + 1, wp, wlen);
571 MEMCPY(n, wp, wlen);
572 exp->args[exp->argsoff]->len = dlen + len + 1;
573 ++exp->argsoff;
574 excp->argv = exp->args;
575 excp->argc = exp->argsoff;
577 closedir(dirp);
579 if (off == exp->argsoff) {
581 * If we didn't find a match, complain that the expansion
582 * failed. We can't know for certain that's the error, but
583 * it's a good guess, and it matches historic practice.
585 msgq(sp, M_ERR, "304|Shell expansion failed");
586 return (1);
588 qsort(exp->args + off, exp->argsoff - off, sizeof(ARGS *), argv_comp);
589 return (0);
593 * argv_comp --
594 * Alphabetic comparison.
596 static int
597 argv_comp(const void *a, const void *b)
599 return (STRCMP((*(ARGS **)a)->bp, (*(ARGS **)b)->bp));
603 * argv_sexp --
604 * Fork a shell, pipe a command through it, and read the output into
605 * a buffer.
607 static int
608 argv_sexp(SCR *sp, CHAR_T **bpp, size_t *blenp, size_t *lenp)
610 enum { SEXP_ERR, SEXP_EXPANSION_ERR, SEXP_OK } rval;
611 FILE *ifp;
612 pid_t pid;
613 size_t blen, len;
614 int ch, std_output[2];
615 CHAR_T *bp, *p;
616 char *sh, *sh_path;
617 char *np;
618 size_t nlen;
620 /* Secure means no shell access. */
621 if (O_ISSET(sp, O_SECURE)) {
622 msgq(sp, M_ERR,
623 "289|Shell expansions not supported when the secure edit option is set");
624 return (1);
627 sh_path = O_STR(sp, O_SHELL);
628 if ((sh = strrchr(sh_path, '/')) == NULL)
629 sh = sh_path;
630 else
631 ++sh;
633 /* Local copies of the buffer variables. */
634 bp = *bpp;
635 blen = *blenp;
638 * There are two different processes running through this code, named
639 * the utility (the shell) and the parent. The utility reads standard
640 * input and writes standard output and standard error output. The
641 * parent writes to the utility, reads its standard output and ignores
642 * its standard error output. Historically, the standard error output
643 * was discarded by vi, as it produces a lot of noise when file patterns
644 * don't match.
646 * The parent reads std_output[0], and the utility writes std_output[1].
648 ifp = NULL;
649 std_output[0] = std_output[1] = -1;
650 if (pipe(std_output) < 0) {
651 msgq(sp, M_SYSERR, "pipe");
652 return (1);
654 if ((ifp = fdopen(std_output[0], "r")) == NULL) {
655 msgq(sp, M_SYSERR, "fdopen");
656 goto err;
660 * Do the minimal amount of work possible, the shell is going to run
661 * briefly and then exit. We sincerely hope.
663 switch (pid = vfork()) {
664 case -1: /* Error. */
665 msgq(sp, M_SYSERR, "vfork");
666 err: if (ifp != NULL)
667 (void)fclose(ifp);
668 else if (std_output[0] != -1)
669 close(std_output[0]);
670 if (std_output[1] != -1)
671 close(std_output[0]);
672 return (1);
673 case 0: /* Utility. */
674 /* Redirect stdout to the write end of the pipe. */
675 (void)dup2(std_output[1], STDOUT_FILENO);
677 /* Close the utility's file descriptors. */
678 (void)close(std_output[0]);
679 (void)close(std_output[1]);
680 (void)close(STDERR_FILENO);
683 * XXX
684 * Assume that all shells have -c.
686 INT2CHAR(sp, bp, STRLEN(bp)+1, np, nlen);
687 execl(sh_path, sh, "-c", np, (char *)NULL);
688 msgq_str(sp, M_SYSERR, sh_path, "118|Error: execl: %s");
689 _exit(127);
690 default: /* Parent. */
691 /* Close the pipe ends the parent won't use. */
692 (void)close(std_output[1]);
693 break;
697 * Copy process standard output into a buffer.
699 * !!!
700 * Historic vi apparently discarded leading \n and \r's from
701 * the shell output stream. We don't on the grounds that any
702 * shell that does that is broken.
704 for (p = bp, len = 0, ch = EOF;
705 (ch = getc(ifp)) != EOF; *p++ = ch, blen-=sizeof(CHAR_T), ++len)
706 if (blen < 5) {
707 ADD_SPACE_GOTOW(sp, bp, *blenp, *blenp * 2);
708 p = bp + len;
709 blen = *blenp - len;
712 /* Delete the final newline, nul terminate the string. */
713 if (p > bp && (p[-1] == '\n' || p[-1] == '\r')) {
714 --p;
715 --len;
717 *p = '\0';
718 *lenp = len;
719 *bpp = bp; /* *blenp is already updated. */
721 if (ferror(ifp))
722 goto ioerr;
723 if (fclose(ifp)) {
724 ioerr: msgq_str(sp, M_ERR, sh, "119|I/O error: %s");
725 alloc_err: rval = SEXP_ERR;
726 } else
727 rval = SEXP_OK;
730 * Wait for the process. If the shell process fails (e.g., "echo $q"
731 * where q wasn't a defined variable) or if the returned string has
732 * no characters or only blank characters, (e.g., "echo $5"), complain
733 * that the shell expansion failed. We can't know for certain that's
734 * the error, but it's a good guess, and it matches historic practice.
735 * This won't catch "echo foo_$5", but that's not a common error and
736 * historic vi didn't catch it either.
738 if (proc_wait(sp, (long)pid, sh, 1, 0))
739 rval = SEXP_EXPANSION_ERR;
741 for (p = bp; len; ++p, --len)
742 if (!ISBLANK(*p))
743 break;
744 if (len == 0)
745 rval = SEXP_EXPANSION_ERR;
747 if (rval == SEXP_EXPANSION_ERR)
748 msgq(sp, M_ERR, "304|Shell expansion failed");
750 return (rval == SEXP_OK ? 0 : 1);