From e553f96f94da0526cc74234d846ef716772e0fcc Mon Sep 17 00:00:00 2001 From: Ryan Dahl Date: Wed, 31 Aug 2011 11:50:08 -0700 Subject: [PATCH] unix: split out uv_spawn into src/unix/process.c --- config-unix.mk | 7 +- src/unix/core.c | 271 +---------------------------------------- src/unix/internal.h | 22 +++- src/unix/process.c | 287 ++++++++++++++++++++++++++++++++++++++++++++ uv.gyp | 1 + 5 files changed, 313 insertions(+), 275 deletions(-) create mode 100644 src/unix/process.c diff --git a/config-unix.mk b/config-unix.mk index 3b5a161b..2ffd2c52 100644 --- a/config-unix.mk +++ b/config-unix.mk @@ -85,8 +85,8 @@ endif RUNNER_LIBS= RUNNER_SRC=test/runner-unix.c -uv.a: src/unix/core.o src/unix/fs.o src/unix/udp.o src/unix/cares.o src/unix/error.o src/uv-common.o src/uv-platform.o src/unix/ev/ev.o src/unix/uv-eio.o src/unix/eio/eio.o $(CARES_OBJS) - $(AR) rcs uv.a src/unix/core.o src/unix/fs.o src/unix/udp.o src/unix/cares.o src/unix/error.o src/uv-platform.o src/uv-common.o src/unix/uv-eio.o src/unix/ev/ev.o src/unix/eio/eio.o $(CARES_OBJS) +uv.a: src/unix/core.o src/unix/fs.o src/unix/udp.o src/unix/cares.o src/unix/error.o src/unix/process.o src/uv-common.o src/uv-platform.o src/unix/ev/ev.o src/unix/uv-eio.o src/unix/eio/eio.o $(CARES_OBJS) + $(AR) rcs uv.a src/unix/core.o src/unix/fs.o src/unix/udp.o src/unix/cares.o src/unix/error.o src/unix/process.o src/uv-platform.o src/uv-common.o src/unix/uv-eio.o src/unix/ev/ev.o src/unix/eio/eio.o $(CARES_OBJS) src/uv-platform.o: src/unix/$(UV_OS_FILE) include/uv.h include/uv-private/uv-unix.h $(CC) $(CSTDFLAG) $(CPPFLAGS) $(CFLAGS) -c src/unix/$(UV_OS_FILE) -o src/uv-platform.o @@ -106,6 +106,9 @@ src/unix/udp.o: src/unix/udp.c include/uv.h include/uv-private/uv-unix.h src/uni src/unix/error.o: src/unix/error.c include/uv.h include/uv-private/uv-unix.h src/unix/internal.h $(CC) $(CSTDFLAG) $(CPPFLAGS) -Isrc/ $(CFLAGS) -c src/unix/error.c -o src/unix/error.o +src/unix/process.o: src/unix/process.c include/uv.h include/uv-private/uv-unix.h src/unix/internal.h + $(CC) $(CSTDFLAG) $(CPPFLAGS) -Isrc/ $(CFLAGS) -c src/unix/process.c -o src/unix/process.o + src/uv-common.o: src/uv-common.c include/uv.h include/uv-private/uv-unix.h $(CC) $(CSTDFLAG) $(CPPFLAGS) $(CFLAGS) -c src/uv-common.c -o src/uv-common.o diff --git a/src/unix/core.c b/src/unix/core.c index 502ea6e3..1548d988 100644 --- a/src/unix/core.c +++ b/src/unix/core.c @@ -41,7 +41,6 @@ #include #include /* PATH_MAX */ #include /* writev */ -#include #if defined(__linux__) @@ -77,19 +76,10 @@ #include #endif - -# ifdef __APPLE__ -# include -# define environ (*_NSGetEnviron()) -# else -extern char **environ; -# endif - static uv_loop_t default_loop_struct; static uv_loop_t* default_loop_ptr; void uv__next(EV_P_ ev_idle* watcher, int revents); -static int uv__stream_open(uv_stream_t*, int fd, int flags); static void uv__finish_close(uv_handle_t* handle); static int uv_tcp_listen(uv_tcp_t* tcp, int backlog, uv_connection_cb cb); @@ -112,16 +102,6 @@ static int uv__accept(int sockfd, struct sockaddr* saddr, socklen_t len); size_t uv__strlcpy(char* dst, const char* src, size_t size); -/* flags */ -enum { - UV_CLOSING = 0x00000001, /* uv_close() called but not finished. */ - UV_CLOSED = 0x00000002, /* close(2) finished. */ - UV_READING = 0x00000004, /* uv_read_start() called. */ - UV_SHUTTING = 0x00000008, /* uv_shutdown() called but not complete. */ - UV_SHUT = 0x00000010, /* Write side closed. */ - UV_READABLE = 0x00000020, /* The stream is readable */ - UV_WRITABLE = 0x00000040 /* The stream is writable */ -}; void uv_init() { @@ -357,7 +337,7 @@ int uv_tcp_bind6(uv_tcp_t* tcp, struct sockaddr_in6 addr) { } -static int uv__stream_open(uv_stream_t* stream, int fd, int flags) { +int uv__stream_open(uv_stream_t* stream, int fd, int flags) { socklen_t yes; assert(fd >= 0); @@ -2008,252 +1988,3 @@ uv_stream_t* uv_std_handle(uv_loop_t* loop, uv_std_type type) { return NULL; } - -static void uv__chld(EV_P_ ev_child* watcher, int revents) { - int status = watcher->rstatus; - int exit_status = 0; - int term_signal = 0; - uv_process_t *process = watcher->data; - - assert(&process->child_watcher == watcher); - assert(revents & EV_CHILD); - - ev_child_stop(EV_A_ &process->child_watcher); - - if (WIFEXITED(status)) { - exit_status = WEXITSTATUS(status); - } - - if (WIFSIGNALED(status)) { - term_signal = WTERMSIG(status); - } - - if (process->exit_cb) { - process->exit_cb(process, exit_status, term_signal); - } -} - -#ifndef SPAWN_WAIT_EXEC -# define SPAWN_WAIT_EXEC 1 -#endif - -int uv_spawn(uv_loop_t* loop, uv_process_t* process, - uv_process_options_t options) { - /* - * Save environ in the case that we get it clobbered - * 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 }; -#if SPAWN_WAIT_EXEC - int signal_pipe[2] = { -1, -1 }; - struct pollfd pfd; -#endif - int status; - pid_t pid; - - uv__handle_init(loop, (uv_handle_t*)process, UV_PROCESS); - loop->counters.process_init++; - - process->exit_cb = options.exit_cb; - - if (options.stdin_stream) { - if (options.stdin_stream->type != UV_NAMED_PIPE) { - errno = EINVAL; - goto error; - } - - if (pipe(stdin_pipe) < 0) { - goto error; - } - uv__cloexec(stdin_pipe[0], 1); - uv__cloexec(stdin_pipe[1], 1); - } - - if (options.stdout_stream) { - if (options.stdout_stream->type != UV_NAMED_PIPE) { - errno = EINVAL; - goto error; - } - - if (pipe(stdout_pipe) < 0) { - goto error; - } - uv__cloexec(stdout_pipe[0], 1); - uv__cloexec(stdout_pipe[1], 1); - } - - if (options.stderr_stream) { - if (options.stderr_stream->type != UV_NAMED_PIPE) { - errno = EINVAL; - goto error; - } - - if (pipe(stderr_pipe) < 0) { - goto error; - } - uv__cloexec(stderr_pipe[0], 1); - uv__cloexec(stderr_pipe[1], 1); - } - - /* This pipe is used by the parent to wait until - * the child has called `execve()`. We need this - * to avoid the following race condition: - * - * if ((pid = fork()) > 0) { - * kill(pid, SIGTERM); - * } - * else if (pid == 0) { - * execve("/bin/cat", argp, envp); - * } - * - * The parent sends a signal immediately after forking. - * Since the child may not have called `execve()` yet, - * there is no telling what process receives the signal, - * our fork or /bin/cat. - * - * To avoid ambiguity, we create a pipe with both ends - * marked close-on-exec. Then, after the call to `fork()`, - * the parent polls the read end until it sees POLLHUP. - */ -#if SPAWN_WAIT_EXEC -# ifdef HAVE_PIPE2 - if (pipe2(signal_pipe, O_CLOEXEC | O_NONBLOCK) < 0) { - goto error; - } -# else - if (pipe(signal_pipe) < 0) { - goto error; - } - uv__cloexec(signal_pipe[0], 1); - uv__cloexec(signal_pipe[1], 1); - uv__nonblock(signal_pipe[0], 1); - uv__nonblock(signal_pipe[1], 1); -# endif -#endif - - pid = fork(); - - if (pid == -1) { -#if SPAWN_WAIT_EXEC - uv__close(signal_pipe[0]); - uv__close(signal_pipe[1]); -#endif - environ = save_our_env; - goto error; - } - - if (pid == 0) { - if (stdin_pipe[0] >= 0) { - uv__close(stdin_pipe[1]); - dup2(stdin_pipe[0], STDIN_FILENO); - } - - if (stdout_pipe[1] >= 0) { - uv__close(stdout_pipe[0]); - dup2(stdout_pipe[1], STDOUT_FILENO); - } - - if (stderr_pipe[1] >= 0) { - uv__close(stderr_pipe[0]); - dup2(stderr_pipe[1], STDERR_FILENO); - } - - if (options.cwd && chdir(options.cwd)) { - perror("chdir()"); - _exit(127); - } - - environ = options.env; - - execvp(options.file, options.args); - perror("execvp()"); - _exit(127); - /* Execution never reaches here. */ - } - - /* Parent. */ - - /* Restore environment. */ - environ = save_our_env; - -#if SPAWN_WAIT_EXEC - /* POLLHUP signals child has exited or execve()'d. */ - uv__close(signal_pipe[1]); - do { - pfd.fd = signal_pipe[0]; - pfd.events = POLLIN|POLLHUP; - pfd.revents = 0; - errno = 0, status = poll(&pfd, 1, -1); - } - while (status == -1 && (errno == EINTR || errno == ENOMEM)); - - uv__close(signal_pipe[0]); - uv__close(signal_pipe[1]); - - assert((status == 1) - && "poll() on pipe read end failed"); - assert((pfd.revents & POLLHUP) == POLLHUP - && "no POLLHUP on pipe read end"); -#endif - - process->pid = pid; - - ev_child_init(&process->child_watcher, uv__chld, pid, 0); - 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); - uv__close(stdin_pipe[0]); - uv__nonblock(stdin_pipe[1], 1); - uv__stream_open((uv_stream_t*)options.stdin_stream, stdin_pipe[1], - UV_WRITABLE); - } - - if (stdout_pipe[0] >= 0) { - assert(options.stdout_stream); - assert(stdout_pipe[1] >= 0); - uv__close(stdout_pipe[1]); - uv__nonblock(stdout_pipe[0], 1); - uv__stream_open((uv_stream_t*)options.stdout_stream, stdout_pipe[0], - UV_READABLE); - } - - if (stderr_pipe[0] >= 0) { - assert(options.stderr_stream); - assert(stderr_pipe[1] >= 0); - uv__close(stderr_pipe[1]); - uv__nonblock(stderr_pipe[0], 1); - uv__stream_open((uv_stream_t*)options.stderr_stream, stderr_pipe[0], - UV_READABLE); - } - - return 0; - -error: - uv_err_new(process->loop, errno); - uv__close(stdin_pipe[0]); - uv__close(stdin_pipe[1]); - uv__close(stdout_pipe[0]); - uv__close(stdout_pipe[1]); - uv__close(stderr_pipe[0]); - uv__close(stderr_pipe[1]); - return -1; -} - - -int uv_process_kill(uv_process_t* process, int signum) { - int r = kill(process->pid, signum); - - if (r) { - uv_err_new(process->loop, errno); - return -1; - } else { - return 0; - } -} - diff --git a/src/unix/internal.h b/src/unix/internal.h index a8806c60..cba5f09f 100644 --- a/src/unix/internal.h +++ b/src/unix/internal.h @@ -25,18 +25,34 @@ #include "uv-common.h" #include "uv-eio.h" +/* flags */ +enum { + UV_CLOSING = 0x00000001, /* uv_close() called but not finished. */ + UV_CLOSED = 0x00000002, /* close(2) finished. */ + UV_READING = 0x00000004, /* uv_read_start() called. */ + UV_SHUTTING = 0x00000008, /* uv_shutdown() called but not complete. */ + UV_SHUT = 0x00000010, /* Write side closed. */ + UV_READABLE = 0x00000020, /* The stream is readable */ + UV_WRITABLE = 0x00000040 /* The stream is writable */ +}; + int uv__close(int fd); void uv__req_init(uv_req_t*); void uv__handle_init(uv_loop_t* loop, uv_handle_t* handle, uv_handle_type type); -uv_err_t uv_err_new(uv_loop_t* loop, int sys_error); -uv_err_t uv_err_new_artificial(uv_loop_t* loop, int code); -void uv_fatal_error(const int errorno, const char* syscall); int uv__nonblock(int fd, int set) __attribute__((unused)); int uv__cloexec(int fd, int set) __attribute__((unused)); int uv__socket(int domain, int type, int protocol); +/* error */ +uv_err_t uv_err_new(uv_loop_t* loop, int sys_error); +uv_err_t uv_err_new_artificial(uv_loop_t* loop, int code); +void uv_fatal_error(const int errorno, const char* syscall); + +/* stream */ +int uv__stream_open(uv_stream_t*, int fd, int flags); + /* udp */ void uv__udp_destroy(uv_udp_t* handle); void uv__udp_watcher_stop(uv_udp_t* handle, ev_io* w); diff --git a/src/unix/process.c b/src/unix/process.c new file mode 100644 index 00000000..349e0da6 --- /dev/null +++ b/src/unix/process.c @@ -0,0 +1,287 @@ + +/* Copyright Joyent, Inc. and other Node contributors. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include "uv.h" +#include "internal.h" + +#include +#include +#include +#include +#include +#include + +#ifdef __APPLE__ +# include +# define environ (*_NSGetEnviron()) +#else +extern char **environ; +#endif + + +static void uv__chld(EV_P_ ev_child* watcher, int revents) { + int status = watcher->rstatus; + int exit_status = 0; + int term_signal = 0; + uv_process_t *process = watcher->data; + + assert(&process->child_watcher == watcher); + assert(revents & EV_CHILD); + + ev_child_stop(EV_A_ &process->child_watcher); + + if (WIFEXITED(status)) { + exit_status = WEXITSTATUS(status); + } + + if (WIFSIGNALED(status)) { + term_signal = WTERMSIG(status); + } + + if (process->exit_cb) { + process->exit_cb(process, exit_status, term_signal); + } +} + +#ifndef SPAWN_WAIT_EXEC +# define SPAWN_WAIT_EXEC 1 +#endif + +int uv_spawn(uv_loop_t* loop, uv_process_t* process, + uv_process_options_t options) { + /* + * Save environ in the case that we get it clobbered + * 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 }; +#if SPAWN_WAIT_EXEC + int signal_pipe[2] = { -1, -1 }; + struct pollfd pfd; +#endif + int status; + pid_t pid; + + uv__handle_init(loop, (uv_handle_t*)process, UV_PROCESS); + loop->counters.process_init++; + + process->exit_cb = options.exit_cb; + + if (options.stdin_stream) { + if (options.stdin_stream->type != UV_NAMED_PIPE) { + errno = EINVAL; + goto error; + } + + if (pipe(stdin_pipe) < 0) { + goto error; + } + uv__cloexec(stdin_pipe[0], 1); + uv__cloexec(stdin_pipe[1], 1); + } + + if (options.stdout_stream) { + if (options.stdout_stream->type != UV_NAMED_PIPE) { + errno = EINVAL; + goto error; + } + + if (pipe(stdout_pipe) < 0) { + goto error; + } + uv__cloexec(stdout_pipe[0], 1); + uv__cloexec(stdout_pipe[1], 1); + } + + if (options.stderr_stream) { + if (options.stderr_stream->type != UV_NAMED_PIPE) { + errno = EINVAL; + goto error; + } + + if (pipe(stderr_pipe) < 0) { + goto error; + } + uv__cloexec(stderr_pipe[0], 1); + uv__cloexec(stderr_pipe[1], 1); + } + + /* This pipe is used by the parent to wait until + * the child has called `execve()`. We need this + * to avoid the following race condition: + * + * if ((pid = fork()) > 0) { + * kill(pid, SIGTERM); + * } + * else if (pid == 0) { + * execve("/bin/cat", argp, envp); + * } + * + * The parent sends a signal immediately after forking. + * Since the child may not have called `execve()` yet, + * there is no telling what process receives the signal, + * our fork or /bin/cat. + * + * To avoid ambiguity, we create a pipe with both ends + * marked close-on-exec. Then, after the call to `fork()`, + * the parent polls the read end until it sees POLLHUP. + */ +#if SPAWN_WAIT_EXEC +# ifdef HAVE_PIPE2 + if (pipe2(signal_pipe, O_CLOEXEC | O_NONBLOCK) < 0) { + goto error; + } +# else + if (pipe(signal_pipe) < 0) { + goto error; + } + uv__cloexec(signal_pipe[0], 1); + uv__cloexec(signal_pipe[1], 1); + uv__nonblock(signal_pipe[0], 1); + uv__nonblock(signal_pipe[1], 1); +# endif +#endif + + pid = fork(); + + if (pid == -1) { +#if SPAWN_WAIT_EXEC + uv__close(signal_pipe[0]); + uv__close(signal_pipe[1]); +#endif + environ = save_our_env; + goto error; + } + + if (pid == 0) { + if (stdin_pipe[0] >= 0) { + uv__close(stdin_pipe[1]); + dup2(stdin_pipe[0], STDIN_FILENO); + } + + if (stdout_pipe[1] >= 0) { + uv__close(stdout_pipe[0]); + dup2(stdout_pipe[1], STDOUT_FILENO); + } + + if (stderr_pipe[1] >= 0) { + uv__close(stderr_pipe[0]); + dup2(stderr_pipe[1], STDERR_FILENO); + } + + if (options.cwd && chdir(options.cwd)) { + perror("chdir()"); + _exit(127); + } + + environ = options.env; + + execvp(options.file, options.args); + perror("execvp()"); + _exit(127); + /* Execution never reaches here. */ + } + + /* Parent. */ + + /* Restore environment. */ + environ = save_our_env; + +#if SPAWN_WAIT_EXEC + /* POLLHUP signals child has exited or execve()'d. */ + uv__close(signal_pipe[1]); + do { + pfd.fd = signal_pipe[0]; + pfd.events = POLLIN|POLLHUP; + pfd.revents = 0; + errno = 0, status = poll(&pfd, 1, -1); + } + while (status == -1 && (errno == EINTR || errno == ENOMEM)); + + uv__close(signal_pipe[0]); + uv__close(signal_pipe[1]); + + assert((status == 1) + && "poll() on pipe read end failed"); + assert((pfd.revents & POLLHUP) == POLLHUP + && "no POLLHUP on pipe read end"); +#endif + + process->pid = pid; + + ev_child_init(&process->child_watcher, uv__chld, pid, 0); + 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); + uv__close(stdin_pipe[0]); + uv__nonblock(stdin_pipe[1], 1); + uv__stream_open((uv_stream_t*)options.stdin_stream, stdin_pipe[1], + UV_WRITABLE); + } + + if (stdout_pipe[0] >= 0) { + assert(options.stdout_stream); + assert(stdout_pipe[1] >= 0); + uv__close(stdout_pipe[1]); + uv__nonblock(stdout_pipe[0], 1); + uv__stream_open((uv_stream_t*)options.stdout_stream, stdout_pipe[0], + UV_READABLE); + } + + if (stderr_pipe[0] >= 0) { + assert(options.stderr_stream); + assert(stderr_pipe[1] >= 0); + uv__close(stderr_pipe[1]); + uv__nonblock(stderr_pipe[0], 1); + uv__stream_open((uv_stream_t*)options.stderr_stream, stderr_pipe[0], + UV_READABLE); + } + + return 0; + +error: + uv_err_new(process->loop, errno); + uv__close(stdin_pipe[0]); + uv__close(stdin_pipe[1]); + uv__close(stdout_pipe[0]); + uv__close(stdout_pipe[1]); + uv__close(stderr_pipe[0]); + uv__close(stderr_pipe[1]); + return -1; +} + + +int uv_process_kill(uv_process_t* process, int signum) { + int r = kill(process->pid, signum); + + if (r) { + uv_err_new(process->loop, errno); + return -1; + } else { + return 0; + } +} diff --git a/uv.gyp b/uv.gyp index 1e530c76..ee4af0b7 100644 --- a/uv.gyp +++ b/uv.gyp @@ -148,6 +148,7 @@ 'src/unix/udp.c', 'src/unix/cares.c', 'src/unix/error.c', + 'src/unix/process.c', 'src/unix/internal.h', 'src/unix/eio/ecb.h', 'src/unix/eio/eio.c',