2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
4 * Copyright (c) 2019 The FreeBSD Foundation
6 * This software was developed by BFF Storage Systems, LLC under sponsorship
7 * from the FreeBSD Foundation.
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in the
16 * documentation and/or other materials provided with the distribution.
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32 #include <sys/types.h>
33 #include <sys/extattr.h>
38 #include <semaphore.h>
45 using namespace testing;
47 /* Initial size of files used by these tests */
48 const off_t FILESIZE = 1000;
49 /* Access mode used by all directories in these tests */
50 const mode_t MODE = 0755;
51 const char FULLDIRPATH0[] = "mountpoint/some_dir";
52 const char RELDIRPATH0[] = "some_dir";
53 const char FULLDIRPATH1[] = "mountpoint/other_dir";
54 const char RELDIRPATH1[] = "other_dir";
56 static sem_t *blocked_semaphore;
57 static sem_t *signaled_semaphore;
59 static bool killer_should_sleep = false;
61 /* Don't do anything; all we care about is that the syscall gets interrupted */
62 void sigusr2_handler(int __unused sig) {
64 printf("Signaled! thread %p\n", pthread_self());
69 void* killer(void* target) {
70 /* Wait until the main thread is blocked in fdisp_wait_answ */
71 if (killer_should_sleep)
74 sem_wait(blocked_semaphore);
76 printf("Signalling! thread %p\n", target);
77 pthread_kill((pthread_t)target, SIGUSR2);
78 if (signaled_semaphore != NULL)
79 sem_post(signaled_semaphore);
84 class Interrupt: public FuseTest {
88 Interrupt(): m_child(NULL) {};
90 void expect_lookup(const char *relpath, uint64_t ino)
92 FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, FILESIZE, 1);
96 * Expect a FUSE_MKDIR but don't reply. Instead, just record the unique value
97 * to the provided pointer
99 void expect_mkdir(uint64_t *mkdir_unique)
101 EXPECT_CALL(*m_mock, process(
102 ResultOf([=](auto in) {
103 return (in.header.opcode == FUSE_MKDIR);
106 ).WillOnce(Invoke([=](auto in, auto &out __unused) {
107 *mkdir_unique = in.header.unique;
108 sem_post(blocked_semaphore);
113 * Expect a FUSE_READ but don't reply. Instead, just record the unique value
114 * to the provided pointer
116 void expect_read(uint64_t ino, uint64_t *read_unique)
118 EXPECT_CALL(*m_mock, process(
119 ResultOf([=](auto in) {
120 return (in.header.opcode == FUSE_READ &&
121 in.header.nodeid == ino);
124 ).WillOnce(Invoke([=](auto in, auto &out __unused) {
125 *read_unique = in.header.unique;
126 sem_post(blocked_semaphore);
131 * Expect a FUSE_WRITE but don't reply. Instead, just record the unique value
132 * to the provided pointer
134 void expect_write(uint64_t ino, uint64_t *write_unique)
136 EXPECT_CALL(*m_mock, process(
137 ResultOf([=](auto in) {
138 return (in.header.opcode == FUSE_WRITE &&
139 in.header.nodeid == ino);
142 ).WillOnce(Invoke([=](auto in, auto &out __unused) {
143 *write_unique = in.header.unique;
144 sem_post(blocked_semaphore);
148 void setup_interruptor(pthread_t target, bool sleep = false)
150 ASSERT_NE(SIG_ERR, signal(SIGUSR2, sigusr2_handler)) << strerror(errno);
151 killer_should_sleep = sleep;
152 ASSERT_EQ(0, pthread_create(&m_child, NULL, killer, (void*)target))
157 const int mprot = PROT_READ | PROT_WRITE;
158 const int mflags = MAP_ANON | MAP_SHARED;
160 signaled_semaphore = NULL;
162 blocked_semaphore = (sem_t*)mmap(NULL, sizeof(*blocked_semaphore),
163 mprot, mflags, -1, 0);
164 ASSERT_NE(MAP_FAILED, blocked_semaphore) << strerror(errno);
165 ASSERT_EQ(0, sem_init(blocked_semaphore, 1, 0)) << strerror(errno);
166 ASSERT_EQ(0, siginterrupt(SIGUSR2, 1));
174 if (m_child != NULL) {
175 pthread_join(m_child, NULL);
177 bzero(&sa, sizeof(sa));
178 sa.sa_handler = SIG_DFL;
179 sigaction(SIGUSR2, &sa, NULL);
181 sem_destroy(blocked_semaphore);
182 munmap(blocked_semaphore, sizeof(*blocked_semaphore));
184 FuseTest::TearDown();
188 static void* mkdir0(void* arg __unused) {
191 r = mkdir(FULLDIRPATH0, MODE);
195 return (void*)(intptr_t)errno;
198 static void* read1(void* arg) {
199 const size_t bufsize = FILESIZE;
201 int fd = (int)(intptr_t)arg;
204 r = read(fd, buf, bufsize);
208 return (void*)(intptr_t)errno;
212 * An interrupt operation that gets received after the original command is
213 * complete should generate an EAGAIN response.
215 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */
216 TEST_F(Interrupt, already_complete)
220 uint64_t mkdir_unique = 0;
223 self = pthread_self();
225 EXPECT_LOOKUP(1, RELDIRPATH0)
227 .WillOnce(Invoke(ReturnErrno(ENOENT)));
228 expect_mkdir(&mkdir_unique);
229 EXPECT_CALL(*m_mock, process(
230 ResultOf([&](auto in) {
231 return (in.header.opcode == FUSE_INTERRUPT &&
232 in.body.interrupt.unique == mkdir_unique);
235 ).WillOnce(Invoke([&](auto in, auto &out) {
236 // First complete the mkdir request
237 std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
238 out0->header.unique = mkdir_unique;
239 SET_OUT_HEADER_LEN(*out0, entry);
240 out0->body.create.entry.attr.mode = S_IFDIR | MODE;
241 out0->body.create.entry.nodeid = ino;
242 out.push_back(std::move(out0));
244 // Then, respond EAGAIN to the interrupt request
245 std::unique_ptr<mockfs_buf_out> out1(new mockfs_buf_out);
246 out1->header.unique = in.header.unique;
247 out1->header.error = -EAGAIN;
248 out1->header.len = sizeof(out1->header);
249 out.push_back(std::move(out1));
251 EXPECT_LOOKUP(1, RELDIRPATH0)
253 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
254 SET_OUT_HEADER_LEN(out, entry);
255 out.body.entry.attr.mode = S_IFDIR | MODE;
256 out.body.entry.nodeid = ino;
257 out.body.entry.attr.nlink = 2;
260 setup_interruptor(self);
261 EXPECT_EQ(0, mkdir(FULLDIRPATH0, MODE)) << strerror(errno);
263 * The final syscall simply ensures that the test's main thread doesn't
264 * end before the daemon finishes responding to the FUSE_INTERRUPT.
266 EXPECT_EQ(0, access(FULLDIRPATH0, F_OK)) << strerror(errno);
270 * If a FUSE file system returns ENOSYS for a FUSE_INTERRUPT operation, the
271 * kernel should not attempt to interrupt any other operations on that mount
274 TEST_F(Interrupt, enosys)
276 uint64_t ino0 = 42, ino1 = 43;;
277 uint64_t mkdir_unique;
283 self = pthread_self();
284 ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno);
285 ASSERT_EQ(0, sem_init(&sem1, 0, 0)) << strerror(errno);
287 EXPECT_LOOKUP(1, RELDIRPATH1).WillOnce(Invoke(ReturnErrno(ENOENT)));
288 EXPECT_LOOKUP(1, RELDIRPATH0).WillOnce(Invoke(ReturnErrno(ENOENT)));
289 expect_mkdir(&mkdir_unique);
290 EXPECT_CALL(*m_mock, process(
291 ResultOf([&](auto in) {
292 return (in.header.opcode == FUSE_INTERRUPT &&
293 in.body.interrupt.unique == mkdir_unique);
297 .WillOnce(Invoke([&](auto in, auto &out) {
298 // reject FUSE_INTERRUPT and respond to the FUSE_MKDIR
299 std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
300 std::unique_ptr<mockfs_buf_out> out1(new mockfs_buf_out);
302 out0->header.unique = in.header.unique;
303 out0->header.error = -ENOSYS;
304 out0->header.len = sizeof(out0->header);
305 out.push_back(std::move(out0));
307 SET_OUT_HEADER_LEN(*out1, entry);
308 out1->body.create.entry.attr.mode = S_IFDIR | MODE;
309 out1->body.create.entry.nodeid = ino1;
310 out1->header.unique = mkdir_unique;
311 out.push_back(std::move(out1));
313 EXPECT_CALL(*m_mock, process(
314 ResultOf([&](auto in) {
315 return (in.header.opcode == FUSE_MKDIR);
319 .WillOnce(Invoke([&](auto in, auto &out) {
320 std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
325 SET_OUT_HEADER_LEN(*out0, entry);
326 out0->body.create.entry.attr.mode = S_IFDIR | MODE;
327 out0->body.create.entry.nodeid = ino0;
328 out0->header.unique = in.header.unique;
329 out.push_back(std::move(out0));
332 setup_interruptor(self);
333 /* First mkdir operation should finish synchronously */
334 ASSERT_EQ(0, mkdir(FULLDIRPATH1, MODE)) << strerror(errno);
336 ASSERT_EQ(0, pthread_create(&th0, NULL, mkdir0, NULL))
341 * th0 should be blocked waiting for the fuse daemon thread.
342 * Signal it. No FUSE_INTERRUPT should result
344 pthread_kill(th0, SIGUSR1);
345 /* Allow the daemon thread to proceed */
347 pthread_join(th0, &thr0_value);
348 /* Second mkdir should've finished without error */
349 EXPECT_EQ(0, (intptr_t)thr0_value);
353 * Upon receipt of a fatal signal, fusefs should return ASAP after sending
356 TEST_F(Interrupt, fatal_signal)
360 uint64_t mkdir_unique;
363 ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno);
364 self = pthread_self();
366 EXPECT_LOOKUP(1, RELDIRPATH0).WillOnce(Invoke(ReturnErrno(ENOENT)));
367 expect_mkdir(&mkdir_unique);
368 EXPECT_CALL(*m_mock, process(
369 ResultOf([&](auto in) {
370 return (in.header.opcode == FUSE_INTERRUPT &&
371 in.body.interrupt.unique == mkdir_unique);
374 ).WillOnce(Invoke([&](auto in __unused, auto &out __unused) {
376 /* Don't respond. The process should exit anyway */
379 fork(false, &status, [&] {
386 /* SIGUSR2 terminates the process by default */
387 bzero(&sa, sizeof(sa));
388 sa.sa_handler = SIG_DFL;
389 r = sigaction(SIGUSR2, &sa, NULL);
394 self = pthread_self();
395 r = pthread_create(&killer_th, NULL, killer, (void*)self);
397 perror("pthread_create");
401 mkdir(FULLDIRPATH0, MODE);
404 ASSERT_EQ(SIGUSR2, WTERMSIG(status));
406 EXPECT_EQ(0, sem_wait(&sem)) << strerror(errno);
411 * A FUSE filesystem is legally allowed to ignore INTERRUPT operations, and
412 * complete the original operation whenever it damn well pleases.
414 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */
415 TEST_F(Interrupt, ignore)
419 uint64_t mkdir_unique;
421 self = pthread_self();
423 EXPECT_LOOKUP(1, RELDIRPATH0).WillOnce(Invoke(ReturnErrno(ENOENT)));
424 expect_mkdir(&mkdir_unique);
425 EXPECT_CALL(*m_mock, process(
426 ResultOf([&](auto in) {
427 return (in.header.opcode == FUSE_INTERRUPT &&
428 in.body.interrupt.unique == mkdir_unique);
431 ).WillOnce(Invoke([&](auto in __unused, auto &out) {
432 // Ignore FUSE_INTERRUPT; respond to the FUSE_MKDIR
433 std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
434 out0->header.unique = mkdir_unique;
435 SET_OUT_HEADER_LEN(*out0, entry);
436 out0->body.create.entry.attr.mode = S_IFDIR | MODE;
437 out0->body.create.entry.nodeid = ino;
438 out.push_back(std::move(out0));
441 setup_interruptor(self);
442 ASSERT_EQ(0, mkdir(FULLDIRPATH0, MODE)) << strerror(errno);
446 * A restartable operation (basically, anything except write or setextattr)
447 * that hasn't yet been sent to userland can be interrupted without sending
448 * FUSE_INTERRUPT, and will be automatically restarted.
450 TEST_F(Interrupt, in_kernel_restartable)
452 const char FULLPATH1[] = "mountpoint/other_file.txt";
453 const char RELPATH1[] = "other_file.txt";
454 uint64_t ino0 = 42, ino1 = 43;
456 pthread_t self, th0, th1;
458 void *thr0_value, *thr1_value;
460 ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno);
461 ASSERT_EQ(0, sem_init(&sem1, 0, 0)) << strerror(errno);
462 self = pthread_self();
464 EXPECT_LOOKUP(1, RELDIRPATH0).WillOnce(Invoke(ReturnErrno(ENOENT)));
465 expect_lookup(RELPATH1, ino1);
466 expect_open(ino1, 0, 1);
467 EXPECT_CALL(*m_mock, process(
468 ResultOf([=](auto in) {
469 return (in.header.opcode == FUSE_MKDIR);
472 ).WillOnce(Invoke(ReturnImmediate([&](auto in __unused, auto& out) {
473 /* Let the next write proceed */
475 /* Pause the daemon thread so it won't read the next op */
478 SET_OUT_HEADER_LEN(out, entry);
479 out.body.create.entry.attr.mode = S_IFDIR | MODE;
480 out.body.create.entry.nodeid = ino0;
482 FuseTest::expect_read(ino1, 0, FILESIZE, 0, NULL);
484 fd1 = open(FULLPATH1, O_RDONLY);
485 ASSERT_LE(0, fd1) << strerror(errno);
487 /* Use a separate thread for each operation */
488 ASSERT_EQ(0, pthread_create(&th0, NULL, mkdir0, NULL))
491 sem_wait(&sem1); /* Sequence the two operations */
493 ASSERT_EQ(0, pthread_create(&th1, NULL, read1, (void*)(intptr_t)fd1))
496 setup_interruptor(self, true);
498 pause(); /* Wait for signal */
500 /* Unstick the daemon */
501 ASSERT_EQ(0, sem_post(&sem0)) << strerror(errno);
503 /* Wait awhile to make sure the signal generates no FUSE_INTERRUPT */
506 pthread_join(th1, &thr1_value);
507 pthread_join(th0, &thr0_value);
508 EXPECT_EQ(0, (intptr_t)thr1_value);
509 EXPECT_EQ(0, (intptr_t)thr0_value);
515 * An operation that hasn't yet been sent to userland can be interrupted
516 * without sending FUSE_INTERRUPT. If it's a non-restartable operation (write
517 * or setextattr) it will return EINTR.
519 TEST_F(Interrupt, in_kernel_nonrestartable)
521 const char FULLPATH1[] = "mountpoint/other_file.txt";
522 const char RELPATH1[] = "other_file.txt";
523 const char value[] = "whatever";
524 ssize_t value_len = strlen(value) + 1;
525 uint64_t ino0 = 42, ino1 = 43;
526 int ns = EXTATTR_NAMESPACE_USER;
533 ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno);
534 ASSERT_EQ(0, sem_init(&sem1, 0, 0)) << strerror(errno);
535 self = pthread_self();
537 EXPECT_LOOKUP(1, RELDIRPATH0).WillOnce(Invoke(ReturnErrno(ENOENT)));
538 expect_lookup(RELPATH1, ino1);
539 expect_open(ino1, 0, 1);
540 EXPECT_CALL(*m_mock, process(
541 ResultOf([=](auto in) {
542 return (in.header.opcode == FUSE_MKDIR);
545 ).WillOnce(Invoke(ReturnImmediate([&](auto in __unused, auto& out) {
546 /* Let the next write proceed */
548 /* Pause the daemon thread so it won't read the next op */
551 SET_OUT_HEADER_LEN(out, entry);
552 out.body.create.entry.attr.mode = S_IFDIR | MODE;
553 out.body.create.entry.nodeid = ino0;
556 fd1 = open(FULLPATH1, O_WRONLY);
557 ASSERT_LE(0, fd1) << strerror(errno);
559 /* Use a separate thread for the first write */
560 ASSERT_EQ(0, pthread_create(&th0, NULL, mkdir0, NULL))
563 sem_wait(&sem1); /* Sequence the two operations */
565 setup_interruptor(self, true);
567 r = extattr_set_fd(fd1, ns, "foo", (void*)value, value_len);
568 EXPECT_EQ(EINTR, errno);
570 /* Unstick the daemon */
571 ASSERT_EQ(0, sem_post(&sem0)) << strerror(errno);
573 /* Wait awhile to make sure the signal generates no FUSE_INTERRUPT */
576 pthread_join(th0, &thr0_value);
577 EXPECT_EQ(0, (intptr_t)thr0_value);
583 * A syscall that gets interrupted while blocking on FUSE I/O should send a
584 * FUSE_INTERRUPT command to the fuse filesystem, which should then send EINTR
585 * in response to the _original_ operation. The kernel should ultimately
586 * return EINTR to userspace
588 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */
589 TEST_F(Interrupt, in_progress)
592 uint64_t mkdir_unique;
594 self = pthread_self();
596 EXPECT_LOOKUP(1, RELDIRPATH0).WillOnce(Invoke(ReturnErrno(ENOENT)));
597 expect_mkdir(&mkdir_unique);
598 EXPECT_CALL(*m_mock, process(
599 ResultOf([&](auto in) {
600 return (in.header.opcode == FUSE_INTERRUPT &&
601 in.body.interrupt.unique == mkdir_unique);
604 ).WillOnce(Invoke([&](auto in __unused, auto &out) {
605 std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
606 out0->header.error = -EINTR;
607 out0->header.unique = mkdir_unique;
608 out0->header.len = sizeof(out0->header);
609 out.push_back(std::move(out0));
612 setup_interruptor(self);
613 ASSERT_EQ(-1, mkdir(FULLDIRPATH0, MODE));
614 EXPECT_EQ(EINTR, errno);
617 /* Reads should also be interruptible */
618 TEST_F(Interrupt, in_progress_read)
620 const char FULLPATH[] = "mountpoint/some_file.txt";
621 const char RELPATH[] = "some_file.txt";
622 const size_t bufsize = 80;
627 uint64_t read_unique;
629 self = pthread_self();
631 expect_lookup(RELPATH, ino);
632 expect_open(ino, 0, 1);
633 expect_read(ino, &read_unique);
634 EXPECT_CALL(*m_mock, process(
635 ResultOf([&](auto in) {
636 return (in.header.opcode == FUSE_INTERRUPT &&
637 in.body.interrupt.unique == read_unique);
640 ).WillOnce(Invoke([&](auto in __unused, auto &out) {
641 std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
642 out0->header.error = -EINTR;
643 out0->header.unique = read_unique;
644 out0->header.len = sizeof(out0->header);
645 out.push_back(std::move(out0));
648 fd = open(FULLPATH, O_RDONLY);
649 ASSERT_LE(0, fd) << strerror(errno);
651 setup_interruptor(self);
652 ASSERT_EQ(-1, read(fd, buf, bufsize));
653 EXPECT_EQ(EINTR, errno);
656 /* FUSE_INTERRUPT operations should take priority over other pending ops */
657 TEST_F(Interrupt, priority)
661 uint64_t mkdir_unique;
665 ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno);
666 ASSERT_EQ(0, sem_init(&sem1, 0, 0)) << strerror(errno);
667 self = pthread_self();
669 EXPECT_LOOKUP(1, RELDIRPATH0).WillOnce(Invoke(ReturnErrno(ENOENT)));
670 EXPECT_LOOKUP(1, RELDIRPATH1).WillOnce(Invoke(ReturnErrno(ENOENT)));
671 EXPECT_CALL(*m_mock, process(
672 ResultOf([=](auto in) {
673 return (in.header.opcode == FUSE_MKDIR);
677 .WillOnce(Invoke(ReturnImmediate([&](auto in, auto& out) {
678 mkdir_unique = in.header.unique;
680 /* Let the next mkdir proceed */
683 /* Pause the daemon thread so it won't read the next op */
686 /* Finally, interrupt the original op */
687 out.header.error = -EINTR;
688 out.header.unique = mkdir_unique;
689 out.header.len = sizeof(out.header);
692 * FUSE_INTERRUPT should be received before the second FUSE_MKDIR,
693 * even though it was generated later
695 EXPECT_CALL(*m_mock, process(
696 ResultOf([&](auto in) {
697 return (in.header.opcode == FUSE_INTERRUPT &&
698 in.body.interrupt.unique == mkdir_unique);
702 .WillOnce(Invoke(ReturnErrno(EAGAIN)));
703 EXPECT_CALL(*m_mock, process(
704 ResultOf([&](auto in) {
705 return (in.header.opcode == FUSE_MKDIR);
709 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
710 SET_OUT_HEADER_LEN(out, entry);
711 out.body.create.entry.attr.mode = S_IFDIR | MODE;
712 out.body.create.entry.nodeid = ino1;
715 /* Use a separate thread for the first mkdir */
716 ASSERT_EQ(0, pthread_create(&th0, NULL, mkdir0, NULL))
719 signaled_semaphore = &sem0;
721 sem_wait(&sem1); /* Sequence the two mkdirs */
722 setup_interruptor(th0, true);
723 ASSERT_EQ(0, mkdir(FULLDIRPATH1, MODE)) << strerror(errno);
725 pthread_join(th0, NULL);
731 * If the FUSE filesystem receives the FUSE_INTERRUPT operation before
732 * processing the original, then it should wait for "some timeout" for the
733 * original operation to arrive. If not, it should send EAGAIN to the
734 * INTERRUPT operation, and the kernel should requeue the INTERRUPT.
736 * In this test, we'll pretend that the INTERRUPT arrives too soon, gets
737 * EAGAINed, then the kernel requeues it, and the second time around it
738 * successfully interrupts the original
740 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */
741 TEST_F(Interrupt, too_soon)
745 uint64_t mkdir_unique;
747 self = pthread_self();
749 EXPECT_LOOKUP(1, RELDIRPATH0).WillOnce(Invoke(ReturnErrno(ENOENT)));
750 expect_mkdir(&mkdir_unique);
752 EXPECT_CALL(*m_mock, process(
753 ResultOf([&](auto in) {
754 return (in.header.opcode == FUSE_INTERRUPT &&
755 in.body.interrupt.unique == mkdir_unique);
759 .WillOnce(Invoke(ReturnErrno(EAGAIN)));
761 EXPECT_CALL(*m_mock, process(
762 ResultOf([&](auto in) {
763 return (in.header.opcode == FUSE_INTERRUPT &&
764 in.body.interrupt.unique == mkdir_unique);
768 .WillOnce(Invoke([&](auto in __unused, auto &out __unused) {
769 std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
770 out0->header.error = -EINTR;
771 out0->header.unique = mkdir_unique;
772 out0->header.len = sizeof(out0->header);
773 out.push_back(std::move(out0));
776 setup_interruptor(self);
777 ASSERT_EQ(-1, mkdir(FULLDIRPATH0, MODE));
778 EXPECT_EQ(EINTR, errno);
782 // TODO: add a test where write returns EWOULDBLOCK