]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - tests/sys/fs/fusefs/interrupt.cc
fusefs: make the tests more cplusplusy
[FreeBSD/FreeBSD.git] / tests / sys / fs / fusefs / interrupt.cc
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
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 */
62 void sigusr2_handler(int __unused sig) {
63         if (verbosity > 1) {
64                 printf("Signaled!  thread %p\n", pthread_self());
65         }
66
67 }
68
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
88 Interrupt(): m_child(NULL) {};
89
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  */
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  */
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  */
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
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
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
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 static void* mkdir0(void* arg __unused) {
189         ssize_t r;
190
191         r = mkdir(FULLDIRPATH0, MODE);
192         if (r >= 0)
193                 return 0;
194         else
195                 return (void*)(intptr_t)errno;
196 }
197
198 static void* read1(void* arg) {
199         const size_t bufsize = FILESIZE;
200         char buf[bufsize];
201         int fd = (int)(intptr_t)arg;
202         ssize_t r;
203
204         r = read(fd, buf, bufsize);
205         if (r >= 0)
206                 return 0;
207         else
208                 return (void*)(intptr_t)errno;
209 }
210
211 /* 
212  * An interrupt operation that gets received after the original command is
213  * complete should generate an EAGAIN response.
214  */
215 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */
216 TEST_F(Interrupt, already_complete)
217 {
218         uint64_t ino = 42;
219         pthread_t self;
220         uint64_t mkdir_unique = 0;
221         Sequence seq;
222
223         self = pthread_self();
224
225         EXPECT_LOOKUP(1, RELDIRPATH0)
226         .InSequence(seq)
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);
233                 }, Eq(true)),
234                 _)
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));
243
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));
250         }));
251         EXPECT_LOOKUP(1, RELDIRPATH0)
252         .InSequence(seq)
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;
258         })));
259
260         setup_interruptor(self);
261         EXPECT_EQ(0, mkdir(FULLDIRPATH0, MODE)) << strerror(errno);
262         /* 
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.
265          */
266         EXPECT_EQ(0, access(FULLDIRPATH0, F_OK)) << strerror(errno);
267 }
268
269 /*
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
272  * point.
273  */
274 TEST_F(Interrupt, enosys)
275 {
276         uint64_t ino0 = 42, ino1 = 43;;
277         uint64_t mkdir_unique;
278         pthread_t self, th0;
279         sem_t sem0, sem1;
280         void *thr0_value;
281         Sequence seq;
282
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);
286
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);
294                 }, Eq(true)),
295                 _)
296         ).InSequence(seq)
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);
301
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));
306
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));
312         }));
313         EXPECT_CALL(*m_mock, process(
314                 ResultOf([&](auto in) {
315                         return (in.header.opcode == FUSE_MKDIR);
316                 }, Eq(true)),
317                 _)
318         ).InSequence(seq)
319         .WillOnce(Invoke([&](auto in, auto &out) {
320                 std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
321
322                 sem_post(&sem0);
323                 sem_wait(&sem1);
324
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));
330         }));
331
332         setup_interruptor(self);
333         /* First mkdir operation should finish synchronously */
334         ASSERT_EQ(0, mkdir(FULLDIRPATH1, MODE)) << strerror(errno);
335
336         ASSERT_EQ(0, pthread_create(&th0, NULL, mkdir0, NULL))
337                 << strerror(errno);
338
339         sem_wait(&sem0);
340         /*
341          * th0 should be blocked waiting for the fuse daemon thread.
342          * Signal it.  No FUSE_INTERRUPT should result
343          */
344         pthread_kill(th0, SIGUSR1);
345         /* Allow the daemon thread to proceed */
346         sem_post(&sem1);
347         pthread_join(th0, &thr0_value);
348         /* Second mkdir should've finished without error */
349         EXPECT_EQ(0, (intptr_t)thr0_value);
350 }
351
352 /*
353  * Upon receipt of a fatal signal, fusefs should return ASAP after sending
354  * FUSE_INTERRUPT.
355  */
356 TEST_F(Interrupt, fatal_signal)
357 {
358         int status;
359         pthread_t self;
360         uint64_t mkdir_unique;
361         sem_t sem;
362
363         ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno);
364         self = pthread_self();
365
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);
372                 }, Eq(true)),
373                 _)
374         ).WillOnce(Invoke([&](auto in __unused, auto &out __unused) {
375                 sem_post(&sem);
376                 /* Don't respond.  The process should exit anyway */
377         }));
378
379         fork(false, &status, [&] {
380         }, [&]() {
381                 struct sigaction sa;
382                 int r;
383                 pthread_t killer_th;
384                 pthread_t self;
385
386                 /* SIGUSR2 terminates the process by default */
387                 bzero(&sa, sizeof(sa));
388                 sa.sa_handler = SIG_DFL;
389                 r = sigaction(SIGUSR2, &sa, NULL);
390                 if (r != 0) {
391                         perror("sigaction");
392                         return 1;
393                 }
394                 self = pthread_self();
395                 r = pthread_create(&killer_th, NULL, killer, (void*)self);
396                 if (r != 0) {
397                         perror("pthread_create");
398                         return 1;
399                 }
400
401                 mkdir(FULLDIRPATH0, MODE);
402                 return 1;
403         });
404         ASSERT_EQ(SIGUSR2, WTERMSIG(status));
405
406         EXPECT_EQ(0, sem_wait(&sem)) << strerror(errno);
407         sem_destroy(&sem);
408 }
409
410 /*
411  * A FUSE filesystem is legally allowed to ignore INTERRUPT operations, and
412  * complete the original operation whenever it damn well pleases.
413  */
414 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */
415 TEST_F(Interrupt, ignore)
416 {
417         uint64_t ino = 42;
418         pthread_t self;
419         uint64_t mkdir_unique;
420
421         self = pthread_self();
422
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);
429                 }, Eq(true)),
430                 _)
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));
439         }));
440
441         setup_interruptor(self);
442         ASSERT_EQ(0, mkdir(FULLDIRPATH0, MODE)) << strerror(errno);
443 }
444
445 /*
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.
449  */
450 TEST_F(Interrupt, in_kernel_restartable)
451 {
452         const char FULLPATH1[] = "mountpoint/other_file.txt";
453         const char RELPATH1[] = "other_file.txt";
454         uint64_t ino0 = 42, ino1 = 43;
455         int fd1;
456         pthread_t self, th0, th1;
457         sem_t sem0, sem1;
458         void *thr0_value, *thr1_value;
459
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();
463
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);
470                 }, Eq(true)),
471                 _)
472         ).WillOnce(Invoke(ReturnImmediate([&](auto in __unused, auto& out) {
473                 /* Let the next write proceed */
474                 sem_post(&sem1);
475                 /* Pause the daemon thread so it won't read the next op */
476                 sem_wait(&sem0);
477
478                 SET_OUT_HEADER_LEN(out, entry);
479                 out.body.create.entry.attr.mode = S_IFDIR | MODE;
480                 out.body.create.entry.nodeid = ino0;
481         })));
482         FuseTest::expect_read(ino1, 0, FILESIZE, 0, NULL);
483
484         fd1 = open(FULLPATH1, O_RDONLY);
485         ASSERT_LE(0, fd1) << strerror(errno);
486
487         /* Use a separate thread for each operation */
488         ASSERT_EQ(0, pthread_create(&th0, NULL, mkdir0, NULL))
489                 << strerror(errno);
490
491         sem_wait(&sem1);        /* Sequence the two operations */
492
493         ASSERT_EQ(0, pthread_create(&th1, NULL, read1, (void*)(intptr_t)fd1))
494                 << strerror(errno);
495
496         setup_interruptor(self, true);
497
498         pause();                /* Wait for signal */
499
500         /* Unstick the daemon */
501         ASSERT_EQ(0, sem_post(&sem0)) << strerror(errno);
502
503         /* Wait awhile to make sure the signal generates no FUSE_INTERRUPT */
504         nap();
505
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);
510         sem_destroy(&sem1);
511         sem_destroy(&sem0);
512 }
513
514 /*
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.
518  */
519 TEST_F(Interrupt, in_kernel_nonrestartable)
520 {
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;
527         int fd1;
528         pthread_t self, th0;
529         sem_t sem0, sem1;
530         void *thr0_value;
531         ssize_t r;
532
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();
536
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);
543                 }, Eq(true)),
544                 _)
545         ).WillOnce(Invoke(ReturnImmediate([&](auto in __unused, auto& out) {
546                 /* Let the next write proceed */
547                 sem_post(&sem1);
548                 /* Pause the daemon thread so it won't read the next op */
549                 sem_wait(&sem0);
550
551                 SET_OUT_HEADER_LEN(out, entry);
552                 out.body.create.entry.attr.mode = S_IFDIR | MODE;
553                 out.body.create.entry.nodeid = ino0;
554         })));
555
556         fd1 = open(FULLPATH1, O_WRONLY);
557         ASSERT_LE(0, fd1) << strerror(errno);
558
559         /* Use a separate thread for the first write */
560         ASSERT_EQ(0, pthread_create(&th0, NULL, mkdir0, NULL))
561                 << strerror(errno);
562
563         sem_wait(&sem1);        /* Sequence the two operations */
564
565         setup_interruptor(self, true);
566
567         r = extattr_set_fd(fd1, ns, "foo", (void*)value, value_len);
568         EXPECT_EQ(EINTR, errno);
569
570         /* Unstick the daemon */
571         ASSERT_EQ(0, sem_post(&sem0)) << strerror(errno);
572
573         /* Wait awhile to make sure the signal generates no FUSE_INTERRUPT */
574         nap();
575
576         pthread_join(th0, &thr0_value);
577         EXPECT_EQ(0, (intptr_t)thr0_value);
578         sem_destroy(&sem1);
579         sem_destroy(&sem0);
580 }
581
582 /* 
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
587  */
588 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */
589 TEST_F(Interrupt, in_progress)
590 {
591         pthread_t self;
592         uint64_t mkdir_unique;
593
594         self = pthread_self();
595
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);
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 = mkdir_unique;
608                 out0->header.len = sizeof(out0->header);
609                 out.push_back(std::move(out0));
610         }));
611
612         setup_interruptor(self);
613         ASSERT_EQ(-1, mkdir(FULLDIRPATH0, MODE));
614         EXPECT_EQ(EINTR, errno);
615 }
616
617 /* Reads should also be interruptible */
618 TEST_F(Interrupt, in_progress_read)
619 {
620         const char FULLPATH[] = "mountpoint/some_file.txt";
621         const char RELPATH[] = "some_file.txt";
622         const size_t bufsize = 80;
623         char buf[bufsize];
624         uint64_t ino = 42;
625         int fd;
626         pthread_t self;
627         uint64_t read_unique;
628
629         self = pthread_self();
630
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);
638                 }, Eq(true)),
639                 _)
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));
646         }));
647
648         fd = open(FULLPATH, O_RDONLY);
649         ASSERT_LE(0, fd) << strerror(errno);
650
651         setup_interruptor(self);
652         ASSERT_EQ(-1, read(fd, buf, bufsize));
653         EXPECT_EQ(EINTR, errno);
654 }
655
656 /* FUSE_INTERRUPT operations should take priority over other pending ops */
657 TEST_F(Interrupt, priority)
658 {
659         Sequence seq;
660         uint64_t ino1 = 43;
661         uint64_t mkdir_unique;
662         pthread_t self, th0;
663         sem_t sem0, sem1;
664
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();
668
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);
674                 }, Eq(true)),
675                 _)
676         ).InSequence(seq)
677         .WillOnce(Invoke(ReturnImmediate([&](auto in, auto& out) {
678                 mkdir_unique = in.header.unique;
679
680                 /* Let the next mkdir proceed */
681                 sem_post(&sem1);
682
683                 /* Pause the daemon thread so it won't read the next op */
684                 sem_wait(&sem0);
685
686                 /* Finally, interrupt the original op */
687                 out.header.error = -EINTR;
688                 out.header.unique = mkdir_unique;
689                 out.header.len = sizeof(out.header);
690         })));
691         /* 
692          * FUSE_INTERRUPT should be received before the second FUSE_MKDIR,
693          * even though it was generated later
694          */
695         EXPECT_CALL(*m_mock, process(
696                 ResultOf([&](auto in) {
697                         return (in.header.opcode == FUSE_INTERRUPT &&
698                                 in.body.interrupt.unique == mkdir_unique);
699                 }, Eq(true)),
700                 _)
701         ).InSequence(seq)
702         .WillOnce(Invoke(ReturnErrno(EAGAIN)));
703         EXPECT_CALL(*m_mock, process(
704                 ResultOf([&](auto in) {
705                         return (in.header.opcode == FUSE_MKDIR);
706                 }, Eq(true)),
707                 _)
708         ).InSequence(seq)
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;
713         })));
714
715         /* Use a separate thread for the first mkdir */
716         ASSERT_EQ(0, pthread_create(&th0, NULL, mkdir0, NULL))
717                 << strerror(errno);
718
719         signaled_semaphore = &sem0;
720
721         sem_wait(&sem1);        /* Sequence the two mkdirs */
722         setup_interruptor(th0, true);
723         ASSERT_EQ(0, mkdir(FULLDIRPATH1, MODE)) << strerror(errno);
724
725         pthread_join(th0, NULL);
726         sem_destroy(&sem1);
727         sem_destroy(&sem0);
728 }
729
730 /*
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.
735  *
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
739  */
740 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */
741 TEST_F(Interrupt, too_soon)
742 {
743         Sequence seq;
744         pthread_t self;
745         uint64_t mkdir_unique;
746
747         self = pthread_self();
748
749         EXPECT_LOOKUP(1, RELDIRPATH0).WillOnce(Invoke(ReturnErrno(ENOENT)));
750         expect_mkdir(&mkdir_unique);
751
752         EXPECT_CALL(*m_mock, process(
753                 ResultOf([&](auto in) {
754                         return (in.header.opcode == FUSE_INTERRUPT &&
755                                 in.body.interrupt.unique == mkdir_unique);
756                 }, Eq(true)),
757                 _)
758         ).InSequence(seq)
759         .WillOnce(Invoke(ReturnErrno(EAGAIN)));
760
761         EXPECT_CALL(*m_mock, process(
762                 ResultOf([&](auto in) {
763                         return (in.header.opcode == FUSE_INTERRUPT &&
764                                 in.body.interrupt.unique == mkdir_unique);
765                 }, Eq(true)),
766                 _)
767         ).InSequence(seq)
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));
774         }));
775
776         setup_interruptor(self);
777         ASSERT_EQ(-1, mkdir(FULLDIRPATH0, MODE));
778         EXPECT_EQ(EINTR, errno);
779 }
780
781
782 // TODO: add a test where write returns EWOULDBLOCK