mirror of
https://github.com/vim/vim.git
synced 2026-05-28 00:21:37 +02:00
patch 9.2.0250: system() does not support bypassing the shell
Problem: system() and systemlist() only accept a String, requiring
manual shell escaping for arguments with special characters.
Solution: Accept a List as the first argument and execute the command
bypassing the shell (Yasuhiro Matsumoto).
fixes: #19789
closes: #19791
Signed-off-by: Yasuhiro Matsumoto <mattn.jp@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
committed by
Christian Brabandt
parent
bde5832216
commit
30f012d8bc
+36
-7
@@ -699,7 +699,8 @@ synconcealed({lnum}, {col}) List info about concealing
|
||||
synstack({lnum}, {col}) List stack of syntax IDs at {lnum} and
|
||||
{col}
|
||||
system({expr} [, {input}]) String output of shell command/filter {expr}
|
||||
systemlist({expr} [, {input}]) List output of shell command/filter {expr}
|
||||
systemlist({expr} [, {input}])
|
||||
List output of shell command/filter {expr}
|
||||
tabpagebuflist([{arg}]) List list of buffer numbers in tab page
|
||||
tabpagenr([{arg}]) Number number of current or last tab page
|
||||
tabpagewinnr({tabarg} [, {arg}])
|
||||
@@ -11694,6 +11695,30 @@ system({expr} [, {input}]) *system()* *E677*
|
||||
Get the output of the shell command {expr} as a |String|. See
|
||||
|systemlist()| to get the output as a |List|.
|
||||
|
||||
{expr} can be a |String| or a |List|.
|
||||
When {expr} is a |String|, the command is executed through the
|
||||
shell (see below for how the command is constructed).
|
||||
|
||||
*E1575*
|
||||
When {expr} is a |List|, the first item is the executable and
|
||||
the remaining items are passed as arguments directly. The
|
||||
command is executed without using a shell, similar to
|
||||
|job_start()|. Since no shell is involved, shell features
|
||||
such as redirection, piping, globbing, environment variable
|
||||
expansion and backtick expansion will not work. Characters
|
||||
like ">" are passed as literal arguments to the command, not
|
||||
interpreted as redirection. Use this form when arguments may
|
||||
contain special characters that should not be interpreted by
|
||||
the shell. Example: >
|
||||
:let out = system(['grep', '-r', 'pattern', '.'])
|
||||
< With the String form ">" would be shell redirection, but
|
||||
with a List it is passed as a literal argument: >
|
||||
:let out = system(['echo', 'hello', '>', 'world'])
|
||||
< This outputs "hello > world", not redirect to a file.
|
||||
|
||||
To use the shell explicitly with a List: >
|
||||
:let out = system(['/bin/sh', '-c', 'echo $HOME'])
|
||||
<
|
||||
When {input} is given and is a |String| this string is written
|
||||
to a file and passed as stdin to the command. The string is
|
||||
written as-is, you need to take care of using the correct line
|
||||
@@ -11719,11 +11744,11 @@ system({expr} [, {input}]) *system()* *E677*
|
||||
being echoed on the screen. >
|
||||
:silent let f = system('ls *.vim')
|
||||
<
|
||||
Note: Use |shellescape()| or |::S| with |expand()| or
|
||||
|fnamemodify()| to escape special characters in a command
|
||||
argument. Newlines in {expr} may cause the command to fail.
|
||||
The characters in 'shellquote' and 'shellxquote' may also
|
||||
cause trouble.
|
||||
Note: When {expr} is a String, use |shellescape()| or |::S|
|
||||
with |expand()| or |fnamemodify()| to escape special
|
||||
characters in a command argument. Newlines in {expr} may
|
||||
cause the command to fail. The characters in 'shellquote'
|
||||
and 'shellxquote' may also cause trouble.
|
||||
This is not to be used for interactive commands.
|
||||
|
||||
The result is a String. Example: >
|
||||
@@ -11736,7 +11761,8 @@ system({expr} [, {input}]) *system()* *E677*
|
||||
To avoid the string being truncated at a NUL, all NUL
|
||||
characters are replaced with SOH (0x01).
|
||||
|
||||
The command executed is constructed using several options:
|
||||
When {expr} is a String, the command executed is constructed
|
||||
using several options:
|
||||
'shell' 'shellcmdflag' 'shellxquote' {expr} 'shellredir' {tmp} 'shellxquote'
|
||||
({tmp} is an automatically generated file name).
|
||||
For Unix, braces are put around {expr} to allow for
|
||||
@@ -11763,6 +11789,9 @@ system({expr} [, {input}]) *system()* *E677*
|
||||
systemlist({expr} [, {input}]) *systemlist()*
|
||||
Same as |system()|, but returns a |List| with lines (parts of
|
||||
output separated by NL) with NULs transformed into NLs.
|
||||
Like |system()|, {expr} can be a |String| (executed through
|
||||
the shell) or a |List| (executed directly without a shell).
|
||||
See |system()| for details.
|
||||
Output is the same as |readfile()| will output with {binary}
|
||||
argument set to "b", except that there is no extra empty item
|
||||
when the result ends in a NL.
|
||||
|
||||
@@ -4771,6 +4771,7 @@ E1571 builtin.txt /*E1571*
|
||||
E1572 options.txt /*E1572*
|
||||
E1573 channel.txt /*E1573*
|
||||
E1574 channel.txt /*E1574*
|
||||
E1575 builtin.txt /*E1575*
|
||||
E158 sign.txt /*E158*
|
||||
E159 sign.txt /*E159*
|
||||
E16 cmdline.txt /*E16*
|
||||
|
||||
@@ -52611,6 +52611,8 @@ Other ~
|
||||
- New "leadtab" value for the 'listchars' setting.
|
||||
- Improved |:set+=|, |:set^=| and |:set-=| handling of comma-separated "key:value"
|
||||
pairs individually (e.g. 'listchars', 'fillchars', 'diffopt').
|
||||
- |system()| and |systemlist()| functions accept a list as first argument,
|
||||
bypassing the shell completely.
|
||||
|
||||
xxd ~
|
||||
---
|
||||
|
||||
+4
-2
@@ -3795,8 +3795,6 @@ EXTERN char e_osc_response_timed_out[]
|
||||
#ifdef FEAT_EVAL
|
||||
EXTERN char e_cannot_add_listener_in_listener_callback[]
|
||||
INIT(= N_("E1569: Cannot use listener_add in a listener callback"));
|
||||
#endif
|
||||
#ifdef FEAT_EVAL
|
||||
EXTERN char e_cannot_add_redraw_listener_in_listener_callback[]
|
||||
INIT(= N_("E1570: Cannot use redraw_listener_add in a redraw listener callback"));
|
||||
EXTERN char e_no_redraw_listener_callbacks_defined[]
|
||||
@@ -3810,3 +3808,7 @@ EXTERN char e_cannot_listen_on_port[]
|
||||
EXTERN char e_gethostbyname_in_channel_listen[]
|
||||
INIT(= N_("E1574: gethostbyname(): cannot resolve hostname in channel_listen()"));
|
||||
#endif
|
||||
#ifdef FEAT_EVAL
|
||||
EXTERN char e_cannot_create_pipes[]
|
||||
INIT(= N_("E1575: Cannot create pipes"));
|
||||
#endif
|
||||
|
||||
+1
-1
@@ -1380,7 +1380,7 @@ static argcheck_T arg45_sign_place[] = {arg_number, arg_string, arg_string, arg_
|
||||
static argcheck_T arg23_slice[] = {arg_slice1, arg_number, arg_number};
|
||||
static argcheck_T arg13_sortuniq[] = {arg_list_any_mod, arg_sort_how, arg_dict_any};
|
||||
static argcheck_T arg24_strpart[] = {arg_string, arg_number, arg_number, arg_bool};
|
||||
static argcheck_T arg12_system[] = {arg_string, arg_str_or_nr_or_list};
|
||||
static argcheck_T arg12_system[] = {arg_string_or_list_any, arg_str_or_nr_or_list};
|
||||
static argcheck_T arg23_win_execute[] = {arg_number, arg_string_or_list_string, arg_string};
|
||||
static argcheck_T arg23_writefile[] = {arg_list_or_blob, arg_string, arg_string};
|
||||
static argcheck_T arg24_match_func[] = {arg_string_or_list_any, arg_string, arg_number, arg_number};
|
||||
|
||||
+60
-3
@@ -2511,6 +2511,9 @@ get_cmd_output_as_rettv(
|
||||
FILE *fd;
|
||||
list_T *list = NULL;
|
||||
int flags = SHELL_SILENT;
|
||||
int use_argv = FALSE;
|
||||
char **argv = NULL;
|
||||
int argc = 0;
|
||||
|
||||
rettv->v_type = VAR_STRING;
|
||||
rettv->vval.v_string = NULL;
|
||||
@@ -2518,7 +2521,7 @@ get_cmd_output_as_rettv(
|
||||
goto errret;
|
||||
|
||||
if (in_vim9script()
|
||||
&& (check_for_string_arg(argvars, 0) == FAIL
|
||||
&& (check_for_string_or_list_arg(argvars, 0) == FAIL
|
||||
|| check_for_opt_string_or_number_or_list_arg(argvars, 1)
|
||||
== FAIL))
|
||||
return;
|
||||
@@ -2598,6 +2601,47 @@ get_cmd_output_as_rettv(
|
||||
}
|
||||
}
|
||||
|
||||
// When the command is a List, execute directly without the shell.
|
||||
if (argvars[0].v_type == VAR_LIST)
|
||||
{
|
||||
list_T *l = argvars[0].vval.v_list;
|
||||
|
||||
if (l == NULL || l->lv_len < 1)
|
||||
{
|
||||
emsg(_(e_invalid_argument));
|
||||
goto errret;
|
||||
}
|
||||
if (build_argv_from_list(l, &argv, &argc) == FAIL)
|
||||
goto errret;
|
||||
if (argc == 0 || *skipwhite((char_u *)argv[0]) == NUL)
|
||||
{
|
||||
emsg(_(e_invalid_argument));
|
||||
goto errret;
|
||||
}
|
||||
use_argv = TRUE;
|
||||
|
||||
if (p_verbose > 3)
|
||||
{
|
||||
int i;
|
||||
garray_T ga;
|
||||
|
||||
verbose_enter();
|
||||
ga_init2(&ga, 1, 200);
|
||||
for (i = 0; i < argc; ++i)
|
||||
{
|
||||
if (i > 0)
|
||||
ga_append(&ga, ' ');
|
||||
ga_concat(&ga, (char_u *)argv[i]);
|
||||
}
|
||||
ga_append(&ga, NUL);
|
||||
smsg(_("Executing directly: \"%s\""), (char *)ga.ga_data);
|
||||
msg_putchar_attr('\n', 0);
|
||||
cursor_on();
|
||||
verbose_leave();
|
||||
ga_clear(&ga);
|
||||
}
|
||||
}
|
||||
|
||||
// Omit SHELL_COOKED when invoked with ":silent". Avoids that the shell
|
||||
// echoes typeahead, that messes up the display.
|
||||
if (!msg_silent)
|
||||
@@ -2612,7 +2656,10 @@ get_cmd_output_as_rettv(
|
||||
char_u *end;
|
||||
int i;
|
||||
|
||||
res = get_cmd_output(tv_get_string(&argvars[0]), infile, flags, &len);
|
||||
if (use_argv)
|
||||
res = mch_get_cmd_output_direct(argv, infile, flags, &len);
|
||||
else
|
||||
res = get_cmd_output(tv_get_string(&argvars[0]), infile, flags, &len);
|
||||
if (res == NULL)
|
||||
goto errret;
|
||||
|
||||
@@ -2652,7 +2699,10 @@ get_cmd_output_as_rettv(
|
||||
}
|
||||
else
|
||||
{
|
||||
res = get_cmd_output(tv_get_string(&argvars[0]), infile, flags, NULL);
|
||||
if (use_argv)
|
||||
res = mch_get_cmd_output_direct(argv, infile, flags, NULL);
|
||||
else
|
||||
res = get_cmd_output(tv_get_string(&argvars[0]), infile, flags, NULL);
|
||||
# ifdef USE_CRNL
|
||||
// translate <CR><NL> into <NL>
|
||||
if (res != NULL)
|
||||
@@ -2674,6 +2724,13 @@ get_cmd_output_as_rettv(
|
||||
}
|
||||
|
||||
errret:
|
||||
if (argv != NULL)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; argv[i] != NULL; i++)
|
||||
vim_free(argv[i]);
|
||||
vim_free(argv);
|
||||
}
|
||||
if (infile != NULL)
|
||||
{
|
||||
mch_remove(infile);
|
||||
|
||||
+148
@@ -5915,6 +5915,154 @@ mch_call_shell(
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(FEAT_EVAL)
|
||||
/*
|
||||
* Execute "argv" directly without the shell and return the output.
|
||||
* Used by system() and systemlist() when the command is a List.
|
||||
* "infile" is an optional temp file for stdin input.
|
||||
* "flags" is SHELL_SILENT etc.
|
||||
* When "ret_len" is not NULL, set it to the length of the output.
|
||||
* Returns the output in allocated memory (or NULL on error).
|
||||
* Sets v:shell_error to the exit status.
|
||||
*/
|
||||
char_u *
|
||||
mch_get_cmd_output_direct(
|
||||
char **argv,
|
||||
char_u *infile,
|
||||
int flags UNUSED,
|
||||
int *ret_len)
|
||||
{
|
||||
pid_t pid;
|
||||
int fd_out[2] = {-1, -1};
|
||||
int status = -1;
|
||||
char_u *buffer = NULL;
|
||||
garray_T ga;
|
||||
SIGSET_DECL(curset)
|
||||
|
||||
ga_init2(&ga, 1, 4096);
|
||||
|
||||
ch_log(NULL, "directly executing: %s", argv[0]);
|
||||
|
||||
if (pipe(fd_out) < 0)
|
||||
{
|
||||
emsg(_(e_cannot_create_pipes));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
BLOCK_SIGNALS(&curset);
|
||||
pid = fork();
|
||||
if (pid == -1)
|
||||
{
|
||||
UNBLOCK_SIGNALS(&curset);
|
||||
close(fd_out[0]);
|
||||
close(fd_out[1]);
|
||||
emsg(_("\nCannot fork\n"));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (pid == 0)
|
||||
{
|
||||
// child process
|
||||
reset_signals();
|
||||
UNBLOCK_SIGNALS(&curset);
|
||||
|
||||
if (ch_log_active())
|
||||
{
|
||||
ch_log(NULL, "closing channel log in the child process");
|
||||
ch_logfile((char_u *)"", (char_u *)"");
|
||||
}
|
||||
|
||||
// Set up stdin.
|
||||
if (infile != NULL)
|
||||
{
|
||||
int fd_in = open((char *)infile, O_RDONLY);
|
||||
if (fd_in >= 0)
|
||||
{
|
||||
close(0);
|
||||
vim_ignored = dup(fd_in);
|
||||
close(fd_in);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int nullfd = open("/dev/null", O_RDONLY);
|
||||
if (nullfd >= 0)
|
||||
{
|
||||
close(0);
|
||||
vim_ignored = dup(nullfd);
|
||||
close(nullfd);
|
||||
}
|
||||
}
|
||||
|
||||
// Set up stdout: write end of pipe.
|
||||
close(fd_out[0]);
|
||||
close(1);
|
||||
vim_ignored = dup(fd_out[1]);
|
||||
// Also redirect stderr to the pipe.
|
||||
close(2);
|
||||
vim_ignored = dup(fd_out[1]);
|
||||
close(fd_out[1]);
|
||||
|
||||
execvp(argv[0], argv);
|
||||
_exit(127);
|
||||
// NOTREACHED
|
||||
}
|
||||
|
||||
// parent process
|
||||
UNBLOCK_SIGNALS(&curset);
|
||||
close(fd_out[1]);
|
||||
|
||||
// Read output from child.
|
||||
for (;;)
|
||||
{
|
||||
char buf[4096];
|
||||
int n;
|
||||
|
||||
n = (int)read(fd_out[0], buf, sizeof(buf));
|
||||
if (n <= 0)
|
||||
break;
|
||||
ga_grow(&ga, n);
|
||||
mch_memmove((char *)ga.ga_data + ga.ga_len, buf, n);
|
||||
ga.ga_len += n;
|
||||
}
|
||||
close(fd_out[0]);
|
||||
|
||||
// Wait for child to finish.
|
||||
(void)waitpid(pid, &status, 0);
|
||||
if (WIFEXITED(status))
|
||||
status = WEXITSTATUS(status);
|
||||
else
|
||||
status = -1;
|
||||
set_vim_var_nr(VV_SHELL_ERROR, (long)status);
|
||||
|
||||
if (ga.ga_len > 0)
|
||||
{
|
||||
buffer = alloc(ga.ga_len + 1);
|
||||
if (buffer != NULL)
|
||||
{
|
||||
mch_memmove(buffer, ga.ga_data, ga.ga_len);
|
||||
if (ret_len == NULL)
|
||||
{
|
||||
int i;
|
||||
|
||||
// Change NUL into SOH, otherwise the string is truncated.
|
||||
for (i = 0; i < ga.ga_len; ++i)
|
||||
if (buffer[i] == NUL)
|
||||
buffer[i] = 1;
|
||||
buffer[ga.ga_len] = NUL;
|
||||
}
|
||||
else
|
||||
*ret_len = ga.ga_len;
|
||||
}
|
||||
}
|
||||
else if (ret_len != NULL)
|
||||
*ret_len = 0;
|
||||
|
||||
ga_clear(&ga);
|
||||
return buffer;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(FEAT_JOB_CHANNEL)
|
||||
void
|
||||
mch_job_start(char **argv, job_T *job, jobopt_T *options, int is_terminal)
|
||||
|
||||
+213
@@ -5960,6 +5960,219 @@ create_pipe_pair(HANDLE handles[2])
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
# if defined(FEAT_EVAL)
|
||||
/*
|
||||
* Execute "argv" directly without the shell and return the output.
|
||||
* Used by system() and systemlist() when the command is a List.
|
||||
* "infile" is an optional temp file for stdin input.
|
||||
* When "ret_len" is not NULL, set it to the length of the output.
|
||||
* Returns the output in allocated memory (or NULL on error).
|
||||
* Sets v:shell_error to the exit status.
|
||||
*/
|
||||
char_u *
|
||||
mch_get_cmd_output_direct(
|
||||
char **argv,
|
||||
char_u *infile,
|
||||
int flags UNUSED,
|
||||
int *ret_len)
|
||||
{
|
||||
STARTUPINFO si;
|
||||
PROCESS_INFORMATION pi;
|
||||
SECURITY_ATTRIBUTES saAttr;
|
||||
HANDLE hChildStdoutRd = INVALID_HANDLE_VALUE;
|
||||
HANDLE hChildStdoutWr = INVALID_HANDLE_VALUE;
|
||||
HANDLE hChildStdinRd = INVALID_HANDLE_VALUE;
|
||||
garray_T cmd_ga;
|
||||
garray_T out_ga;
|
||||
char_u *buffer = NULL;
|
||||
DWORD exit_code = (DWORD)-1;
|
||||
int i;
|
||||
|
||||
// Build a command string from argv.
|
||||
ga_init2(&cmd_ga, 1, 256);
|
||||
for (i = 0; argv[i] != NULL; i++)
|
||||
{
|
||||
char_u *arg = (char_u *)argv[i];
|
||||
char_u *s = arg;
|
||||
int has_spaces = FALSE;
|
||||
int j;
|
||||
|
||||
for (j = 0; s[j] != NUL; j++)
|
||||
if (s[j] == ' ' || s[j] == '\t' || s[j] == '"')
|
||||
{
|
||||
has_spaces = TRUE;
|
||||
break;
|
||||
}
|
||||
|
||||
if (i > 0)
|
||||
ga_append(&cmd_ga, ' ');
|
||||
|
||||
if (has_spaces)
|
||||
{
|
||||
int num_bs;
|
||||
|
||||
ga_append(&cmd_ga, '"');
|
||||
for (j = 0; arg[j] != NUL; j++)
|
||||
{
|
||||
num_bs = 0;
|
||||
while (arg[j] == '\\')
|
||||
{
|
||||
num_bs++;
|
||||
j++;
|
||||
}
|
||||
|
||||
if (arg[j] == NUL)
|
||||
{
|
||||
// Backslashes before closing quote must be doubled.
|
||||
while (num_bs-- > 0)
|
||||
{
|
||||
ga_append(&cmd_ga, '\\');
|
||||
ga_append(&cmd_ga, '\\');
|
||||
}
|
||||
break;
|
||||
}
|
||||
else if (arg[j] == '"')
|
||||
{
|
||||
// Backslashes before a double quote must be doubled,
|
||||
// and the double quote must be escaped.
|
||||
while (num_bs-- > 0)
|
||||
{
|
||||
ga_append(&cmd_ga, '\\');
|
||||
ga_append(&cmd_ga, '\\');
|
||||
}
|
||||
ga_append(&cmd_ga, '\\');
|
||||
ga_append(&cmd_ga, '"');
|
||||
}
|
||||
else
|
||||
{
|
||||
while (num_bs-- > 0)
|
||||
ga_append(&cmd_ga, '\\');
|
||||
ga_append(&cmd_ga, arg[j]);
|
||||
}
|
||||
}
|
||||
ga_append(&cmd_ga, '"');
|
||||
}
|
||||
else
|
||||
ga_concat(&cmd_ga, arg);
|
||||
}
|
||||
ga_append(&cmd_ga, NUL);
|
||||
|
||||
ga_init2(&out_ga, 1, 4096);
|
||||
|
||||
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
|
||||
saAttr.bInheritHandle = TRUE;
|
||||
saAttr.lpSecurityDescriptor = NULL;
|
||||
|
||||
// Create a pipe for the child's stdout.
|
||||
if (!CreatePipe(&hChildStdoutRd, &hChildStdoutWr, &saAttr, 0)
|
||||
|| !SetHandleInformation(hChildStdoutRd, HANDLE_FLAG_INHERIT, 0))
|
||||
{
|
||||
emsg(_(e_cannot_create_pipes));
|
||||
goto done;
|
||||
}
|
||||
|
||||
// Set up stdin from infile if provided.
|
||||
if (infile != NULL)
|
||||
{
|
||||
WCHAR *winfile = enc_to_utf16(infile, NULL);
|
||||
|
||||
if (winfile != NULL)
|
||||
{
|
||||
hChildStdinRd = CreateFileW(winfile, GENERIC_READ,
|
||||
FILE_SHARE_READ, &saAttr, OPEN_EXISTING,
|
||||
FILE_ATTRIBUTE_NORMAL, NULL);
|
||||
vim_free(winfile);
|
||||
}
|
||||
}
|
||||
|
||||
ZeroMemory(&pi, sizeof(pi));
|
||||
ZeroMemory(&si, sizeof(si));
|
||||
si.cb = sizeof(si);
|
||||
si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
|
||||
si.wShowWindow = SW_HIDE;
|
||||
si.hStdOutput = hChildStdoutWr;
|
||||
si.hStdError = hChildStdoutWr;
|
||||
si.hStdInput = (hChildStdinRd != INVALID_HANDLE_VALUE)
|
||||
? hChildStdinRd : INVALID_HANDLE_VALUE;
|
||||
|
||||
ch_log(NULL, "directly executing: %s", (char *)cmd_ga.ga_data);
|
||||
|
||||
// Create the child process directly, without going through the shell.
|
||||
if (!vim_create_process((char *)cmd_ga.ga_data, TRUE,
|
||||
CREATE_DEFAULT_ERROR_MODE | CREATE_NEW_PROCESS_GROUP,
|
||||
&si, &pi, NULL, NULL))
|
||||
{
|
||||
semsg(_(e_invalid_argument_str), cmd_ga.ga_data);
|
||||
goto done;
|
||||
}
|
||||
|
||||
// Close the write end of stdout pipe and stdin in the parent so that
|
||||
// ReadFile() will get EOF when the child process exits.
|
||||
CloseHandle(hChildStdoutWr);
|
||||
hChildStdoutWr = INVALID_HANDLE_VALUE;
|
||||
if (hChildStdinRd != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
CloseHandle(hChildStdinRd);
|
||||
hChildStdinRd = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
// Read output from child process.
|
||||
for (;;)
|
||||
{
|
||||
char buf[4096];
|
||||
DWORD n;
|
||||
|
||||
if (!ReadFile(hChildStdoutRd, buf, sizeof(buf), &n, NULL) || n == 0)
|
||||
break;
|
||||
if (ga_grow(&out_ga, (int)n) == OK)
|
||||
{
|
||||
mch_memmove((char *)out_ga.ga_data + out_ga.ga_len, buf, n);
|
||||
out_ga.ga_len += (int)n;
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for child to finish and get exit code.
|
||||
WaitForSingleObject(pi.hProcess, INFINITE);
|
||||
GetExitCodeProcess(pi.hProcess, &exit_code);
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
|
||||
set_vim_var_nr(VV_SHELL_ERROR, (long)exit_code);
|
||||
|
||||
if (out_ga.ga_len > 0)
|
||||
{
|
||||
buffer = alloc(out_ga.ga_len + 1);
|
||||
if (buffer != NULL)
|
||||
{
|
||||
mch_memmove(buffer, out_ga.ga_data, out_ga.ga_len);
|
||||
if (ret_len == NULL)
|
||||
{
|
||||
// Change NUL into SOH, otherwise the string is truncated.
|
||||
for (i = 0; i < out_ga.ga_len; ++i)
|
||||
if (buffer[i] == NUL)
|
||||
buffer[i] = 1;
|
||||
buffer[out_ga.ga_len] = NUL;
|
||||
}
|
||||
else
|
||||
*ret_len = out_ga.ga_len;
|
||||
}
|
||||
}
|
||||
else if (ret_len != NULL)
|
||||
*ret_len = 0;
|
||||
|
||||
done:
|
||||
ga_clear(&cmd_ga);
|
||||
ga_clear(&out_ga);
|
||||
if (hChildStdoutRd != INVALID_HANDLE_VALUE)
|
||||
CloseHandle(hChildStdoutRd);
|
||||
if (hChildStdoutWr != INVALID_HANDLE_VALUE)
|
||||
CloseHandle(hChildStdoutWr);
|
||||
if (hChildStdinRd != INVALID_HANDLE_VALUE)
|
||||
CloseHandle(hChildStdinRd);
|
||||
return buffer;
|
||||
}
|
||||
# endif
|
||||
|
||||
void
|
||||
mch_job_start(char *cmd, job_T *job, jobopt_T *options)
|
||||
{
|
||||
|
||||
Generated
+8
-1
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: Vim\n"
|
||||
"Report-Msgid-Bugs-To: vim-dev@vim.org\n"
|
||||
"POT-Creation-Date: 2026-03-20 20:44+0800\n"
|
||||
"POT-Creation-Date: 2026-03-25 21:51+0000\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@@ -2269,6 +2269,10 @@ msgstr ""
|
||||
msgid "Beep!"
|
||||
msgstr ""
|
||||
|
||||
#, c-format
|
||||
msgid "Executing directly: \"%s\""
|
||||
msgstr ""
|
||||
|
||||
#, c-format
|
||||
msgid "Calling shell to execute: \"%s\""
|
||||
msgstr ""
|
||||
@@ -8849,6 +8853,9 @@ msgstr ""
|
||||
msgid "E1574: gethostbyname(): cannot resolve hostname in channel_listen()"
|
||||
msgstr ""
|
||||
|
||||
msgid "E1575: Cannot create pipes"
|
||||
msgstr ""
|
||||
|
||||
#. type of cmdline window or 0
|
||||
#. result of cmdline window or 0
|
||||
#. buffer of cmdline window or NULL
|
||||
|
||||
@@ -63,6 +63,7 @@ void mch_set_shellsize(void);
|
||||
void mch_new_shellsize(void);
|
||||
int unix_build_argv(char_u *cmd, char ***argvp, char_u **sh_tofree, char_u **shcf_tofree);
|
||||
int mch_call_shell(char_u *cmd, int options);
|
||||
char_u *mch_get_cmd_output_direct(char **argv, char_u *infile, int flags, int *ret_len);
|
||||
void mch_job_start(char **argv, job_T *job, jobopt_T *options, int is_terminal);
|
||||
char *mch_job_status(job_T *job);
|
||||
job_T *mch_detect_ended_job(job_T *job_list);
|
||||
|
||||
@@ -50,6 +50,7 @@ void mch_new_shellsize(void);
|
||||
void mch_set_winsize_now(void);
|
||||
int mch_call_shell(char_u *cmd, int options);
|
||||
void win32_build_env(dict_T *env, garray_T *gap, int is_terminal);
|
||||
char_u *mch_get_cmd_output_direct(char **argv, char_u *infile, int flags, int *ret_len);
|
||||
void mch_job_start(char *cmd, job_T *job, jobopt_T *options);
|
||||
char *mch_job_status(job_T *job);
|
||||
job_T *mch_detect_ended_job(job_T *job_list);
|
||||
|
||||
@@ -210,4 +210,47 @@ func Test_system_with_powershell()
|
||||
endtry
|
||||
endfunc
|
||||
|
||||
func Test_system_list_arg()
|
||||
CheckExecutable python3
|
||||
|
||||
" When the command is a List, it is executed directly without the shell.
|
||||
" Shell meta characters should not be interpreted but passed as-is.
|
||||
|
||||
" Redirect characters should be passed literally.
|
||||
let out = system(['python3', '-c', 'import sys; print(sys.argv[1])', '<foo>'])
|
||||
call assert_match('^<foo>', out)
|
||||
|
||||
" Environment variable syntax should not be expanded.
|
||||
if has('win32')
|
||||
let out = system(['python3', '-c', 'import sys; print(sys.argv[1])', '%USERPROFILE%'])
|
||||
call assert_match('^%USERPROFILE%', out)
|
||||
else
|
||||
let out = system(['python3', '-c', 'import sys; print(sys.argv[1])', '$HOME'])
|
||||
call assert_match('^\$HOME', out)
|
||||
endif
|
||||
|
||||
" Spaces in arguments should be preserved without shell word splitting.
|
||||
let out = system(['python3', '-c', 'import sys; print(sys.argv[1])', 'hello world'])
|
||||
call assert_match('^hello world', out)
|
||||
|
||||
" Pipe and ampersand should be passed literally.
|
||||
let out = system(['python3', '-c', 'import sys; print(sys.argv[1])', 'a&b|c'])
|
||||
call assert_match('^a&b|c', out)
|
||||
|
||||
" systemlist() should work too.
|
||||
let out = systemlist(['python3', '-c', 'print("line1"); print("line2")'])
|
||||
call assert_match('^line1', out[0])
|
||||
call assert_match('^line2', out[1])
|
||||
|
||||
" v:shell_error should be set.
|
||||
call system(['python3', '-c', 'import sys; sys.exit(42)'])
|
||||
call assert_equal(42, v:shell_error)
|
||||
call system(['python3', '-c', 'import sys; sys.exit(0)'])
|
||||
call assert_equal(0, v:shell_error)
|
||||
|
||||
" Invalid arguments.
|
||||
call assert_fails('call system([])', 'E474:')
|
||||
call assert_fails('call systemlist([])', 'E474:')
|
||||
endfunc
|
||||
|
||||
" vim: shiftwidth=2 sts=2 expandtab
|
||||
|
||||
@@ -4658,13 +4658,13 @@ def Test_synstack()
|
||||
enddef
|
||||
|
||||
def Test_system()
|
||||
v9.CheckSourceDefAndScriptFailure(['system(1)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
|
||||
v9.CheckSourceDefAndScriptFailure(['system(1)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1222: String or List required for argument 1'])
|
||||
v9.CheckSourceDefAndScriptFailure(['system("a", {})'], ['E1013: Argument 2: type mismatch, expected string but got dict<any>', 'E1224: String, Number or List required for argument 2'])
|
||||
assert_equal("123\n", system('echo 123'))
|
||||
enddef
|
||||
|
||||
def Test_systemlist()
|
||||
v9.CheckSourceDefAndScriptFailure(['systemlist(1)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1174: String required for argument 1'])
|
||||
v9.CheckSourceDefAndScriptFailure(['systemlist(1)'], ['E1013: Argument 1: type mismatch, expected string but got number', 'E1222: String or List required for argument 1'])
|
||||
v9.CheckSourceDefAndScriptFailure(['systemlist("a", {})'], ['E1013: Argument 2: type mismatch, expected string but got dict<any>', 'E1224: String, Number or List required for argument 2'])
|
||||
if has('win32')
|
||||
call assert_equal(["123\r"], systemlist('echo 123'))
|
||||
|
||||
@@ -734,6 +734,8 @@ static char *(features[]) =
|
||||
|
||||
static int included_patches[] =
|
||||
{ /* Add new patch number below this line */
|
||||
/**/
|
||||
250,
|
||||
/**/
|
||||
249,
|
||||
/**/
|
||||
|
||||
Reference in New Issue
Block a user