aboutsummaryrefslogtreecommitdiff
path: root/tests/sys/fs/fusefs/interrupt.cc
diff options
context:
space:
mode:
Diffstat (limited to 'tests/sys/fs/fusefs/interrupt.cc')
-rw-r--r--tests/sys/fs/fusefs/interrupt.cc796
1 files changed, 796 insertions, 0 deletions
diff --git a/tests/sys/fs/fusefs/interrupt.cc b/tests/sys/fs/fusefs/interrupt.cc
new file mode 100644
index 000000000000..3bfd4a834932
--- /dev/null
+++ b/tests/sys/fs/fusefs/interrupt.cc
@@ -0,0 +1,796 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright (c) 2019 The FreeBSD Foundation
+ *
+ * This software was developed by BFF Storage Systems, LLC under sponsorship
+ * from the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+extern "C" {
+#include <sys/types.h>
+#include <sys/extattr.h>
+#include <sys/mman.h>
+#include <sys/wait.h>
+#include <fcntl.h>
+#include <pthread.h>
+#include <semaphore.h>
+#include <signal.h>
+}
+
+#include "mockfs.hh"
+#include "utils.hh"
+
+using namespace testing;
+
+/* Initial size of files used by these tests */
+const off_t FILESIZE = 1000;
+/* Access mode used by all directories in these tests */
+const mode_t MODE = 0755;
+const char FULLDIRPATH0[] = "mountpoint/some_dir";
+const char RELDIRPATH0[] = "some_dir";
+const char FULLDIRPATH1[] = "mountpoint/other_dir";
+const char RELDIRPATH1[] = "other_dir";
+
+static sem_t *blocked_semaphore;
+static sem_t *signaled_semaphore;
+
+static bool killer_should_sleep = false;
+
+/* Don't do anything; all we care about is that the syscall gets interrupted */
+void sigusr2_handler(int __unused sig) {
+ if (verbosity > 1) {
+ printf("Signaled! thread %p\n", pthread_self());
+ }
+
+}
+
+void* killer(void* target) {
+ /* Wait until the main thread is blocked in fdisp_wait_answ */
+ if (killer_should_sleep)
+ nap();
+ else
+ sem_wait(blocked_semaphore);
+ if (verbosity > 1)
+ printf("Signalling! thread %p\n", target);
+ pthread_kill((pthread_t)target, SIGUSR2);
+ if (signaled_semaphore != NULL)
+ sem_post(signaled_semaphore);
+
+ return(NULL);
+}
+
+class Interrupt: public FuseTest {
+public:
+pthread_t m_child;
+
+Interrupt(): m_child(NULL) {};
+
+void expect_lookup(const char *relpath, uint64_t ino)
+{
+ FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, FILESIZE, 1);
+}
+
+/*
+ * Expect a FUSE_MKDIR but don't reply. Instead, just record the unique value
+ * to the provided pointer
+ */
+void expect_mkdir(uint64_t *mkdir_unique)
+{
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ return (in.header.opcode == FUSE_MKDIR);
+ }, Eq(true)),
+ _)
+ ).WillOnce(Invoke([=](auto in, auto &out __unused) {
+ *mkdir_unique = in.header.unique;
+ sem_post(blocked_semaphore);
+ }));
+}
+
+/*
+ * Expect a FUSE_READ but don't reply. Instead, just record the unique value
+ * to the provided pointer
+ */
+void expect_read(uint64_t ino, uint64_t *read_unique)
+{
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ return (in.header.opcode == FUSE_READ &&
+ in.header.nodeid == ino);
+ }, Eq(true)),
+ _)
+ ).WillOnce(Invoke([=](auto in, auto &out __unused) {
+ *read_unique = in.header.unique;
+ sem_post(blocked_semaphore);
+ }));
+}
+
+/*
+ * Expect a FUSE_WRITE but don't reply. Instead, just record the unique value
+ * to the provided pointer
+ */
+void expect_write(uint64_t ino, uint64_t *write_unique)
+{
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ return (in.header.opcode == FUSE_WRITE &&
+ in.header.nodeid == ino);
+ }, Eq(true)),
+ _)
+ ).WillOnce(Invoke([=](auto in, auto &out __unused) {
+ *write_unique = in.header.unique;
+ sem_post(blocked_semaphore);
+ }));
+}
+
+void setup_interruptor(pthread_t target, bool sleep = false)
+{
+ ASSERT_NE(SIG_ERR, signal(SIGUSR2, sigusr2_handler)) << strerror(errno);
+ killer_should_sleep = sleep;
+ ASSERT_EQ(0, pthread_create(&m_child, NULL, killer, (void*)target))
+ << strerror(errno);
+}
+
+void SetUp() {
+ const int mprot = PROT_READ | PROT_WRITE;
+ const int mflags = MAP_ANON | MAP_SHARED;
+
+ signaled_semaphore = NULL;
+
+ blocked_semaphore = (sem_t*)mmap(NULL, sizeof(*blocked_semaphore),
+ mprot, mflags, -1, 0);
+ ASSERT_NE(MAP_FAILED, blocked_semaphore) << strerror(errno);
+ ASSERT_EQ(0, sem_init(blocked_semaphore, 1, 0)) << strerror(errno);
+ ASSERT_EQ(0, siginterrupt(SIGUSR2, 1));
+
+ FuseTest::SetUp();
+}
+
+void TearDown() {
+ struct sigaction sa;
+
+ if (m_child != NULL) {
+ pthread_join(m_child, NULL);
+ }
+ bzero(&sa, sizeof(sa));
+ sa.sa_handler = SIG_DFL;
+ sigaction(SIGUSR2, &sa, NULL);
+
+ sem_destroy(blocked_semaphore);
+ munmap(blocked_semaphore, sizeof(*blocked_semaphore));
+
+ FuseTest::TearDown();
+}
+};
+
+class Intr: public Interrupt {};
+
+class Nointr: public Interrupt {
+ void SetUp() {
+ m_nointr = true;
+ Interrupt::SetUp();
+ }
+};
+
+static void* mkdir0(void* arg __unused) {
+ ssize_t r;
+
+ r = mkdir(FULLDIRPATH0, MODE);
+ if (r >= 0)
+ return 0;
+ else
+ return (void*)(intptr_t)errno;
+}
+
+static void* read1(void* arg) {
+ const size_t bufsize = FILESIZE;
+ char buf[bufsize];
+ int fd = (int)(intptr_t)arg;
+ ssize_t r;
+
+ r = read(fd, buf, bufsize);
+ if (r >= 0)
+ return 0;
+ else
+ return (void*)(intptr_t)errno;
+}
+
+/*
+ * An interrupt operation that gets received after the original command is
+ * complete should generate an EAGAIN response.
+ */
+/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */
+TEST_F(Intr, already_complete)
+{
+ uint64_t ino = 42;
+ pthread_t self;
+ uint64_t mkdir_unique = 0;
+ Sequence seq;
+
+ self = pthread_self();
+
+ EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0)
+ .InSequence(seq)
+ .WillOnce(Invoke(ReturnErrno(ENOENT)));
+ expect_mkdir(&mkdir_unique);
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([&](auto in) {
+ return (in.header.opcode == FUSE_INTERRUPT &&
+ in.body.interrupt.unique == mkdir_unique);
+ }, Eq(true)),
+ _)
+ ).WillOnce(Invoke([&](auto in, auto &out) {
+ // First complete the mkdir request
+ std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
+ out0->header.unique = mkdir_unique;
+ SET_OUT_HEADER_LEN(*out0, entry);
+ out0->body.create.entry.attr.mode = S_IFDIR | MODE;
+ out0->body.create.entry.nodeid = ino;
+ out.push_back(std::move(out0));
+
+ // Then, respond EAGAIN to the interrupt request
+ std::unique_ptr<mockfs_buf_out> out1(new mockfs_buf_out);
+ out1->header.unique = in.header.unique;
+ out1->header.error = -EAGAIN;
+ out1->header.len = sizeof(out1->header);
+ out.push_back(std::move(out1));
+ }));
+ EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0)
+ .InSequence(seq)
+ .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
+ SET_OUT_HEADER_LEN(out, entry);
+ out.body.entry.attr.mode = S_IFDIR | MODE;
+ out.body.entry.nodeid = ino;
+ out.body.entry.attr.nlink = 2;
+ })));
+
+ setup_interruptor(self);
+ EXPECT_EQ(0, mkdir(FULLDIRPATH0, MODE)) << strerror(errno);
+ /*
+ * The final syscall simply ensures that the test's main thread doesn't
+ * end before the daemon finishes responding to the FUSE_INTERRUPT.
+ */
+ EXPECT_EQ(0, access(FULLDIRPATH0, F_OK)) << strerror(errno);
+}
+
+/*
+ * If a FUSE file system returns ENOSYS for a FUSE_INTERRUPT operation, the
+ * kernel should not attempt to interrupt any other operations on that mount
+ * point.
+ */
+TEST_F(Intr, enosys)
+{
+ uint64_t ino0 = 42, ino1 = 43;;
+ uint64_t mkdir_unique;
+ pthread_t self, th0;
+ sem_t sem0, sem1;
+ void *thr0_value;
+ Sequence seq;
+
+ self = pthread_self();
+ ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno);
+ ASSERT_EQ(0, sem_init(&sem1, 0, 0)) << strerror(errno);
+
+ EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH1)
+ .WillOnce(Invoke(ReturnErrno(ENOENT)));
+ EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0)
+ .WillOnce(Invoke(ReturnErrno(ENOENT)));
+ expect_mkdir(&mkdir_unique);
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([&](auto in) {
+ return (in.header.opcode == FUSE_INTERRUPT &&
+ in.body.interrupt.unique == mkdir_unique);
+ }, Eq(true)),
+ _)
+ ).InSequence(seq)
+ .WillOnce(Invoke([&](auto in, auto &out) {
+ // reject FUSE_INTERRUPT and respond to the FUSE_MKDIR
+ std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
+ std::unique_ptr<mockfs_buf_out> out1(new mockfs_buf_out);
+
+ out0->header.unique = in.header.unique;
+ out0->header.error = -ENOSYS;
+ out0->header.len = sizeof(out0->header);
+ out.push_back(std::move(out0));
+
+ SET_OUT_HEADER_LEN(*out1, entry);
+ out1->body.create.entry.attr.mode = S_IFDIR | MODE;
+ out1->body.create.entry.nodeid = ino1;
+ out1->header.unique = mkdir_unique;
+ out.push_back(std::move(out1));
+ }));
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([&](auto in) {
+ return (in.header.opcode == FUSE_MKDIR);
+ }, Eq(true)),
+ _)
+ ).InSequence(seq)
+ .WillOnce(Invoke([&](auto in, auto &out) {
+ std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
+
+ sem_post(&sem0);
+ sem_wait(&sem1);
+
+ SET_OUT_HEADER_LEN(*out0, entry);
+ out0->body.create.entry.attr.mode = S_IFDIR | MODE;
+ out0->body.create.entry.nodeid = ino0;
+ out0->header.unique = in.header.unique;
+ out.push_back(std::move(out0));
+ }));
+
+ setup_interruptor(self);
+ /* First mkdir operation should finish synchronously */
+ ASSERT_EQ(0, mkdir(FULLDIRPATH1, MODE)) << strerror(errno);
+
+ ASSERT_EQ(0, pthread_create(&th0, NULL, mkdir0, NULL))
+ << strerror(errno);
+
+ sem_wait(&sem0);
+ /*
+ * th0 should be blocked waiting for the fuse daemon thread.
+ * Signal it. No FUSE_INTERRUPT should result
+ */
+ pthread_kill(th0, SIGUSR1);
+ /* Allow the daemon thread to proceed */
+ sem_post(&sem1);
+ pthread_join(th0, &thr0_value);
+ /* Second mkdir should've finished without error */
+ EXPECT_EQ(0, (intptr_t)thr0_value);
+}
+
+/*
+ * A FUSE filesystem is legally allowed to ignore INTERRUPT operations, and
+ * complete the original operation whenever it damn well pleases.
+ */
+/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */
+TEST_F(Intr, ignore)
+{
+ uint64_t ino = 42;
+ pthread_t self;
+ uint64_t mkdir_unique;
+
+ self = pthread_self();
+
+ EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0)
+ .WillOnce(Invoke(ReturnErrno(ENOENT)));
+ expect_mkdir(&mkdir_unique);
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([&](auto in) {
+ return (in.header.opcode == FUSE_INTERRUPT &&
+ in.body.interrupt.unique == mkdir_unique);
+ }, Eq(true)),
+ _)
+ ).WillOnce(Invoke([&](auto in __unused, auto &out) {
+ // Ignore FUSE_INTERRUPT; respond to the FUSE_MKDIR
+ std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
+ out0->header.unique = mkdir_unique;
+ SET_OUT_HEADER_LEN(*out0, entry);
+ out0->body.create.entry.attr.mode = S_IFDIR | MODE;
+ out0->body.create.entry.nodeid = ino;
+ out.push_back(std::move(out0));
+ }));
+
+ setup_interruptor(self);
+ ASSERT_EQ(0, mkdir(FULLDIRPATH0, MODE)) << strerror(errno);
+}
+
+/*
+ * A restartable operation (basically, anything except write or setextattr)
+ * that hasn't yet been sent to userland can be interrupted without sending
+ * FUSE_INTERRUPT, and will be automatically restarted.
+ */
+TEST_F(Intr, in_kernel_restartable)
+{
+ const char FULLPATH1[] = "mountpoint/other_file.txt";
+ const char RELPATH1[] = "other_file.txt";
+ uint64_t ino0 = 42, ino1 = 43;
+ int fd1;
+ pthread_t self, th0, th1;
+ sem_t sem0, sem1;
+ void *thr0_value, *thr1_value;
+
+ ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno);
+ ASSERT_EQ(0, sem_init(&sem1, 0, 0)) << strerror(errno);
+ self = pthread_self();
+
+ EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0)
+ .WillOnce(Invoke(ReturnErrno(ENOENT)));
+ expect_lookup(RELPATH1, ino1);
+ expect_open(ino1, 0, 1);
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ return (in.header.opcode == FUSE_MKDIR);
+ }, Eq(true)),
+ _)
+ ).WillOnce(Invoke(ReturnImmediate([&](auto in __unused, auto& out) {
+ /* Let the next write proceed */
+ sem_post(&sem1);
+ /* Pause the daemon thread so it won't read the next op */
+ sem_wait(&sem0);
+
+ SET_OUT_HEADER_LEN(out, entry);
+ out.body.create.entry.attr.mode = S_IFDIR | MODE;
+ out.body.create.entry.nodeid = ino0;
+ })));
+ FuseTest::expect_read(ino1, 0, FILESIZE, 0, NULL);
+
+ fd1 = open(FULLPATH1, O_RDONLY);
+ ASSERT_LE(0, fd1) << strerror(errno);
+
+ /* Use a separate thread for each operation */
+ ASSERT_EQ(0, pthread_create(&th0, NULL, mkdir0, NULL))
+ << strerror(errno);
+
+ sem_wait(&sem1); /* Sequence the two operations */
+
+ ASSERT_EQ(0, pthread_create(&th1, NULL, read1, (void*)(intptr_t)fd1))
+ << strerror(errno);
+
+ setup_interruptor(self, true);
+
+ pause(); /* Wait for signal */
+
+ /* Unstick the daemon */
+ ASSERT_EQ(0, sem_post(&sem0)) << strerror(errno);
+
+ /* Wait awhile to make sure the signal generates no FUSE_INTERRUPT */
+ nap();
+
+ pthread_join(th1, &thr1_value);
+ pthread_join(th0, &thr0_value);
+ EXPECT_EQ(0, (intptr_t)thr1_value);
+ EXPECT_EQ(0, (intptr_t)thr0_value);
+ sem_destroy(&sem1);
+ sem_destroy(&sem0);
+
+ leak(fd1);
+}
+
+/*
+ * An operation that hasn't yet been sent to userland can be interrupted
+ * without sending FUSE_INTERRUPT. If it's a non-restartable operation (write
+ * or setextattr) it will return EINTR.
+ */
+TEST_F(Intr, in_kernel_nonrestartable)
+{
+ const char FULLPATH1[] = "mountpoint/other_file.txt";
+ const char RELPATH1[] = "other_file.txt";
+ const char value[] = "whatever";
+ ssize_t value_len = strlen(value) + 1;
+ uint64_t ino0 = 42, ino1 = 43;
+ int ns = EXTATTR_NAMESPACE_USER;
+ int fd1;
+ pthread_t self, th0;
+ sem_t sem0, sem1;
+ void *thr0_value;
+ ssize_t r;
+
+ ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno);
+ ASSERT_EQ(0, sem_init(&sem1, 0, 0)) << strerror(errno);
+ self = pthread_self();
+
+ EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0)
+ .WillOnce(Invoke(ReturnErrno(ENOENT)));
+ expect_lookup(RELPATH1, ino1);
+ expect_open(ino1, 0, 1);
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ return (in.header.opcode == FUSE_MKDIR);
+ }, Eq(true)),
+ _)
+ ).WillOnce(Invoke(ReturnImmediate([&](auto in __unused, auto& out) {
+ /* Let the next write proceed */
+ sem_post(&sem1);
+ /* Pause the daemon thread so it won't read the next op */
+ sem_wait(&sem0);
+
+ SET_OUT_HEADER_LEN(out, entry);
+ out.body.create.entry.attr.mode = S_IFDIR | MODE;
+ out.body.create.entry.nodeid = ino0;
+ })));
+
+ fd1 = open(FULLPATH1, O_WRONLY);
+ ASSERT_LE(0, fd1) << strerror(errno);
+
+ /* Use a separate thread for the first write */
+ ASSERT_EQ(0, pthread_create(&th0, NULL, mkdir0, NULL))
+ << strerror(errno);
+
+ sem_wait(&sem1); /* Sequence the two operations */
+
+ setup_interruptor(self, true);
+
+ r = extattr_set_fd(fd1, ns, "foo", (const void*)value, value_len);
+ EXPECT_NE(0, r);
+ EXPECT_EQ(EINTR, errno);
+
+ /* Unstick the daemon */
+ ASSERT_EQ(0, sem_post(&sem0)) << strerror(errno);
+
+ /* Wait awhile to make sure the signal generates no FUSE_INTERRUPT */
+ nap();
+
+ pthread_join(th0, &thr0_value);
+ EXPECT_EQ(0, (intptr_t)thr0_value);
+ sem_destroy(&sem1);
+ sem_destroy(&sem0);
+
+ leak(fd1);
+}
+
+/*
+ * A syscall that gets interrupted while blocking on FUSE I/O should send a
+ * FUSE_INTERRUPT command to the fuse filesystem, which should then send EINTR
+ * in response to the _original_ operation. The kernel should ultimately
+ * return EINTR to userspace
+ */
+/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */
+TEST_F(Intr, in_progress)
+{
+ pthread_t self;
+ uint64_t mkdir_unique;
+
+ self = pthread_self();
+
+ EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0)
+ .WillOnce(Invoke(ReturnErrno(ENOENT)));
+ expect_mkdir(&mkdir_unique);
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([&](auto in) {
+ return (in.header.opcode == FUSE_INTERRUPT &&
+ in.body.interrupt.unique == mkdir_unique);
+ }, Eq(true)),
+ _)
+ ).WillOnce(Invoke([&](auto in __unused, auto &out) {
+ std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
+ out0->header.error = -EINTR;
+ out0->header.unique = mkdir_unique;
+ out0->header.len = sizeof(out0->header);
+ out.push_back(std::move(out0));
+ }));
+
+ setup_interruptor(self);
+ ASSERT_EQ(-1, mkdir(FULLDIRPATH0, MODE));
+ EXPECT_EQ(EINTR, errno);
+}
+
+/* Reads should also be interruptible */
+TEST_F(Intr, in_progress_read)
+{
+ const char FULLPATH[] = "mountpoint/some_file.txt";
+ const char RELPATH[] = "some_file.txt";
+ const size_t bufsize = 80;
+ char buf[bufsize];
+ uint64_t ino = 42;
+ int fd;
+ pthread_t self;
+ uint64_t read_unique;
+
+ self = pthread_self();
+
+ expect_lookup(RELPATH, ino);
+ expect_open(ino, 0, 1);
+ expect_read(ino, &read_unique);
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([&](auto in) {
+ return (in.header.opcode == FUSE_INTERRUPT &&
+ in.body.interrupt.unique == read_unique);
+ }, Eq(true)),
+ _)
+ ).WillOnce(Invoke([&](auto in __unused, auto &out) {
+ std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
+ out0->header.error = -EINTR;
+ out0->header.unique = read_unique;
+ out0->header.len = sizeof(out0->header);
+ out.push_back(std::move(out0));
+ }));
+
+ fd = open(FULLPATH, O_RDONLY);
+ ASSERT_LE(0, fd) << strerror(errno);
+
+ setup_interruptor(self);
+ ASSERT_EQ(-1, read(fd, buf, bufsize));
+ EXPECT_EQ(EINTR, errno);
+
+ leak(fd);
+}
+
+/*
+ * When mounted with -o nointr, fusefs will block signals while waiting for the
+ * server.
+ */
+TEST_F(Nointr, block)
+{
+ uint64_t ino = 42;
+ pthread_t self;
+ sem_t sem0;
+
+ ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno);
+ signaled_semaphore = &sem0;
+ self = pthread_self();
+
+ EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0)
+ .WillOnce(Invoke(ReturnErrno(ENOENT)));
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ return (in.header.opcode == FUSE_MKDIR);
+ }, Eq(true)),
+ _)
+ ).WillOnce(Invoke(ReturnImmediate([&](auto in __unused, auto& out) {
+ /* Let the killer proceed */
+ sem_post(blocked_semaphore);
+
+ /* Wait until after the signal has been sent */
+ sem_wait(signaled_semaphore);
+ /* Allow time for the mkdir thread to receive the signal */
+ nap();
+
+ /* Finally, complete the original op */
+ SET_OUT_HEADER_LEN(out, entry);
+ out.body.create.entry.attr.mode = S_IFDIR | MODE;
+ out.body.create.entry.nodeid = ino;
+ })));
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([&](auto in) {
+ return (in.header.opcode == FUSE_INTERRUPT);
+ }, Eq(true)),
+ _)
+ ).Times(0);
+
+ setup_interruptor(self);
+ ASSERT_EQ(0, mkdir(FULLDIRPATH0, MODE)) << strerror(errno);
+
+ sem_destroy(&sem0);
+}
+
+/* FUSE_INTERRUPT operations should take priority over other pending ops */
+TEST_F(Intr, priority)
+{
+ Sequence seq;
+ uint64_t ino1 = 43;
+ uint64_t mkdir_unique;
+ pthread_t th0;
+ sem_t sem0, sem1;
+
+ ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno);
+ ASSERT_EQ(0, sem_init(&sem1, 0, 0)) << strerror(errno);
+
+ EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0)
+ .WillOnce(Invoke(ReturnErrno(ENOENT)));
+ EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH1)
+ .WillOnce(Invoke(ReturnErrno(ENOENT)));
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([=](auto in) {
+ return (in.header.opcode == FUSE_MKDIR);
+ }, Eq(true)),
+ _)
+ ).InSequence(seq)
+ .WillOnce(Invoke(ReturnImmediate([&](auto in, auto& out) {
+ mkdir_unique = in.header.unique;
+
+ /* Let the next mkdir proceed */
+ sem_post(&sem1);
+
+ /* Pause the daemon thread so it won't read the next op */
+ sem_wait(&sem0);
+
+ /* Finally, interrupt the original op */
+ out.header.error = -EINTR;
+ out.header.unique = mkdir_unique;
+ out.header.len = sizeof(out.header);
+ })));
+ /*
+ * FUSE_INTERRUPT should be received before the second FUSE_MKDIR,
+ * even though it was generated later
+ */
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([&](auto in) {
+ return (in.header.opcode == FUSE_INTERRUPT &&
+ in.body.interrupt.unique == mkdir_unique);
+ }, Eq(true)),
+ _)
+ ).InSequence(seq)
+ .WillOnce(Invoke(ReturnErrno(EAGAIN)));
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([&](auto in) {
+ return (in.header.opcode == FUSE_MKDIR);
+ }, Eq(true)),
+ _)
+ ).InSequence(seq)
+ .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
+ SET_OUT_HEADER_LEN(out, entry);
+ out.body.create.entry.attr.mode = S_IFDIR | MODE;
+ out.body.create.entry.nodeid = ino1;
+ })));
+
+ /* Use a separate thread for the first mkdir */
+ ASSERT_EQ(0, pthread_create(&th0, NULL, mkdir0, NULL))
+ << strerror(errno);
+
+ signaled_semaphore = &sem0;
+
+ sem_wait(&sem1); /* Sequence the two mkdirs */
+ setup_interruptor(th0, true);
+ ASSERT_EQ(0, mkdir(FULLDIRPATH1, MODE)) << strerror(errno);
+
+ pthread_join(th0, NULL);
+ sem_destroy(&sem1);
+ sem_destroy(&sem0);
+}
+
+/*
+ * If the FUSE filesystem receives the FUSE_INTERRUPT operation before
+ * processing the original, then it should wait for "some timeout" for the
+ * original operation to arrive. If not, it should send EAGAIN to the
+ * INTERRUPT operation, and the kernel should requeue the INTERRUPT.
+ *
+ * In this test, we'll pretend that the INTERRUPT arrives too soon, gets
+ * EAGAINed, then the kernel requeues it, and the second time around it
+ * successfully interrupts the original
+ */
+/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */
+TEST_F(Intr, too_soon)
+{
+ Sequence seq;
+ pthread_t self;
+ uint64_t mkdir_unique;
+
+ self = pthread_self();
+
+ EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0)
+ .WillOnce(Invoke(ReturnErrno(ENOENT)));
+ expect_mkdir(&mkdir_unique);
+
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([&](auto in) {
+ return (in.header.opcode == FUSE_INTERRUPT &&
+ in.body.interrupt.unique == mkdir_unique);
+ }, Eq(true)),
+ _)
+ ).InSequence(seq)
+ .WillOnce(Invoke(ReturnErrno(EAGAIN)));
+
+ EXPECT_CALL(*m_mock, process(
+ ResultOf([&](auto in) {
+ return (in.header.opcode == FUSE_INTERRUPT &&
+ in.body.interrupt.unique == mkdir_unique);
+ }, Eq(true)),
+ _)
+ ).InSequence(seq)
+ .WillOnce(Invoke([&](auto in __unused, auto &out __unused) {
+ std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
+ out0->header.error = -EINTR;
+ out0->header.unique = mkdir_unique;
+ out0->header.len = sizeof(out0->header);
+ out.push_back(std::move(out0));
+ }));
+
+ setup_interruptor(self);
+ ASSERT_EQ(-1, mkdir(FULLDIRPATH0, MODE));
+ EXPECT_EQ(EINTR, errno);
+}
+
+
+// TODO: add a test where write returns EWOULDBLOCK