windows: improve spawn stdio support

* Make using an existing stream for stdio actually work
* Support up to 256 stdio channels
* Fix some minor bugs
This commit is contained in:
Bert Belder 2012-06-01 17:47:20 +02:00
parent dc7a62d114
commit 3ec9c67f93
3 changed files with 381 additions and 202 deletions

View File

@ -454,7 +454,7 @@ RB_HEAD(uv_timer_tree_s, uv_timer_s);
struct uv_process_close_s { \
UV_REQ_FIELDS \
} close_req; \
HANDLE child_stdio[3]; \
void* child_stdio_buffer; \
int exit_signal; \
DWORD spawn_errno; \
HANDLE wait_handle; \

View File

@ -1175,7 +1175,7 @@ typedef enum {
* flags may be specified to create a duplex data stream.
*/
UV_READABLE_PIPE = 0x10,
UV_WRITABLE_PIPE = 0x20,
UV_WRITABLE_PIPE = 0x20
} uv_stdio_flags;
typedef struct uv_stdio_container_s {
@ -1221,10 +1221,16 @@ typedef struct uv_process_options_s {
uv_gid_t gid;
/*
* A container of stdio streams (stdin/stdout/stderr)
* The `stdio` field points to an array of uv_stdio_container_t structs that
* describe the file descriptors that will be made available to the child
* process. The convention is that stdio[0] points to stdin, fd 1 is used for
* stdout, and fd 2 is stderr.
*
* Note that on windows file descriptors greater than 2 are available to the
* child process only if the child processes uses the MSVCRT runtime.
*/
uv_stdio_container_t* stdio;
int stdio_count;
uv_stdio_container_t* stdio;
} uv_process_options_t;
/*

View File

@ -22,15 +22,28 @@
#include "uv.h"
#include "internal.h"
#include <assert.h>
#include <io.h>
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>
#include <signal.h>
#include <windows.h>
#define SIGKILL 9
/* CRT file descriptor mode flags */
#define FOPEN 0x01
#define FEOFLAG 0x02
#define FCRLF 0x04
#define FPIPE 0x08
#define FNOINHERIT 0x10
#define FAPPEND 0x20
#define FDEV 0x40
#define FTEXT 0x80
typedef struct env_var {
const char* narrow;
const wchar_t* wide;
@ -41,6 +54,7 @@ typedef struct env_var {
#define E_V(str) { str "=", L##str, sizeof(str), 0, 0 }
#define UTF8_TO_UTF16(s, t) \
size = uv_utf8_to_utf16(s, NULL, 0) * sizeof(wchar_t); \
t = (wchar_t*)malloc(size); \
@ -54,6 +68,37 @@ typedef struct env_var {
}
/* The `child_stdio_buffer` buffer has the following layout:
* int number_of_fds
* unsigned char crt_flags[number_of_fds]
* HANDLE os_handle[number_of_fds]
*/
#define CHILD_STDIO_SIZE(count) \
(sizeof(int) + \
sizeof(unsigned char) * (count) + \
sizeof(uintptr_t) * (count))
#define CHILD_STDIO_COUNT(buffer) \
*((unsigned int*) (buffer))
#define CHILD_STDIO_LPRESERVED2(buffer) \
((LPBYTE) (buffer))
#define CHILD_STDIO_CBRESERVED2(buffer) \
CHILD_STDIO_SIZE(CHILD_STDIO_COUNT((buffer)))
#define CHILD_STDIO_CRT_FLAGS(buffer, fd) \
*((unsigned char*) (buffer) + sizeof(int) + fd)
#define CHILD_STDIO_HANDLE(buffer, fd) \
*((HANDLE*) ((unsigned char*) (buffer) + \
sizeof(int) + \
sizeof(unsigned char) * \
CHILD_STDIO_COUNT((buffer)) + \
sizeof(HANDLE) * (fd)))
static void uv_process_init(uv_loop_t* loop, uv_process_t* handle) {
uv_handle_init(loop, (uv_handle_t*) handle);
handle->type = UV_PROCESS;
@ -63,9 +108,7 @@ static void uv_process_init(uv_loop_t* loop, uv_process_t* handle) {
handle->wait_handle = INVALID_HANDLE_VALUE;
handle->process_handle = INVALID_HANDLE_VALUE;
handle->close_handle = INVALID_HANDLE_VALUE;
handle->child_stdio[0] = INVALID_HANDLE_VALUE;
handle->child_stdio[1] = INVALID_HANDLE_VALUE;
handle->child_stdio[2] = INVALID_HANDLE_VALUE;
handle->child_stdio_buffer = NULL;
uv_req_init(loop, (uv_req_t*)&handle->exit_req);
handle->exit_req.type = UV_PROCESS_EXIT;
@ -594,6 +637,153 @@ wchar_t* make_program_env(char** env_block) {
}
static int uv_create_stdio_pipe_pair(uv_loop_t* loop, uv_pipe_t* server_pipe,
HANDLE* child_pipe_ptr, unsigned int flags) {
char pipe_name[64];
SECURITY_ATTRIBUTES sa;
DWORD server_access = 0;
DWORD client_access = 0;
HANDLE child_pipe = INVALID_HANDLE_VALUE;
if (flags & UV_READABLE_PIPE) {
server_access |= PIPE_ACCESS_OUTBOUND;
client_access |= GENERIC_READ | FILE_WRITE_ATTRIBUTES;
}
if (flags & UV_WRITABLE_PIPE) {
server_access |= PIPE_ACCESS_INBOUND;
client_access |= GENERIC_WRITE;
}
/* Create server pipe handle. */
if (uv_stdio_pipe_server(loop,
server_pipe,
server_access,
pipe_name,
sizeof(pipe_name)) < 0) {
goto error;
}
/* Create child pipe handle. */
sa.nLength = sizeof sa;
sa.lpSecurityDescriptor = NULL;
sa.bInheritHandle = TRUE;
child_pipe = CreateFileA(pipe_name,
client_access,
0,
&sa,
OPEN_EXISTING,
server_pipe->ipc ? FILE_FLAG_OVERLAPPED : 0,
NULL);
if (child_pipe == INVALID_HANDLE_VALUE) {
uv__set_sys_error(loop, GetLastError());
goto error;
}
#ifndef NDEBUG
/* Validate that the pipe was opened in the right mode. */
{
DWORD mode;
BOOL r = GetNamedPipeHandleState(child_pipe,
&mode,
NULL,
NULL,
NULL,
NULL,
0);
assert(r == TRUE);
assert(mode == (PIPE_READMODE_BYTE | PIPE_WAIT));
}
#endif
/* Do a blocking ConnectNamedPipe. This should not block because we have */
/* both ends of the pipe created. */
if (!ConnectNamedPipe(server_pipe->handle, NULL)) {
if (GetLastError() != ERROR_PIPE_CONNECTED) {
uv__set_sys_error(loop, GetLastError());
goto error;
}
}
*child_pipe_ptr = child_pipe;
return 0;
error:
if (server_pipe->handle != INVALID_HANDLE_VALUE) {
uv_pipe_cleanup(loop, server_pipe);
}
if (child_pipe != INVALID_HANDLE_VALUE) {
CloseHandle(child_pipe);
}
return -1;
}
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);
}
static void set_child_stdio_noinherit(void* buffer) {
int i, count;
count = CHILD_STDIO_COUNT(buffer);
for (i = 0; i < count; i++) {
HANDLE handle = CHILD_STDIO_HANDLE(buffer, i);
if (handle != INVALID_HANDLE_VALUE) {
SetHandleInformation(handle, HANDLE_FLAG_INHERIT, 0);
}
}
}
static void close_and_free_child_stdio(void* buffer) {
int i, count;
count = CHILD_STDIO_COUNT(buffer);
for (i = 0; i < count; i++) {
HANDLE handle = CHILD_STDIO_HANDLE(buffer, i);
if (handle != INVALID_HANDLE_VALUE) {
CloseHandle(handle);
}
}
free(buffer);
}
/*
* Called on Windows thread-pool thread to indicate that
* a child process has exited.
@ -636,7 +826,7 @@ static DWORD WINAPI spawn_failure(void* data) {
char unknown[] = "unknown error\n";
uv_process_t* process = (uv_process_t*) data;
uv_loop_t* loop = process->loop;
HANDLE child_stderr = process->child_stdio[2];
HANDLE child_stderr = CHILD_STDIO_HANDLE(process->child_stdio_buffer, 2);
char* buf = NULL;
DWORD count, written;
@ -670,20 +860,6 @@ static DWORD WINAPI spawn_failure(void* data) {
}
static void close_child_stdio(uv_process_t* process) {
int i;
HANDLE handle;
for (i = 0; i < ARRAY_SIZE(process->child_stdio); i++) {
handle = process->child_stdio[i];
if (handle != INVALID_HANDLE_VALUE) {
CloseHandle(handle);
process->child_stdio[i] = INVALID_HANDLE_VALUE;
}
}
}
/* Called on main thread after a child process has exited. */
void uv_process_proc_exit(uv_loop_t* loop, uv_process_t* handle) {
DWORD exit_code;
@ -749,147 +925,190 @@ void uv_process_endgame(uv_loop_t* loop, uv_process_t* handle) {
CloseHandle(handle->process_handle);
/* Clean up the child stdio ends that may have been left open. */
close_child_stdio(handle);
if (handle->child_stdio_buffer != NULL) {
close_and_free_child_stdio(handle->child_stdio_buffer);
}
uv__handle_close(handle);
}
}
static int uv_create_stdio_pipe_pair(uv_loop_t* loop, uv_pipe_t* server_pipe,
HANDLE* child_pipe, DWORD server_access, DWORD child_access,
int overlapped) {
int err;
SECURITY_ATTRIBUTES sa = { sizeof(SECURITY_ATTRIBUTES), NULL, TRUE };
char pipe_name[64];
DWORD mode = PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT;
static int init_child_stdio(uv_loop_t* loop, uv_process_options_t* options,
void** buffer_ptr) {
void* buffer;
int count, i;
if (server_pipe->type != UV_NAMED_PIPE) {
uv__set_artificial_error(loop, UV_EINVAL);
err = -1;
goto done;
count = options->stdio_count;
if (count < 0 || count > 255) {
/* Only support FDs 0-255 */
uv__set_artificial_error(loop, UV_ENOTSUP);
return -1;
} else if (count < 3) {
/* There should always be at least 3 stdio handles. */
count = 3;
}
/* Create server pipe handle. */
err = uv_stdio_pipe_server(loop,
server_pipe,
server_access,
pipe_name,
sizeof(pipe_name));
if (err) {
goto done;
}
/* Create child pipe handle. */
*child_pipe = CreateFileA(pipe_name,
child_access,
0,
&sa,
OPEN_EXISTING,
overlapped ? FILE_FLAG_OVERLAPPED : 0,
NULL);
if (*child_pipe == INVALID_HANDLE_VALUE) {
uv__set_sys_error(loop, GetLastError());
err = -1;
goto done;
}
if (!SetNamedPipeHandleState(*child_pipe, &mode, NULL, NULL)) {
uv__set_sys_error(loop, GetLastError());
err = -1;
goto done;
}
/* Do a blocking ConnectNamedPipe. This should not block because
* we have both ends of the pipe created.
*/
if (!ConnectNamedPipe(server_pipe->handle, NULL)) {
if (GetLastError() != ERROR_PIPE_CONNECTED) {
uv__set_sys_error(loop, GetLastError());
err = -1;
goto done;
}
}
err = 0;
done:
if (err) {
if (server_pipe->handle != INVALID_HANDLE_VALUE) {
uv_pipe_cleanup(loop, server_pipe);
}
if (*child_pipe != INVALID_HANDLE_VALUE) {
CloseHandle(*child_pipe);
*child_pipe = INVALID_HANDLE_VALUE;
}
}
return err;
}
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());
/* Allocate the child stdio buffer */
buffer = malloc(CHILD_STDIO_SIZE(count));
if (buffer == NULL) {
uv__set_artificial_error(loop, UV_ENOMEM);
return -1;
}
/* Prepopulate the buffer with INVALID_HANDLE_VALUE handles so we can */
/* clean up on failure. */
CHILD_STDIO_COUNT(buffer) = count;
for (i = 0; i < count; i++) {
CHILD_STDIO_CRT_FLAGS(buffer, i) = 0;
CHILD_STDIO_HANDLE(buffer, i) = INVALID_HANDLE_VALUE;
}
for (i = 0; i < options->stdio_count; i++) {
uv_stdio_container_t fdopt = options->stdio[i];
switch (fdopt.flags & (UV_IGNORE | UV_CREATE_PIPE | UV_INHERIT_FD |
UV_INHERIT_STREAM)) {
case UV_IGNORE:
/* The child is not supposed to inherit this handle. It has already */
/* been initialized to INVALID_HANDLE_VALUE, so just keep it like */
/* that. */
continue;
case UV_CREATE_PIPE: {
/* Create a pair of two connected pipe ends; one end is turned into */
/* an uv_pipe_t for use by the parent. The other one is given to */
/* the child. */
uv_pipe_t* parent_pipe = (uv_pipe_t*) fdopt.data.stream;
HANDLE child_pipe;
/* Create a new, connected pipe pair. stdio[i].stream should point */
/* to an uninitialized, but not connected pipe handle. */
assert(fdopt.data.stream->type == UV_NAMED_PIPE);
assert(!(fdopt.data.stream->flags & UV_HANDLE_CONNECTION));
assert(!(fdopt.data.stream->flags & UV_HANDLE_PIPESERVER));
if (uv_create_stdio_pipe_pair(loop,
parent_pipe,
&child_pipe,
fdopt.flags) < 0) {
goto error;
}
CHILD_STDIO_HANDLE(buffer, i) = child_pipe;
CHILD_STDIO_CRT_FLAGS(buffer, i) = FOPEN | FPIPE;
break;
}
case UV_INHERIT_FD: {
/* Inherit a raw FD. */
HANDLE child_handle;
/* Make an inheritable duplicate of the handle. */
if (duplicate_fd(loop, fdopt.data.fd, &child_handle) < 0) {
goto error;
}
/* Figure out what the type is. */
switch (GetFileType(child_handle)) {
case FILE_TYPE_DISK:
CHILD_STDIO_CRT_FLAGS(buffer, i) = FOPEN;
break;
case FILE_TYPE_PIPE:
CHILD_STDIO_CRT_FLAGS(buffer, i) = FOPEN | FPIPE;
case FILE_TYPE_CHAR:
case FILE_TYPE_REMOTE:
CHILD_STDIO_CRT_FLAGS(buffer, i) = FOPEN | FDEV;
break;
case FILE_TYPE_UNKNOWN:
if (GetLastError != 0) {
uv__set_sys_error(loop, GetLastError());
CloseHandle(child_handle);
goto error;
}
CHILD_STDIO_CRT_FLAGS(buffer, i) = FOPEN | FDEV;
break;
default:
assert(0);
}
CHILD_STDIO_HANDLE(buffer, i) = child_handle;
break;
}
case UV_INHERIT_STREAM: {
/* Use an existing stream as the stdio handle for the child. */
HANDLE stream_handle, child_handle;
unsigned char crt_flags;
uv_stream_t* stream = fdopt.data.stream;
/* Leech the handle out of the stream. */
if (stream->type = UV_TTY) {
stream_handle = ((uv_tty_t*) stream)->handle;
crt_flags = FOPEN | FDEV;
} else if (stream->type == UV_NAMED_PIPE &&
stream->flags & UV_HANDLE_CONNECTED) {
stream_handle = ((uv_pipe_t*) stream)->handle;
crt_flags = FOPEN | FPIPE;
} else {
stream_handle = INVALID_HANDLE_VALUE;
crt_flags = 0;
}
if (stream_handle == NULL ||
stream_handle == INVALID_HANDLE_VALUE) {
/* The handle is already closed, or not yet created, or the */
/* stream type is not supported. */
uv__set_artificial_error(loop, UV_ENOTSUP);
goto error;
}
/* Make an inheritable copy of the handle. */
if (duplicate_handle(loop,
stream_handle,
&child_handle) < 0) {
goto error;
}
CHILD_STDIO_HANDLE(buffer, i) = child_handle;
CHILD_STDIO_CRT_FLAGS(buffer, i) = crt_flags;
}
default:
assert(0);
}
}
*buffer_ptr = buffer;
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);
error:
close_and_free_child_stdio(buffer);
return -1;
}
int uv_spawn(uv_loop_t* loop, uv_process_t* process,
uv_process_options_t options) {
int err = 0, keep_child_stdio_open = 0;
int size, err = 0, keep_child_stdio_open = 0;
wchar_t* path = NULL;
int size, i, overlapped;
DWORD server_access, child_access,
process_flags = CREATE_UNICODE_ENVIRONMENT;
BOOL result;
wchar_t* application_path = NULL, *application = NULL, *arguments = NULL,
*env = NULL, *cwd = NULL;
HANDLE* child_stdio = process->child_stdio;
*env = NULL, *cwd = NULL;
STARTUPINFOW startup;
PROCESS_INFORMATION info;
uv_pipe_t* pipe;
DWORD process_flags;
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_DETACHED |
@ -911,8 +1130,11 @@ int uv_spawn(uv_loop_t* loop, uv_process_t* process,
if (size) {
cwd = (wchar_t*)malloc(size);
if (!cwd) {
uv_fatal_error(ERROR_OUTOFMEMORY, "malloc");
uv__set_artificial_error(loop, UV_ENOMEM);
err = -1;
goto done;
}
GetCurrentDirectoryW(size, cwd);
} else {
uv__set_sys_error(loop, GetLastError());
@ -940,60 +1162,10 @@ int uv_spawn(uv_loop_t* loop, uv_process_t* process,
application_path = application;
}
for (i = 0; i < options.stdio_count || i < 3; i++) {
if (i >= options.stdio_count ||
options.stdio[i].flags == UV_IGNORE) {
child_stdio[i] = INVALID_HANDLE_VALUE;
continue;
}
if (options.stdio[i].flags & UV_INHERIT_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 (init_child_stdio(loop, &options, &process->child_stdio_buffer) < 0) {
err = -1;
goto done;
}
startup.cb = sizeof(startup);
@ -1001,12 +1173,13 @@ int uv_spawn(uv_loop_t* loop, uv_process_t* process,
startup.lpDesktop = NULL;
startup.lpTitle = NULL;
startup.dwFlags = STARTF_USESTDHANDLES;
startup.cbReserved2 = 0;
startup.lpReserved2 = NULL;
startup.hStdInput = child_stdio[0];
startup.hStdOutput = child_stdio[1];
startup.hStdError = child_stdio[2];
startup.cbReserved2 = CHILD_STDIO_CBRESERVED2(process->child_stdio_buffer);
startup.lpReserved2 = CHILD_STDIO_LPRESERVED2(process->child_stdio_buffer);
startup.hStdInput = CHILD_STDIO_HANDLE(process->child_stdio_buffer, 0);
startup.hStdOutput = CHILD_STDIO_HANDLE(process->child_stdio_buffer, 1);
startup.hStdError = CHILD_STDIO_HANDLE(process->child_stdio_buffer, 2);
process_flags = CREATE_UNICODE_ENVIRONMENT;
if (options.flags & UV_PROCESS_DETACHED) {
process_flags |= DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP;
}
@ -1064,19 +1237,19 @@ done:
free(env);
free(path);
/* Under normal circumstances we should close the stdio handles now - */
/* the child now has its own duplicates, or something went horribly wrong. */
/* Under normal circumstances we should close the stdio handles now - the */
/* the child now has its own duplicates, or something went horribly wrong */
/* The only exception is when CreateProcess has failed, then we actually */
/* need to keep the stdio handles to report the error asynchronously. */
if (!keep_child_stdio_open) {
close_child_stdio(process);
if (process->child_stdio_buffer == NULL) {
/* Something went wrong before child stdio was initialized. */
} else if (!keep_child_stdio_open) {
close_and_free_child_stdio(process->child_stdio_buffer);
process->child_stdio_buffer = NULL;
} else {
/* We're keeping the handles open, the thread pool is going to have */
/* it's way with them. But at least make them non-inheritable. */
int i;
for (i = 0; i < ARRAY_SIZE(process->child_stdio); i++) {
SetHandleInformation(child_stdio[i], HANDLE_FLAG_INHERIT, 0);
}
set_child_stdio_noinherit(process->child_stdio_buffer);
}
if (err == 0) {