system('foo &', 'bar'): Show error, don't crash.

Closes #3529
Closes #5241

In Vim,
    :echo system('cat - &', 'foo')
works because for both system() and :! Vim writes input to a temp file and uses
shell syntax to redirect the file to the backgrounded `cat` (get_cmd_output()
.. make_filter_cmd()).

In Nvim,
    :echo system('cat - &', 'foo')
fails because we write the input directly via pipes (shell.c:do_os_system()),
but (per POSIX[1]) backgrounded process input stream is redirected from
/dev/null (unless overridden by shell redirection; supported only by some shells
[2]), so our writes are ignored, the process exits quickly, and if we are
writing data larger than the buffer size we'll see EPIPE.

This still works:
    :%w !tee > foo1358.txt &
but this does not:
    :%w !tee foo1358.txt &
though it *should* (why doesn't it?) because we still do the temp file dance
in do_bang() .. do_filter().

[1] http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_09_03_02
[2] http://unix.stackexchange.com/a/71218
This commit is contained in:
Justin M. Keyes
2016-10-18 14:39:08 +02:00
parent 16da3a6fe0
commit 9706664b88
5 changed files with 33 additions and 19 deletions

View File

@@ -6838,23 +6838,30 @@ system({cmd} [, {input}]) *system()* *E677*
Get the output of the shell command {cmd} as a |string|. {cmd} Get the output of the shell command {cmd} as a |string|. {cmd}
will be run the same as in |jobstart()|. See |systemlist()| will be run the same as in |jobstart()|. See |systemlist()|
to get the output as a |List|. to get the output as a |List|.
Not to be used for interactive commands.
When {input} is given and is a string this string is written If {input} is a string it is written to a pipe and passed as
to a file and passed as stdin to the command. The string is stdin to the command. The string is written as-is, line
written as-is, you need to take care of using the correct line separators are not changed.
separators yourself. If {input} is a |List| it is written to the pipe as
If {input} is given and is a |List| it is written to the file |writefile()| does with {binary} set to "b" (i.e. with
in a way |writefile()| does with {binary} set to "b" (i.e. a newline between each list item, and newlines inside list
with a newline between each list item with newlines inside items converted to NULs).
list items converted to NULs). *E5677*
Pipes are not used. Note: system() cannot write to or read from backgrounded ("&")
shell commands, e.g.: >
:echo system("cat - &", "foo"))
< which is equivalent to: >
$ echo foo | bash -c 'cat - &'
< The pipes are disconnected (unless overridden by shell
redirection syntax) before input can reach it. Use
|jobstart()| instead.
Note: Use |shellescape()| or |::S| with |expand()| or Note: Use |shellescape()| or |::S| with |expand()| or
|fnamemodify()| to escape special characters in a command |fnamemodify()| to escape special characters in a command
argument. Newlines in {cmd} may cause the command to fail. argument. Newlines in {cmd} may cause the command to fail.
The characters in 'shellquote' and 'shellxquote' may also The characters in 'shellquote' and 'shellxquote' may also
cause trouble. cause trouble.
This is not to be used for interactive commands.
The result is a String. Example: > The result is a String. Example: >
:let files = system("ls " . shellescape(expand('%:h'))) :let files = system("ls " . shellescape(expand('%:h')))
@@ -6869,9 +6876,6 @@ system({cmd} [, {input}]) *system()* *E677*
The command executed is constructed using several options when The command executed is constructed using several options when
{cmd} is a string: 'shell' 'shellcmdflag' {cmd} {cmd} is a string: 'shell' 'shellcmdflag' {cmd}
The command will be executed in "cooked" mode, so that a
CTRL-C will interrupt the command (on Unix at least).
The resulting error code can be found in |v:shell_error|. The resulting error code can be found in |v:shell_error|.
This function will fail in |restricted-mode|. This function will fail in |restricted-mode|.

View File

@@ -141,10 +141,10 @@ are always available and may be used simultaneously in separate plugins. The
`neovim` pip package must be installed to use Python plugins in Nvim (see `neovim` pip package must be installed to use Python plugins in Nvim (see
|provider-python|). |provider-python|).
|:!| and |system()| do not support "interactive" commands; use |:terminal| for |:!| does not support "interactive" commands. Use |:terminal| instead.
that instead. Terminal Vim supports interactive |:!| and |system()|, but gui (GUI Vim has a similar limitation, see ":help gui-pty" in Vim.)
Vim does not. See ":help gui-pty" in Vim:
http://vimdoc.sourceforge.net/htmldoc/gui_x11.html#gui-pty |system()| does not support writing/reading "backgrounded" commands. |E5677|
|mkdir()| behaviour changed: |mkdir()| behaviour changed:
1. Assuming /tmp/foo does not exist and /tmp can be written to 1. Assuming /tmp/foo does not exist and /tmp can be written to

View File

@@ -112,8 +112,8 @@ static void read_cb(uv_stream_t *uvstream, ssize_t cnt, const uv_buf_t *buf)
// to `alloc_cb` will return the same unused pointer(`rbuffer_produced` // to `alloc_cb` will return the same unused pointer(`rbuffer_produced`
// won't be called) // won't be called)
&& cnt != 0) { && cnt != 0) {
DLOG("Closing Stream(%p) because of %s(%zd)", stream, DLOG("Closing Stream (%p): %s (%s)", stream,
uv_strerror((int)cnt), cnt); uv_err_name((int)cnt), os_strerror((int)cnt));
// Read error or EOF, either way stop the stream and invoke the callback // Read error or EOF, either way stop the stream and invoke the callback
// with eof == true // with eof == true
uv_read_stop(uvstream); uv_read_stop(uvstream);

View File

@@ -545,6 +545,16 @@ static size_t write_output(char *output, size_t remaining, bool to_buffer,
static void shell_write_cb(Stream *stream, void *data, int status) static void shell_write_cb(Stream *stream, void *data, int status)
{ {
if (status) {
// Can happen if system() tries to send input to a shell command that was
// backgrounded (:call system("cat - &", "foo")). #3529 #5241
EMSG2(_("E5677: Error writing input to shell-command: %s"),
uv_err_name(status));
}
if (stream->closed) { // Process may have exited before this write.
ELOG("stream was already closed");
return;
}
stream_close(stream, NULL, NULL); stream_close(stream, NULL, NULL);
} }