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.
This commit is contained in:
Igor Zinkovsky 2012-05-15 11:26:16 -07:00 committed by Bert Belder
parent e4f23aacec
commit 5a34f19970
8 changed files with 262 additions and 72 deletions

View File

@ -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;
/*

View File

@ -22,6 +22,7 @@
#include "uv.h"
#include "internal.h"
#include <io.h>
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
@ -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. */

View File

@ -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;
}
}

View File

@ -19,6 +19,7 @@
* IN THE SOFTWARE.
*/
#include <fcntl.h>
#include <io.h>
#include <malloc.h>
#include <stdio.h>
@ -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);

View File

@ -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);

View File

@ -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)

View File

@ -21,6 +21,7 @@
#include "uv.h"
#include "task.h"
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@ -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 = &in;
options.stdio = stdio;
options.stdio[0].flags = UV_CREATE_PIPE | UV_READABLE_PIPE;
options.stdio[0].data.stream = (uv_stream_t*)&in;
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 = &in;
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*)&in;
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 = &in;
options.stdio = stdio;
options.stdio[0].flags = UV_CREATE_PIPE | UV_READABLE_PIPE;
options.stdio[0].data.stream = (uv_stream_t*)&in;
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

View File

@ -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 = &in;
options.stdio = stdio;
options.stdio[0].flags = UV_CREATE_PIPE | UV_READABLE_PIPE;
options.stdio[0].data.stream = (uv_stream_t*)&in;
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);