xref: /freebsd/tests/sys/fs/fusefs/interrupt.cc (revision d26d63a4af4f693b23f5ac389cc6e0d8938cd96b)
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(FUSE_ROOT_ID, 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(FUSE_ROOT_ID, 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(FUSE_ROOT_ID, RELDIRPATH1)
288 	.WillOnce(Invoke(ReturnErrno(ENOENT)));
289 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0)
290 	.WillOnce(Invoke(ReturnErrno(ENOENT)));
291 	expect_mkdir(&mkdir_unique);
292 	EXPECT_CALL(*m_mock, process(
293 		ResultOf([&](auto in) {
294 			return (in.header.opcode == FUSE_INTERRUPT &&
295 				in.body.interrupt.unique == mkdir_unique);
296 		}, Eq(true)),
297 		_)
298 	).InSequence(seq)
299 	.WillOnce(Invoke([&](auto in, auto &out) {
300 		// reject FUSE_INTERRUPT and respond to the FUSE_MKDIR
301 		std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
302 		std::unique_ptr<mockfs_buf_out> out1(new mockfs_buf_out);
303 
304 		out0->header.unique = in.header.unique;
305 		out0->header.error = -ENOSYS;
306 		out0->header.len = sizeof(out0->header);
307 		out.push_back(std::move(out0));
308 
309 		SET_OUT_HEADER_LEN(*out1, entry);
310 		out1->body.create.entry.attr.mode = S_IFDIR | MODE;
311 		out1->body.create.entry.nodeid = ino1;
312 		out1->header.unique = mkdir_unique;
313 		out.push_back(std::move(out1));
314 	}));
315 	EXPECT_CALL(*m_mock, process(
316 		ResultOf([&](auto in) {
317 			return (in.header.opcode == FUSE_MKDIR);
318 		}, Eq(true)),
319 		_)
320 	).InSequence(seq)
321 	.WillOnce(Invoke([&](auto in, auto &out) {
322 		std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
323 
324 		sem_post(&sem0);
325 		sem_wait(&sem1);
326 
327 		SET_OUT_HEADER_LEN(*out0, entry);
328 		out0->body.create.entry.attr.mode = S_IFDIR | MODE;
329 		out0->body.create.entry.nodeid = ino0;
330 		out0->header.unique = in.header.unique;
331 		out.push_back(std::move(out0));
332 	}));
333 
334 	setup_interruptor(self);
335 	/* First mkdir operation should finish synchronously */
336 	ASSERT_EQ(0, mkdir(FULLDIRPATH1, MODE)) << strerror(errno);
337 
338 	ASSERT_EQ(0, pthread_create(&th0, NULL, mkdir0, NULL))
339 		<< strerror(errno);
340 
341 	sem_wait(&sem0);
342 	/*
343 	 * th0 should be blocked waiting for the fuse daemon thread.
344 	 * Signal it.  No FUSE_INTERRUPT should result
345 	 */
346 	pthread_kill(th0, SIGUSR1);
347 	/* Allow the daemon thread to proceed */
348 	sem_post(&sem1);
349 	pthread_join(th0, &thr0_value);
350 	/* Second mkdir should've finished without error */
351 	EXPECT_EQ(0, (intptr_t)thr0_value);
352 }
353 
354 /*
355  * A FUSE filesystem is legally allowed to ignore INTERRUPT operations, and
356  * complete the original operation whenever it damn well pleases.
357  */
358 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */
359 TEST_F(Interrupt, ignore)
360 {
361 	uint64_t ino = 42;
362 	pthread_t self;
363 	uint64_t mkdir_unique;
364 
365 	self = pthread_self();
366 
367 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0)
368 	.WillOnce(Invoke(ReturnErrno(ENOENT)));
369 	expect_mkdir(&mkdir_unique);
370 	EXPECT_CALL(*m_mock, process(
371 		ResultOf([&](auto in) {
372 			return (in.header.opcode == FUSE_INTERRUPT &&
373 				in.body.interrupt.unique == mkdir_unique);
374 		}, Eq(true)),
375 		_)
376 	).WillOnce(Invoke([&](auto in __unused, auto &out) {
377 		// Ignore FUSE_INTERRUPT; respond to the FUSE_MKDIR
378 		std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
379 		out0->header.unique = mkdir_unique;
380 		SET_OUT_HEADER_LEN(*out0, entry);
381 		out0->body.create.entry.attr.mode = S_IFDIR | MODE;
382 		out0->body.create.entry.nodeid = ino;
383 		out.push_back(std::move(out0));
384 	}));
385 
386 	setup_interruptor(self);
387 	ASSERT_EQ(0, mkdir(FULLDIRPATH0, MODE)) << strerror(errno);
388 }
389 
390 /*
391  * A restartable operation (basically, anything except write or setextattr)
392  * that hasn't yet been sent to userland can be interrupted without sending
393  * FUSE_INTERRUPT, and will be automatically restarted.
394  */
395 TEST_F(Interrupt, in_kernel_restartable)
396 {
397 	const char FULLPATH1[] = "mountpoint/other_file.txt";
398 	const char RELPATH1[] = "other_file.txt";
399 	uint64_t ino0 = 42, ino1 = 43;
400 	int fd1;
401 	pthread_t self, th0, th1;
402 	sem_t sem0, sem1;
403 	void *thr0_value, *thr1_value;
404 
405 	ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno);
406 	ASSERT_EQ(0, sem_init(&sem1, 0, 0)) << strerror(errno);
407 	self = pthread_self();
408 
409 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0)
410 	.WillOnce(Invoke(ReturnErrno(ENOENT)));
411 	expect_lookup(RELPATH1, ino1);
412 	expect_open(ino1, 0, 1);
413 	EXPECT_CALL(*m_mock, process(
414 		ResultOf([=](auto in) {
415 			return (in.header.opcode == FUSE_MKDIR);
416 		}, Eq(true)),
417 		_)
418 	).WillOnce(Invoke(ReturnImmediate([&](auto in __unused, auto& out) {
419 		/* Let the next write proceed */
420 		sem_post(&sem1);
421 		/* Pause the daemon thread so it won't read the next op */
422 		sem_wait(&sem0);
423 
424 		SET_OUT_HEADER_LEN(out, entry);
425 		out.body.create.entry.attr.mode = S_IFDIR | MODE;
426 		out.body.create.entry.nodeid = ino0;
427 	})));
428 	FuseTest::expect_read(ino1, 0, FILESIZE, 0, NULL);
429 
430 	fd1 = open(FULLPATH1, O_RDONLY);
431 	ASSERT_LE(0, fd1) << strerror(errno);
432 
433 	/* Use a separate thread for each operation */
434 	ASSERT_EQ(0, pthread_create(&th0, NULL, mkdir0, NULL))
435 		<< strerror(errno);
436 
437 	sem_wait(&sem1);	/* Sequence the two operations */
438 
439 	ASSERT_EQ(0, pthread_create(&th1, NULL, read1, (void*)(intptr_t)fd1))
440 		<< strerror(errno);
441 
442 	setup_interruptor(self, true);
443 
444 	pause();		/* Wait for signal */
445 
446 	/* Unstick the daemon */
447 	ASSERT_EQ(0, sem_post(&sem0)) << strerror(errno);
448 
449 	/* Wait awhile to make sure the signal generates no FUSE_INTERRUPT */
450 	nap();
451 
452 	pthread_join(th1, &thr1_value);
453 	pthread_join(th0, &thr0_value);
454 	EXPECT_EQ(0, (intptr_t)thr1_value);
455 	EXPECT_EQ(0, (intptr_t)thr0_value);
456 	sem_destroy(&sem1);
457 	sem_destroy(&sem0);
458 }
459 
460 /*
461  * An operation that hasn't yet been sent to userland can be interrupted
462  * without sending FUSE_INTERRUPT.  If it's a non-restartable operation (write
463  * or setextattr) it will return EINTR.
464  */
465 TEST_F(Interrupt, in_kernel_nonrestartable)
466 {
467 	const char FULLPATH1[] = "mountpoint/other_file.txt";
468 	const char RELPATH1[] = "other_file.txt";
469 	const char value[] = "whatever";
470 	ssize_t value_len = strlen(value) + 1;
471 	uint64_t ino0 = 42, ino1 = 43;
472 	int ns = EXTATTR_NAMESPACE_USER;
473 	int fd1;
474 	pthread_t self, th0;
475 	sem_t sem0, sem1;
476 	void *thr0_value;
477 	ssize_t r;
478 
479 	ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno);
480 	ASSERT_EQ(0, sem_init(&sem1, 0, 0)) << strerror(errno);
481 	self = pthread_self();
482 
483 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0)
484 	.WillOnce(Invoke(ReturnErrno(ENOENT)));
485 	expect_lookup(RELPATH1, ino1);
486 	expect_open(ino1, 0, 1);
487 	EXPECT_CALL(*m_mock, process(
488 		ResultOf([=](auto in) {
489 			return (in.header.opcode == FUSE_MKDIR);
490 		}, Eq(true)),
491 		_)
492 	).WillOnce(Invoke(ReturnImmediate([&](auto in __unused, auto& out) {
493 		/* Let the next write proceed */
494 		sem_post(&sem1);
495 		/* Pause the daemon thread so it won't read the next op */
496 		sem_wait(&sem0);
497 
498 		SET_OUT_HEADER_LEN(out, entry);
499 		out.body.create.entry.attr.mode = S_IFDIR | MODE;
500 		out.body.create.entry.nodeid = ino0;
501 	})));
502 
503 	fd1 = open(FULLPATH1, O_WRONLY);
504 	ASSERT_LE(0, fd1) << strerror(errno);
505 
506 	/* Use a separate thread for the first write */
507 	ASSERT_EQ(0, pthread_create(&th0, NULL, mkdir0, NULL))
508 		<< strerror(errno);
509 
510 	sem_wait(&sem1);	/* Sequence the two operations */
511 
512 	setup_interruptor(self, true);
513 
514 	r = extattr_set_fd(fd1, ns, "foo", (void*)value, value_len);
515 	EXPECT_EQ(EINTR, errno);
516 
517 	/* Unstick the daemon */
518 	ASSERT_EQ(0, sem_post(&sem0)) << strerror(errno);
519 
520 	/* Wait awhile to make sure the signal generates no FUSE_INTERRUPT */
521 	nap();
522 
523 	pthread_join(th0, &thr0_value);
524 	EXPECT_EQ(0, (intptr_t)thr0_value);
525 	sem_destroy(&sem1);
526 	sem_destroy(&sem0);
527 }
528 
529 /*
530  * A syscall that gets interrupted while blocking on FUSE I/O should send a
531  * FUSE_INTERRUPT command to the fuse filesystem, which should then send EINTR
532  * in response to the _original_ operation.  The kernel should ultimately
533  * return EINTR to userspace
534  */
535 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */
536 TEST_F(Interrupt, in_progress)
537 {
538 	pthread_t self;
539 	uint64_t mkdir_unique;
540 
541 	self = pthread_self();
542 
543 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0)
544 	.WillOnce(Invoke(ReturnErrno(ENOENT)));
545 	expect_mkdir(&mkdir_unique);
546 	EXPECT_CALL(*m_mock, process(
547 		ResultOf([&](auto in) {
548 			return (in.header.opcode == FUSE_INTERRUPT &&
549 				in.body.interrupt.unique == mkdir_unique);
550 		}, Eq(true)),
551 		_)
552 	).WillOnce(Invoke([&](auto in __unused, auto &out) {
553 		std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
554 		out0->header.error = -EINTR;
555 		out0->header.unique = mkdir_unique;
556 		out0->header.len = sizeof(out0->header);
557 		out.push_back(std::move(out0));
558 	}));
559 
560 	setup_interruptor(self);
561 	ASSERT_EQ(-1, mkdir(FULLDIRPATH0, MODE));
562 	EXPECT_EQ(EINTR, errno);
563 }
564 
565 /* Reads should also be interruptible */
566 TEST_F(Interrupt, in_progress_read)
567 {
568 	const char FULLPATH[] = "mountpoint/some_file.txt";
569 	const char RELPATH[] = "some_file.txt";
570 	const size_t bufsize = 80;
571 	char buf[bufsize];
572 	uint64_t ino = 42;
573 	int fd;
574 	pthread_t self;
575 	uint64_t read_unique;
576 
577 	self = pthread_self();
578 
579 	expect_lookup(RELPATH, ino);
580 	expect_open(ino, 0, 1);
581 	expect_read(ino, &read_unique);
582 	EXPECT_CALL(*m_mock, process(
583 		ResultOf([&](auto in) {
584 			return (in.header.opcode == FUSE_INTERRUPT &&
585 				in.body.interrupt.unique == read_unique);
586 		}, Eq(true)),
587 		_)
588 	).WillOnce(Invoke([&](auto in __unused, auto &out) {
589 		std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
590 		out0->header.error = -EINTR;
591 		out0->header.unique = read_unique;
592 		out0->header.len = sizeof(out0->header);
593 		out.push_back(std::move(out0));
594 	}));
595 
596 	fd = open(FULLPATH, O_RDONLY);
597 	ASSERT_LE(0, fd) << strerror(errno);
598 
599 	setup_interruptor(self);
600 	ASSERT_EQ(-1, read(fd, buf, bufsize));
601 	EXPECT_EQ(EINTR, errno);
602 }
603 
604 /* FUSE_INTERRUPT operations should take priority over other pending ops */
605 TEST_F(Interrupt, priority)
606 {
607 	Sequence seq;
608 	uint64_t ino1 = 43;
609 	uint64_t mkdir_unique;
610 	pthread_t self, th0;
611 	sem_t sem0, sem1;
612 
613 	ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno);
614 	ASSERT_EQ(0, sem_init(&sem1, 0, 0)) << strerror(errno);
615 	self = pthread_self();
616 
617 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0)
618 	.WillOnce(Invoke(ReturnErrno(ENOENT)));
619 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH1)
620 	.WillOnce(Invoke(ReturnErrno(ENOENT)));
621 	EXPECT_CALL(*m_mock, process(
622 		ResultOf([=](auto in) {
623 			return (in.header.opcode == FUSE_MKDIR);
624 		}, Eq(true)),
625 		_)
626 	).InSequence(seq)
627 	.WillOnce(Invoke(ReturnImmediate([&](auto in, auto& out) {
628 		mkdir_unique = in.header.unique;
629 
630 		/* Let the next mkdir proceed */
631 		sem_post(&sem1);
632 
633 		/* Pause the daemon thread so it won't read the next op */
634 		sem_wait(&sem0);
635 
636 		/* Finally, interrupt the original op */
637 		out.header.error = -EINTR;
638 		out.header.unique = mkdir_unique;
639 		out.header.len = sizeof(out.header);
640 	})));
641 	/*
642 	 * FUSE_INTERRUPT should be received before the second FUSE_MKDIR,
643 	 * even though it was generated later
644 	 */
645 	EXPECT_CALL(*m_mock, process(
646 		ResultOf([&](auto in) {
647 			return (in.header.opcode == FUSE_INTERRUPT &&
648 				in.body.interrupt.unique == mkdir_unique);
649 		}, Eq(true)),
650 		_)
651 	).InSequence(seq)
652 	.WillOnce(Invoke(ReturnErrno(EAGAIN)));
653 	EXPECT_CALL(*m_mock, process(
654 		ResultOf([&](auto in) {
655 			return (in.header.opcode == FUSE_MKDIR);
656 		}, Eq(true)),
657 		_)
658 	).InSequence(seq)
659 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
660 		SET_OUT_HEADER_LEN(out, entry);
661 		out.body.create.entry.attr.mode = S_IFDIR | MODE;
662 		out.body.create.entry.nodeid = ino1;
663 	})));
664 
665 	/* Use a separate thread for the first mkdir */
666 	ASSERT_EQ(0, pthread_create(&th0, NULL, mkdir0, NULL))
667 		<< strerror(errno);
668 
669 	signaled_semaphore = &sem0;
670 
671 	sem_wait(&sem1);	/* Sequence the two mkdirs */
672 	setup_interruptor(th0, true);
673 	ASSERT_EQ(0, mkdir(FULLDIRPATH1, MODE)) << strerror(errno);
674 
675 	pthread_join(th0, NULL);
676 	sem_destroy(&sem1);
677 	sem_destroy(&sem0);
678 }
679 
680 /*
681  * If the FUSE filesystem receives the FUSE_INTERRUPT operation before
682  * processing the original, then it should wait for "some timeout" for the
683  * original operation to arrive.  If not, it should send EAGAIN to the
684  * INTERRUPT operation, and the kernel should requeue the INTERRUPT.
685  *
686  * In this test, we'll pretend that the INTERRUPT arrives too soon, gets
687  * EAGAINed, then the kernel requeues it, and the second time around it
688  * successfully interrupts the original
689  */
690 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */
691 TEST_F(Interrupt, too_soon)
692 {
693 	Sequence seq;
694 	pthread_t self;
695 	uint64_t mkdir_unique;
696 
697 	self = pthread_self();
698 
699 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0)
700 	.WillOnce(Invoke(ReturnErrno(ENOENT)));
701 	expect_mkdir(&mkdir_unique);
702 
703 	EXPECT_CALL(*m_mock, process(
704 		ResultOf([&](auto in) {
705 			return (in.header.opcode == FUSE_INTERRUPT &&
706 				in.body.interrupt.unique == mkdir_unique);
707 		}, Eq(true)),
708 		_)
709 	).InSequence(seq)
710 	.WillOnce(Invoke(ReturnErrno(EAGAIN)));
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([&](auto in __unused, auto &out __unused) {
720 		std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
721 		out0->header.error = -EINTR;
722 		out0->header.unique = mkdir_unique;
723 		out0->header.len = sizeof(out0->header);
724 		out.push_back(std::move(out0));
725 	}));
726 
727 	setup_interruptor(self);
728 	ASSERT_EQ(-1, mkdir(FULLDIRPATH0, MODE));
729 	EXPECT_EQ(EINTR, errno);
730 }
731 
732 
733 // TODO: add a test where write returns EWOULDBLOCK
734