/* * 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; return -1; } if (handle->ipc) return uv__make_socketpair(fds, flags); else return uv__make_pipe(fds, flags); }
/* * Used for initializing stdio streams like options.stdin_stream. Returns * zero on success. */ static int uv__process_init_stdio(uv_stdio_container_t* container, int fds[2], int writable) { int fd = -1; switch (container->flags & (UV_IGNORE | UV_CREATE_PIPE | UV_INHERIT_FD | UV_INHERIT_STREAM | UV_NATIVE_PIPE)) { case UV_IGNORE: return 0; case 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); case UV_NATIVE_PIPE: assert(container->data.stream != NULL); if(uv__make_pipe(*(int**)container->data.pipe, 0) == -1) return -1; if (writable) fds[1] = container->data.pipe->write; else fds[0] = container->data.pipe->read; return 0; case UV_INHERIT_FD: case UV_INHERIT_STREAM: if (container->flags & UV_INHERIT_FD) { fd = container->data.fd; } else { fd = container->data.stream->fd; } if (fd == -1) { errno = EINVAL; return -1; } fds[writable ? 1 : 0] = fd; return 0; default: assert(0 && "Unexpected flags"); return -1; } }
int uv__async_start(uv_loop_t* loop, struct uv__async* wa, uv__async_cb cb) { int pipefd[2]; int err; if (wa->io_watcher.fd != -1) return 0; err = uv__async_eventfd(); if (err >= 0) { pipefd[0] = err; pipefd[1] = -1; } else if (err == -ENOSYS) { err = uv__make_pipe(pipefd, UV__F_NONBLOCK); #if defined(__linux__) /* Save a file descriptor by opening one of the pipe descriptors as * read/write through the procfs. That file descriptor can then * function as both ends of the pipe. */ if (err == 0) { char buf[32]; int fd; snprintf(buf, sizeof(buf), "/proc/self/fd/%d", pipefd[0]); fd = uv__open_cloexec(buf, O_RDWR); if (fd >= 0) { uv__close(pipefd[0]); uv__close(pipefd[1]); pipefd[0] = fd; pipefd[1] = fd; } } #endif } if (err < 0) return err; uv__io_init(&wa->io_watcher, uv__async_io, pipefd[0]); uv__io_start(loop, &wa->io_watcher, POLLIN); wa->wfd = pipefd[1]; wa->cb = cb; return 0; }
int uv_spawn(uv_loop_t* loop, uv_process_t* process, const uv_process_options_t* options) { int signal_pipe[2] = { -1, -1 }; int (*pipes)[2]; int stdio_count; QUEUE* q; ssize_t r; pid_t pid; int err; int exec_errorno; int i; assert(options->file != NULL); assert(!(options->flags & ~(UV_PROCESS_DETACHED | UV_PROCESS_SETGID | UV_PROCESS_SETUID | UV_PROCESS_WINDOWS_HIDE | UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS))); uv__handle_init(loop, (uv_handle_t*)process, UV_PROCESS); QUEUE_INIT(&process->queue); stdio_count = options->stdio_count; if (stdio_count < 3) stdio_count = 3; err = -ENOMEM; pipes = malloc(stdio_count * sizeof(*pipes)); if (pipes == NULL) goto error; for (i = 0; i < stdio_count; i++) { pipes[i][0] = -1; pipes[i][1] = -1; } for (i = 0; i < options->stdio_count; i++) { err = uv__process_init_stdio(options->stdio + i, pipes[i]); if (err) goto error; } /* 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 EOFs or errors with EPIPE. */ err = uv__make_pipe(signal_pipe, 0); if (err) goto error; uv_signal_start(&loop->child_watcher, uv__chld, SIGCHLD); /* Acquire write lock to prevent opening new fds in worker threads */ uv_rwlock_wrlock(&loop->cloexec_lock); pid = fork(); if (pid == -1) { err = -errno; uv_rwlock_wrunlock(&loop->cloexec_lock); uv__close(signal_pipe[0]); uv__close(signal_pipe[1]); goto error; } if (pid == 0) { uv__process_child_init(options, stdio_count, pipes, signal_pipe[1]); abort(); } /* Release lock in parent process */ uv_rwlock_wrunlock(&loop->cloexec_lock); uv__close(signal_pipe[1]); process->status = 0; exec_errorno = 0; do r = read(signal_pipe[0], &exec_errorno, sizeof(exec_errorno)); while (r == -1 && errno == EINTR); if (r == 0) ; /* okay, EOF */ else if (r == sizeof(exec_errorno)) ; /* okay, read errorno */ else if (r == -1 && errno == EPIPE) ; /* okay, got EPIPE */ else abort(); uv__close(signal_pipe[0]); for (i = 0; i < options->stdio_count; i++) { err = uv__process_open_stream(options->stdio + i, pipes[i], i == 0); if (err == 0) continue; while (i--) uv__process_close_stream(options->stdio + i); goto error; } /* Only activate this handle if exec() happened successfully */ if (exec_errorno == 0) { q = uv__process_queue(loop, pid); QUEUE_INSERT_TAIL(q, &process->queue); uv__handle_start(process); } process->pid = pid; process->exit_cb = options->exit_cb; free(pipes); return exec_errorno; error: if (pipes != NULL) { for (i = 0; i < stdio_count; i++) { if (i < options->stdio_count) if (options->stdio[i].flags & (UV_INHERIT_FD | UV_INHERIT_STREAM)) continue; if (pipes[i][0] != -1) close(pipes[i][0]); if (pipes[i][1] != -1) close(pipes[i][1]); } free(pipes); } return err; }
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; int flags; uv__handle_init(loop, (uv_handle_t*)process, UV_PROCESS); loop->counters.process_init++; 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; } if (options.stderr_stream && uv__process_init_pipe(options.stderr_stream, stderr_pipe, 0)) { goto error; } /* 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 if (uv__make_pipe(signal_pipe, UV__F_NONBLOCK)) goto error; #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); } else { /* Reset flags that might be set by Node */ uv__cloexec(STDIN_FILENO, 0); uv__nonblock(STDIN_FILENO, 0); } if (stdout_pipe[1] >= 0) { uv__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) { uv__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); } 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)); assert((status == 1) && "poll() on pipe read end failed"); uv__close(signal_pipe[0]); #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); flags = UV_WRITABLE | (options.stdin_stream->ipc ? UV_READABLE : 0); uv__stream_open((uv_stream_t*)options.stdin_stream, stdin_pipe[1], flags); } 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); flags = UV_READABLE | (options.stdout_stream->ipc ? UV_WRITABLE : 0); uv__stream_open((uv_stream_t*)options.stdout_stream, stdout_pipe[0], flags); } 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); flags = UV_READABLE | (options.stderr_stream->ipc ? UV_WRITABLE : 0); uv__stream_open((uv_stream_t*)options.stderr_stream, stderr_pipe[0], flags); } return 0; error: uv__set_sys_error(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_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* 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 i; if (pipes == NULL) { errno = ENOMEM; goto error; } assert(options.file != NULL); assert(!(options.flags & ~(UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS | UV_PROCESS_DETACHED | UV_PROCESS_SETGID | UV_PROCESS_SETUID))); uv__handle_init(loop, (uv_handle_t*)process, UV_PROCESS); loop->counters.process_init++; uv__handle_start(process); process->exit_cb = options.exit_cb; /* Init pipe pairs */ for (i = 0; i < options.stdio_count; i++) { pipes[i * 2] = -1; pipes[i * 2 + 1] = -1; } /* 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 * 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 if (uv__make_pipe(signal_pipe, UV__F_NONBLOCK)) goto error; #endif pid = fork(); if (pid == -1) { #if SPAWN_WAIT_EXEC close(signal_pipe[0]); close(signal_pipe[1]); #endif environ = save_our_env; goto error; } if (pid == 0) { /* Child */ if (options.flags & UV_PROCESS_DETACHED) { setsid(); } /* 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)) { perror("chdir()"); _exit(127); } if ((options.flags & UV_PROCESS_SETGID) && setgid(options.gid)) { perror("setgid()"); _exit(127); } if ((options.flags & UV_PROCESS_SETUID) && setuid(options.uid)) { perror("setuid()"); _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. */ 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)); assert((status == 1) && "poll() on pipe read end failed"); close(signal_pipe[0]); #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; 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]); } goto error; } } free(pipes); return 0; error: uv__set_sys_error(process->loop, errno); for (i = 0; i < options.stdio_count; i++) { close(pipes[i * 2]); close(pipes[i * 2 + 1]); } free(pipes); return -1; }