unix: report errors for unpollable fds

Libuv would abort() when trying to watch a file descriptor that is
not compatible with epoll-style polling; file descriptors referring
to on-disk files fall into this category.

File descriptors that libuv creates itself are not an issue but
external ones that come in through the uv_poll_init() API are.

Make uv_poll_init() check whether the file descriptor is accepted by
the underlying system call and return an error when it's not.

Fixes: https://github.com/libuv/libuv/issues/658
PR-URL: https://github.com/libuv/libuv/pull/659
Reviewed-By: Saúl Ibarra Corretgé <saghul@gmail.com>
This commit is contained in:
Ben Noordhuis 2015-12-20 02:29:11 +01:00
parent c21a75a16c
commit a0b56059cf
8 changed files with 102 additions and 1 deletions

View File

@ -91,6 +91,24 @@ void uv__platform_loop_delete(uv_loop_t* loop) {
}
int uv__io_check_fd(uv_loop_t* loop, int fd) {
struct poll_ctl pc;
pc.events = POLLIN;
pc.cmd = PS_MOD; /* Equivalent to PS_ADD if the fd is not in the pollset. */
pc.fd = fd;
if (pollset_ctl(loop->backend_fd, &pc, 1))
return -errno;
pc.cmd = PS_DELETE;
if (pollset_ctl(loop->backend_fd, &pc, 1))
abort();
return 0;
}
void uv__io_poll(uv_loop_t* loop, int timeout) {
struct pollfd events[1024];
struct pollfd pqry;

View File

@ -180,6 +180,7 @@ void uv__io_stop(uv_loop_t* loop, uv__io_t* w, unsigned int events);
void uv__io_close(uv_loop_t* loop, uv__io_t* w);
void uv__io_feed(uv_loop_t* loop, uv__io_t* w);
int uv__io_active(const uv__io_t* w, unsigned int events);
int uv__io_check_fd(uv_loop_t* loop, int fd);
void uv__io_poll(uv_loop_t* loop, int timeout); /* in milliseconds or -1 */
/* async */

View File

@ -48,6 +48,24 @@ int uv__kqueue_init(uv_loop_t* loop) {
}
int uv__io_check_fd(uv_loop_t* loop, int fd) {
struct kevent ev;
int rc;
rc = 0;
EV_SET(&ev, fd, EVFILT_READ, EV_ADD, 0, 0, 0);
if (kevent(loop->backend_fd, &ev, 1, NULL, 0, NULL))
rc = -errno;
EV_SET(&ev, fd, EVFILT_READ, EV_DELETE, 0, 0, 0);
if (rc == 0)
if (kevent(loop->backend_fd, &ev, 1, NULL, 0, NULL))
abort();
return rc;
}
void uv__io_poll(uv_loop_t* loop, int timeout) {
struct kevent events[1024];
struct kevent* ev;

View File

@ -140,6 +140,26 @@ void uv__platform_invalidate_fd(uv_loop_t* loop, int fd) {
}
int uv__io_check_fd(uv_loop_t* loop, int fd) {
struct uv__epoll_event e;
int rc;
e.events = UV__EPOLLIN;
e.data = -1;
rc = 0;
if (uv__epoll_ctl(loop->backend_fd, UV__EPOLL_CTL_ADD, fd, &e))
if (errno != EEXIST)
rc = -errno;
if (rc == 0)
if (uv__epoll_ctl(loop->backend_fd, UV__EPOLL_CTL_DEL, fd, &e))
abort();
return rc;
}
void uv__io_poll(uv_loop_t* loop, int timeout) {
/* A bug in kernels < 2.6.37 makes timeouts larger than ~30 minutes
* effectively infinite on 32 bits architectures. To avoid blocking

View File

@ -53,6 +53,10 @@ static void uv__poll_io(uv_loop_t* loop, uv__io_t* w, unsigned int events) {
int uv_poll_init(uv_loop_t* loop, uv_poll_t* handle, int fd) {
int err;
err = uv__io_check_fd(loop, fd);
if (err)
return err;
err = uv__nonblock(fd, 1);
if (err)
return err;

View File

@ -116,6 +116,17 @@ void uv__platform_invalidate_fd(uv_loop_t* loop, int fd) {
}
int uv__io_check_fd(uv_loop_t* loop, int fd) {
if (port_associate(loop->backend_fd, PORT_SOURCE_FD, fd, POLLIN, 0))
return -errno;
if (port_dissociate(loop->backend_fd, PORT_SOURCE_FD, fd))
abort();
return 0;
}
void uv__io_poll(uv_loop_t* loop, int timeout) {
struct port_event events[1024];
struct port_event* pe;

View File

@ -305,6 +305,7 @@ TEST_DECLARE (dlerror)
TEST_DECLARE (poll_duplex)
TEST_DECLARE (poll_unidirectional)
TEST_DECLARE (poll_close)
TEST_DECLARE (poll_bad_fdtype)
TEST_DECLARE (ip4_addr)
TEST_DECLARE (ip6_addr_link_local)
@ -608,6 +609,7 @@ TASK_LIST_START
TEST_ENTRY (poll_duplex)
TEST_ENTRY (poll_unidirectional)
TEST_ENTRY (poll_close)
TEST_ENTRY (poll_bad_fdtype)
TEST_ENTRY (socket_buffer_size)

View File

@ -21,7 +21,9 @@
#include <errno.h>
#ifndef _WIN32
#ifdef _WIN32
# include <fcntl.h>
#else
# include <sys/socket.h>
# include <unistd.h>
#endif
@ -558,3 +560,28 @@ TEST_IMPL(poll_unidirectional) {
start_poll_test();
return 0;
}
/* Windows won't let you open a directory so we open a file instead.
* OS X lets you poll a file so open the $PWD instead. Both fail
* on Linux so it doesn't matter which one we pick. Both succeed
* on FreeBSD and Solaris so skip the test on those platforms.
*/
TEST_IMPL(poll_bad_fdtype) {
#if !defined(__DragonFly__) && !defined(__FreeBSD__) && !defined(__sun)
uv_poll_t poll_handle;
int fd;
#if defined(_WIN32)
fd = open("test/fixtures/empty_file", O_RDONLY);
#else
fd = open(".", O_RDONLY);
#endif
ASSERT(fd != -1);
ASSERT(0 != uv_poll_init(uv_default_loop(), &poll_handle, fd));
ASSERT(0 == close(fd));
#endif
MAKE_VALGRIND_HAPPY();
return 0;
}