From d3147b661b52917352bd20c969e887d0fea001a7 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 12 Sep 2020 19:48:12 -0500 Subject: [PATCH 1/4] Add :pipe to os/spawn for piping to subprocess. Similar to Python's subprocess.PIPE, this creates and manages pipes automatically for the caller. --- src/core/io.c | 4 ++ src/core/os.c | 89 ++++++++++++++++++++++++++++++++++++++++----- src/include/janet.h | 10 +++++ 3 files changed, 94 insertions(+), 9 deletions(-) diff --git a/src/core/io.c b/src/core/io.c index 3283dd98..d9636db0 100644 --- a/src/core/io.c +++ b/src/core/io.c @@ -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)); } diff --git a/src/core/os.c b/src/core/os.c index 76dacc05..6d3ed18b 100644 --- a/src/core/os.c +++ b/src/core/os.c @@ -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.") }, { diff --git a/src/include/janet.h b/src/include/janet.h index 53aa406d..a3bf38cf 100644 --- a/src/include/janet.h +++ b/src/include/janet.h @@ -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); From 524c9b50d4a28896fdb8695f81c9abe2b304ad8f Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sat, 12 Sep 2020 19:56:48 -0500 Subject: [PATCH 2/4] Add windows implementation for piping. --- src/core/os.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/core/os.c b/src/core/os.c index 6d3ed18b..7d83ebb9 100644 --- a/src/core/os.c +++ b/src/core/os.c @@ -418,12 +418,18 @@ static Janet os_proc_kill(int32_t argc, Janet *argv) { 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 { - + if (!CreatePipe(handles, handles + 1, NULL, 0)) janet_panic("failed to create pipe"); + if (reverse) { + JanetHandle temp = handles[0]; + handles[0] = handles[1]; + handles[1] = temp; } + *handle = handles[1]; + int fd = _open_osfhandle((intptr_t) handles[0], reverse ? _O_WRONLY : _O_RDONLY); + if (fd == -1) janet_panic("could not create file for piping"); + FILE *f = _fdopen(fd, reverse ? "w" : "r"); + if (NULL == f) janet_panic(strerror(errno)); + return janet_makejfile(f, reverse ? JANET_FILE_WRITE : JANET_FILE_READ); #else if (pipe(handles)) janet_panic(strerror(errno)); if (reverse) { From 2cbf4d8ad1f9540573bd8b74de85572d80d60874 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sun, 13 Sep 2020 08:38:37 -0500 Subject: [PATCH 3/4] Update documentation for thread/send and thread/receive. --- src/core/thread.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/core/thread.c b/src/core/thread.c index 44ad4b88..7a19c707 100644 --- a/src/core/thread.c +++ b/src/core/thread.c @@ -724,15 +724,18 @@ static const JanetReg threadlib_cfuns[] = { }, { "thread/send", cfun_thread_send, - JDOC("(thread/send thread msg)\n\n" - "Send a message to the thread. This will never block and returns thread immediately. " + JDOC("(thread/send thread msgi &opt timeout)\n\n" + "Send a message to the thread. By default, the timeout is 1 second, but an optional timeout " + "in seconds can be provided. Use math/inf for no timeout. " "Will throw an error if there is a problem sending the message.") }, { "thread/receive", cfun_thread_receive, JDOC("(thread/receive &opt timeout)\n\n" - "Get a message sent to this thread. If timeout is provided, an error will be thrown after the timeout has elapsed but " - "no messages are received.") + "Get a message sent to this thread. If timeout (in seconds) is provided, an error " + "will be thrown after the timeout has elapsed but " + "no messages are received. The default timeout is 1 second, and math/inf cam be passed to " + "turn off the timeout.") }, { "thread/close", cfun_thread_close, From 4c9624db64b28ca4fc5b7d94c26c35205f437b64 Mon Sep 17 00:00:00 2001 From: Calvin Rose Date: Sun, 13 Sep 2020 11:06:49 -0500 Subject: [PATCH 4/4] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 03e1380d..d6970c62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ All notable changes to this project will be documented in this file. ## Unreleased - ??? +- Add :pipe option to `os/spawn`. +- Fix docstring typos. ## 1.12.1 - 2020-09-07 - Make `zero?`, `one?`, `pos?`, and `neg?` polymorphic.