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