1 /*-
2 * SPDX-License-Identifier: BSD-2-Clause
3 *
4 * Copyright (c) 2019 The FreeBSD Foundation
5 *
6 * This software was developed by BFF Storage Systems, LLC under sponsorship
7 * from the FreeBSD Foundation.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
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.
17 *
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
28 * SUCH DAMAGE.
29 */
30
31 extern "C" {
32 #include <sys/types.h>
33 #include <sys/extattr.h>
34 #include <sys/mman.h>
35 #include <sys/wait.h>
36 #include <fcntl.h>
37 #include <pthread.h>
38 #include <semaphore.h>
39 #include <signal.h>
40 }
41
42 #include "mockfs.hh"
43 #include "utils.hh"
44
45 using namespace testing;
46
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";
55
56 static sem_t *blocked_semaphore;
57 static sem_t *signaled_semaphore;
58
59 static bool killer_should_sleep = false;
60
61 /* Don't do anything; all we care about is that the syscall gets interrupted */
sigusr2_handler(int __unused sig)62 void sigusr2_handler(int __unused sig) {
63 if (verbosity > 1) {
64 printf("Signaled! thread %p\n", pthread_self());
65 }
66
67 }
68
killer(void * target)69 void* killer(void* target) {
70 /* Wait until the main thread is blocked in fdisp_wait_answ */
71 if (killer_should_sleep)
72 nap();
73 else
74 sem_wait(blocked_semaphore);
75 if (verbosity > 1)
76 printf("Signalling! thread %p\n", target);
77 pthread_kill((pthread_t)target, SIGUSR2);
78 if (signaled_semaphore != NULL)
79 sem_post(signaled_semaphore);
80
81 return(NULL);
82 }
83
84 class Interrupt: public FuseTest {
85 public:
86 pthread_t m_child;
87
Interrupt()88 Interrupt(): m_child(NULL) {};
89
expect_lookup(const char * relpath,uint64_t ino)90 void expect_lookup(const char *relpath, uint64_t ino)
91 {
92 FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, FILESIZE, 1);
93 }
94
95 /*
96 * Expect a FUSE_MKDIR but don't reply. Instead, just record the unique value
97 * to the provided pointer
98 */
expect_mkdir(uint64_t * mkdir_unique)99 void expect_mkdir(uint64_t *mkdir_unique)
100 {
101 EXPECT_CALL(*m_mock, process(
102 ResultOf([=](auto in) {
103 return (in.header.opcode == FUSE_MKDIR);
104 }, Eq(true)),
105 _)
106 ).WillOnce(Invoke([=](auto in, auto &out __unused) {
107 *mkdir_unique = in.header.unique;
108 sem_post(blocked_semaphore);
109 }));
110 }
111
112 /*
113 * Expect a FUSE_READ but don't reply. Instead, just record the unique value
114 * to the provided pointer
115 */
expect_read(uint64_t ino,uint64_t * read_unique)116 void expect_read(uint64_t ino, uint64_t *read_unique)
117 {
118 EXPECT_CALL(*m_mock, process(
119 ResultOf([=](auto in) {
120 return (in.header.opcode == FUSE_READ &&
121 in.header.nodeid == ino);
122 }, Eq(true)),
123 _)
124 ).WillOnce(Invoke([=](auto in, auto &out __unused) {
125 *read_unique = in.header.unique;
126 sem_post(blocked_semaphore);
127 }));
128 }
129
130 /*
131 * Expect a FUSE_WRITE but don't reply. Instead, just record the unique value
132 * to the provided pointer
133 */
expect_write(uint64_t ino,uint64_t * write_unique)134 void expect_write(uint64_t ino, uint64_t *write_unique)
135 {
136 EXPECT_CALL(*m_mock, process(
137 ResultOf([=](auto in) {
138 return (in.header.opcode == FUSE_WRITE &&
139 in.header.nodeid == ino);
140 }, Eq(true)),
141 _)
142 ).WillOnce(Invoke([=](auto in, auto &out __unused) {
143 *write_unique = in.header.unique;
144 sem_post(blocked_semaphore);
145 }));
146 }
147
setup_interruptor(pthread_t target,bool sleep=false)148 void setup_interruptor(pthread_t target, bool sleep = false)
149 {
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))
153 << strerror(errno);
154 }
155
SetUp()156 void SetUp() {
157 const int mprot = PROT_READ | PROT_WRITE;
158 const int mflags = MAP_ANON | MAP_SHARED;
159
160 signaled_semaphore = NULL;
161
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));
167
168 FuseTest::SetUp();
169 }
170
TearDown()171 void TearDown() {
172 struct sigaction sa;
173
174 if (m_child != NULL) {
175 pthread_join(m_child, NULL);
176 }
177 bzero(&sa, sizeof(sa));
178 sa.sa_handler = SIG_DFL;
179 sigaction(SIGUSR2, &sa, NULL);
180
181 sem_destroy(blocked_semaphore);
182 munmap(blocked_semaphore, sizeof(*blocked_semaphore));
183
184 FuseTest::TearDown();
185 }
186 };
187
188 class Intr: public Interrupt {};
189
190 class Nointr: public Interrupt {
SetUp()191 void SetUp() {
192 m_nointr = true;
193 Interrupt::SetUp();
194 }
195 };
196
mkdir0(void * arg __unused)197 static void* mkdir0(void* arg __unused) {
198 ssize_t r;
199
200 r = mkdir(FULLDIRPATH0, MODE);
201 if (r >= 0)
202 return 0;
203 else
204 return (void*)(intptr_t)errno;
205 }
206
read1(void * arg)207 static void* read1(void* arg) {
208 const size_t bufsize = FILESIZE;
209 char buf[bufsize];
210 int fd = (int)(intptr_t)arg;
211 ssize_t r;
212
213 r = read(fd, buf, bufsize);
214 if (r >= 0)
215 return 0;
216 else
217 return (void*)(intptr_t)errno;
218 }
219
220 /*
221 * An interrupt operation that gets received after the original command is
222 * complete should generate an EAGAIN response.
223 */
224 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */
TEST_F(Intr,already_complete)225 TEST_F(Intr, already_complete)
226 {
227 uint64_t ino = 42;
228 pthread_t self;
229 uint64_t mkdir_unique = 0;
230 Sequence seq;
231
232 self = pthread_self();
233
234 EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0)
235 .InSequence(seq)
236 .WillOnce(Invoke(ReturnErrno(ENOENT)));
237 expect_mkdir(&mkdir_unique);
238 EXPECT_CALL(*m_mock, process(
239 ResultOf([&](auto in) {
240 return (in.header.opcode == FUSE_INTERRUPT &&
241 in.body.interrupt.unique == mkdir_unique);
242 }, Eq(true)),
243 _)
244 ).WillOnce(Invoke([&](auto in, auto &out) {
245 // First complete the mkdir request
246 std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
247 out0->header.unique = mkdir_unique;
248 SET_OUT_HEADER_LEN(*out0, entry);
249 out0->body.create.entry.attr.mode = S_IFDIR | MODE;
250 out0->body.create.entry.nodeid = ino;
251 out.push_back(std::move(out0));
252
253 // Then, respond EAGAIN to the interrupt request
254 std::unique_ptr<mockfs_buf_out> out1(new mockfs_buf_out);
255 out1->header.unique = in.header.unique;
256 out1->header.error = -EAGAIN;
257 out1->header.len = sizeof(out1->header);
258 out.push_back(std::move(out1));
259 }));
260 EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0)
261 .InSequence(seq)
262 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
263 SET_OUT_HEADER_LEN(out, entry);
264 out.body.entry.attr.mode = S_IFDIR | MODE;
265 out.body.entry.nodeid = ino;
266 out.body.entry.attr.nlink = 2;
267 })));
268
269 setup_interruptor(self);
270 EXPECT_EQ(0, mkdir(FULLDIRPATH0, MODE)) << strerror(errno);
271 /*
272 * The final syscall simply ensures that the test's main thread doesn't
273 * end before the daemon finishes responding to the FUSE_INTERRUPT.
274 */
275 EXPECT_EQ(0, access(FULLDIRPATH0, F_OK)) << strerror(errno);
276 }
277
278 /*
279 * If a FUSE file system returns ENOSYS for a FUSE_INTERRUPT operation, the
280 * kernel should not attempt to interrupt any other operations on that mount
281 * point.
282 */
TEST_F(Intr,enosys)283 TEST_F(Intr, enosys)
284 {
285 uint64_t ino0 = 42, ino1 = 43;;
286 uint64_t mkdir_unique;
287 pthread_t self, th0;
288 sem_t sem0, sem1;
289 void *thr0_value;
290 Sequence seq;
291
292 self = pthread_self();
293 ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno);
294 ASSERT_EQ(0, sem_init(&sem1, 0, 0)) << strerror(errno);
295
296 EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH1)
297 .WillOnce(Invoke(ReturnErrno(ENOENT)));
298 EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0)
299 .WillOnce(Invoke(ReturnErrno(ENOENT)));
300 expect_mkdir(&mkdir_unique);
301 EXPECT_CALL(*m_mock, process(
302 ResultOf([&](auto in) {
303 return (in.header.opcode == FUSE_INTERRUPT &&
304 in.body.interrupt.unique == mkdir_unique);
305 }, Eq(true)),
306 _)
307 ).InSequence(seq)
308 .WillOnce(Invoke([&](auto in, auto &out) {
309 // reject FUSE_INTERRUPT and respond to the FUSE_MKDIR
310 std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
311 std::unique_ptr<mockfs_buf_out> out1(new mockfs_buf_out);
312
313 out0->header.unique = in.header.unique;
314 out0->header.error = -ENOSYS;
315 out0->header.len = sizeof(out0->header);
316 out.push_back(std::move(out0));
317
318 SET_OUT_HEADER_LEN(*out1, entry);
319 out1->body.create.entry.attr.mode = S_IFDIR | MODE;
320 out1->body.create.entry.nodeid = ino1;
321 out1->header.unique = mkdir_unique;
322 out.push_back(std::move(out1));
323 }));
324 EXPECT_CALL(*m_mock, process(
325 ResultOf([&](auto in) {
326 return (in.header.opcode == FUSE_MKDIR);
327 }, Eq(true)),
328 _)
329 ).InSequence(seq)
330 .WillOnce(Invoke([&](auto in, auto &out) {
331 std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
332
333 sem_post(&sem0);
334 sem_wait(&sem1);
335
336 SET_OUT_HEADER_LEN(*out0, entry);
337 out0->body.create.entry.attr.mode = S_IFDIR | MODE;
338 out0->body.create.entry.nodeid = ino0;
339 out0->header.unique = in.header.unique;
340 out.push_back(std::move(out0));
341 }));
342
343 setup_interruptor(self);
344 /* First mkdir operation should finish synchronously */
345 ASSERT_EQ(0, mkdir(FULLDIRPATH1, MODE)) << strerror(errno);
346
347 ASSERT_EQ(0, pthread_create(&th0, NULL, mkdir0, NULL))
348 << strerror(errno);
349
350 sem_wait(&sem0);
351 /*
352 * th0 should be blocked waiting for the fuse daemon thread.
353 * Signal it. No FUSE_INTERRUPT should result
354 */
355 pthread_kill(th0, SIGUSR1);
356 /* Allow the daemon thread to proceed */
357 sem_post(&sem1);
358 pthread_join(th0, &thr0_value);
359 /* Second mkdir should've finished without error */
360 EXPECT_EQ(0, (intptr_t)thr0_value);
361 }
362
363 /*
364 * A FUSE filesystem is legally allowed to ignore INTERRUPT operations, and
365 * complete the original operation whenever it damn well pleases.
366 */
367 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */
TEST_F(Intr,ignore)368 TEST_F(Intr, ignore)
369 {
370 uint64_t ino = 42;
371 pthread_t self;
372 uint64_t mkdir_unique;
373
374 self = pthread_self();
375
376 EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0)
377 .WillOnce(Invoke(ReturnErrno(ENOENT)));
378 expect_mkdir(&mkdir_unique);
379 EXPECT_CALL(*m_mock, process(
380 ResultOf([&](auto in) {
381 return (in.header.opcode == FUSE_INTERRUPT &&
382 in.body.interrupt.unique == mkdir_unique);
383 }, Eq(true)),
384 _)
385 ).WillOnce(Invoke([&](auto in __unused, auto &out) {
386 // Ignore FUSE_INTERRUPT; respond to the FUSE_MKDIR
387 std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
388 out0->header.unique = mkdir_unique;
389 SET_OUT_HEADER_LEN(*out0, entry);
390 out0->body.create.entry.attr.mode = S_IFDIR | MODE;
391 out0->body.create.entry.nodeid = ino;
392 out.push_back(std::move(out0));
393 }));
394
395 setup_interruptor(self);
396 ASSERT_EQ(0, mkdir(FULLDIRPATH0, MODE)) << strerror(errno);
397 }
398
399 /*
400 * A restartable operation (basically, anything except write or setextattr)
401 * that hasn't yet been sent to userland can be interrupted without sending
402 * FUSE_INTERRUPT, and will be automatically restarted.
403 */
TEST_F(Intr,in_kernel_restartable)404 TEST_F(Intr, in_kernel_restartable)
405 {
406 const char FULLPATH1[] = "mountpoint/other_file.txt";
407 const char RELPATH1[] = "other_file.txt";
408 uint64_t ino0 = 42, ino1 = 43;
409 int fd1;
410 pthread_t self, th0, th1;
411 sem_t sem0, sem1;
412 void *thr0_value, *thr1_value;
413
414 ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno);
415 ASSERT_EQ(0, sem_init(&sem1, 0, 0)) << strerror(errno);
416 self = pthread_self();
417
418 EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0)
419 .WillOnce(Invoke(ReturnErrno(ENOENT)));
420 expect_lookup(RELPATH1, ino1);
421 expect_open(ino1, 0, 1);
422 EXPECT_CALL(*m_mock, process(
423 ResultOf([=](auto in) {
424 return (in.header.opcode == FUSE_MKDIR);
425 }, Eq(true)),
426 _)
427 ).WillOnce(Invoke(ReturnImmediate([&](auto in __unused, auto& out) {
428 /* Let the next write proceed */
429 sem_post(&sem1);
430 /* Pause the daemon thread so it won't read the next op */
431 sem_wait(&sem0);
432
433 SET_OUT_HEADER_LEN(out, entry);
434 out.body.create.entry.attr.mode = S_IFDIR | MODE;
435 out.body.create.entry.nodeid = ino0;
436 })));
437 FuseTest::expect_read(ino1, 0, FILESIZE, 0, NULL);
438
439 fd1 = open(FULLPATH1, O_RDONLY);
440 ASSERT_LE(0, fd1) << strerror(errno);
441
442 /* Use a separate thread for each operation */
443 ASSERT_EQ(0, pthread_create(&th0, NULL, mkdir0, NULL))
444 << strerror(errno);
445
446 sem_wait(&sem1); /* Sequence the two operations */
447
448 ASSERT_EQ(0, pthread_create(&th1, NULL, read1, (void*)(intptr_t)fd1))
449 << strerror(errno);
450
451 setup_interruptor(self, true);
452
453 pause(); /* Wait for signal */
454
455 /* Unstick the daemon */
456 ASSERT_EQ(0, sem_post(&sem0)) << strerror(errno);
457
458 /* Wait awhile to make sure the signal generates no FUSE_INTERRUPT */
459 nap();
460
461 pthread_join(th1, &thr1_value);
462 pthread_join(th0, &thr0_value);
463 EXPECT_EQ(0, (intptr_t)thr1_value);
464 EXPECT_EQ(0, (intptr_t)thr0_value);
465 sem_destroy(&sem1);
466 sem_destroy(&sem0);
467
468 leak(fd1);
469 }
470
471 /*
472 * An operation that hasn't yet been sent to userland can be interrupted
473 * without sending FUSE_INTERRUPT. If it's a non-restartable operation (write
474 * or setextattr) it will return EINTR.
475 */
TEST_F(Intr,in_kernel_nonrestartable)476 TEST_F(Intr, in_kernel_nonrestartable)
477 {
478 const char FULLPATH1[] = "mountpoint/other_file.txt";
479 const char RELPATH1[] = "other_file.txt";
480 const char value[] = "whatever";
481 ssize_t value_len = strlen(value) + 1;
482 uint64_t ino0 = 42, ino1 = 43;
483 int ns = EXTATTR_NAMESPACE_USER;
484 int fd1;
485 pthread_t self, th0;
486 sem_t sem0, sem1;
487 void *thr0_value;
488 ssize_t r;
489
490 ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno);
491 ASSERT_EQ(0, sem_init(&sem1, 0, 0)) << strerror(errno);
492 self = pthread_self();
493
494 EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0)
495 .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);
501 }, Eq(true)),
502 _)
503 ).WillOnce(Invoke(ReturnImmediate([&](auto in __unused, auto& out) {
504 /* Let the next write proceed */
505 sem_post(&sem1);
506 /* Pause the daemon thread so it won't read the next op */
507 sem_wait(&sem0);
508
509 SET_OUT_HEADER_LEN(out, entry);
510 out.body.create.entry.attr.mode = S_IFDIR | MODE;
511 out.body.create.entry.nodeid = ino0;
512 })));
513
514 fd1 = open(FULLPATH1, O_WRONLY);
515 ASSERT_LE(0, fd1) << strerror(errno);
516
517 /* Use a separate thread for the first write */
518 ASSERT_EQ(0, pthread_create(&th0, NULL, mkdir0, NULL))
519 << strerror(errno);
520
521 sem_wait(&sem1); /* Sequence the two operations */
522
523 setup_interruptor(self, true);
524
525 r = extattr_set_fd(fd1, ns, "foo", (const void*)value, value_len);
526 EXPECT_NE(0, r);
527 EXPECT_EQ(EINTR, errno);
528
529 /* Unstick the daemon */
530 ASSERT_EQ(0, sem_post(&sem0)) << strerror(errno);
531
532 /* Wait awhile to make sure the signal generates no FUSE_INTERRUPT */
533 nap();
534
535 pthread_join(th0, &thr0_value);
536 EXPECT_EQ(0, (intptr_t)thr0_value);
537 sem_destroy(&sem1);
538 sem_destroy(&sem0);
539
540 leak(fd1);
541 }
542
543 /*
544 * A syscall that gets interrupted while blocking on FUSE I/O should send a
545 * FUSE_INTERRUPT command to the fuse filesystem, which should then send EINTR
546 * in response to the _original_ operation. The kernel should ultimately
547 * return EINTR to userspace
548 */
549 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */
TEST_F(Intr,in_progress)550 TEST_F(Intr, in_progress)
551 {
552 pthread_t self;
553 uint64_t mkdir_unique;
554
555 self = pthread_self();
556
557 EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0)
558 .WillOnce(Invoke(ReturnErrno(ENOENT)));
559 expect_mkdir(&mkdir_unique);
560 EXPECT_CALL(*m_mock, process(
561 ResultOf([&](auto in) {
562 return (in.header.opcode == FUSE_INTERRUPT &&
563 in.body.interrupt.unique == mkdir_unique);
564 }, Eq(true)),
565 _)
566 ).WillOnce(Invoke([&](auto in __unused, auto &out) {
567 std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
568 out0->header.error = -EINTR;
569 out0->header.unique = mkdir_unique;
570 out0->header.len = sizeof(out0->header);
571 out.push_back(std::move(out0));
572 }));
573
574 setup_interruptor(self);
575 ASSERT_EQ(-1, mkdir(FULLDIRPATH0, MODE));
576 EXPECT_EQ(EINTR, errno);
577 }
578
579 /* Reads should also be interruptible */
TEST_F(Intr,in_progress_read)580 TEST_F(Intr, in_progress_read)
581 {
582 const char FULLPATH[] = "mountpoint/some_file.txt";
583 const char RELPATH[] = "some_file.txt";
584 const size_t bufsize = 80;
585 char buf[bufsize];
586 uint64_t ino = 42;
587 int fd;
588 pthread_t self;
589 uint64_t read_unique;
590
591 self = pthread_self();
592
593 expect_lookup(RELPATH, ino);
594 expect_open(ino, 0, 1);
595 expect_read(ino, &read_unique);
596 EXPECT_CALL(*m_mock, process(
597 ResultOf([&](auto in) {
598 return (in.header.opcode == FUSE_INTERRUPT &&
599 in.body.interrupt.unique == read_unique);
600 }, Eq(true)),
601 _)
602 ).WillOnce(Invoke([&](auto in __unused, auto &out) {
603 std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
604 out0->header.error = -EINTR;
605 out0->header.unique = read_unique;
606 out0->header.len = sizeof(out0->header);
607 out.push_back(std::move(out0));
608 }));
609
610 fd = open(FULLPATH, O_RDONLY);
611 ASSERT_LE(0, fd) << strerror(errno);
612
613 setup_interruptor(self);
614 ASSERT_EQ(-1, read(fd, buf, bufsize));
615 EXPECT_EQ(EINTR, errno);
616
617 leak(fd);
618 }
619
620 /*
621 * When mounted with -o nointr, fusefs will block signals while waiting for the
622 * server.
623 */
TEST_F(Nointr,block)624 TEST_F(Nointr, block)
625 {
626 uint64_t ino = 42;
627 pthread_t self;
628 sem_t sem0;
629
630 ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno);
631 signaled_semaphore = &sem0;
632 self = pthread_self();
633
634 EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0)
635 .WillOnce(Invoke(ReturnErrno(ENOENT)));
636 EXPECT_CALL(*m_mock, process(
637 ResultOf([=](auto in) {
638 return (in.header.opcode == FUSE_MKDIR);
639 }, Eq(true)),
640 _)
641 ).WillOnce(Invoke(ReturnImmediate([&](auto in __unused, auto& out) {
642 /* Let the killer proceed */
643 sem_post(blocked_semaphore);
644
645 /* Wait until after the signal has been sent */
646 sem_wait(signaled_semaphore);
647 /* Allow time for the mkdir thread to receive the signal */
648 nap();
649
650 /* Finally, complete the original op */
651 SET_OUT_HEADER_LEN(out, entry);
652 out.body.create.entry.attr.mode = S_IFDIR | MODE;
653 out.body.create.entry.nodeid = ino;
654 })));
655 EXPECT_CALL(*m_mock, process(
656 ResultOf([&](auto in) {
657 return (in.header.opcode == FUSE_INTERRUPT);
658 }, Eq(true)),
659 _)
660 ).Times(0);
661
662 setup_interruptor(self);
663 ASSERT_EQ(0, mkdir(FULLDIRPATH0, MODE)) << strerror(errno);
664
665 sem_destroy(&sem0);
666 }
667
668 /* FUSE_INTERRUPT operations should take priority over other pending ops */
TEST_F(Intr,priority)669 TEST_F(Intr, priority)
670 {
671 Sequence seq;
672 uint64_t ino1 = 43;
673 uint64_t mkdir_unique;
674 pthread_t th0;
675 sem_t sem0, sem1;
676
677 ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno);
678 ASSERT_EQ(0, sem_init(&sem1, 0, 0)) << strerror(errno);
679
680 EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0)
681 .WillOnce(Invoke(ReturnErrno(ENOENT)));
682 EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH1)
683 .WillOnce(Invoke(ReturnErrno(ENOENT)));
684 EXPECT_CALL(*m_mock, process(
685 ResultOf([=](auto in) {
686 return (in.header.opcode == FUSE_MKDIR);
687 }, Eq(true)),
688 _)
689 ).InSequence(seq)
690 .WillOnce(Invoke(ReturnImmediate([&](auto in, auto& out) {
691 mkdir_unique = in.header.unique;
692
693 /* Let the next mkdir proceed */
694 sem_post(&sem1);
695
696 /* Pause the daemon thread so it won't read the next op */
697 sem_wait(&sem0);
698
699 /* Finally, interrupt the original op */
700 out.header.error = -EINTR;
701 out.header.unique = mkdir_unique;
702 out.header.len = sizeof(out.header);
703 })));
704 /*
705 * FUSE_INTERRUPT should be received before the second FUSE_MKDIR,
706 * even though it was generated later
707 */
708 EXPECT_CALL(*m_mock, process(
709 ResultOf([&](auto in) {
710 return (in.header.opcode == FUSE_INTERRUPT &&
711 in.body.interrupt.unique == mkdir_unique);
712 }, Eq(true)),
713 _)
714 ).InSequence(seq)
715 .WillOnce(Invoke(ReturnErrno(EAGAIN)));
716 EXPECT_CALL(*m_mock, process(
717 ResultOf([&](auto in) {
718 return (in.header.opcode == FUSE_MKDIR);
719 }, Eq(true)),
720 _)
721 ).InSequence(seq)
722 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
723 SET_OUT_HEADER_LEN(out, entry);
724 out.body.create.entry.attr.mode = S_IFDIR | MODE;
725 out.body.create.entry.nodeid = ino1;
726 })));
727
728 /* Use a separate thread for the first mkdir */
729 ASSERT_EQ(0, pthread_create(&th0, NULL, mkdir0, NULL))
730 << strerror(errno);
731
732 signaled_semaphore = &sem0;
733
734 sem_wait(&sem1); /* Sequence the two mkdirs */
735 setup_interruptor(th0, true);
736 ASSERT_EQ(0, mkdir(FULLDIRPATH1, MODE)) << strerror(errno);
737
738 pthread_join(th0, NULL);
739 sem_destroy(&sem1);
740 sem_destroy(&sem0);
741 }
742
743 /*
744 * If the FUSE filesystem receives the FUSE_INTERRUPT operation before
745 * processing the original, then it should wait for "some timeout" for the
746 * original operation to arrive. If not, it should send EAGAIN to the
747 * INTERRUPT operation, and the kernel should requeue the INTERRUPT.
748 *
749 * In this test, we'll pretend that the INTERRUPT arrives too soon, gets
750 * EAGAINed, then the kernel requeues it, and the second time around it
751 * successfully interrupts the original
752 */
753 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */
TEST_F(Intr,too_soon)754 TEST_F(Intr, too_soon)
755 {
756 Sequence seq;
757 pthread_t self;
758 uint64_t mkdir_unique;
759
760 self = pthread_self();
761
762 EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0)
763 .WillOnce(Invoke(ReturnErrno(ENOENT)));
764 expect_mkdir(&mkdir_unique);
765
766 EXPECT_CALL(*m_mock, process(
767 ResultOf([&](auto in) {
768 return (in.header.opcode == FUSE_INTERRUPT &&
769 in.body.interrupt.unique == mkdir_unique);
770 }, Eq(true)),
771 _)
772 ).InSequence(seq)
773 .WillOnce(Invoke(ReturnErrno(EAGAIN)));
774
775 EXPECT_CALL(*m_mock, process(
776 ResultOf([&](auto in) {
777 return (in.header.opcode == FUSE_INTERRUPT &&
778 in.body.interrupt.unique == mkdir_unique);
779 }, Eq(true)),
780 _)
781 ).InSequence(seq)
782 .WillOnce(Invoke([&](auto in __unused, auto &out __unused) {
783 std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
784 out0->header.error = -EINTR;
785 out0->header.unique = mkdir_unique;
786 out0->header.len = sizeof(out0->header);
787 out.push_back(std::move(out0));
788 }));
789
790 setup_interruptor(self);
791 ASSERT_EQ(-1, mkdir(FULLDIRPATH0, MODE));
792 EXPECT_EQ(EINTR, errno);
793 }
794
795
796 // TODO: add a test where write returns EWOULDBLOCK
797