diff --git a/include/uv-private/uv-unix.h b/include/uv-private/uv-unix.h index c588b2d4..083e53c8 100644 --- a/include/uv-private/uv-unix.h +++ b/include/uv-private/uv-unix.h @@ -258,7 +258,8 @@ struct uv__io_s { int retcode; #define UV_PROCESS_PRIVATE_FIELDS \ - ev_child child_watcher; + ev_child child_watcher; \ + int errorno; \ #define UV_FS_PRIVATE_FIELDS \ struct stat statbuf; \ diff --git a/src/unix/process.c b/src/unix/process.c index 89339966..df16ff5b 100644 --- a/src/unix/process.c +++ b/src/unix/process.c @@ -53,17 +53,19 @@ static void uv__chld(EV_P_ ev_child* watcher, int revents) { ev_child_stop(EV_A_ &process->child_watcher); - if (WIFEXITED(status)) { + if (process->exit_cb == NULL) + return; + + if (WIFEXITED(status)) exit_status = WEXITSTATUS(status); - } - if (WIFSIGNALED(status)) { + if (WIFSIGNALED(status)) term_signal = WTERMSIG(status); - } - if (process->exit_cb) { - process->exit_cb(process, exit_status, term_signal); - } + if (process->errorno) + 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, int stdio_count, - int* pipes) { + int* pipes, + int error_fd) { int i; 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); if (use_fd < 0) { + uv__write_int(error_fd, errno); perror("failed to open stdio"); _exit(127); } @@ -243,16 +274,19 @@ static void uv__process_child_init(uv_process_options_t options, } 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); } @@ -260,6 +294,7 @@ static void uv__process_child_init(uv_process_options_t options, environ = options.env; execvp(options.file, options.args); + uv__write_int(error_fd, errno); perror("execvp()"); _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 signal_pipe[2] = { -1, -1 }; struct pollfd pfd; - int status; pid_t pid; int i; + int r; if (pipes == NULL) { 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); loop->counters.process_init++; - uv__handle_start(process); - - process->exit_cb = options.exit_cb; /* Init pipe pairs */ 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) { - /* Child */ - uv__process_child_init(options, stdio_count, pipes); - - /* Execution never reaches here. */ + uv__process_child_init(options, stdio_count, pipes, signal_pipe[1]); + abort(); } - /* Parent. */ - /* Restore environment. */ environ = save_our_env; /* 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)); + 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; - assert((status == 1) && "poll() on pipe read end failed"); close(signal_pipe[0]); - 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; + process->exit_cb = options.exit_cb; + process->pid = pid; for (i = 0; i < options.stdio_count; i++) { 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); return 0; diff --git a/test/test-list.h b/test/test-list.h index 8d201c92..d7884b1a 100644 --- a/test/test-list.h +++ b/test/test-list.h @@ -123,6 +123,7 @@ TEST_DECLARE (getsockname_tcp) TEST_DECLARE (getsockname_udp) TEST_DECLARE (fail_always) TEST_DECLARE (pass_always) +TEST_DECLARE (spawn_fails) TEST_DECLARE (spawn_exit_code) TEST_DECLARE (spawn_stdout) TEST_DECLARE (spawn_stdin) @@ -350,6 +351,7 @@ TASK_LIST_START TEST_ENTRY (poll_unidirectional) TEST_ENTRY (poll_close) + TEST_ENTRY (spawn_fails) TEST_ENTRY (spawn_exit_code) TEST_ENTRY (spawn_stdout) TEST_ENTRY (spawn_stdin) diff --git a/test/test-spawn.c b/test/test-spawn.c index 06b211bc..3576fc58 100644 --- a/test/test-spawn.c +++ b/test/test-spawn.c @@ -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) { int r;