]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - tests/sys/fs/fusefs/interrupt.cc
fusefs: remove an obsolete fifo test
[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/wait.h>
35 #include <fcntl.h>
36 #include <pthread.h>
37 #include <semaphore.h>
38 #include <signal.h>
39 }
40
41 #include "mockfs.hh"
42 #include "utils.hh"
43
44 using namespace testing;
45
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";
54
55 static sem_t *signaled_semaphore;
56
57 /* Don't do anything; all we care about is that the syscall gets interrupted */
58 void sigusr2_handler(int __unused sig) {
59         if (verbosity > 1) {
60                 printf("Signaled!  thread %p\n", pthread_self());
61         }
62
63 }
64
65 void* killer(void* target) {
66         /* 
67          * Sleep for awhile so we can be mostly confident that the main thread
68          * is already blocked in write(2)
69          */
70         usleep(250'000);
71         if (verbosity > 1)
72                 printf("Signalling!  thread %p\n", target);
73         pthread_kill((pthread_t)target, SIGUSR2);
74         if (signaled_semaphore != NULL)
75                 sem_post(signaled_semaphore);
76
77         return(NULL);
78 }
79
80 class Interrupt: public FuseTest {
81 public:
82 pthread_t m_child;
83
84 Interrupt(): m_child(NULL) {};
85
86 void expect_lookup(const char *relpath, uint64_t ino)
87 {
88         FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, FILESIZE, 1);
89 }
90
91 /* 
92  * Expect a FUSE_MKDIR but don't reply.  Instead, just record the unique value
93  * to the provided pointer
94  */
95 void expect_mkdir(uint64_t *mkdir_unique)
96 {
97         EXPECT_CALL(*m_mock, process(
98                 ResultOf([=](auto in) {
99                         return (in->header.opcode == FUSE_MKDIR);
100                                 
101                 }, Eq(true)),
102                 _)
103         ).WillOnce(Invoke([=](auto in, auto &out __unused) {
104                 *mkdir_unique = in->header.unique;
105         }));
106 }
107
108 /* 
109  * Expect a FUSE_READ but don't reply.  Instead, just record the unique value
110  * to the provided pointer
111  */
112 void expect_read(uint64_t ino, uint64_t *read_unique)
113 {
114         EXPECT_CALL(*m_mock, process(
115                 ResultOf([=](auto in) {
116                         return (in->header.opcode == FUSE_READ &&
117                                 in->header.nodeid == ino);
118                 }, Eq(true)),
119                 _)
120         ).WillOnce(Invoke([=](auto in, auto &out __unused) {
121                 *read_unique = in->header.unique;
122         }));
123 }
124
125 /* 
126  * Expect a FUSE_WRITE but don't reply.  Instead, just record the unique value
127  * to the provided pointer
128  */
129 void expect_write(uint64_t ino, uint64_t *write_unique)
130 {
131         EXPECT_CALL(*m_mock, process(
132                 ResultOf([=](auto in) {
133                         return (in->header.opcode == FUSE_WRITE &&
134                                 in->header.nodeid == ino);
135                 }, Eq(true)),
136                 _)
137         ).WillOnce(Invoke([=](auto in, auto &out __unused) {
138                 *write_unique = in->header.unique;
139         }));
140 }
141
142 void setup_interruptor(pthread_t target)
143 {
144         ASSERT_NE(SIG_ERR, signal(SIGUSR2, sigusr2_handler)) << strerror(errno);
145         ASSERT_EQ(0, pthread_create(&m_child, NULL, killer, (void*)target))
146                 << strerror(errno);
147 }
148
149 void SetUp() {
150         signaled_semaphore = NULL;
151         FuseTest::SetUp();
152 }
153
154 void TearDown() {
155         struct sigaction sa;
156
157         if (m_child != NULL) {
158                 pthread_join(m_child, NULL);
159         }
160         bzero(&sa, sizeof(sa));
161         sa.sa_handler = SIG_DFL;
162         sigaction(SIGUSR2, &sa, NULL);
163
164         FuseTest::TearDown();
165 }
166 };
167
168 static void* mkdir0(void* arg __unused) {
169         ssize_t r;
170
171         r = mkdir(FULLDIRPATH0, MODE);
172         if (r >= 0)
173                 return 0;
174         else
175                 return (void*)(intptr_t)errno;
176 }
177
178 static void* read1(void* arg) {
179         const size_t bufsize = FILESIZE;
180         char buf[bufsize];
181         int fd = (int)(intptr_t)arg;
182         ssize_t r;
183
184         r = read(fd, buf, bufsize);
185         if (r >= 0)
186                 return 0;
187         else
188                 return (void*)(intptr_t)errno;
189 }
190
191 /* 
192  * An interrupt operation that gets received after the original command is
193  * complete should generate an EAGAIN response.
194  */
195 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */
196 TEST_F(Interrupt, already_complete)
197 {
198         uint64_t ino = 42;
199         pthread_t self;
200         uint64_t mkdir_unique = 0;
201
202         self = pthread_self();
203
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);
210                 }, Eq(true)),
211                 _)
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;
219                 out.push_back(out0);
220
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);
226                 out.push_back(out1);
227         }));
228
229         setup_interruptor(self);
230         EXPECT_EQ(0, mkdir(FULLDIRPATH0, MODE)) << strerror(errno);
231 }
232
233 /*
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
236  * point.
237  */
238 TEST_F(Interrupt, enosys)
239 {
240         uint64_t ino0 = 42, ino1 = 43;;
241         uint64_t mkdir_unique;
242         pthread_t self, th0;
243         sem_t sem0, sem1;
244         void *thr0_value;
245         Sequence seq;
246
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);
250
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);
258                 }, Eq(true)),
259                 _)
260         ).InSequence(seq)
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;
265
266                 out0->header.unique = in->header.unique;
267                 out0->header.error = -ENOSYS;
268                 out0->header.len = sizeof(out0->header);
269                 out.push_back(out0);
270
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;
275                 out.push_back(out1);
276         }));
277         EXPECT_CALL(*m_mock, process(
278                 ResultOf([&](auto in) {
279                         return (in->header.opcode == FUSE_MKDIR);
280                 }, Eq(true)),
281                 _)
282         ).InSequence(seq)
283         .WillOnce(Invoke([&](auto in, auto &out) {
284                 auto out0 = new mockfs_buf_out;
285
286                 sem_post(&sem0);
287                 sem_wait(&sem1);
288
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;
293                 out.push_back(out0);
294         }));
295
296         setup_interruptor(self);
297         /* First mkdir operation should finish synchronously */
298         ASSERT_EQ(0, mkdir(FULLDIRPATH1, MODE)) << strerror(errno);
299
300         ASSERT_EQ(0, pthread_create(&th0, NULL, mkdir0, NULL))
301                 << strerror(errno);
302
303         sem_wait(&sem0);
304         /*
305          * th0 should be blocked waiting for the fuse daemon thread.
306          * Signal it.  No FUSE_INTERRUPT should result
307          */
308         pthread_kill(th0, SIGUSR1);
309         /* Allow the daemon thread to proceed */
310         sem_post(&sem1);
311         pthread_join(th0, &thr0_value);
312         /* Second mkdir should've finished without error */
313         EXPECT_EQ(0, (intptr_t)thr0_value);
314 }
315
316 /*
317  * Upon receipt of a fatal signal, fusefs should return ASAP after sending
318  * FUSE_INTERRUPT.
319  */
320 TEST_F(Interrupt, fatal_signal)
321 {
322         int status;
323         pthread_t self;
324         uint64_t mkdir_unique;
325
326         self = pthread_self();
327
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);
334                 }, Eq(true)),
335                 _)
336         ).WillOnce(Invoke([&](auto in __unused, auto &out __unused) {
337                 /* Don't respond.  The process should exit anyway */
338         }));
339
340         fork(false, &status, [&] {
341         }, [&]() {
342                 struct sigaction sa;
343                 int r;
344                 pthread_t killer_th;
345                 pthread_t self;
346
347                 /* SIGUSR2 terminates the process by default */
348                 bzero(&sa, sizeof(sa));
349                 sa.sa_handler = SIG_DFL;
350                 r = sigaction(SIGUSR2, &sa, NULL);
351                 if (r != 0) {
352                         perror("sigaction");
353                         return 1;
354                 }
355                 self = pthread_self();
356                 r = pthread_create(&killer_th, NULL, killer, (void*)self);
357                 if (r != 0) {
358                         perror("pthread_create");
359                         return 1;
360                 }
361
362                 mkdir(FULLDIRPATH0, MODE);
363                 return 1;
364         });
365         ASSERT_EQ(SIGUSR2, WTERMSIG(status));
366 }
367
368 /*
369  * A FUSE filesystem is legally allowed to ignore INTERRUPT operations, and
370  * complete the original operation whenever it damn well pleases.
371  */
372 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */
373 TEST_F(Interrupt, ignore)
374 {
375         uint64_t ino = 42;
376         pthread_t self;
377         uint64_t mkdir_unique;
378
379         self = pthread_self();
380
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);
387                 }, Eq(true)),
388                 _)
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;
396                 out.push_back(out0);
397         }));
398
399         setup_interruptor(self);
400         ASSERT_EQ(0, mkdir(FULLDIRPATH0, MODE)) << strerror(errno);
401 }
402
403 /*
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.
407  */
408 TEST_F(Interrupt, in_kernel_restartable)
409 {
410         const char FULLPATH1[] = "mountpoint/other_file.txt";
411         const char RELPATH1[] = "other_file.txt";
412         uint64_t ino0 = 42, ino1 = 43;
413         int fd1;
414         pthread_t self, th0, th1;
415         sem_t sem0, sem1;
416         void *thr0_value, *thr1_value;
417
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();
421
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);
428                 }, Eq(true)),
429                 _)
430         ).WillOnce(Invoke(ReturnImmediate([&](auto in __unused, auto out) {
431                 /* Let the next write proceed */
432                 sem_post(&sem1);
433                 /* Pause the daemon thread so it won't read the next op */
434                 sem_wait(&sem0);
435
436                 SET_OUT_HEADER_LEN(out, entry);
437                 out->body.create.entry.attr.mode = S_IFDIR | MODE;
438                 out->body.create.entry.nodeid = ino0;
439         })));
440         FuseTest::expect_read(ino1, 0, FILESIZE, 0, NULL);
441
442         fd1 = open(FULLPATH1, O_RDONLY);
443         ASSERT_LE(0, fd1) << strerror(errno);
444
445         /* Use a separate thread for each operation */
446         ASSERT_EQ(0, pthread_create(&th0, NULL, mkdir0, NULL))
447                 << strerror(errno);
448
449         sem_wait(&sem1);        /* Sequence the two operations */
450
451         ASSERT_EQ(0, pthread_create(&th1, NULL, read1, (void*)(intptr_t)fd1))
452                 << strerror(errno);
453
454         setup_interruptor(self);
455
456         pause();                /* Wait for signal */
457
458         /* Unstick the daemon */
459         ASSERT_EQ(0, sem_post(&sem0)) << strerror(errno);
460
461         /* Wait awhile to make sure the signal generates no FUSE_INTERRUPT */
462         usleep(250'000);
463
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);
468         sem_destroy(&sem1);
469         sem_destroy(&sem0);
470 }
471
472 /*
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.
476  */
477 TEST_F(Interrupt, in_kernel_nonrestartable)
478 {
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;
485         int fd1;
486         pthread_t self, th0;
487         sem_t sem0, sem1;
488         void *thr0_value;
489         ssize_t r;
490
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();
494
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);
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         setup_interruptor(self);
522
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);
526
527         /* Unstick the daemon */
528         ASSERT_EQ(0, sem_post(&sem0)) << strerror(errno);
529
530         /* Wait awhile to make sure the signal generates no FUSE_INTERRUPT */
531         usleep(250'000);
532
533         pthread_join(th0, &thr0_value);
534         EXPECT_EQ(0, (intptr_t)thr0_value);
535         sem_destroy(&sem1);
536         sem_destroy(&sem0);
537 }
538
539 /* 
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
544  */
545 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */
546 TEST_F(Interrupt, in_progress)
547 {
548         pthread_t self;
549         uint64_t mkdir_unique;
550
551         self = pthread_self();
552
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);
559                 }, Eq(true)),
560                 _)
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);
566                 out.push_back(out0);
567         }));
568
569         setup_interruptor(self);
570         ASSERT_EQ(-1, mkdir(FULLDIRPATH0, MODE));
571         EXPECT_EQ(EINTR, errno);
572 }
573
574 /* Reads should also be interruptible */
575 TEST_F(Interrupt, in_progress_read)
576 {
577         const char FULLPATH[] = "mountpoint/some_file.txt";
578         const char RELPATH[] = "some_file.txt";
579         const size_t bufsize = 80;
580         char buf[bufsize];
581         uint64_t ino = 42;
582         int fd;
583         pthread_t self;
584         uint64_t read_unique;
585
586         self = pthread_self();
587
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);
595                 }, Eq(true)),
596                 _)
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);
602                 out.push_back(out0);
603         }));
604
605         fd = open(FULLPATH, O_RDONLY);
606         ASSERT_LE(0, fd) << strerror(errno);
607
608         setup_interruptor(self);
609         ASSERT_EQ(-1, read(fd, buf, bufsize));
610         EXPECT_EQ(EINTR, errno);
611 }
612
613 /* FUSE_INTERRUPT operations should take priority over other pending ops */
614 TEST_F(Interrupt, priority)
615 {
616         Sequence seq;
617         uint64_t ino1 = 43;
618         uint64_t mkdir_unique;
619         pthread_t self, th0;
620         sem_t sem0, sem1;
621
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();
625
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);
631                 }, Eq(true)),
632                 _)
633         ).InSequence(seq)
634         .WillOnce(Invoke(ReturnImmediate([&](auto in, auto out) {
635                 mkdir_unique = in->header.unique;
636
637                 /* Let the next mkdir proceed */
638                 sem_post(&sem1);
639
640                 /* Pause the daemon thread so it won't read the next op */
641                 sem_wait(&sem0);
642
643                 /* Finally, interrupt the original op */
644                 out->header.error = -EINTR;
645                 out->header.unique = mkdir_unique;
646                 out->header.len = sizeof(out->header);
647         })));
648         /* 
649          * FUSE_INTERRUPT should be received before the second FUSE_MKDIR,
650          * even though it was generated later
651          */
652         EXPECT_CALL(*m_mock, process(
653                 ResultOf([&](auto in) {
654                         return (in->header.opcode == FUSE_INTERRUPT &&
655                                 in->body.interrupt.unique == mkdir_unique);
656                 }, Eq(true)),
657                 _)
658         ).InSequence(seq)
659         .WillOnce(Invoke(ReturnErrno(EAGAIN)));
660         EXPECT_CALL(*m_mock, process(
661                 ResultOf([&](auto in) {
662                         return (in->header.opcode == FUSE_MKDIR);
663                 }, Eq(true)),
664                 _)
665         ).InSequence(seq)
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;
670         })));
671
672         /* Use a separate thread for the first mkdir */
673         ASSERT_EQ(0, pthread_create(&th0, NULL, mkdir0, NULL))
674                 << strerror(errno);
675
676         signaled_semaphore = &sem0;
677
678         sem_wait(&sem1);        /* Sequence the two mkdirs */
679         setup_interruptor(th0);
680         ASSERT_EQ(0, mkdir(FULLDIRPATH1, MODE)) << strerror(errno);
681
682         /* Wait awhile to make sure the signal generates no FUSE_INTERRUPT */
683         usleep(250'000);
684
685         pthread_join(th0, NULL);
686         sem_destroy(&sem1);
687         sem_destroy(&sem0);
688 }
689
690 /*
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.
695  *
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
699  */
700 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */
701 TEST_F(Interrupt, too_soon)
702 {
703         Sequence seq;
704         pthread_t self;
705         uint64_t mkdir_unique;
706
707         self = pthread_self();
708
709         EXPECT_LOOKUP(1, RELDIRPATH0).WillOnce(Invoke(ReturnErrno(ENOENT)));
710         expect_mkdir(&mkdir_unique);
711
712         EXPECT_CALL(*m_mock, process(
713                 ResultOf([&](auto in) {
714                         return (in->header.opcode == FUSE_INTERRUPT &&
715                                 in->body.interrupt.unique == mkdir_unique);
716                 }, Eq(true)),
717                 _)
718         ).InSequence(seq)
719         .WillOnce(Invoke(ReturnErrno(EAGAIN)));
720
721         EXPECT_CALL(*m_mock, process(
722                 ResultOf([&](auto in) {
723                         return (in->header.opcode == FUSE_INTERRUPT &&
724                                 in->body.interrupt.unique == mkdir_unique);
725                 }, Eq(true)),
726                 _)
727         ).InSequence(seq)
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);
733                 out.push_back(out0);
734         }));
735
736         setup_interruptor(self);
737         ASSERT_EQ(-1, mkdir(FULLDIRPATH0, MODE));
738         EXPECT_EQ(EINTR, errno);
739 }
740
741
742 // TODO: add a test where write returns EWOULDBLOCK