diff --git a/sys/fs/fuse/fuse_device.c b/sys/fs/fuse/fuse_device.c --- a/sys/fs/fuse/fuse_device.c +++ b/sys/fs/fuse/fuse_device.c @@ -278,15 +278,17 @@ return (events & (POLLHUP|POLLIN|POLLRDNORM|POLLOUT|POLLWRNORM)); + if (fdata_get_dead(data)) + revents |= POLLHUP; if (events & (POLLIN | POLLRDNORM)) { fuse_lck_mtx_lock(data->ms_mtx); - if (fdata_get_dead(data) || STAILQ_FIRST(&data->ms_head)) + if (STAILQ_FIRST(&data->ms_head)) revents |= events & (POLLIN | POLLRDNORM); else selrecord(td, &data->ks_rsel); fuse_lck_mtx_unlock(data->ms_mtx); } - if (events & (POLLOUT | POLLWRNORM)) { + if (!fdata_get_dead(data) && (events & (POLLOUT | POLLWRNORM))) { revents |= events & (POLLOUT | POLLWRNORM); } return (revents); @@ -319,6 +321,10 @@ "we know early on that reader should be kicked so we " "don't wait for news"); fuse_lck_mtx_unlock(data->ms_mtx); + /* + * ENXIO would make more sense. But return ENODEV for + * compatibility with the Linux implementation. + */ return (ENODEV); } if (!(tick = fuse_ms_pop(data))) { diff --git a/tests/sys/fs/fusefs/dev_fuse_poll.cc b/tests/sys/fs/fusefs/dev_fuse_poll.cc --- a/tests/sys/fs/fusefs/dev_fuse_poll.cc +++ b/tests/sys/fs/fusefs/dev_fuse_poll.cc @@ -33,7 +33,10 @@ */ extern "C" { +#include + #include +#include #include #include } @@ -79,6 +82,13 @@ } }; +class Poll: public FuseTest { + virtual void SetUp() { + m_pm = POLL; + FuseTest::SetUp(); + } +}; + TEST_P(DevFusePoll, access) { expect_access(FUSE_ROOT_ID, X_OK, 0); @@ -227,3 +237,46 @@ sem_destroy(&sem0); sem_destroy(&sem1); } + +/* After unmount, kevent(/dev/fuse) should return EV_EOF */ +TEST_F(Kqueue, HUP) +{ + struct timespec timeout_ts; + struct kevent changes[1]; + struct kevent events[1]; + const int timeout_ms = 999; + int nready; + + expect_destroy(0); + + m_mock->unmount(); + timeout_ts.tv_sec = 0; + timeout_ts.tv_nsec = timeout_ms * 1'000'000; + EV_SET(&changes[0], m_mock->m_fuse_fd, EVFILT_READ, EV_ADD | EV_ONESHOT, + 0, 0, 0); + nready = kevent(m_mock->m_kq, &changes[0], 1, &events[0], 1, + &timeout_ts); + ASSERT_LE(0, nready) << strerror(errno); + ASSERT_EQ(1, nready); + ASSERT_EQ(events[0].ident, (uintptr_t)m_mock->m_fuse_fd); + ASSERT_TRUE(events[0].flags & EV_EOF); +} + +/* After unmount, poll(/dev/fuse) should return POLLHUP */ +TEST_F(Poll, HUP) +{ + const int timeout_ms = 999; + pollfd fds[1]; + int nready; + + expect_destroy(0); + + m_mock->unmount(); + fds[0].fd = m_mock->m_fuse_fd; + fds[0].events = POLLIN | POLLOUT; + nready = poll(fds, 1, timeout_ms); + ASSERT_LE(0, nready) << strerror(errno); + ASSERT_EQ(1, nready); + ASSERT_TRUE(fds[0].revents & POLLHUP); + ASSERT_FALSE(fds[0].revents & (POLLIN | POLLOUT)); +} diff --git a/tests/sys/fs/fusefs/mockfs.hh b/tests/sys/fs/fusefs/mockfs.hh --- a/tests/sys/fs/fusefs/mockfs.hh +++ b/tests/sys/fs/fusefs/mockfs.hh @@ -286,14 +286,9 @@ */ pthread_t m_daemon_id; - /* file descriptor of /dev/fuse control device */ - volatile int m_fuse_fd; - /* The minor version of the kernel API that this mock daemon targets */ uint32_t m_kernel_minor_version; - int m_kq; - /* * If nonzero, the maximum size in bytes of a read that the kernel will * send to the server. @@ -348,6 +343,11 @@ /* pid of child process, for two-process test cases */ pid_t m_child_pid; + int m_kq; + + /* file descriptor of /dev/fuse control device */ + volatile int m_fuse_fd; + /* Maximum size of a FUSE_WRITE write */ uint32_t m_maxwrite; diff --git a/tests/sys/fs/fusefs/mockfs.cc b/tests/sys/fs/fusefs/mockfs.cc --- a/tests/sys/fs/fusefs/mockfs.cc +++ b/tests/sys/fs/fusefs/mockfs.cc @@ -423,7 +423,6 @@ const char *fsname, const char *subtype) : m_daemon_id(NULL), m_kernel_minor_version(kernel_minor_version), - m_kq(pm == KQ ? kqueue() : -1), m_maxread(max_read), m_maxreadahead(max_readahead), m_pid(getpid()), @@ -431,6 +430,7 @@ m_pm(pm), m_time_gran(time_gran), m_child_pid(-1), + m_kq(pm == KQ ? kqueue() : -1), m_maxwrite(MIN(max_write, max_max_write)), m_nready(-1), m_quit(false)