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.
13 static const char sccsid
[] = "$Id: ex_argv.c,v 11.2 2012/10/09 23:00:29 zy Exp $";
16 #include <sys/types.h>
17 #include <sys/queue.h>
20 #include <bitstring.h>
31 #include "../common/common.h"
33 static int argv_alloc(SCR
*, size_t);
34 static int argv_comp(const void *, const void *);
35 static int argv_fexp(SCR
*, EXCMD
*,
36 CHAR_T
*, size_t, CHAR_T
*, size_t *, CHAR_T
**, size_t *, int);
37 static int argv_sexp(SCR
*, CHAR_T
**, size_t *, size_t *);
38 static int argv_flt_user(SCR
*, EXCMD
*, CHAR_T
*, size_t);
42 * Build a prototype arguments list.
44 * PUBLIC: int argv_init(SCR *, EXCMD *);
47 argv_init(SCR
*sp
, EXCMD
*excp
)
55 excp
->argv
= exp
->args
;
56 excp
->argc
= exp
->argsoff
;
62 * Append a string to the argument list.
64 * PUBLIC: int argv_exp0(SCR *, EXCMD *, CHAR_T *, size_t);
67 argv_exp0(SCR
*sp
, EXCMD
*excp
, CHAR_T
*cmd
, size_t cmdlen
)
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
;
77 excp
->argv
= exp
->args
;
78 excp
->argc
= exp
->argsoff
;
84 * Do file name expansion on a string, and append it to the
87 * PUBLIC: int argv_exp1(SCR *, EXCMD *, CHAR_T *, size_t, int);
90 argv_exp1(SCR
*sp
, EXCMD
*excp
, CHAR_T
*cmd
, size_t cmdlen
, int is_bang
)
96 GET_SPACE_RETW(sp
, bp
, blen
, 512);
100 if (argv_fexp(sp
, excp
, cmd
, cmdlen
, bp
, &len
, &bp
, &blen
, is_bang
)) {
101 FREE_SPACEW(sp
, bp
, blen
);
105 /* If it's empty, we're done. */
107 for (p
= bp
, t
= bp
+ len
; p
< t
; ++p
)
115 (void)argv_exp0(sp
, excp
, bp
, len
);
117 ret
: FREE_SPACEW(sp
, bp
, blen
);
123 * Do file name and shell expansion on a string, and append it to
126 * PUBLIC: int argv_exp2(SCR *, EXCMD *, CHAR_T *, size_t);
129 argv_exp2(SCR
*sp
, EXCMD
*excp
, CHAR_T
*cmd
, size_t cmdlen
)
135 GET_SPACE_RETW(sp
, bp
, blen
, 512);
137 #define SHELLECHO L("echo ")
138 #define SHELLOFFSET (SIZE(SHELLECHO) - 1)
139 MEMCPY(bp
, SHELLECHO
, SHELLOFFSET
);
140 p
= bp
+ SHELLOFFSET
;
143 #if defined(DEBUG) && 0
144 TRACE(sp
, "file_argv: {%.*s}\n", (int)cmdlen
, cmd
);
147 if (argv_fexp(sp
, excp
, cmd
, cmdlen
, p
, &len
, &bp
, &blen
, 0)) {
152 #if defined(DEBUG) && 0
153 TRACE(sp
, "before shell: %d: {%s}\n", len
, bp
);
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.
166 if (opts_empty(sp
, O_SHELL
, 1) || opts_empty(sp
, O_SHELLMETA
, 1))
169 p
= bp
+ SHELLOFFSET
;
170 n
= len
- SHELLOFFSET
;
171 for (; n
> 0; --n
, ++p
)
172 if (IS_SHELLMETA(sp
, *p
))
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.
189 p
= bp
+ SHELLOFFSET
;
191 rval
= argv_exp3(sp
, excp
, p
, len
);
194 if (argv_sexp(sp
, &bp
, &blen
, &len
)) {
199 rval
= argv_exp3(sp
, excp
, p
, len
);
203 err
: FREE_SPACEW(sp
, bp
, blen
);
209 * Take a string and break it up into an argv, which is appended
210 * to the argument list.
212 * PUBLIC: int argv_exp3(SCR *, EXCMD *, CHAR_T *, size_t);
215 argv_exp3(SCR
*sp
, EXCMD
*excp
, CHAR_T
*cmd
, size_t cmdlen
)
222 for (exp
= EXP(sp
); cmdlen
> 0; ++exp
->argsoff
) {
223 /* Skip any leading whitespace. */
224 for (; cmdlen
> 0; --cmdlen
, ++cmd
) {
233 * Determine the length of this whitespace delimited
238 * Skip any character preceded by the user's quoting
241 for (ap
= cmd
, len
= 0; cmdlen
> 0; ++cmd
, --cmdlen
, ++len
) {
243 if (IS_ESCAPE(sp
, excp
, ch
) && cmdlen
> 1) {
246 } else if (cmdskip(ch
))
251 * Copy the argument into place.
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
))
265 excp
->argv
= exp
->args
;
266 excp
->argc
= exp
->argsoff
;
268 #if defined(DEBUG) && 0
269 for (cnt
= 0; cnt
< exp
->argsoff
; ++cnt
)
270 TRACE(sp
, "arg %d: {%s}\n", cnt
, exp
->argv
[cnt
]);
277 * Filter the ex commands with a prefix, and append the results to
280 * PUBLIC: int argv_flt_ex(SCR *, EXCMD *, CHAR_T *, size_t);
283 argv_flt_ex(SCR
*sp
, EXCMD
*excp
, CHAR_T
*cmd
, size_t cmdlen
)
292 for (off
= exp
->argsoff
, cp
= cmds
; cp
->name
!= NULL
; ++cp
) {
293 len
= STRLEN(cp
->name
);
295 (cmdlen
> len
|| MEMCMP(cmd
, cp
->name
, cmdlen
)))
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
;
303 excp
->argv
= exp
->args
;
304 excp
->argc
= exp
->argsoff
;
312 * Filter the ~user list on the system with a prefix, and append
313 * the results to the argument list.
316 argv_flt_user(SCR
*sp
, EXCMD
*excp
, CHAR_T
*uname
, size_t ulen
)
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
)
333 while ((pw
= getpwent()) != NULL
) {
334 len
= strlen(pw
->pw_name
);
336 (nlen
> len
|| memcmp(np
, pw
->pw_name
, nlen
)))
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
;
346 excp
->argv
= exp
->args
;
347 excp
->argc
= exp
->argsoff
;
352 qsort(exp
->args
+ off
, exp
->argsoff
- off
, sizeof(ARGS
*), argv_comp
);
358 * Do file name and bang command expansion.
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
)
365 size_t blen
, len
, off
, tlen
;
370 /* Replace file name characters. */
371 for (bp
= *bpp
, blen
= *blenp
, len
= *lenp
; cmdlen
> 0; --cmdlen
, ++cmd
)
377 if (exp
->lastbcomm
== NULL
) {
379 "115|No previous command to replace \"!\"");
382 len
+= tlen
= STRLEN(exp
->lastbcomm
);
384 ADD_SPACE_RETW(sp
, bp
, blen
, len
);
386 MEMCPY(p
, exp
->lastbcomm
, tlen
);
388 F_SET(excp
, E_MODIFY
);
391 if ((t
= sp
->frp
->name
) == NULL
) {
393 "116|No filename to substitute for %%");
399 ADD_SPACE_RETW(sp
, bp
, blen
, len
);
401 CHAR2INT(sp
, t
, tlen
, wp
, wlen
);
404 F_SET(excp
, E_MODIFY
);
407 if ((t
= sp
->alt_name
) == NULL
) {
409 "117|No filename to substitute for #");
412 len
+= tlen
= strlen(t
);
414 ADD_SPACE_RETW(sp
, bp
, blen
, len
);
416 CHAR2INT(sp
, t
, tlen
, wp
, wlen
);
419 F_SET(excp
, E_MODIFY
);
425 * Strip any backslashes that protected the file
426 * expansion characters.
429 (cmd
[1] == '%' || cmd
[1] == '#' || cmd
[1] == '!')) {
437 ADD_SPACE_RETW(sp
, bp
, blen
, len
);
442 /* Nul termination. */
445 ADD_SPACE_RETW(sp
, bp
, blen
, len
);
449 /* Return the new string length, buffer, buffer length. */
458 * Make more space for arguments.
461 argv_alloc(SCR
*sp
, size_t len
)
468 * Allocate room for another argument, always leaving
469 * enough room for an ARGS structure with a length of 0.
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
) {
481 memset(&exp
->args
[exp
->argscnt
], 0, INCREMENT
* sizeof(ARGS
*));
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
)
492 /* First argument buffer. */
495 if (ap
->blen
< len
+ 1) {
497 REALLOC(sp
, ap
->bp
, CHAR_T
*, ap
->blen
* sizeof(CHAR_T
));
498 if (ap
->bp
== NULL
) {
501 F_CLR(ap
, A_ALLOCATED
);
502 mem
: msgq(sp
, M_SYSERR
, NULL
);
505 F_SET(ap
, A_ALLOCATED
);
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
)
514 /* 0 length serves as end-of-argument marker. */
515 exp
->args
[off
]->len
= 0;
521 * Free up argument structures.
523 * PUBLIC: int argv_free(SCR *);
532 if (exp
->args
!= NULL
) {
533 for (off
= 0; off
< exp
->argscnt
; ++off
) {
534 if (exp
->args
[off
] == NULL
)
536 if (F_ISSET(exp
->args
[off
], A_ALLOCATED
))
537 free(exp
->args
[off
]->bp
);
538 free(exp
->args
[off
]);
550 * Find all file names matching the prefix and append them to the
553 * PUBLIC: int argv_flt_path(SCR *, EXCMD *, CHAR_T *, size_t);
556 argv_flt_path(SCR
*sp
, EXCMD
*excp
, CHAR_T
*path
, size_t plen
)
562 size_t dlen
, len
, nlen
;
565 char *name
, *tp
, *epd
= NULL
;
571 /* Set up the name and length for comparison. */
572 if ((path
= v_wstrdup(sp
, path
, plen
)) == NULL
)
574 if ((p
= STRRCHR(path
, '/')) == NULL
) {
578 /* Filter ~user list instead. */
579 rc
= argv_flt_user(sp
, excp
, path
, plen
);
598 INT2CHAR(sp
, dname
, dlen
+ 1, tp
, nlen
);
599 if ((epd
= expanduser(tp
)) != NULL
)
601 if ((dirp
= opendir(tp
)) == NULL
) {
608 INT2CHAR(sp
, np
, STRLEN(np
), tp
, nlen
);
609 if ((name
= v_strdup(sp
, tp
, nlen
)) == NULL
) {
614 for (off
= exp
->argsoff
; (dp
= readdir(dirp
)) != NULL
;) {
616 if (dp
->d_name
[0] == '.')
621 if (len
< nlen
|| memcmp(dp
->d_name
, name
, nlen
))
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
;
630 MEMCPY(n
, dname
, dlen
);
632 if (dlen
> 1 || dname
[0] != '/')
634 exp
->args
[exp
->argsoff
]->len
= dlen
+ 1;
637 exp
->args
[exp
->argsoff
]->len
+= wlen
- 1;
639 excp
->argv
= exp
->args
;
640 excp
->argc
= exp
->argsoff
;
646 qsort(exp
->args
+ off
, exp
->argsoff
- off
, sizeof(ARGS
*), argv_comp
);
652 * Alphabetic comparison.
655 argv_comp(const void *a
, const void *b
)
657 return (STRCMP((*(ARGS
**)a
)->bp
, (*(ARGS
**)b
)->bp
));
662 * Fork a shell, pipe a command through it, and read the output into
666 argv_sexp(SCR
*sp
, CHAR_T
**bpp
, size_t *blenp
, size_t *lenp
)
668 enum { SEXP_ERR
, SEXP_EXPANSION_ERR
, SEXP_OK
} rval
;
672 int ch
, std_output
[2];
678 /* Secure means no shell access. */
679 if (O_ISSET(sp
, O_SECURE
)) {
681 "289|Shell expansions not supported when the secure edit option is set");
685 sh_path
= O_STR(sp
, O_SHELL
);
686 if ((sh
= strrchr(sh_path
, '/')) == NULL
)
691 /* Local copies of the buffer variables. */
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
704 * The parent reads std_output[0], and the utility writes std_output[1].
707 std_output
[0] = std_output
[1] = -1;
708 if (pipe(std_output
) < 0) {
709 msgq(sp
, M_SYSERR
, "pipe");
712 if ((ifp
= fdopen(std_output
[0], "r")) == NULL
) {
713 msgq(sp
, M_SYSERR
, "fdopen");
718 * Do the minimal amount of work possible, the shell is going to run
719 * briefly and then exit. We sincerely hope.
721 switch (pid
= vfork()) {
722 case -1: /* Error. */
723 msgq(sp
, M_SYSERR
, "vfork");
724 err
: if (ifp
!= NULL
)
726 else if (std_output
[0] != -1)
727 close(std_output
[0]);
728 if (std_output
[1] != -1)
729 close(std_output
[0]);
731 case 0: /* Utility. */
732 /* Redirect stdout to the write end of the pipe. */
733 (void)dup2(std_output
[1], STDOUT_FILENO
);
735 /* Close the utility's file descriptors. */
736 (void)close(std_output
[0]);
737 (void)close(std_output
[1]);
738 (void)close(STDERR_FILENO
);
742 * Assume that all shells have -c.
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");
748 default: /* Parent. */
749 /* Close the pipe ends the parent won't use. */
750 (void)close(std_output
[1]);
755 * Copy process standard output into a buffer.
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.
762 for (p
= bp
, len
= 0, ch
= EOF
;
763 (ch
= GETC(ifp
)) != EOF
; *p
++ = ch
, blen
-=sizeof(CHAR_T
), ++len
)
765 ADD_SPACE_GOTOW(sp
, bp
, *blenp
, *blenp
* 2);
770 /* Delete the final newline, nul terminate the string. */
771 if (p
> bp
&& (p
[-1] == '\n' || p
[-1] == '\r')) {
777 *bpp
= bp
; /* *blenp is already updated. */
782 ioerr
: msgq_str(sp
, M_ERR
, sh
, "119|I/O error: %s");
783 alloc_err
: rval
= SEXP_ERR
;
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.
796 if (proc_wait(sp
, (long)pid
, sh
, 1, 0))
797 rval
= SEXP_EXPANSION_ERR
;
799 for (p
= bp
; len
; ++p
, --len
)
803 rval
= SEXP_EXPANSION_ERR
;
805 if (rval
== SEXP_EXPANSION_ERR
)
806 msgq(sp
, M_ERR
, "304|Shell expansion failed");
808 return (rval
== SEXP_OK
? 0 : 1);
813 * Escape a string into an ex and shell argument.
815 * PUBLIC: CHAR_T *argv_esc(SCR *, EXCMD *, CHAR_T *, size_t);
818 argv_esc(SCR
*sp
, EXCMD
*excp
, CHAR_T
*str
, size_t len
)
824 GET_SPACE_GOTOW(sp
, bp
, blen
, len
+ 1);
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.
832 * All ex expansion characters, "!%#", are double escaped.
834 for (p
= bp
; len
> 0; ++str
, --len
) {
837 if (blen
/ sizeof(CHAR_T
) - off
< 3) {
838 ADD_SPACE_GOTOW(sp
, bp
, blen
, off
+ 3);
841 if (cmdskip(ch
) || ch
== '\n' ||
842 IS_ESCAPE(sp
, excp
, ch
)) /* Ex. */
845 case '~': /* ~user. */
849 case '+': /* Ex +cmd. */
853 case '!': case '%': case '#': /* Ex exp. */
857 case ',': case '-': case '.': case '/': /* Safe. */
858 case ':': case '=': case '@': case '_':
860 default: /* Unsafe. */
861 if (isascii(ch
) && !isalnum(ch
))
876 * Unescape an escaped ex and shell argument.
878 * PUBLIC: CHAR_T *argv_uesc(SCR *, EXCMD *, CHAR_T *, size_t);
881 argv_uesc(SCR
*sp
, EXCMD
*excp
, CHAR_T
*str
, size_t len
)
886 GET_SPACE_GOTOW(sp
, bp
, blen
, len
+ 1);
888 for (p
= bp
; len
> 0; ++str
, --len
) {
889 if (IS_ESCAPE(sp
, excp
, *str
)) {
893 } else if (*str
== '\\') {
898 /* Check for double escaping. */
899 if (*str
== '\\' && len
> 1)
901 case '!': case '%': case '#':