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>
37 #include <semaphore.h>
44 using namespace testing;
46 /* Initial size of files used by these tests */
47 const off_t FILESIZE = 1000;
48 /* Access mode used by all directories in these tests */
49 const mode_t MODE = 0755;
50 const char FULLDIRPATH0[] = "mountpoint/some_dir";
51 const char RELDIRPATH0[] = "some_dir";
52 const char FULLDIRPATH1[] = "mountpoint/other_dir";
53 const char RELDIRPATH1[] = "other_dir";
55 static sem_t *signaled_semaphore;
57 /* Don't do anything; all we care about is that the syscall gets interrupted */
58 void sigusr2_handler(int __unused sig) {
60 printf("Signaled! thread %p\n", pthread_self());
65 void* killer(void* target) {
67 * Sleep for awhile so we can be mostly confident that the main thread
68 * is already blocked in write(2)
72 printf("Signalling! thread %p\n", target);
73 pthread_kill((pthread_t)target, SIGUSR2);
74 if (signaled_semaphore != NULL)
75 sem_post(signaled_semaphore);
80 class Interrupt: public FuseTest {
84 Interrupt(): m_child(NULL) {};
86 void expect_lookup(const char *relpath, uint64_t ino)
88 FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, FILESIZE, 1);
92 * Expect a FUSE_MKDIR but don't reply. Instead, just record the unique value
93 * to the provided pointer
95 void expect_mkdir(uint64_t *mkdir_unique)
97 EXPECT_CALL(*m_mock, process(
98 ResultOf([=](auto in) {
99 return (in->header.opcode == FUSE_MKDIR);
103 ).WillOnce(Invoke([=](auto in, auto &out __unused) {
104 *mkdir_unique = in->header.unique;
109 * Expect a FUSE_READ but don't reply. Instead, just record the unique value
110 * to the provided pointer
112 void expect_read(uint64_t ino, uint64_t *read_unique)
114 EXPECT_CALL(*m_mock, process(
115 ResultOf([=](auto in) {
116 return (in->header.opcode == FUSE_READ &&
117 in->header.nodeid == ino);
120 ).WillOnce(Invoke([=](auto in, auto &out __unused) {
121 *read_unique = in->header.unique;
126 * Expect a FUSE_WRITE but don't reply. Instead, just record the unique value
127 * to the provided pointer
129 void expect_write(uint64_t ino, uint64_t *write_unique)
131 EXPECT_CALL(*m_mock, process(
132 ResultOf([=](auto in) {
133 return (in->header.opcode == FUSE_WRITE &&
134 in->header.nodeid == ino);
137 ).WillOnce(Invoke([=](auto in, auto &out __unused) {
138 *write_unique = in->header.unique;
142 void setup_interruptor(pthread_t target)
144 ASSERT_NE(SIG_ERR, signal(SIGUSR2, sigusr2_handler)) << strerror(errno);
145 ASSERT_EQ(0, pthread_create(&m_child, NULL, killer, (void*)target))
150 signaled_semaphore = NULL;
157 if (m_child != NULL) {
158 pthread_join(m_child, NULL);
160 bzero(&sa, sizeof(sa));
161 sa.sa_handler = SIG_DFL;
162 sigaction(SIGUSR2, &sa, NULL);
164 FuseTest::TearDown();
168 static void* mkdir0(void* arg __unused) {
171 r = mkdir(FULLDIRPATH0, MODE);
175 return (void*)(intptr_t)errno;
178 static void* read1(void* arg) {
179 const size_t bufsize = FILESIZE;
181 int fd = (int)(intptr_t)arg;
184 r = read(fd, buf, bufsize);
188 return (void*)(intptr_t)errno;
192 * An interrupt operation that gets received after the original command is
193 * complete should generate an EAGAIN response.
195 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */
196 TEST_F(Interrupt, already_complete)
200 uint64_t mkdir_unique = 0;
202 self = pthread_self();
204 EXPECT_LOOKUP(1, RELDIRPATH0).WillOnce(Invoke(ReturnErrno(ENOENT)));
205 expect_mkdir(&mkdir_unique);
206 EXPECT_CALL(*m_mock, process(
207 ResultOf([&](auto in) {
208 return (in->header.opcode == FUSE_INTERRUPT &&
209 in->body.interrupt.unique == mkdir_unique);
212 ).WillOnce(Invoke([&](auto in, auto &out) {
213 // First complete the mkdir request
214 auto out0 = new mockfs_buf_out;
215 out0->header.unique = mkdir_unique;
216 SET_OUT_HEADER_LEN(out0, entry);
217 out0->body.create.entry.attr.mode = S_IFDIR | MODE;
218 out0->body.create.entry.nodeid = ino;
221 // Then, respond EAGAIN to the interrupt request
222 auto out1 = new mockfs_buf_out;
223 out1->header.unique = in->header.unique;
224 out1->header.error = -EAGAIN;
225 out1->header.len = sizeof(out1->header);
229 setup_interruptor(self);
230 EXPECT_EQ(0, mkdir(FULLDIRPATH0, MODE)) << strerror(errno);
234 * If a FUSE file system returns ENOSYS for a FUSE_INTERRUPT operation, the
235 * kernel should not attempt to interrupt any other operations on that mount
238 TEST_F(Interrupt, enosys)
240 uint64_t ino0 = 42, ino1 = 43;;
241 uint64_t mkdir_unique;
247 self = pthread_self();
248 ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno);
249 ASSERT_EQ(0, sem_init(&sem1, 0, 0)) << strerror(errno);
251 EXPECT_LOOKUP(1, RELDIRPATH1).WillOnce(Invoke(ReturnErrno(ENOENT)));
252 EXPECT_LOOKUP(1, RELDIRPATH0).WillOnce(Invoke(ReturnErrno(ENOENT)));
253 expect_mkdir(&mkdir_unique);
254 EXPECT_CALL(*m_mock, process(
255 ResultOf([&](auto in) {
256 return (in->header.opcode == FUSE_INTERRUPT &&
257 in->body.interrupt.unique == mkdir_unique);
261 .WillOnce(Invoke([&](auto in, auto &out) {
262 // reject FUSE_INTERRUPT and respond to the FUSE_WRITE
263 auto out0 = new mockfs_buf_out;
264 auto out1 = new mockfs_buf_out;
266 out0->header.unique = in->header.unique;
267 out0->header.error = -ENOSYS;
268 out0->header.len = sizeof(out0->header);
271 SET_OUT_HEADER_LEN(out1, entry);
272 out1->body.create.entry.attr.mode = S_IFDIR | MODE;
273 out1->body.create.entry.nodeid = ino1;
274 out1->header.unique = mkdir_unique;
277 EXPECT_CALL(*m_mock, process(
278 ResultOf([&](auto in) {
279 return (in->header.opcode == FUSE_MKDIR);
283 .WillOnce(Invoke([&](auto in, auto &out) {
284 auto out0 = new mockfs_buf_out;
289 SET_OUT_HEADER_LEN(out0, entry);
290 out0->body.create.entry.attr.mode = S_IFDIR | MODE;
291 out0->body.create.entry.nodeid = ino0;
292 out0->header.unique = in->header.unique;
296 setup_interruptor(self);
297 /* First mkdir operation should finish synchronously */
298 ASSERT_EQ(0, mkdir(FULLDIRPATH1, MODE)) << strerror(errno);
300 ASSERT_EQ(0, pthread_create(&th0, NULL, mkdir0, NULL))
305 * th0 should be blocked waiting for the fuse daemon thread.
306 * Signal it. No FUSE_INTERRUPT should result
308 pthread_kill(th0, SIGUSR1);
309 /* Allow the daemon thread to proceed */
311 pthread_join(th0, &thr0_value);
312 /* Second mkdir should've finished without error */
313 EXPECT_EQ(0, (intptr_t)thr0_value);
317 * Upon receipt of a fatal signal, fusefs should return ASAP after sending
320 TEST_F(Interrupt, fatal_signal)
324 uint64_t mkdir_unique;
326 self = pthread_self();
328 EXPECT_LOOKUP(1, RELDIRPATH0).WillOnce(Invoke(ReturnErrno(ENOENT)));
329 expect_mkdir(&mkdir_unique);
330 EXPECT_CALL(*m_mock, process(
331 ResultOf([&](auto in) {
332 return (in->header.opcode == FUSE_INTERRUPT &&
333 in->body.interrupt.unique == mkdir_unique);
336 ).WillOnce(Invoke([&](auto in __unused, auto &out __unused) {
337 /* Don't respond. The process should exit anyway */
340 fork(false, &status, [&] {
347 /* SIGUSR2 terminates the process by default */
348 bzero(&sa, sizeof(sa));
349 sa.sa_handler = SIG_DFL;
350 r = sigaction(SIGUSR2, &sa, NULL);
355 self = pthread_self();
356 r = pthread_create(&killer_th, NULL, killer, (void*)self);
358 perror("pthread_create");
362 mkdir(FULLDIRPATH0, MODE);
365 ASSERT_EQ(SIGUSR2, WTERMSIG(status));
369 * A FUSE filesystem is legally allowed to ignore INTERRUPT operations, and
370 * complete the original operation whenever it damn well pleases.
372 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */
373 TEST_F(Interrupt, ignore)
377 uint64_t mkdir_unique;
379 self = pthread_self();
381 EXPECT_LOOKUP(1, RELDIRPATH0).WillOnce(Invoke(ReturnErrno(ENOENT)));
382 expect_mkdir(&mkdir_unique);
383 EXPECT_CALL(*m_mock, process(
384 ResultOf([&](auto in) {
385 return (in->header.opcode == FUSE_INTERRUPT &&
386 in->body.interrupt.unique == mkdir_unique);
389 ).WillOnce(Invoke([&](auto in __unused, auto &out) {
390 // Ignore FUSE_INTERRUPT; respond to the FUSE_MKDIR
391 auto out0 = new mockfs_buf_out;
392 out0->header.unique = mkdir_unique;
393 SET_OUT_HEADER_LEN(out0, entry);
394 out0->body.create.entry.attr.mode = S_IFDIR | MODE;
395 out0->body.create.entry.nodeid = ino;
399 setup_interruptor(self);
400 ASSERT_EQ(0, mkdir(FULLDIRPATH0, MODE)) << strerror(errno);
404 * A restartable operation (basically, anything except write or setextattr)
405 * that hasn't yet been sent to userland can be interrupted without sending
406 * FUSE_INTERRUPT, and will be automatically restarted.
408 TEST_F(Interrupt, in_kernel_restartable)
410 const char FULLPATH1[] = "mountpoint/other_file.txt";
411 const char RELPATH1[] = "other_file.txt";
412 uint64_t ino0 = 42, ino1 = 43;
414 pthread_t self, th0, th1;
416 void *thr0_value, *thr1_value;
418 ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno);
419 ASSERT_EQ(0, sem_init(&sem1, 0, 0)) << strerror(errno);
420 self = pthread_self();
422 EXPECT_LOOKUP(1, RELDIRPATH0).WillOnce(Invoke(ReturnErrno(ENOENT)));
423 expect_lookup(RELPATH1, ino1);
424 expect_open(ino1, 0, 1);
425 EXPECT_CALL(*m_mock, process(
426 ResultOf([=](auto in) {
427 return (in->header.opcode == FUSE_MKDIR);
430 ).WillOnce(Invoke(ReturnImmediate([&](auto in __unused, auto out) {
431 /* Let the next write proceed */
433 /* Pause the daemon thread so it won't read the next op */
436 SET_OUT_HEADER_LEN(out, entry);
437 out->body.create.entry.attr.mode = S_IFDIR | MODE;
438 out->body.create.entry.nodeid = ino0;
440 FuseTest::expect_read(ino1, 0, FILESIZE, 0, NULL);
442 fd1 = open(FULLPATH1, O_RDONLY);
443 ASSERT_LE(0, fd1) << strerror(errno);
445 /* Use a separate thread for each operation */
446 ASSERT_EQ(0, pthread_create(&th0, NULL, mkdir0, NULL))
449 sem_wait(&sem1); /* Sequence the two operations */
451 ASSERT_EQ(0, pthread_create(&th1, NULL, read1, (void*)(intptr_t)fd1))
454 setup_interruptor(self);
456 pause(); /* Wait for signal */
458 /* Unstick the daemon */
459 ASSERT_EQ(0, sem_post(&sem0)) << strerror(errno);
461 /* Wait awhile to make sure the signal generates no FUSE_INTERRUPT */
464 pthread_join(th1, &thr1_value);
465 pthread_join(th0, &thr0_value);
466 EXPECT_EQ(0, (intptr_t)thr1_value);
467 EXPECT_EQ(0, (intptr_t)thr0_value);
473 * An operation that hasn't yet been sent to userland can be interrupted
474 * without sending FUSE_INTERRUPT. If it's a non-restartable operation (write
475 * or setextattr) it will return EINTR.
477 TEST_F(Interrupt, in_kernel_nonrestartable)
479 const char FULLPATH1[] = "mountpoint/other_file.txt";
480 const char RELPATH1[] = "other_file.txt";
481 const char value[] = "whatever";
482 ssize_t value_len = strlen(value) + 1;
483 uint64_t ino0 = 42, ino1 = 43;
484 int ns = EXTATTR_NAMESPACE_USER;
491 ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno);
492 ASSERT_EQ(0, sem_init(&sem1, 0, 0)) << strerror(errno);
493 self = pthread_self();
495 EXPECT_LOOKUP(1, RELDIRPATH0).WillOnce(Invoke(ReturnErrno(ENOENT)));
496 expect_lookup(RELPATH1, ino1);
497 expect_open(ino1, 0, 1);
498 EXPECT_CALL(*m_mock, process(
499 ResultOf([=](auto in) {
500 return (in->header.opcode == FUSE_MKDIR);
503 ).WillOnce(Invoke(ReturnImmediate([&](auto in __unused, auto out) {
504 /* Let the next write proceed */
506 /* Pause the daemon thread so it won't read the next op */
509 SET_OUT_HEADER_LEN(out, entry);
510 out->body.create.entry.attr.mode = S_IFDIR | MODE;
511 out->body.create.entry.nodeid = ino0;
514 fd1 = open(FULLPATH1, O_WRONLY);
515 ASSERT_LE(0, fd1) << strerror(errno);
517 /* Use a separate thread for the first write */
518 ASSERT_EQ(0, pthread_create(&th0, NULL, mkdir0, NULL))
521 setup_interruptor(self);
523 sem_wait(&sem1); /* Sequence the two operations */
524 r = extattr_set_fd(fd1, ns, "foo", (void*)value, value_len);
525 EXPECT_EQ(EINTR, errno);
527 /* Unstick the daemon */
528 ASSERT_EQ(0, sem_post(&sem0)) << strerror(errno);
530 /* Wait awhile to make sure the signal generates no FUSE_INTERRUPT */
533 pthread_join(th0, &thr0_value);
534 EXPECT_EQ(0, (intptr_t)thr0_value);
540 * A syscall that gets interrupted while blocking on FUSE I/O should send a
541 * FUSE_INTERRUPT command to the fuse filesystem, which should then send EINTR
542 * in response to the _original_ operation. The kernel should ultimately
543 * return EINTR to userspace
545 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */
546 TEST_F(Interrupt, in_progress)
549 uint64_t mkdir_unique;
551 self = pthread_self();
553 EXPECT_LOOKUP(1, RELDIRPATH0).WillOnce(Invoke(ReturnErrno(ENOENT)));
554 expect_mkdir(&mkdir_unique);
555 EXPECT_CALL(*m_mock, process(
556 ResultOf([&](auto in) {
557 return (in->header.opcode == FUSE_INTERRUPT &&
558 in->body.interrupt.unique == mkdir_unique);
561 ).WillOnce(Invoke([&](auto in __unused, auto &out) {
562 auto out0 = new mockfs_buf_out;
563 out0->header.error = -EINTR;
564 out0->header.unique = mkdir_unique;
565 out0->header.len = sizeof(out0->header);
569 setup_interruptor(self);
570 ASSERT_EQ(-1, mkdir(FULLDIRPATH0, MODE));
571 EXPECT_EQ(EINTR, errno);
574 /* Reads should also be interruptible */
575 TEST_F(Interrupt, in_progress_read)
577 const char FULLPATH[] = "mountpoint/some_file.txt";
578 const char RELPATH[] = "some_file.txt";
579 const size_t bufsize = 80;
584 uint64_t read_unique;
586 self = pthread_self();
588 expect_lookup(RELPATH, ino);
589 expect_open(ino, 0, 1);
590 expect_read(ino, &read_unique);
591 EXPECT_CALL(*m_mock, process(
592 ResultOf([&](auto in) {
593 return (in->header.opcode == FUSE_INTERRUPT &&
594 in->body.interrupt.unique == read_unique);
597 ).WillOnce(Invoke([&](auto in __unused, auto &out) {
598 auto out0 = new mockfs_buf_out;
599 out0->header.error = -EINTR;
600 out0->header.unique = read_unique;
601 out0->header.len = sizeof(out0->header);
605 fd = open(FULLPATH, O_RDONLY);
606 ASSERT_LE(0, fd) << strerror(errno);
608 setup_interruptor(self);
609 ASSERT_EQ(-1, read(fd, buf, bufsize));
610 EXPECT_EQ(EINTR, errno);
613 /* FUSE_INTERRUPT operations should take priority over other pending ops */
614 TEST_F(Interrupt, priority)
618 uint64_t mkdir_unique;
622 ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno);
623 ASSERT_EQ(0, sem_init(&sem1, 0, 0)) << strerror(errno);
624 self = pthread_self();
626 EXPECT_LOOKUP(1, RELDIRPATH0).WillOnce(Invoke(ReturnErrno(ENOENT)));
627 EXPECT_LOOKUP(1, RELDIRPATH1).WillOnce(Invoke(ReturnErrno(ENOENT)));
628 EXPECT_CALL(*m_mock, process(
629 ResultOf([=](auto in) {
630 return (in->header.opcode == FUSE_MKDIR);
634 .WillOnce(Invoke(ReturnImmediate([&](auto in, auto out) {
635 mkdir_unique = in->header.unique;
637 /* Let the next mkdir proceed */
640 /* Pause the daemon thread so it won't read the next op */
643 /* Finally, interrupt the original op */
644 out->header.error = -EINTR;
645 out->header.unique = mkdir_unique;
646 out->header.len = sizeof(out->header);
649 * FUSE_INTERRUPT should be received before the second FUSE_MKDIR,
650 * even though it was generated later
652 EXPECT_CALL(*m_mock, process(
653 ResultOf([&](auto in) {
654 return (in->header.opcode == FUSE_INTERRUPT &&
655 in->body.interrupt.unique == mkdir_unique);
659 .WillOnce(Invoke(ReturnErrno(EAGAIN)));
660 EXPECT_CALL(*m_mock, process(
661 ResultOf([&](auto in) {
662 return (in->header.opcode == FUSE_MKDIR);
666 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
667 SET_OUT_HEADER_LEN(out, entry);
668 out->body.create.entry.attr.mode = S_IFDIR | MODE;
669 out->body.create.entry.nodeid = ino1;
672 /* Use a separate thread for the first mkdir */
673 ASSERT_EQ(0, pthread_create(&th0, NULL, mkdir0, NULL))
676 signaled_semaphore = &sem0;
678 sem_wait(&sem1); /* Sequence the two mkdirs */
679 setup_interruptor(th0);
680 ASSERT_EQ(0, mkdir(FULLDIRPATH1, MODE)) << strerror(errno);
682 /* Wait awhile to make sure the signal generates no FUSE_INTERRUPT */
685 pthread_join(th0, NULL);
691 * If the FUSE filesystem receives the FUSE_INTERRUPT operation before
692 * processing the original, then it should wait for "some timeout" for the
693 * original operation to arrive. If not, it should send EAGAIN to the
694 * INTERRUPT operation, and the kernel should requeue the INTERRUPT.
696 * In this test, we'll pretend that the INTERRUPT arrives too soon, gets
697 * EAGAINed, then the kernel requeues it, and the second time around it
698 * successfully interrupts the original
700 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */
701 TEST_F(Interrupt, too_soon)
705 uint64_t mkdir_unique;
707 self = pthread_self();
709 EXPECT_LOOKUP(1, RELDIRPATH0).WillOnce(Invoke(ReturnErrno(ENOENT)));
710 expect_mkdir(&mkdir_unique);
712 EXPECT_CALL(*m_mock, process(
713 ResultOf([&](auto in) {
714 return (in->header.opcode == FUSE_INTERRUPT &&
715 in->body.interrupt.unique == mkdir_unique);
719 .WillOnce(Invoke(ReturnErrno(EAGAIN)));
721 EXPECT_CALL(*m_mock, process(
722 ResultOf([&](auto in) {
723 return (in->header.opcode == FUSE_INTERRUPT &&
724 in->body.interrupt.unique == mkdir_unique);
728 .WillOnce(Invoke([&](auto in __unused, auto &out __unused) {
729 auto out0 = new mockfs_buf_out;
730 out0->header.error = -EINTR;
731 out0->header.unique = mkdir_unique;
732 out0->header.len = sizeof(out0->header);
736 setup_interruptor(self);
737 ASSERT_EQ(-1, mkdir(FULLDIRPATH0, MODE));
738 EXPECT_EQ(EINTR, errno);
742 // TODO: add a test where write returns EWOULDBLOCK