Skip to content
This repository has been archived by the owner on May 4, 2018. It is now read-only.

Commit

Permalink
windows: allow specifying FDs to be inherited by a child process
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Igor Zinkovsky authored and piscisaureus committed May 28, 2012
1 parent e4f23aa commit 5a34f19
Show file tree
Hide file tree
Showing 8 changed files with 261 additions and 71 deletions.
31 changes: 25 additions & 6 deletions include/uv.h
Expand Up @@ -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. */
Expand Down Expand Up @@ -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;

/*
Expand Down
162 changes: 114 additions & 48 deletions src/win/process.c
Expand Up @@ -22,6 +22,7 @@
#include "uv.h"
#include "internal.h"

#include <io.h>
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
Expand Down Expand Up @@ -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 |
Expand Down Expand Up @@ -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);
for (i = 0; i < options.stdio_count; i++) {
if (options.stdio[i].flags == UV_IGNORE) {
continue;
}

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 = uv_create_stdio_pipe_pair(
loop,
options.stdin_stream,
&child_stdio[0],
PIPE_ACCESS_OUTBOUND,
GENERIC_READ | FILE_WRITE_ATTRIBUTES,
0);
err = -1;
uv__set_artificial_error(loop, UV_ENOTSUP);
}

if (err) {
goto done;
}
} else {
err = duplicate_std_handle(loop, STD_INPUT_HANDLE, &child_stdio[0]);
}
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 (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);
Expand Down Expand Up @@ -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. */
Expand Down
10 changes: 8 additions & 2 deletions test/benchmark-spawn.c
Expand Up @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -153,4 +159,4 @@ BENCHMARK_IMPL(spawn) {
(double) N / (double) (end_time - start_time) * 1000.0);

return 0;
}
}
5 changes: 5 additions & 0 deletions test/runner-win.c
Expand Up @@ -19,6 +19,7 @@
* IN THE SOFTWARE.
*/

#include <fcntl.h>
#include <io.h>
#include <malloc.h>
#include <stdio.h>
Expand All @@ -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);
Expand Down
8 changes: 7 additions & 1 deletion test/test-ipc.c
Expand Up @@ -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);
Expand All @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions test/test-list.h
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down

0 comments on commit 5a34f19

Please sign in to comment.