diff --git a/include/uv-unix.h b/include/uv-unix.h index 45006092..34695713 100644 --- a/include/uv-unix.h +++ b/include/uv-unix.h @@ -169,6 +169,7 @@ typedef struct { void* wq[2]; \ uv_mutex_t wq_mutex; \ uv_async_t wq_async; \ + uv_rwlock_t cloexec_lock; \ uv_handle_t* closing_handles; \ void* process_handles[1][2]; \ void* prepare_handles[2]; \ diff --git a/src/unix/fs.c b/src/unix/fs.c index 1aa6539c..ce34fdb9 100644 --- a/src/unix/fs.c +++ b/src/unix/fs.c @@ -608,6 +608,9 @@ static void uv__fs_work(struct uv__work* w) { int retry_on_eintr; uv_fs_t* req; ssize_t r; +#ifdef O_CLOEXEC + static int no_cloexec_support; +#endif /* O_CLOEXEC */ req = container_of(w, uv_fs_t, work_req); retry_on_eintr = !(req->fs_type == UV_FS_CLOSE); @@ -634,7 +637,6 @@ static void uv__fs_work(struct uv__work* w) { X(LSTAT, uv__fs_lstat(req->path, &req->statbuf)); X(LINK, link(req->path, req->new_path)); X(MKDIR, mkdir(req->path, req->mode)); - X(OPEN, open(req->path, req->flags, req->mode)); X(READ, uv__fs_read(req)); X(READDIR, uv__fs_readdir(req)); X(READLINK, uv__fs_readlink(req)); @@ -646,6 +648,35 @@ static void uv__fs_work(struct uv__work* w) { X(UNLINK, unlink(req->path)); X(UTIME, uv__fs_utime(req)); X(WRITE, uv__fs_write(req)); + case UV_FS_OPEN: +#ifdef O_CLOEXEC + /* Try O_CLOEXEC before entering locks */ + if (!no_cloexec_support) { + r = open(req->path, req->flags | O_CLOEXEC, req->mode); + if (r >= 0) + break; + if (errno != EINVAL) + break; + no_cloexec_support = 1; + } +#endif /* O_CLOEXEC */ + if (req->cb != NULL) + uv_rwlock_rdlock(&req->loop->cloexec_lock); + r = open(req->path, req->flags, req->mode); + + /* + * In case of failure `uv__cloexec` will leave error in `errno`, + * so it is enough to just set `r` to `-1`. + */ + if (r >= 0 && uv__cloexec(r, 1) != 0) { + r = uv__close(r); + if (r != 0 && r != -EINPROGRESS) + abort(); + r = -1; + } + if (req->cb != NULL) + uv_rwlock_rdunlock(&req->loop->cloexec_lock); + break; default: abort(); } diff --git a/src/unix/internal.h b/src/unix/internal.h index 0ea82b51..4a4656a5 100644 --- a/src/unix/internal.h +++ b/src/unix/internal.h @@ -27,6 +27,7 @@ #include #include /* abort */ #include /* strrchr */ +#include /* O_CLOEXEC, may be */ #if defined(__STRICT_ANSI__) # define inline __inline @@ -111,6 +112,14 @@ # define UV__POLLHUP 8 #endif +#if !defined(O_CLOEXEC) && defined(__FreeBSD__) +/* + * It may be that we are just missing `__POSIX_VISIBLE >= 200809`. + * Try using fixed value const and give up, if it doesn't work + */ +# define O_CLOEXEC 0x00100000 +#endif + /* handle flags */ enum { UV_CLOSING = 0x01, /* uv_close() called but not finished. */ diff --git a/src/unix/loop.c b/src/unix/loop.c index 94a5c038..9ca93f3b 100644 --- a/src/unix/loop.c +++ b/src/unix/loop.c @@ -117,6 +117,9 @@ static int uv__loop_init(uv_loop_t* loop, int default_loop) { for (i = 0; i < ARRAY_SIZE(loop->process_handles); i++) QUEUE_INIT(loop->process_handles + i); + if (uv_rwlock_init(&loop->cloexec_lock)) + abort(); + if (uv_mutex_init(&loop->wq_mutex)) abort(); @@ -151,6 +154,12 @@ static void uv__loop_delete(uv_loop_t* loop) { uv_mutex_unlock(&loop->wq_mutex); uv_mutex_destroy(&loop->wq_mutex); + /* + * Note that all thread pool stuff is finished at this point and + * it is safe to just destroy rw lock + */ + uv_rwlock_destroy(&loop->cloexec_lock); + #if 0 assert(QUEUE_EMPTY(&loop->pending_queue)); assert(QUEUE_EMPTY(&loop->watcher_queue)); diff --git a/src/unix/process.c b/src/unix/process.c index 6f96b754..0fc8f640 100644 --- a/src/unix/process.c +++ b/src/unix/process.c @@ -422,10 +422,13 @@ int uv_spawn(uv_loop_t* loop, 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; @@ -436,6 +439,8 @@ int uv_spawn(uv_loop_t* loop, abort(); } + /* Release lock in parent process */ + uv_rwlock_wrunlock(&loop->cloexec_lock); uv__close(signal_pipe[1]); process->status = 0; diff --git a/test/run-tests.c b/test/run-tests.c index 2ba2cfde..cd50ee09 100644 --- a/test/run-tests.c +++ b/test/run-tests.c @@ -152,5 +152,16 @@ static int maybe_run_test(int argc, char **argv) { return 1; } +#ifndef _WIN32 + if (strcmp(argv[1], "spawn_helper8") == 0) { + int fd; + ASSERT(sizeof(fd) == read(0, &fd, sizeof(fd))); + ASSERT(fd > 2); + ASSERT(-1 == write(fd, "x", 1)); + + return 1; + } +#endif /* !_WIN32 */ + return run_test(argv[1], 0, 1); } diff --git a/test/test-list.h b/test/test-list.h index 6fbf3bcc..0cccd9c6 100644 --- a/test/test-list.h +++ b/test/test-list.h @@ -155,6 +155,7 @@ TEST_DECLARE (getsockname_udp) TEST_DECLARE (fail_always) TEST_DECLARE (pass_always) TEST_DECLARE (spawn_fails) +TEST_DECLARE (spawn_fs_open) TEST_DECLARE (spawn_exit_code) TEST_DECLARE (spawn_stdout) TEST_DECLARE (spawn_stdin) @@ -445,6 +446,7 @@ TASK_LIST_START TEST_ENTRY (poll_close) TEST_ENTRY (spawn_fails) + TEST_ENTRY (spawn_fs_open) 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 5f71fce2..e73e8ad8 100644 --- a/test/test-spawn.c +++ b/test/test-spawn.c @@ -1049,3 +1049,41 @@ TEST_IMPL(spawn_auto_unref) { MAKE_VALGRIND_HAPPY(); return 0; } + + +#ifndef _WIN32 +TEST_IMPL(spawn_fs_open) { + int fd; + uv_fs_t fs_req; + uv_pipe_t in; + uv_write_t write_req; + uv_buf_t buf; + uv_stdio_container_t stdio[1]; + + fd = uv_fs_open(uv_default_loop(), &fs_req, "/dev/null", O_RDWR, 0, NULL); + ASSERT(fd >= 0); + + init_process_options("spawn_helper8", exit_cb); + + ASSERT(0 == uv_pipe_init(uv_default_loop(), &in, 0)); + + options.stdio = stdio; + options.stdio[0].flags = UV_CREATE_PIPE | UV_READABLE_PIPE; + options.stdio[0].data.stream = (uv_stream_t*) ∈ + options.stdio_count = 1; + + ASSERT(0 == uv_spawn(uv_default_loop(), &process, &options)); + + buf = uv_buf_init((char*) &fd, sizeof(fd)); + ASSERT(0 == uv_write(&write_req, (uv_stream_t*) &in, &buf, 1, write_cb)); + + ASSERT(0 == uv_run(uv_default_loop(), UV_RUN_DEFAULT)); + ASSERT(0 == uv_fs_close(uv_default_loop(), &fs_req, fd, NULL)); + + ASSERT(exit_cb_called == 1); + ASSERT(close_cb_called == 2); /* One for `in`, one for process */ + + MAKE_VALGRIND_HAPPY(); + return 0; +} +#endif /* !_WIN32 */