509 lines
11 KiB
C
509 lines
11 KiB
C
/* 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 <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
#include <unistd.h>
|
|
#include <fcntl.h>
|
|
#include <poll.h>
|
|
|
|
#ifdef __APPLE__
|
|
# include <TargetConditionals.h>
|
|
#endif
|
|
|
|
#if defined(__APPLE__) && !TARGET_OS_IPHONE
|
|
# include <crt_externs.h>
|
|
# define environ (*_NSGetEnviron())
|
|
#else
|
|
extern char **environ;
|
|
#endif
|
|
|
|
|
|
static ngx_queue_t* uv__process_queue(uv_loop_t* loop, int pid) {
|
|
assert(pid > 0);
|
|
return loop->process_handles + pid % ARRAY_SIZE(loop->process_handles);
|
|
}
|
|
|
|
|
|
static uv_process_t* uv__process_find(uv_loop_t* loop, int pid) {
|
|
uv_process_t* handle;
|
|
ngx_queue_t* h;
|
|
ngx_queue_t* q;
|
|
|
|
h = uv__process_queue(loop, pid);
|
|
|
|
ngx_queue_foreach(q, h) {
|
|
handle = ngx_queue_data(q, uv_process_t, queue);
|
|
if (handle->pid == pid) return handle;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static void uv__chld(uv_signal_t* handle, int signum) {
|
|
uv_process_t* process;
|
|
int exit_status;
|
|
int term_signal;
|
|
int status;
|
|
pid_t pid;
|
|
|
|
assert(signum == SIGCHLD);
|
|
|
|
for (;;) {
|
|
pid = waitpid(-1, &status, WNOHANG);
|
|
|
|
if (pid == 0)
|
|
return;
|
|
|
|
if (pid == -1) {
|
|
if (errno == ECHILD)
|
|
return; /* XXX stop signal watcher? */
|
|
else
|
|
abort();
|
|
}
|
|
|
|
process = uv__process_find(handle->loop, pid);
|
|
if (process == NULL)
|
|
continue; /* XXX bug? abort? */
|
|
|
|
if (process->exit_cb == NULL)
|
|
continue;
|
|
|
|
exit_status = 0;
|
|
term_signal = 0;
|
|
|
|
if (WIFEXITED(status))
|
|
exit_status = WEXITSTATUS(status);
|
|
|
|
if (WIFSIGNALED(status))
|
|
term_signal = WTERMSIG(status);
|
|
|
|
if (process->errorno)
|
|
uv__set_sys_error(process->loop, process->errorno);
|
|
|
|
process->exit_cb(process, exit_status, term_signal);
|
|
}
|
|
}
|
|
|
|
|
|
int uv__make_socketpair(int fds[2], int flags) {
|
|
#if __linux__
|
|
if (socketpair(AF_UNIX, SOCK_STREAM | UV__SOCK_CLOEXEC | flags, 0, fds) == 0)
|
|
return 0;
|
|
|
|
/* Retry on EINVAL, it means SOCK_CLOEXEC is not supported.
|
|
* Anything else is a genuine error.
|
|
*/
|
|
if (errno != EINVAL)
|
|
return -1;
|
|
#endif
|
|
|
|
if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds))
|
|
return -1;
|
|
|
|
uv__cloexec(fds[0], 1);
|
|
uv__cloexec(fds[1], 1);
|
|
|
|
if (flags & UV__F_NONBLOCK) {
|
|
uv__nonblock(fds[0], 1);
|
|
uv__nonblock(fds[1], 1);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
int uv__make_pipe(int fds[2], int flags) {
|
|
#if __linux__
|
|
if (uv__pipe2(fds, flags | UV__O_CLOEXEC) == 0)
|
|
return 0;
|
|
|
|
if (errno != ENOSYS)
|
|
return -1;
|
|
#endif
|
|
|
|
if (pipe(fds))
|
|
return -1;
|
|
|
|
uv__cloexec(fds[0], 1);
|
|
uv__cloexec(fds[1], 1);
|
|
|
|
if (flags & UV__F_NONBLOCK) {
|
|
uv__nonblock(fds[0], 1);
|
|
uv__nonblock(fds[1], 1);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* 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 fd = -1;
|
|
switch (container->flags & (UV_IGNORE | UV_CREATE_PIPE | UV_INHERIT_FD |
|
|
UV_INHERIT_STREAM)) {
|
|
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_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[1] = fd;
|
|
|
|
return 0;
|
|
default:
|
|
assert(0 && "Unexpected flags");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
|
|
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 child_fd;
|
|
int flags;
|
|
int fd;
|
|
|
|
fd = fds[0];
|
|
child_fd = fds[1];
|
|
|
|
/* 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);
|
|
}
|
|
|
|
|
|
static int uv__read_int(int fd) {
|
|
ssize_t n;
|
|
int val;
|
|
|
|
do
|
|
n = read(fd, &val, sizeof(val));
|
|
while (n == -1 && errno == EINTR);
|
|
|
|
assert(n == sizeof(val));
|
|
return val;
|
|
}
|
|
|
|
|
|
static void uv__write_int(int fd, int val) {
|
|
ssize_t n;
|
|
|
|
do
|
|
n = write(fd, &val, sizeof(val));
|
|
while (n == -1 && errno == EINTR);
|
|
|
|
if (n == -1 && errno == EPIPE)
|
|
return; /* parent process has quit */
|
|
|
|
assert(n == sizeof(val));
|
|
}
|
|
|
|
|
|
static void uv__process_child_init(uv_process_options_t options,
|
|
int stdio_count,
|
|
int (*pipes)[2],
|
|
int error_fd) {
|
|
int close_fd;
|
|
int use_fd;
|
|
int i;
|
|
|
|
if (options.flags & UV_PROCESS_DETACHED)
|
|
setsid();
|
|
|
|
for (i = 0; i < stdio_count; i++) {
|
|
close_fd = pipes[i][0];
|
|
use_fd = pipes[i][1];
|
|
|
|
if (use_fd >= 0)
|
|
close(close_fd);
|
|
else if (i >= 3)
|
|
continue;
|
|
else {
|
|
/* redirect stdin, stdout and stderr to /dev/null even if UV_IGNORE is
|
|
* set
|
|
*/
|
|
use_fd = open("/dev/null", i == 0 ? O_RDONLY : O_RDWR);
|
|
|
|
if (use_fd == -1) {
|
|
uv__write_int(error_fd, errno);
|
|
perror("failed to open stdio");
|
|
_exit(127);
|
|
}
|
|
}
|
|
|
|
if (i == use_fd)
|
|
uv__cloexec(use_fd, 0);
|
|
else {
|
|
dup2(use_fd, i);
|
|
close(use_fd);
|
|
}
|
|
}
|
|
|
|
if (options.cwd && chdir(options.cwd)) {
|
|
uv__write_int(error_fd, errno);
|
|
perror("chdir()");
|
|
_exit(127);
|
|
}
|
|
|
|
if ((options.flags & UV_PROCESS_SETGID) && setgid(options.gid)) {
|
|
uv__write_int(error_fd, errno);
|
|
perror("setgid()");
|
|
_exit(127);
|
|
}
|
|
|
|
if ((options.flags & UV_PROCESS_SETUID) && setuid(options.uid)) {
|
|
uv__write_int(error_fd, errno);
|
|
perror("setuid()");
|
|
_exit(127);
|
|
}
|
|
|
|
environ = options.env;
|
|
|
|
execvp(options.file, options.args);
|
|
uv__write_int(error_fd, errno);
|
|
perror("execvp()");
|
|
_exit(127);
|
|
}
|
|
|
|
|
|
int uv_spawn(uv_loop_t* loop,
|
|
uv_process_t* process,
|
|
const uv_process_options_t options) {
|
|
int signal_pipe[2] = { -1, -1 };
|
|
struct pollfd pfd;
|
|
int (*pipes)[2];
|
|
int stdio_count;
|
|
ngx_queue_t* q;
|
|
pid_t pid;
|
|
int i;
|
|
int r;
|
|
|
|
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);
|
|
ngx_queue_init(&process->queue);
|
|
|
|
stdio_count = options.stdio_count;
|
|
if (stdio_count < 3)
|
|
stdio_count = 3;
|
|
|
|
pipes = malloc(stdio_count * sizeof(*pipes));
|
|
if (pipes == NULL) {
|
|
errno = ENOMEM;
|
|
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++)
|
|
if (uv__process_init_stdio(options.stdio + i, pipes[i]))
|
|
goto error;
|
|
|
|
/* swap stdin file descriptors, it's the only writable stream */
|
|
{
|
|
int* p = pipes[0];
|
|
int t = p[0];
|
|
p[0] = p[1];
|
|
p[1] = t;
|
|
}
|
|
|
|
/* 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 (uv__make_pipe(signal_pipe, UV__F_NONBLOCK))
|
|
goto error;
|
|
|
|
pid = fork();
|
|
|
|
if (pid == -1) {
|
|
close(signal_pipe[0]);
|
|
close(signal_pipe[1]);
|
|
goto error;
|
|
}
|
|
|
|
if (pid == 0) {
|
|
uv__process_child_init(options, stdio_count, pipes, signal_pipe[1]);
|
|
abort();
|
|
}
|
|
|
|
/* POLLHUP signals child has exited or execve()'d. */
|
|
close(signal_pipe[1]);
|
|
pfd.revents = 0;
|
|
pfd.events = POLLIN|POLLHUP;
|
|
pfd.fd = signal_pipe[0];
|
|
|
|
do
|
|
r = poll(&pfd, 1, -1);
|
|
while (r == -1 && errno == EINTR);
|
|
|
|
assert((r == 1) && "poll()_on read end of pipe failed");
|
|
assert((pfd.revents & (POLLIN|POLLHUP)) && "unexpected poll() revents");
|
|
|
|
if (pfd.revents & POLLIN)
|
|
process->errorno = uv__read_int(signal_pipe[0]);
|
|
else /* POLLHUP */
|
|
process->errorno = 0;
|
|
|
|
close(signal_pipe[0]);
|
|
|
|
for (i = 0; i < options.stdio_count; i++) {
|
|
if (uv__process_open_stream(options.stdio + i, pipes[i], i == 0)) {
|
|
while (i--) uv__process_close_stream(options.stdio + i);
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
q = uv__process_queue(loop, pid);
|
|
ngx_queue_insert_tail(q, &process->queue);
|
|
uv_signal_start(&loop->child_watcher, uv__chld, SIGCHLD);
|
|
|
|
process->pid = pid;
|
|
process->exit_cb = options.exit_cb;
|
|
uv__handle_start(process);
|
|
|
|
free(pipes);
|
|
return 0;
|
|
|
|
error:
|
|
uv__set_sys_error(process->loop, errno);
|
|
|
|
for (i = 0; i < stdio_count; i++) {
|
|
close(pipes[i][0]);
|
|
close(pipes[i][1]);
|
|
}
|
|
free(pipes);
|
|
|
|
return -1;
|
|
}
|
|
|
|
|
|
int uv_process_kill(uv_process_t* process, int signum) {
|
|
int r = kill(process->pid, signum);
|
|
|
|
if (r) {
|
|
uv__set_sys_error(process->loop, errno);
|
|
return -1;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
uv_err_t uv_kill(int pid, int signum) {
|
|
int r = kill(pid, signum);
|
|
|
|
if (r) {
|
|
return uv__new_sys_error(errno);
|
|
} else {
|
|
return uv_ok_;
|
|
}
|
|
}
|
|
|
|
|
|
void uv__process_close(uv_process_t* handle) {
|
|
/* TODO stop signal watcher when this is the last handle */
|
|
ngx_queue_remove(&handle->queue);
|
|
uv__handle_stop(handle);
|
|
}
|