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; }
static void uv__process_child_init(const uv_process_options_t* options, int stdio_count, int (*pipes)[2], int error_fd) { int close_fd; int use_fd; int fd; if (options->flags & UV_PROCESS_DETACHED) setsid(); /* First duplicate low numbered fds, since it's not safe to duplicate them, * they could get replaced. Example: swapping stdout and stderr; without * this fd 2 (stderr) would be duplicated into fd 1, thus making both * stdout and stderr go to the same fd, which was not the intention. */ for (fd = 0; fd < stdio_count; fd++) { use_fd = pipes[fd][1]; if (use_fd < 0 || use_fd >= fd) continue; pipes[fd][1] = fcntl(use_fd, F_DUPFD, stdio_count); if (pipes[fd][1] == -1) { uv__write_int(error_fd, -errno); _exit(127); } } for (fd = 0; fd < stdio_count; fd++) { close_fd = pipes[fd][0]; use_fd = pipes[fd][1]; if (use_fd < 0) { if (fd >= 3) continue; else { /* redirect stdin, stdout and stderr to /dev/null even if UV_IGNORE is * set */ use_fd = open("/dev/null", fd == 0 ? O_RDONLY : O_RDWR); close_fd = use_fd; if (use_fd == -1) { uv__write_int(error_fd, -errno); _exit(127); } } } if (fd == use_fd) uv__cloexec(use_fd, 0); else fd = dup2(use_fd, fd); if (fd == -1) { uv__write_int(error_fd, -errno); _exit(127); } if (fd <= 2) uv__nonblock(fd, 0); if (close_fd >= stdio_count) uv__close(close_fd); } for (fd = 0; fd < stdio_count; fd++) { use_fd = pipes[fd][1]; if (use_fd >= stdio_count) uv__close(use_fd); } if (options->cwd != NULL && chdir(options->cwd)) { uv__write_int(error_fd, -errno); _exit(127); } if (options->flags & (UV_PROCESS_SETUID | UV_PROCESS_SETGID)) { /* When dropping privileges from root, the `setgroups` call will * remove any extraneous groups. If we don't call this, then * even though our uid has dropped, we may still have groups * that enable us to do super-user things. This will fail if we * aren't root, so don't bother checking the return value, this * is just done as an optimistic privilege dropping function. */ SAVE_ERRNO(setgroups(0, NULL)); } if ((options->flags & UV_PROCESS_SETGID) && setgid(options->gid)) { uv__write_int(error_fd, -errno); _exit(127); } if ((options->flags & UV_PROCESS_SETUID) && setuid(options->uid)) { uv__write_int(error_fd, -errno); _exit(127); } if (options->env != NULL) { environ = options->env; } execvp(options->file, options->args); uv__write_int(error_fd, -errno); _exit(127); }