diff options
Diffstat (limited to 'tests/sys/fs/fusefs/interrupt.cc')
-rw-r--r-- | tests/sys/fs/fusefs/interrupt.cc | 796 |
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 |