* Copyright (c) 1993, 1994
* The Regents of the University of California. All rights reserved.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
static char sccsid
[] = "@(#)ex_argv.c 9.4 (Berkeley) 12/2/94";
static int argv_alloc
__P((SCR
*, size_t));
static int argv_fexp
__P((SCR
*, EXCMDARG
*,
char *, size_t, char *, size_t *, char **, size_t *, int));
static int argv_sexp
__P((SCR
*, char **, size_t *, size_t *));
* Build a prototype arguments list.
excp
->argc
= exp
->argsoff
;
* Append a string to the argument list.
argv_exp0(sp
, excp
, cmd
, cmdlen
)
memmove(exp
->args
[exp
->argsoff
]->bp
, cmd
, cmdlen
);
exp
->args
[exp
->argsoff
]->bp
[cmdlen
] = '\0';
exp
->args
[exp
->argsoff
]->len
= cmdlen
;
excp
->argc
= exp
->argsoff
;
* Do file name expansion on a string, and append it to the
argv_exp1(sp
, excp
, cmd
, cmdlen
, is_bang
)
GET_SPACE_RET(sp
, bp
, blen
, 512);
if (argv_fexp(sp
, excp
, cmd
, cmdlen
, bp
, &len
, &bp
, &blen
, is_bang
)) {
FREE_SPACE(sp
, bp
, blen
);
/* If it's empty, we're done. */
for (p
= bp
, t
= bp
+ len
; p
< t
; ++p
)
(void)argv_exp0(sp
, excp
, bp
, len
);
ret
: FREE_SPACE(sp
, bp
, blen
);
* Do file name and shell expansion on a string, and append it to
argv_exp2(sp
, excp
, cmd
, cmdlen
)
GET_SPACE_RET(sp
, bp
, blen
, 512);
#define SHELLECHO "echo "
#define SHELLOFFSET (sizeof(SHELLECHO) - 1)
memmove(bp
, SHELLECHO
, SHELLOFFSET
);
TRACE(sp
, "file_argv: {%.*s}\n", (int)cmdlen
, cmd
);
if (argv_fexp(sp
, excp
, cmd
, cmdlen
, p
, &len
, &bp
, &blen
, 0)) {
TRACE(sp
, "before shell: %d: {%s}\n", len
, bp
);
* Do shell word expansion -- it's very, very hard to figure out what
* magic characters the user's shell expects. Historically, it was a
* union of v7 shell and csh meta characters. We match that practice
* by default, so ":read \%" tries to read a file named '%'. It would
* make more sense to pass any special characters through the shell,
* but then, if your shell was csh, the above example will behave
* differently in nvi than in vi. If you want to get other characters
* passed through to your shell, change the "meta" option.
* To avoid a function call per character, we do a first pass through
* the meta characters looking for characters that aren't expected
for (p
= mp
= O_STR(sp
, O_SHELLMETA
); *p
!= '\0'; ++p
)
if (isblank(*p
) || isalnum(*p
))
for (p
= bp
, n
= len
; n
> 0; --n
, ++p
)
if (strchr(mp
, *p
) != NULL
)
for (p
= bp
, n
= len
; n
> 0; --n
, ++p
)
!isalnum(*p
) && strchr(mp
, *p
) != NULL
)
if (argv_sexp(sp
, &bp
, &blen
, &len
)) {
TRACE(sp
, "after shell: %d: {%s}\n", len
, bp
);
rval
= argv_exp3(sp
, excp
, p
, len
);
err
: FREE_SPACE(sp
, bp
, blen
);
* Take a string and break it up into an argv, which is appended
argv_exp3(sp
, excp
, cmd
, cmdlen
)
for (exp
= EXP(sp
); cmdlen
> 0; ++exp
->argsoff
) {
/* Skip any leading whitespace. */
for (; cmdlen
> 0; --cmdlen
, ++cmd
) {
* Determine the length of this whitespace delimited
* Skip any character preceded by the user's quoting
for (ap
= cmd
, len
= 0; cmdlen
> 0; ++cmd
, --cmdlen
, ++len
) {
if (IS_ESCAPE(sp
, ch
) && cmdlen
> 1) {
* Copy the argument into place.
exp
->args
[off
]->len
= len
;
for (p
= exp
->args
[off
]->bp
; len
> 0; --len
, *p
++ = *ap
++)
excp
->argc
= exp
->argsoff
;
for (cnt
= 0; cnt
< exp
->argsoff
; ++cnt
)
TRACE(sp
, "arg %d: {%s}\n", cnt
, exp
->argv
[cnt
]);
* Do file name and bang command expansion.
argv_fexp(sp
, excp
, cmd
, cmdlen
, p
, lenp
, bpp
, blenp
, is_bang
)
size_t cmdlen
, *lenp
, *blenp
;
/* Replace file name characters. */
for (bp
= *bpp
, blen
= *blenp
, len
= *lenp
; cmdlen
> 0; --cmdlen
, ++cmd
)
if (exp
->lastbcomm
== NULL
) {
"123|No previous command to replace \"!\"");
len
+= tlen
= strlen(exp
->lastbcomm
);
ADD_SPACE_RET(sp
, bp
, blen
, len
);
memmove(p
, exp
->lastbcomm
, tlen
);
if ((t
= sp
->frp
->name
) == NULL
) {
"124|No filename to substitute for %%");
ADD_SPACE_RET(sp
, bp
, blen
, len
);
if ((t
= sp
->alt_name
) == NULL
) {
"125|No filename to substitute for #");
ADD_SPACE_RET(sp
, bp
, blen
, len
);
* Strip any backslashes that protected the file
(cmd
[1] == '%' || cmd
[1] == '#' || cmd
[1] == '!')) {
ADD_SPACE_RET(sp
, bp
, blen
, len
);
ADD_SPACE_RET(sp
, bp
, blen
, len
);
/* Return the new string length, buffer, buffer length. */
* Make more space for arguments.
* Allocate room for another argument, always leaving
* enough room for an ARGS structure with a length of 0.
if (exp
->argscnt
== 0 || off
+ 2 >= exp
->argscnt
- 1) {
cnt
= exp
->argscnt
+ INCREMENT
;
REALLOC(sp
, exp
->args
, ARGS
**, cnt
* sizeof(ARGS
*));
memset(&exp
->args
[off
], 0, INCREMENT
* sizeof(ARGS
*));
if (exp
->args
[off
] == NULL
) {
CALLOC(sp
, exp
->args
[off
], ARGS
*, 1, sizeof(ARGS
));
if (exp
->args
[off
] == NULL
)
/* First argument buffer. */
if (ap
->blen
< len
+ 1) {
REALLOC(sp
, ap
->bp
, CHAR_T
*, ap
->blen
* sizeof(CHAR_T
));
mem
: msgq(sp
, M_SYSERR
, NULL
);
if (exp
->args
[++off
] == NULL
) {
CALLOC(sp
, exp
->args
[off
], ARGS
*, 1, sizeof(ARGS
));
if (exp
->args
[off
] == NULL
)
/* 0 length serves as end-of-argument marker. */
* Free up argument structures.
for (off
= 0; off
< exp
->argscnt
; ++off
) {
if (exp
->args
[off
] == NULL
)
if (F_ISSET(exp
->args
[off
], A_ALLOCATED
))
free(exp
->args
[off
]->bp
);
FREE(exp
->args
[off
], sizeof(ARGS
));
FREE(exp
->args
, exp
->argscnt
* sizeof(ARGS
*));
* Fork a shell, pipe a command through it, and read the output into
argv_sexp(sp
, bpp
, blenp
, lenp
)
int ch
, nf1
, nf2
, rval
, err_output
[2], std_output
[2];
char *bp
, *p
, *t
, *sh
, *sh_path
;
sh_path
= O_STR(sp
, O_SHELL
);
if ((sh
= strrchr(sh_path
, '/')) == NULL
)
* There are two different processes running through this code, named
* the utility (the shell) and the parent. The utility reads standard
* input and writes standard output and standard error output. The
* parent reads the standard output and standard error output, copying
* the former into a buffer and the latter into the error message log.
* The parent reads std_output[0] and err_output[0], and the utility
* writes std_output[1] and err_output[1].
std_output
[0] = std_output
[1] = err_output
[0] = err_output
[1] = -1;
if (pipe(std_output
) < 0 || pipe(err_output
)) {
msgq(sp
, M_SYSERR
, "pipe");
if ((ifp
= fdopen(std_output
[0], "r")) == NULL
||
(efp
= fdopen(err_output
[0], "r")) == NULL
) {
msgq(sp
, M_SYSERR
, "fdopen");
* Do the minimal amount of work possible, the shell is going
* to run briefly and then exit. We hope.
msgq(sp
, M_SYSERR
, "vfork");
else if (std_output
[0] != -1)
else if (err_output
[0] != -1)
/* The utility has default signal behavior. */
/* Redirect stdout/stderr to the write end of the pipe. */
(void)dup2(std_output
[1], STDOUT_FILENO
);
(void)dup2(err_output
[1], STDERR_FILENO
);
/* Close the utility's file descriptors. */
(void)close(std_output
[0]);
(void)close(std_output
[1]);
(void)close(err_output
[0]);
(void)close(err_output
[1]);
/* Assume that all shells have -c. */
execl(sh_path
, sh
, "-c", bp
, NULL
);
p
= msg_print(sp
, sh_path
, &nf1
);
msgq(sp
, M_SYSERR
, "126|Error: execl: %s", p
);
/* Close the pipe ends the parent won't use. */
(void)close(std_output
[1]);
(void)close(err_output
[1]);
* Copy process standard output into a buffer.
* Historic vi apparently discarded leading \n and \r's from
* the shell output stream. We don't on the grounds that any
* shell that does that is broken.
for (p
= bp
, len
= 0, ch
= EOF
;
(ch
= getc(ifp
)) != EOF
; *p
++ = ch
, --blen
, ++len
)
ADD_SPACE_GOTO(sp
, bp
, blen
, *blenp
* 2);
/* Delete the final newline, nul terminate the string. */
if (p
> bp
&& (p
[-1] == '\n' || p
[-1] == '\r')) {
*bpp
= bp
; /* *blenp is already updated. */
* Copy process standard error into a buffer. We don't set
* rval if we get error output -- as long as the shell exits
* okay, might as well go ahead.
if ((ch
= getc(efp
)) != EOF
) {
GET_SPACE_RET(sp
, bp
, blen
, 1024);
p
= msg_print(sp
, sh
, &nf1
);
while (fgets(bp
, blen
, efp
) != NULL
) {
if ((t
= strchr(bp
, '\n')) != NULL
)
t
= msg_print(sp
, bp
, &nf2
);
msgq(sp
, M_ERR
, "%s: %s", p
, t
);
FREE_SPACE(sp
, bp
, blen
);
ioerr
: p
= msg_print(sp
, sh
, &nf1
);
msgq(sp
, M_ERR
, "127|I/O error: %s", p
);
/* Wait for the process. */
if (proc_wait(sp
, (long)pid
, sh
, 0))