mirror of
https://github.com/neovim/neovim.git
synced 2025-02-25 18:55:25 -06:00
os/shell: implement os_system
With the goal to support pipe-only system() calls. Notes on the second (vim) argument to f_system() (i.e.: redirected input) and its implications: - When calling system('cat -', ['some', 'list']), vanilla vim (before a recent patch that added support for passing lists) just passes an empty file to the process. This is the same as immediately closing the pipe, which os_system does when no input is given. If we wouldn't close the pipe, the process will linger forever (as is the case with `cat -`). As of now, it's not allowed to pass a non-NULL pointer as the `output` parameter. In other words, it's not possible to signal disinterst in the process output. That may change in the future.
This commit is contained in:
parent
5e0931241d
commit
3d3b233df8
@ -5,6 +5,10 @@
|
|||||||
#include <uv.h>
|
#include <uv.h>
|
||||||
|
|
||||||
#include "nvim/ascii.h"
|
#include "nvim/ascii.h"
|
||||||
|
#include "nvim/lib/kvec.h"
|
||||||
|
#include "nvim/log.h"
|
||||||
|
#include "nvim/os/job.h"
|
||||||
|
#include "nvim/os/rstream.h"
|
||||||
#include "nvim/os/shell.h"
|
#include "nvim/os/shell.h"
|
||||||
#include "nvim/os/signal.h"
|
#include "nvim/os/signal.h"
|
||||||
#include "nvim/types.h"
|
#include "nvim/types.h"
|
||||||
@ -31,6 +35,11 @@ typedef struct {
|
|||||||
garray_T ga;
|
garray_T ga;
|
||||||
} ProcessData;
|
} ProcessData;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char *data;
|
||||||
|
size_t cap;
|
||||||
|
size_t len;
|
||||||
|
} dyn_buffer_t;
|
||||||
|
|
||||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||||
# include "os/shell.c.generated.h"
|
# include "os/shell.c.generated.h"
|
||||||
@ -234,6 +243,128 @@ int os_call_shell(char_u *cmd, ShellOpts opts, char_u *extra_shell_arg)
|
|||||||
return proc_cleanup_exit(&pdata, &proc_opts, opts);
|
return proc_cleanup_exit(&pdata, &proc_opts, opts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// os_system - synchronously execute a command in the shell
|
||||||
|
///
|
||||||
|
/// example:
|
||||||
|
/// char *output = NULL;
|
||||||
|
/// size_t nread = 0;
|
||||||
|
/// int status = os_sytem("ls -la", NULL, 0, &output, &nread);
|
||||||
|
///
|
||||||
|
/// @param cmd The full commandline to be passed to the shell
|
||||||
|
/// @param input The input to the shell (NULL for no input), passed to the
|
||||||
|
/// stdin of the resulting process.
|
||||||
|
/// @param len The length of the input buffer (not used if `input` == NULL)
|
||||||
|
/// @param[out] output A pointer to to a location where the output will be
|
||||||
|
/// allocated and stored. Will point to NULL if the shell
|
||||||
|
/// command did not output anything. NOTE: it's not
|
||||||
|
/// allowed to pass NULL yet
|
||||||
|
/// @param[out] nread the number of bytes in the returned buffer (if the
|
||||||
|
/// returned buffer is not NULL)
|
||||||
|
/// @return the return code of the process, -1 if the process couldn't be
|
||||||
|
/// started properly
|
||||||
|
int os_system(const char *cmd,
|
||||||
|
const char *input,
|
||||||
|
size_t len,
|
||||||
|
char **output,
|
||||||
|
size_t *nread) FUNC_ATTR_NONNULL_ARG(1, 4)
|
||||||
|
{
|
||||||
|
// the output buffer
|
||||||
|
dyn_buffer_t buf;
|
||||||
|
memset(&buf, 0, sizeof(buf));
|
||||||
|
|
||||||
|
char **argv = shell_build_argv((char_u *) cmd, NULL);
|
||||||
|
|
||||||
|
int i;
|
||||||
|
Job *job = job_start(argv,
|
||||||
|
&buf,
|
||||||
|
system_data_cb,
|
||||||
|
system_data_cb,
|
||||||
|
NULL,
|
||||||
|
false,
|
||||||
|
0,
|
||||||
|
&i);
|
||||||
|
|
||||||
|
if (i <= 0) {
|
||||||
|
// couldn't even start the job
|
||||||
|
ELOG("Couldn't start job, error code: '%d'", i);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// write the input, if any
|
||||||
|
if (input) {
|
||||||
|
WBuffer *input_buffer = wstream_new_buffer((char *) input, len, 1, NULL);
|
||||||
|
|
||||||
|
// we want to be notified when the write completes
|
||||||
|
job_write_cb(job, system_write_cb);
|
||||||
|
|
||||||
|
if (!job_write(job, input_buffer)) {
|
||||||
|
// couldn't write, stop the job and tell the user about it
|
||||||
|
job_stop(job);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// close the input stream, let the process know that no input is coming
|
||||||
|
job_close_in(job);
|
||||||
|
}
|
||||||
|
|
||||||
|
int status = job_wait(job, -1);
|
||||||
|
|
||||||
|
// prepare the out parameters if requested
|
||||||
|
if (buf.len == 0) {
|
||||||
|
// no data received from the process, return NULL
|
||||||
|
*output = NULL;
|
||||||
|
free(buf.data);
|
||||||
|
} else {
|
||||||
|
// NUL-terminate to make the output directly usable as a C string
|
||||||
|
buf.data[buf.len] = NUL;
|
||||||
|
*output = buf.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nread) {
|
||||||
|
*nread = buf.len;
|
||||||
|
}
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// dyn_buf_ensure - ensures at least `desired` bytes in buffer
|
||||||
|
///
|
||||||
|
/// TODO(aktau): fold with kvec/garray
|
||||||
|
static void dyn_buf_ensure(dyn_buffer_t *buf, size_t desired)
|
||||||
|
{
|
||||||
|
if (buf->cap >= desired) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
buf->cap = desired;
|
||||||
|
kv_roundup32(buf->cap);
|
||||||
|
buf->data = xrealloc(buf->data, buf->cap);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void system_data_cb(RStream *rstream, void *data, bool eof)
|
||||||
|
{
|
||||||
|
Job *job = data;
|
||||||
|
dyn_buffer_t *buf = job_data(job);
|
||||||
|
|
||||||
|
size_t nread = rstream_available(rstream);
|
||||||
|
|
||||||
|
dyn_buf_ensure(buf, buf->len + nread + 1);
|
||||||
|
rstream_read(rstream, buf->data + buf->len, nread);
|
||||||
|
|
||||||
|
buf->len += nread;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void system_write_cb(WStream *wstream,
|
||||||
|
void *data,
|
||||||
|
size_t pending,
|
||||||
|
int status)
|
||||||
|
{
|
||||||
|
if (pending == 0) {
|
||||||
|
Job *job = data;
|
||||||
|
job_close_in(job);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Parses a command string into a sequence of words, taking quotes into
|
/// Parses a command string into a sequence of words, taking quotes into
|
||||||
/// consideration.
|
/// consideration.
|
||||||
///
|
///
|
||||||
|
Loading…
Reference in New Issue
Block a user