1
0
mirror of https://github.com/janet-lang/janet synced 2024-11-25 01:37:19 +00:00

Add :pipe to os/spawn for piping to subprocess.

Similar to Python's subprocess.PIPE, this creates and manages pipes
automatically for the caller.
This commit is contained in:
Calvin Rose 2020-09-12 19:48:12 -05:00
parent 8763df1cd0
commit d3147b661b
3 changed files with 94 additions and 9 deletions

View File

@ -799,6 +799,10 @@ FILE *janet_getfile(const Janet *argv, int32_t n, int *flags) {
return iof->file;
}
JanetFile *janet_makejfile(FILE *f, int flags) {
return makef(f, flags);
}
Janet janet_makefile(FILE *f, int flags) {
return janet_wrap_abstract(makef(f, flags));
}

View File

@ -414,6 +414,30 @@ static Janet os_proc_kill(int32_t argc, Janet *argv) {
}
}
/* Create piped file for os/execute and os/spawn. */
static JanetFile *make_pipes(JanetHandle *handle, int reverse) {
JanetHandle handles[2];
#ifdef JANET_WINDOWS
bool result = CreatePipe(handles, handles + 1, NULL, 0);
if (result) {
} else {
}
#else
if (pipe(handles)) janet_panic(strerror(errno));
if (reverse) {
JanetHandle temp = handles[0];
handles[0] = handles[1];
handles[1] = temp;
}
*handle = handles[1];
FILE *f = fdopen(handles[0], reverse ? "w" : "r");
if (NULL == f) janet_panic(strerror(errno));
return janet_makejfile(f, reverse ? JANET_FILE_WRITE : JANET_FILE_READ);
#endif
}
static const JanetMethod proc_methods[] = {
{"wait", os_proc_wait},
{"kill", os_proc_kill},
@ -470,6 +494,7 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, int is_async) {
/* Optional stdio redirections */
JanetFile *new_in = NULL, *new_out = NULL, *new_err = NULL;
JanetHandle pipe_in = JANET_HANDLE_NONE, pipe_out = JANET_HANDLE_NONE, pipe_err = JANET_HANDLE_NONE;
/* Get optional redirections */
if (argc > 2) {
@ -477,9 +502,21 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, int is_async) {
Janet maybe_stdin = janet_dictionary_get(tab.kvs, tab.cap, janet_ckeywordv("in"));
Janet maybe_stdout = janet_dictionary_get(tab.kvs, tab.cap, janet_ckeywordv("out"));
Janet maybe_stderr = janet_dictionary_get(tab.kvs, tab.cap, janet_ckeywordv("err"));
if (!janet_checktype(maybe_stdin, JANET_NIL)) new_in = janet_getjfile(&maybe_stdin, 0);
if (!janet_checktype(maybe_stdout, JANET_NIL)) new_out = janet_getjfile(&maybe_stdout, 0);
if (!janet_checktype(maybe_stderr, JANET_NIL)) new_err = janet_getjfile(&maybe_stderr, 0);
if (janet_keyeq(maybe_stdin, "pipe")) {
new_in = make_pipes(&pipe_in, 1);
} else if (!janet_checktype(maybe_stdin, JANET_NIL)) {
new_in = janet_getjfile(&maybe_stdin, 0);
}
if (janet_keyeq(maybe_stdout, "pipe")) {
new_out = make_pipes(&pipe_out, 0);
} else if (!janet_checktype(maybe_stdout, JANET_NIL)) {
new_out = janet_getjfile(&maybe_stdout, 0);
}
if (janet_keyeq(maybe_stderr, "err")) {
new_err = make_pipes(&pipe_err, 0);
} else if (!janet_checktype(maybe_stderr, JANET_NIL)) {
new_err = janet_getjfile(&maybe_stderr, 0);
}
}
/* Result */
@ -502,9 +539,24 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, int is_async) {
const char *path = (const char *) janet_unwrap_string(exargs.items[0]);
/* Do IO redirection */
startupInfo.hStdInput = (HANDLE) _get_osfhandle((new_in == NULL) ? 0 : _fileno(new_in->file));
startupInfo.hStdOutput = (HANDLE) _get_osfhandle((new_out == NULL) ? 1 : _fileno(new_out->file));
startupInfo.hStdError = (HANDLE) _get_osfhandle((new_err == NULL) ? 2 : _fileno(new_err->file));
if (pipe_in != JANET_HANDLE_NONE) {
startupInfo.hStdInput = pipe_in;
} else if (new_in != NULL) {
startupInfo.hStdInput = (HANDLE) _get_osfhandle(_fileno(new_in->file));
}
if (pipe_out != JANET_HANDLE_NONE) {
startupInfo.hStdInput = pipe_out;
} else if (new_out != NULL) {
startupInfo.hStdOutput = (HANDLE) _get_osfhandle(_fileno(new_out->file));
}
if (pipe_err != JANET_HANDLE_NONE) {
startupInfo.hStdInput = pipe_err;
} else if (new_err != NULL) {
startupInfo.hStdError = (HANDLE) _get_osfhandle(_fileno(new_err->file));
}
/* Use _spawn family of functions. */
/* Windows docs say do this before any spawns. */
@ -524,6 +576,10 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, int is_async) {
janet_panic("failed to create process");
}
if (pipe_in != JANET_HANDLE_NONE) CloseHandle(pipe_in);
if (pipe_out != JANET_HANDLE_NONE) CloseHandle(pipe_out);
if (pipe_err != JANET_HANDLE_NONE) CloseHandle(pipe_err);
pHandle = processInfo.hProcess;
tHandle = processInfo.hThread;
@ -559,13 +615,19 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, int is_async) {
/* Posix spawn setup */
posix_spawn_file_actions_t actions;
posix_spawn_file_actions_init(&actions);
if (new_in != NULL) {
if (pipe_in != JANET_HANDLE_NONE) {
posix_spawn_file_actions_adddup2(&actions, pipe_in, 0);
} else if (new_in != NULL) {
posix_spawn_file_actions_adddup2(&actions, fileno(new_in->file), 0);
}
if (new_out != NULL) {
if (pipe_out != JANET_HANDLE_NONE) {
posix_spawn_file_actions_adddup2(&actions, pipe_out, 1);
} else if (new_out != NULL) {
posix_spawn_file_actions_adddup2(&actions, fileno(new_out->file), 1);
}
if (new_err != NULL) {
if (pipe_err != JANET_HANDLE_NONE) {
posix_spawn_file_actions_adddup2(&actions, pipe_err, 2);
} else if (new_err != NULL) {
posix_spawn_file_actions_adddup2(&actions, fileno(new_err->file), 2);
}
@ -582,6 +644,10 @@ static Janet os_execute_impl(int32_t argc, Janet *argv, int is_async) {
posix_spawn_file_actions_destroy(&actions);
if (pipe_in != JANET_HANDLE_NONE) close(pipe_in);
if (pipe_out != JANET_HANDLE_NONE) close(pipe_out);
if (pipe_err != JANET_HANDLE_NONE) close(pipe_err);
if (use_environ) {
janet_unlock_environ();
}
@ -1554,6 +1620,11 @@ static const JanetReg os_cfuns[] = {
"env is a table or struct mapping environment variables to values. It can also "
"contain the keys :in, :out, and :err, which allow redirecting stdio in the subprocess. "
"These arguments should be core/file values. "
"One can also pass in the :pipe keyword "
"for these arguments to create files that will read (for :err and :out) or write (for :in) "
"to the file descriptor of the subprocess. This is only useful in os/spawn, which takes "
"the same parameters as os/execute, but will return an object that contains references to these "
"files via (return-value :in), (return-value :out), and (return-value :err). "
"Returns the exit status of the program.")
},
{

View File

@ -310,6 +310,15 @@ JANET_API extern const char *const janet_type_names[16];
JANET_API extern const char *const janet_signal_names[14];
JANET_API extern const char *const janet_status_names[16];
/* For various IO routines, we want to use an int on posix and HANDLE on windows */
#ifdef JANET_WINDOWS
typedef void *JanetHandle;
#define JANET_HANDLE_NONE NULL
#else
typedef int JanetHandle;
#define JANET_HANDLE_NONE (-1)
#endif
/* Fiber signals */
typedef enum {
JANET_SIGNAL_OK,
@ -1560,6 +1569,7 @@ extern JANET_API const JanetAbstractType janet_file_type;
#define JANET_FILE_NONIL 512
JANET_API Janet janet_makefile(FILE *f, int32_t flags);
JANET_API JanetFile *janet_makejfile(FILE *f, int32_t flags);
JANET_API FILE *janet_getfile(const Janet *argv, int32_t n, int32_t *flags);
JANET_API FILE *janet_dynfile(const char *name, FILE *def);
JANET_API JanetFile *janet_getjfile(const Janet *argv, int32_t n);