From 5a34f19970086b248646e1907207144a72773a14 Mon Sep 17 00:00:00 2001 From: Igor Zinkovsky Date: Tue, 15 May 2012 11:26:16 -0700 Subject: [PATCH] windows: allow specifying FDs to be inherited by a child process Previously the only option was to create a pipe or an ipc channel. This patch makes it possible to inherit a handle that is already open in the parent process. There is also room for setting more than just stdin, stdout and stderr, although this is not supported yet. --- include/uv.h | 31 +++++-- src/win/process.c | 164 ++++++++++++++++++++++++----------- test/benchmark-spawn.c | 10 ++- test/runner-win.c | 5 ++ test/test-ipc.c | 8 +- test/test-list.h | 2 + test/test-spawn.c | 103 +++++++++++++++++++--- test/test-stdio-over-pipes.c | 11 ++- 8 files changed, 262 insertions(+), 72 deletions(-) diff --git a/include/uv.h b/include/uv.h index 27c8f847..f9ffbaf4 100644 --- a/include/uv.h +++ b/include/uv.h @@ -1156,6 +1156,27 @@ UV_EXTERN int uv_getaddrinfo(uv_loop_t*, uv_getaddrinfo_t* handle, UV_EXTERN void uv_freeaddrinfo(struct addrinfo* ai); /* uv_spawn() options */ +typedef enum { + UV_IGNORE = 0x00, + UV_CREATE_PIPE = 0x01, + /* + * UV_READABLE_PIPE and UV_WRITABLE_PIPE flags are set from + * the child process perspective. + */ + UV_READABLE_PIPE = 0x02, + UV_WRITABLE_PIPE = 0x04, + UV_RAW_FD = 0x08 +} uv_stdio_flags; + +typedef struct uv_stdio_container_s { + uv_stdio_flags flags; + + union { + uv_stream_t* stream; + int fd; + } data; +} uv_stdio_container_t; + typedef struct uv_process_options_s { uv_exit_cb exit_cb; /* Called after the process exits. */ const char* file; /* Path to program to execute. */ @@ -1188,14 +1209,12 @@ typedef struct uv_process_options_s { */ uv_uid_t uid; uv_gid_t gid; + /* - * The user should supply pointers to initialized uv_pipe_t structs for - * stdio. This is used to to send or receive input from the subprocess. - * The user is responsible for calling uv_close on them. + * A container of stdio streams (stdin/stdout/stderr) */ - uv_pipe_t* stdin_stream; - uv_pipe_t* stdout_stream; - uv_pipe_t* stderr_stream; + uv_stdio_container_t* stdio; + int stdio_count; } uv_process_options_t; /* diff --git a/src/win/process.c b/src/win/process.c index 481b945c..0ad12a04 100644 --- a/src/win/process.c +++ b/src/win/process.c @@ -22,6 +22,7 @@ #include "uv.h" #include "internal.h" +#include #include #include #include @@ -861,23 +862,66 @@ static int duplicate_std_handle(uv_loop_t* loop, DWORD id, HANDLE* dup) { } +static int duplicate_handle(uv_loop_t* loop, HANDLE handle, HANDLE* dup) { + HANDLE current_process; + + current_process = GetCurrentProcess(); + + if (!DuplicateHandle(current_process, + handle, + current_process, + dup, + 0, + TRUE, + DUPLICATE_SAME_ACCESS)) { + *dup = INVALID_HANDLE_VALUE; + uv__set_sys_error(loop, GetLastError()); + return -1; + } + + return 0; +} + + +static int duplicate_fd(uv_loop_t* loop, int fd, HANDLE* dup) { + HANDLE handle; + + if (fd == -1) { + *dup = INVALID_HANDLE_VALUE; + uv__set_artificial_error(loop, UV_EBADF); + return -1; + } + + handle = (HANDLE)_get_osfhandle(fd); + return duplicate_handle(loop, handle, dup); +} + + int uv_spawn(uv_loop_t* loop, uv_process_t* process, uv_process_options_t options) { int err = 0, keep_child_stdio_open = 0; wchar_t* path = NULL; - int size; + int size, i, overlapped; + DWORD server_access, child_access; BOOL result; wchar_t* application_path = NULL, *application = NULL, *arguments = NULL, *env = NULL, *cwd = NULL; HANDLE* child_stdio = process->child_stdio; STARTUPINFOW startup; PROCESS_INFORMATION info; + uv_pipe_t* pipe; if (options.flags & (UV_PROCESS_SETGID | UV_PROCESS_SETUID)) { uv__set_artificial_error(loop, UV_ENOTSUP); return -1; } + /* Only support FDs 0-2 */ + if (options.stdio_count > 3) { + uv__set_artificial_error(loop, UV_ENOTSUP); + return -1; + } + assert(options.file != NULL); assert(!(options.flags & ~(UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS | UV_PROCESS_SETGID | @@ -927,59 +971,79 @@ int uv_spawn(uv_loop_t* loop, uv_process_t* process, application_path = application; } - /* Create stdio pipes. */ - if (options.stdin_stream) { - if (options.stdin_stream->ipc) { - err = uv_create_stdio_pipe_pair( - loop, - options.stdin_stream, - &child_stdio[0], - PIPE_ACCESS_DUPLEX, - GENERIC_READ | FILE_WRITE_ATTRIBUTES | GENERIC_WRITE, - 1); - } else { - err = uv_create_stdio_pipe_pair( - loop, - options.stdin_stream, - &child_stdio[0], - PIPE_ACCESS_OUTBOUND, - GENERIC_READ | FILE_WRITE_ATTRIBUTES, - 0); + for (i = 0; i < options.stdio_count; i++) { + if (options.stdio[i].flags == UV_IGNORE) { + continue; } - } else { + + if (options.stdio[i].flags & UV_RAW_FD) { + err = duplicate_fd(loop, options.stdio[i].data.fd, &child_stdio[i]); + } else if (options.stdio[i].data.stream->type == UV_NAMED_PIPE) { + pipe = (uv_pipe_t*)options.stdio[i].data.stream; + + if (options.stdio[i].flags & UV_CREATE_PIPE) { + server_access = 0; + child_access = 0; + if (pipe->ipc) { + server_access = PIPE_ACCESS_DUPLEX; + child_access = GENERIC_READ | FILE_WRITE_ATTRIBUTES | GENERIC_WRITE; + overlapped = 1; + } else { + overlapped = 0; + + if (options.stdio[i].flags & UV_READABLE_PIPE) { + server_access |= PIPE_ACCESS_OUTBOUND; + child_access |= GENERIC_READ | FILE_WRITE_ATTRIBUTES; + } + + if (options.stdio[i].flags & UV_WRITABLE_PIPE) { + server_access |= PIPE_ACCESS_INBOUND; + child_access |= GENERIC_WRITE; + } + } + + err = uv_create_stdio_pipe_pair( + loop, + pipe, + &child_stdio[i], + server_access, + child_access, + overlapped); + } else { + err = duplicate_handle(loop, pipe->handle, &child_stdio[i]); + } + } else if(options.stdio[i].data.stream->type == UV_TTY) { + err = duplicate_handle(loop, + ((uv_tty_t*)options.stdio[i].data.stream)->handle, &child_stdio[i]); + } else { + err = -1; + uv__set_artificial_error(loop, UV_ENOTSUP); + } + + if (err) { + goto done; + } + } + + if (child_stdio[0] == INVALID_HANDLE_VALUE) { err = duplicate_std_handle(loop, STD_INPUT_HANDLE, &child_stdio[0]); - } - if (err) { - goto done; + if (err) { + goto done; + } } - if (options.stdout_stream) { - err = uv_create_stdio_pipe_pair( - loop, options.stdout_stream, - &child_stdio[1], - PIPE_ACCESS_INBOUND, - GENERIC_WRITE, - 0); - } else { + if (child_stdio[1] == INVALID_HANDLE_VALUE) { err = duplicate_std_handle(loop, STD_OUTPUT_HANDLE, &child_stdio[1]); - } - if (err) { - goto done; + if (err) { + goto done; + } } - if (options.stderr_stream) { - err = uv_create_stdio_pipe_pair( - loop, - options.stderr_stream, - &child_stdio[2], - PIPE_ACCESS_INBOUND, - GENERIC_WRITE, - 0); - } else { + if (child_stdio[2] == INVALID_HANDLE_VALUE) { err = duplicate_std_handle(loop, STD_ERROR_HANDLE, &child_stdio[2]); - } - if (err) { - goto done; + if (err) { + goto done; + } } startup.cb = sizeof(startup); @@ -1007,9 +1071,11 @@ int uv_spawn(uv_loop_t* loop, uv_process_t* process, process->process_handle = info.hProcess; process->pid = info.dwProcessId; - if (options.stdin_stream && - options.stdin_stream->ipc) { - options.stdin_stream->ipc_pid = info.dwProcessId; + if (options.stdio_count > 0 && + options.stdio[0].flags & UV_CREATE_PIPE && + options.stdio[0].data.stream->type == UV_NAMED_PIPE && + ((uv_pipe_t*)options.stdio[0].data.stream)->ipc) { + ((uv_pipe_t*)options.stdio[0].data.stream)->ipc_pid = info.dwProcessId; } /* Setup notifications for when the child process exits. */ diff --git a/test/benchmark-spawn.c b/test/benchmark-spawn.c index d34f42b9..68bac0ce 100644 --- a/test/benchmark-spawn.c +++ b/test/benchmark-spawn.c @@ -101,6 +101,7 @@ void on_read(uv_stream_t* pipe, ssize_t nread, uv_buf_t buf) { static void spawn() { + uv_stdio_container_t stdio[2]; int r; ASSERT(process_open == 0); @@ -114,7 +115,12 @@ static void spawn() { options.exit_cb = exit_cb; uv_pipe_init(loop, &out, 0); - options.stdout_stream = &out; + + options.stdio = stdio; + options.stdio_count = 2; + options.stdio[0].flags = UV_IGNORE; + options.stdio[1].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE; + options.stdio[1].data.stream = (uv_stream_t*)&out; r = uv_spawn(loop, &process, options); ASSERT(r == 0); @@ -153,4 +159,4 @@ BENCHMARK_IMPL(spawn) { (double) N / (double) (end_time - start_time) * 1000.0); return 0; -} +} \ No newline at end of file diff --git a/test/runner-win.c b/test/runner-win.c index e758dd1d..ad36719c 100644 --- a/test/runner-win.c +++ b/test/runner-win.c @@ -19,6 +19,7 @@ * IN THE SOFTWARE. */ +#include #include #include #include @@ -44,6 +45,10 @@ void platform_init(int argc, char **argv) { SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX); + _setmode(0, _O_BINARY); + _setmode(1, _O_BINARY); + _setmode(2, _O_BINARY); + /* Disable stdio output buffering. */ setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stderr, NULL, _IONBF, 0); diff --git a/test/test-ipc.c b/test/test-ipc.c index 1ea0e7f9..4dacc4b6 100644 --- a/test/test-ipc.c +++ b/test/test-ipc.c @@ -200,6 +200,7 @@ void spawn_helper(uv_pipe_t* channel, char exepath[1024]; char* args[3]; int r; + uv_stdio_container_t stdio[1]; r = uv_pipe_init(uv_default_loop(), channel, 1); ASSERT(r == 0); @@ -218,7 +219,12 @@ void spawn_helper(uv_pipe_t* channel, options.file = exepath; options.args = args; options.exit_cb = exit_cb; - options.stdin_stream = channel; + + options.stdio = stdio; + options.stdio[0].flags = UV_CREATE_PIPE | + UV_READABLE_PIPE | UV_WRITABLE_PIPE; + options.stdio[0].data.stream = (uv_stream_t*)channel; + options.stdio_count = 1; r = uv_spawn(uv_default_loop(), process, options); ASSERT(r == 0); diff --git a/test/test-list.h b/test/test-list.h index 63683f28..ffb1fe23 100644 --- a/test/test-list.h +++ b/test/test-list.h @@ -123,6 +123,7 @@ TEST_DECLARE (spawn_and_kill_with_std) TEST_DECLARE (spawn_and_ping) TEST_DECLARE (spawn_setuid_fails) TEST_DECLARE (spawn_setgid_fails) +TEST_DECLARE (spawn_stdout_to_file) TEST_DECLARE (kill) TEST_DECLARE (fs_file_noent) TEST_DECLARE (fs_file_nametoolong) @@ -334,6 +335,7 @@ TASK_LIST_START TEST_ENTRY (spawn_and_ping) TEST_ENTRY (spawn_setuid_fails) TEST_ENTRY (spawn_setgid_fails) + TEST_ENTRY (spawn_stdout_to_file) TEST_ENTRY (kill) #ifdef _WIN32 TEST_ENTRY (spawn_detect_pipe_name_collisions_on_windows) diff --git a/test/test-spawn.c b/test/test-spawn.c index ccb9538f..d04f4081 100644 --- a/test/test-spawn.c +++ b/test/test-spawn.c @@ -21,6 +21,7 @@ #include "uv.h" #include "task.h" +#include #include #include #include @@ -167,11 +168,16 @@ TEST_IMPL(spawn_exit_code) { TEST_IMPL(spawn_stdout) { int r; uv_pipe_t out; + uv_stdio_container_t stdio[2]; init_process_options("spawn_helper2", exit_cb); uv_pipe_init(uv_default_loop(), &out, 0); - options.stdout_stream = &out; + options.stdio = stdio; + options.stdio[0].flags = UV_IGNORE; + options.stdio[1].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE; + options.stdio[1].data.stream = (uv_stream_t*)&out; + options.stdio_count = 2; r = uv_spawn(uv_default_loop(), &process, options); ASSERT(r == 0); @@ -185,7 +191,59 @@ TEST_IMPL(spawn_stdout) { ASSERT(exit_cb_called == 1); ASSERT(close_cb_called == 2); /* Once for process once for the pipe. */ printf("output is: %s", output); - ASSERT(strcmp("hello world\n", output) == 0 || strcmp("hello world\r\n", output) == 0); + ASSERT(strcmp("hello world\n", output) == 0); + + return 0; +} + + +TEST_IMPL(spawn_stdout_to_file) { + int r; + uv_file file; + uv_fs_t fs_req; + uv_stdio_container_t stdio[2]; + + /* Setup. */ + unlink("stdout_file"); + + init_process_options("spawn_helper2", exit_cb); + + r = uv_fs_open(uv_default_loop(), &fs_req, "stdout_file", O_CREAT | O_RDWR, + S_IREAD | S_IWRITE, NULL); + ASSERT(r != -1); + uv_fs_req_cleanup(&fs_req); + + file = r; + + options.stdio = stdio; + options.stdio[0].flags = UV_IGNORE; + options.stdio[1].flags = UV_RAW_FD; + options.stdio[1].data.fd = file; + options.stdio_count = 2; + + r = uv_spawn(uv_default_loop(), &process, options); + ASSERT(r == 0); + + r = uv_run(uv_default_loop()); + ASSERT(r == 0); + + ASSERT(exit_cb_called == 1); + ASSERT(close_cb_called == 1); + + r = uv_fs_read(uv_default_loop(), &fs_req, file, output, sizeof(output), + 0, NULL); + ASSERT(r == 12); + uv_fs_req_cleanup(&fs_req); + + r = uv_fs_close(uv_default_loop(), &fs_req, file, NULL); + ASSERT(r == 0); + uv_fs_req_cleanup(&fs_req); + + printf("output is: %s", output); + ASSERT(strcmp("hello world\n", output) == 0); + + /* Cleanup. */ + unlink("stdout_file"); return 0; } @@ -197,14 +255,19 @@ TEST_IMPL(spawn_stdin) { uv_pipe_t in; uv_write_t write_req; uv_buf_t buf; + uv_stdio_container_t stdio[2]; char buffer[] = "hello-from-spawn_stdin"; init_process_options("spawn_helper3", exit_cb); uv_pipe_init(uv_default_loop(), &out, 0); uv_pipe_init(uv_default_loop(), &in, 0); - options.stdout_stream = &out; - options.stdin_stream = ∈ + options.stdio = stdio; + options.stdio[0].flags = UV_CREATE_PIPE | UV_READABLE_PIPE; + options.stdio[0].data.stream = (uv_stream_t*)∈ + options.stdio[1].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE; + options.stdio[1].data.stream = (uv_stream_t*)&out; + options.stdio_count = 2; r = uv_spawn(uv_default_loop(), &process, options); ASSERT(r == 0); @@ -259,6 +322,7 @@ TEST_IMPL(spawn_and_kill_with_std) { char message[] = "Nancy's joining me because the message this evening is " "not my message but ours."; uv_buf_t buf; + uv_stdio_container_t stdio[3]; init_process_options("spawn_helper4", kill_cb); @@ -271,9 +335,14 @@ TEST_IMPL(spawn_and_kill_with_std) { r = uv_pipe_init(uv_default_loop(), &err, 0); ASSERT(r == 0); - options.stdin_stream = ∈ - options.stdout_stream = &out; - options.stderr_stream = &err; + options.stdio = stdio; + options.stdio[0].flags = UV_CREATE_PIPE | UV_READABLE_PIPE; + options.stdio[0].data.stream = (uv_stream_t*)∈ + options.stdio[1].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE; + options.stdio[1].data.stream = (uv_stream_t*)&out; + options.stdio[2].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE; + options.stdio[2].data.stream = (uv_stream_t*)&err; + options.stdio_count = 3; r = uv_spawn(uv_default_loop(), &process, options); ASSERT(r == 0); @@ -308,6 +377,7 @@ TEST_IMPL(spawn_and_ping) { uv_write_t write_req; uv_pipe_t in, out; uv_buf_t buf; + uv_stdio_container_t stdio[2]; int r; init_process_options("spawn_helper3", exit_cb); @@ -315,8 +385,12 @@ TEST_IMPL(spawn_and_ping) { uv_pipe_init(uv_default_loop(), &out, 0); uv_pipe_init(uv_default_loop(), &in, 0); - options.stdout_stream = &out; - options.stdin_stream = ∈ + options.stdio = stdio; + options.stdio[0].flags = UV_CREATE_PIPE | UV_READABLE_PIPE; + options.stdio[0].data.stream = (uv_stream_t*)∈ + options.stdio[1].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE; + options.stdio[1].data.stream = (uv_stream_t*)&out; + options.stdio_count = 2; r = uv_spawn(uv_default_loop(), &process, options); ASSERT(r == 0); @@ -384,11 +458,16 @@ TEST_IMPL(spawn_detect_pipe_name_collisions_on_windows) { uv_pipe_t out; char name[64]; HANDLE pipe_handle; + uv_stdio_container_t stdio[2]; init_process_options("spawn_helper2", exit_cb); uv_pipe_init(uv_default_loop(), &out, 0); - options.stdout_stream = &out; + options.stdio = stdio; + options.stdio[0].flags = UV_IGNORE; + options.stdio[1].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE; + options.stdio[1].data.stream = (uv_stream_t*)&out; + options.stdio_count = 2; /* Create a pipe that'll cause a collision. */ _snprintf(name, sizeof(name), "\\\\.\\pipe\\uv\\%p-%d", &out, GetCurrentProcessId()); @@ -414,7 +493,7 @@ TEST_IMPL(spawn_detect_pipe_name_collisions_on_windows) { ASSERT(exit_cb_called == 1); ASSERT(close_cb_called == 2); /* Once for process once for the pipe. */ printf("output is: %s", output); - ASSERT(strcmp("hello world\n", output) == 0 || strcmp("hello world\r\n", output) == 0); + ASSERT(strcmp("hello world\n", output) == 0); return 0; } @@ -680,4 +759,4 @@ TEST_IMPL(spawn_setgid_fails) { return 0; } -#endif +#endif \ No newline at end of file diff --git a/test/test-stdio-over-pipes.c b/test/test-stdio-over-pipes.c index 0a3f04c6..7603027f 100644 --- a/test/test-stdio-over-pipes.c +++ b/test/test-stdio-over-pipes.c @@ -115,14 +115,21 @@ static void on_read(uv_stream_t* tcp, ssize_t nread, uv_buf_t rdbuf) { TEST_IMPL(stdio_over_pipes) { int r; uv_process_t process; + uv_stdio_container_t stdio[2]; + loop = uv_default_loop(); init_process_options("stdio_over_pipes_helper", exit_cb); uv_pipe_init(loop, &out, 0); - options.stdout_stream = &out; uv_pipe_init(loop, &in, 0); - options.stdin_stream = ∈ + + options.stdio = stdio; + options.stdio[0].flags = UV_CREATE_PIPE | UV_READABLE_PIPE; + options.stdio[0].data.stream = (uv_stream_t*)∈ + options.stdio[1].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE; + options.stdio[1].data.stream = (uv_stream_t*)&out; + options.stdio_count = 2; r = uv_spawn(loop, &process, options); ASSERT(r == 0);