unix: retrieve execve() errors in process.c

Make the forked child process send the errno to its parent process when it
fails to spawn a new process.
This commit is contained in:
Ben Noordhuis 2012-08-08 18:28:16 +02:00
parent bf28aa4e78
commit 13467a40d4
4 changed files with 79 additions and 29 deletions

View File

@ -258,7 +258,8 @@ struct uv__io_s {
int retcode; int retcode;
#define UV_PROCESS_PRIVATE_FIELDS \ #define UV_PROCESS_PRIVATE_FIELDS \
ev_child child_watcher; ev_child child_watcher; \
int errorno; \
#define UV_FS_PRIVATE_FIELDS \ #define UV_FS_PRIVATE_FIELDS \
struct stat statbuf; \ struct stat statbuf; \

View File

@ -53,17 +53,19 @@ static void uv__chld(EV_P_ ev_child* watcher, int revents) {
ev_child_stop(EV_A_ &process->child_watcher); ev_child_stop(EV_A_ &process->child_watcher);
if (WIFEXITED(status)) { if (process->exit_cb == NULL)
return;
if (WIFEXITED(status))
exit_status = WEXITSTATUS(status); exit_status = WEXITSTATUS(status);
}
if (WIFSIGNALED(status)) { if (WIFSIGNALED(status))
term_signal = WTERMSIG(status); term_signal = WTERMSIG(status);
}
if (process->exit_cb) { if (process->errorno)
process->exit_cb(process, exit_status, term_signal); uv__set_sys_error(process->loop, process->errorno);
}
process->exit_cb(process, exit_status, term_signal);
} }
@ -202,9 +204,37 @@ static void uv__process_close_stream(uv_stdio_container_t* container) {
} }
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, static void uv__process_child_init(uv_process_options_t options,
int stdio_count, int stdio_count,
int* pipes) { int* pipes,
int error_fd) {
int i; int i;
if (options.flags & UV_PROCESS_DETACHED) { if (options.flags & UV_PROCESS_DETACHED) {
@ -227,6 +257,7 @@ static void uv__process_child_init(uv_process_options_t options,
use_fd = open("/dev/null", i == 0 ? O_RDONLY : O_RDWR); use_fd = open("/dev/null", i == 0 ? O_RDONLY : O_RDWR);
if (use_fd < 0) { if (use_fd < 0) {
uv__write_int(error_fd, errno);
perror("failed to open stdio"); perror("failed to open stdio");
_exit(127); _exit(127);
} }
@ -243,16 +274,19 @@ static void uv__process_child_init(uv_process_options_t options,
} }
if (options.cwd && chdir(options.cwd)) { if (options.cwd && chdir(options.cwd)) {
uv__write_int(error_fd, errno);
perror("chdir()"); perror("chdir()");
_exit(127); _exit(127);
} }
if ((options.flags & UV_PROCESS_SETGID) && setgid(options.gid)) { if ((options.flags & UV_PROCESS_SETGID) && setgid(options.gid)) {
uv__write_int(error_fd, errno);
perror("setgid()"); perror("setgid()");
_exit(127); _exit(127);
} }
if ((options.flags & UV_PROCESS_SETUID) && setuid(options.uid)) { if ((options.flags & UV_PROCESS_SETUID) && setuid(options.uid)) {
uv__write_int(error_fd, errno);
perror("setuid()"); perror("setuid()");
_exit(127); _exit(127);
} }
@ -260,6 +294,7 @@ static void uv__process_child_init(uv_process_options_t options,
environ = options.env; environ = options.env;
execvp(options.file, options.args); execvp(options.file, options.args);
uv__write_int(error_fd, errno);
perror("execvp()"); perror("execvp()");
_exit(127); _exit(127);
} }
@ -277,9 +312,9 @@ int uv_spawn(uv_loop_t* loop, uv_process_t* process,
int* pipes = malloc(2 * stdio_count * sizeof(int)); int* pipes = malloc(2 * stdio_count * sizeof(int));
int signal_pipe[2] = { -1, -1 }; int signal_pipe[2] = { -1, -1 };
struct pollfd pfd; struct pollfd pfd;
int status;
pid_t pid; pid_t pid;
int i; int i;
int r;
if (pipes == NULL) { if (pipes == NULL) {
errno = ENOMEM; errno = ENOMEM;
@ -295,9 +330,6 @@ int uv_spawn(uv_loop_t* loop, uv_process_t* process,
uv__handle_init(loop, (uv_handle_t*)process, UV_PROCESS); uv__handle_init(loop, (uv_handle_t*)process, UV_PROCESS);
loop->counters.process_init++; loop->counters.process_init++;
uv__handle_start(process);
process->exit_cb = options.exit_cb;
/* Init pipe pairs */ /* Init pipe pairs */
for (i = 0; i < stdio_count; i++) { for (i = 0; i < stdio_count; i++) {
@ -345,35 +377,38 @@ int uv_spawn(uv_loop_t* loop, uv_process_t* process,
} }
if (pid == 0) { if (pid == 0) {
/* Child */ uv__process_child_init(options, stdio_count, pipes, signal_pipe[1]);
uv__process_child_init(options, stdio_count, pipes); abort();
/* Execution never reaches here. */
} }
/* Parent. */
/* Restore environment. */ /* Restore environment. */
environ = save_our_env; environ = save_our_env;
/* POLLHUP signals child has exited or execve()'d. */ /* POLLHUP signals child has exited or execve()'d. */
close(signal_pipe[1]); close(signal_pipe[1]);
do { pfd.revents = 0;
pfd.fd = signal_pipe[0]; pfd.events = POLLIN|POLLHUP;
pfd.events = POLLIN|POLLHUP; pfd.fd = signal_pipe[0];
pfd.revents = 0;
errno = 0, status = poll(&pfd, 1, -1); do
} r = poll(&pfd, 1, -1);
while (status == -1 && (errno == EINTR || errno == ENOMEM)); 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;
assert((status == 1) && "poll() on pipe read end failed");
close(signal_pipe[0]); close(signal_pipe[0]);
process->pid = pid;
ev_child_init(&process->child_watcher, uv__chld, pid, 0); ev_child_init(&process->child_watcher, uv__chld, pid, 0);
ev_child_start(process->loop->ev, &process->child_watcher); ev_child_start(process->loop->ev, &process->child_watcher);
process->child_watcher.data = process; process->child_watcher.data = process;
process->exit_cb = options.exit_cb;
process->pid = pid;
for (i = 0; i < options.stdio_count; i++) { for (i = 0; i < options.stdio_count; i++) {
if (uv__process_open_stream(&options.stdio[i], pipes + i * 2, i == 0)) { if (uv__process_open_stream(&options.stdio[i], pipes + i * 2, i == 0)) {
@ -387,6 +422,7 @@ int uv_spawn(uv_loop_t* loop, uv_process_t* process,
} }
} }
uv__handle_start(process);
free(pipes); free(pipes);
return 0; return 0;

View File

@ -123,6 +123,7 @@ TEST_DECLARE (getsockname_tcp)
TEST_DECLARE (getsockname_udp) TEST_DECLARE (getsockname_udp)
TEST_DECLARE (fail_always) TEST_DECLARE (fail_always)
TEST_DECLARE (pass_always) TEST_DECLARE (pass_always)
TEST_DECLARE (spawn_fails)
TEST_DECLARE (spawn_exit_code) TEST_DECLARE (spawn_exit_code)
TEST_DECLARE (spawn_stdout) TEST_DECLARE (spawn_stdout)
TEST_DECLARE (spawn_stdin) TEST_DECLARE (spawn_stdin)
@ -350,6 +351,7 @@ TASK_LIST_START
TEST_ENTRY (poll_unidirectional) TEST_ENTRY (poll_unidirectional)
TEST_ENTRY (poll_close) TEST_ENTRY (poll_close)
TEST_ENTRY (spawn_fails)
TEST_ENTRY (spawn_exit_code) TEST_ENTRY (spawn_exit_code)
TEST_ENTRY (spawn_stdout) TEST_ENTRY (spawn_stdout)
TEST_ENTRY (spawn_stdin) TEST_ENTRY (spawn_stdin)

View File

@ -145,6 +145,17 @@ static void timer_cb(uv_timer_t* handle, int status) {
} }
TEST_IMPL(spawn_fails) {
init_process_options("", exit_cb_failure_expected);
options.file = options.args[0] = "program-that-had-better-not-exist";
ASSERT(0 == uv_spawn(uv_default_loop(), &process, options));
ASSERT(0 != uv_is_active((uv_handle_t*)&process));
ASSERT(0 == uv_run(uv_default_loop()));
ASSERT(uv_last_error(uv_default_loop()).code == UV_ENOENT);
return 0;
}
TEST_IMPL(spawn_exit_code) { TEST_IMPL(spawn_exit_code) {
int r; int r;