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

Commit

Permalink
Browse files Browse the repository at this point in the history
unix: 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. It also makes it possible to set more than just stdin,
stdout and stderr.
  • Loading branch information
indutny authored and piscisaureus committed May 28, 2012
1 parent 5a34f19 commit c0081f0
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 85 deletions.
204 changes: 122 additions & 82 deletions src/unix/process.c
Expand Up @@ -138,19 +138,79 @@ int uv__make_pipe(int fds[2], int flags) {
* Used for initializing stdio streams like options.stdin_stream. Returns
* zero on success.
*/
static int uv__process_init_pipe(uv_pipe_t* handle, int fds[2], int flags) {
if (handle->type != UV_NAMED_PIPE) {
errno = EINVAL;
static int uv__process_init_stdio(uv_stdio_container_t* container, int fds[2],
int writable) {
if (container->flags == UV_IGNORE) {
return 0;
} else if (container->flags & UV_CREATE_PIPE) {
assert(container->data.stream != NULL);

if (container->data.stream->type != UV_NAMED_PIPE) {
errno = EINVAL;
return -1;
}

return uv__make_socketpair(fds, 0);
} else if (container->flags & UV_RAW_FD) {
if (container->data.fd == -1) {
errno = EINVAL;
return -1;
}

if (writable) {
fds[1] = container->data.fd;
} else {
fds[0] = container->data.fd;
}

return 0;
} else {
assert(0 && "Unexpected flags");
return -1;
}
}


if (handle->ipc)
return uv__make_socketpair(fds, flags);
else
return uv__make_pipe(fds, flags);
static int uv__process_stdio_flags(uv_stdio_container_t* container,
int writable) {
if (container->data.stream->type == UV_NAMED_PIPE &&
((uv_pipe_t*)container->data.stream)->ipc) {
return UV_STREAM_READABLE | UV_STREAM_WRITABLE;
} else if (writable) {
return UV_STREAM_WRITABLE;
} else {
return UV_STREAM_READABLE;
}
}


static int uv__process_open_stream(uv_stdio_container_t* container, int fds[2],
int writable) {
int fd = fds[writable ? 1 : 0];
int child_fd = fds[writable ? 0 : 1];
int flags;

/* No need to create stream */
if (!(container->flags & UV_CREATE_PIPE) || fd < 0) {
return 0;
}

assert(child_fd >= 0);
close(child_fd);

uv__nonblock(fd, 1);
flags = uv__process_stdio_flags(container, writable);

return uv__stream_open((uv_stream_t*)container->data.stream, fd, flags);
}


static void uv__process_close_stream(uv_stdio_container_t* container) {
if (!(container->flags & UV_CREATE_PIPE)) return;

uv__stream_close((uv_stream_t*)container->data.stream);
}

#ifndef SPAWN_WAIT_EXEC
# define SPAWN_WAIT_EXEC 1
#endif
Expand All @@ -162,16 +222,21 @@ int uv_spawn(uv_loop_t* loop, uv_process_t* process,
* by the child process.
*/
char** save_our_env = environ;
int stdin_pipe[2] = { -1, -1 };
int stdout_pipe[2] = { -1, -1 };
int stderr_pipe[2] = { -1, -1 };

int* pipes = malloc(2 * options.stdio_count * sizeof(int));

#if SPAWN_WAIT_EXEC
int signal_pipe[2] = { -1, -1 };
struct pollfd pfd;
#endif
int status;
pid_t pid;
int flags;
int i;

if (pipes == NULL) {
errno = ENOMEM;
goto error;
}

assert(options.file != NULL);
assert(!(options.flags & ~(UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS |
Expand All @@ -185,19 +250,17 @@ int uv_spawn(uv_loop_t* loop, uv_process_t* process,

process->exit_cb = options.exit_cb;

if (options.stdin_stream &&
uv__process_init_pipe(options.stdin_stream, stdin_pipe, 0)) {
goto error;
}

if (options.stdout_stream &&
uv__process_init_pipe(options.stdout_stream, stdout_pipe, 0)) {
goto error;
/* Init pipe pairs */
for (i = 0; i < options.stdio_count; i++) {
pipes[i * 2] = -1;
pipes[i * 2 + 1] = -1;
}

if (options.stderr_stream &&
uv__process_init_pipe(options.stderr_stream, stderr_pipe, 0)) {
goto error;
/* Create socketpairs/pipes, or use raw fd */
for (i = 0; i < options.stdio_count; i++) {
if (uv__process_init_stdio(&options.stdio[i], pipes + i * 2, i != 0)) {
goto error;
}
}

/* This pipe is used by the parent to wait until
Expand Down Expand Up @@ -237,31 +300,25 @@ int uv_spawn(uv_loop_t* loop, uv_process_t* process,
}

if (pid == 0) {
if (stdin_pipe[0] >= 0) {
close(stdin_pipe[1]);
dup2(stdin_pipe[0], STDIN_FILENO);
} else {
/* Reset flags that might be set by Node */
uv__cloexec(STDIN_FILENO, 0);
uv__nonblock(STDIN_FILENO, 0);
}

if (stdout_pipe[1] >= 0) {
close(stdout_pipe[0]);
dup2(stdout_pipe[1], STDOUT_FILENO);
} else {
/* Reset flags that might be set by Node */
uv__cloexec(STDOUT_FILENO, 0);
uv__nonblock(STDOUT_FILENO, 0);
}

if (stderr_pipe[1] >= 0) {
close(stderr_pipe[0]);
dup2(stderr_pipe[1], STDERR_FILENO);
} else {
/* Reset flags that might be set by Node */
uv__cloexec(STDERR_FILENO, 0);
uv__nonblock(STDERR_FILENO, 0);
/* Child */

/* Dup fds */
for (i = 0; i < options.stdio_count; i++) {
/*
* stdin has swapped ends of pipe
* (it's the only one readable stream)
*/
int close_fd = i == 0 ? pipes[i * 2 + 1] : pipes[i * 2];
int use_fd = i == 0 ? pipes[i * 2] : pipes[i * 2 + 1];

if (use_fd >= 0) {
close(close_fd);
dup2(use_fd, i);
} else {
/* Reset flags that might be set by Node */
uv__cloexec(i, 0);
uv__nonblock(i, 0);
}
}

if (options.cwd && chdir(options.cwd)) {
Expand Down Expand Up @@ -313,49 +370,32 @@ int uv_spawn(uv_loop_t* loop, uv_process_t* process,
ev_child_start(process->loop->ev, &process->child_watcher);
process->child_watcher.data = process;

if (stdin_pipe[1] >= 0) {
assert(options.stdin_stream);
assert(stdin_pipe[0] >= 0);
close(stdin_pipe[0]);
uv__nonblock(stdin_pipe[1], 1);
flags = UV_STREAM_WRITABLE |
(options.stdin_stream->ipc ? UV_STREAM_READABLE : 0);
uv__stream_open((uv_stream_t*)options.stdin_stream, stdin_pipe[1],
flags);
}
for (i = 0; i < options.stdio_count; i++) {
if (uv__process_open_stream(&options.stdio[i], pipes + i * 2, i == 0)) {
int j;
/* Close all opened streams */
for (j = 0; j < i; j++) {
uv__process_close_stream(&options.stdio[j]);
}

if (stdout_pipe[0] >= 0) {
assert(options.stdout_stream);
assert(stdout_pipe[1] >= 0);
close(stdout_pipe[1]);
uv__nonblock(stdout_pipe[0], 1);
flags = UV_STREAM_READABLE |
(options.stdout_stream->ipc ? UV_STREAM_WRITABLE : 0);
uv__stream_open((uv_stream_t*)options.stdout_stream, stdout_pipe[0],
flags);
goto error;
}
}

if (stderr_pipe[0] >= 0) {
assert(options.stderr_stream);
assert(stderr_pipe[1] >= 0);
close(stderr_pipe[1]);
uv__nonblock(stderr_pipe[0], 1);
flags = UV_STREAM_READABLE |
(options.stderr_stream->ipc ? UV_STREAM_WRITABLE : 0);
uv__stream_open((uv_stream_t*)options.stderr_stream, stderr_pipe[0],
flags);
}
free(pipes);

return 0;

error:
uv__set_sys_error(process->loop, errno);
close(stdin_pipe[0]);
close(stdin_pipe[1]);
close(stdout_pipe[0]);
close(stdout_pipe[1]);
close(stderr_pipe[0]);
close(stderr_pipe[1]);

for (i = 0; i < options.stdio_count; i++) {
close(pipes[i * 2]);
close(pipes[i * 2 + 1]);
}

free(pipes);

return -1;
}

Expand Down
2 changes: 1 addition & 1 deletion test/benchmark-spawn.c
Expand Up @@ -159,4 +159,4 @@ BENCHMARK_IMPL(spawn) {
(double) N / (double) (end_time - start_time) * 1000.0);

return 0;
}
}
2 changes: 1 addition & 1 deletion test/test-ipc.c
Expand Up @@ -617,4 +617,4 @@ int ipc_helper_tcp_connection() {
ASSERT(close_cb_called == 4);

return 0;
}
}
2 changes: 1 addition & 1 deletion test/test-spawn.c
Expand Up @@ -759,4 +759,4 @@ TEST_IMPL(spawn_setgid_fails) {

return 0;
}
#endif
#endif

2 comments on commit c0081f0

@glasser
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After this change, libuv always seems to use socketpair(2) instead of pipe2(2) to create the "pipe"s to communicate with child processes (eg, in Node child_process).

What was the reason for this choice?

Specifically, this breaks the ability to open "/dev/stdin" (etc) or "/proc/self/fd/0" in the child process, since these don't seem to work on socketpair-created fds.

@bnoordhuis
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the record, I've answered Dave's question here: http://groups.google.com/group/nodejs/browse_thread/thread/4b134a2dc95b3399

Please sign in to comment.