xref: /freebsd/tests/sys/fs/fusefs/interrupt.cc (revision fe75646a0234a261c0013bf1840fdac4acaf0cec)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2019 The FreeBSD Foundation
5  *
6  * This software was developed by BFF Storage Systems, LLC under sponsorship
7  * from the FreeBSD Foundation.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
19  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
22  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28  * SUCH DAMAGE.
29  */
30 
31 extern "C" {
32 #include <sys/types.h>
33 #include <sys/extattr.h>
34 #include <sys/mman.h>
35 #include <sys/wait.h>
36 #include <fcntl.h>
37 #include <pthread.h>
38 #include <semaphore.h>
39 #include <signal.h>
40 }
41 
42 #include "mockfs.hh"
43 #include "utils.hh"
44 
45 using namespace testing;
46 
47 /* Initial size of files used by these tests */
48 const off_t FILESIZE = 1000;
49 /* Access mode used by all directories in these tests */
50 const mode_t MODE = 0755;
51 const char FULLDIRPATH0[] = "mountpoint/some_dir";
52 const char RELDIRPATH0[] = "some_dir";
53 const char FULLDIRPATH1[] = "mountpoint/other_dir";
54 const char RELDIRPATH1[] = "other_dir";
55 
56 static sem_t *blocked_semaphore;
57 static sem_t *signaled_semaphore;
58 
59 static bool killer_should_sleep = false;
60 
61 /* Don't do anything; all we care about is that the syscall gets interrupted */
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 class Intr: public Interrupt {};
189 
190 class Nointr: public Interrupt {
191 	void SetUp() {
192 		m_nointr = true;
193 		Interrupt::SetUp();
194 	}
195 };
196 
197 static void* mkdir0(void* arg __unused) {
198 	ssize_t r;
199 
200 	r = mkdir(FULLDIRPATH0, MODE);
201 	if (r >= 0)
202 		return 0;
203 	else
204 		return (void*)(intptr_t)errno;
205 }
206 
207 static void* read1(void* arg) {
208 	const size_t bufsize = FILESIZE;
209 	char buf[bufsize];
210 	int fd = (int)(intptr_t)arg;
211 	ssize_t r;
212 
213 	r = read(fd, buf, bufsize);
214 	if (r >= 0)
215 		return 0;
216 	else
217 		return (void*)(intptr_t)errno;
218 }
219 
220 /*
221  * An interrupt operation that gets received after the original command is
222  * complete should generate an EAGAIN response.
223  */
224 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */
225 TEST_F(Intr, already_complete)
226 {
227 	uint64_t ino = 42;
228 	pthread_t self;
229 	uint64_t mkdir_unique = 0;
230 	Sequence seq;
231 
232 	self = pthread_self();
233 
234 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0)
235 	.InSequence(seq)
236 	.WillOnce(Invoke(ReturnErrno(ENOENT)));
237 	expect_mkdir(&mkdir_unique);
238 	EXPECT_CALL(*m_mock, process(
239 		ResultOf([&](auto in) {
240 			return (in.header.opcode == FUSE_INTERRUPT &&
241 				in.body.interrupt.unique == mkdir_unique);
242 		}, Eq(true)),
243 		_)
244 	).WillOnce(Invoke([&](auto in, auto &out) {
245 		// First complete the mkdir request
246 		std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
247 		out0->header.unique = mkdir_unique;
248 		SET_OUT_HEADER_LEN(*out0, entry);
249 		out0->body.create.entry.attr.mode = S_IFDIR | MODE;
250 		out0->body.create.entry.nodeid = ino;
251 		out.push_back(std::move(out0));
252 
253 		// Then, respond EAGAIN to the interrupt request
254 		std::unique_ptr<mockfs_buf_out> out1(new mockfs_buf_out);
255 		out1->header.unique = in.header.unique;
256 		out1->header.error = -EAGAIN;
257 		out1->header.len = sizeof(out1->header);
258 		out.push_back(std::move(out1));
259 	}));
260 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0)
261 	.InSequence(seq)
262 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
263 		SET_OUT_HEADER_LEN(out, entry);
264 		out.body.entry.attr.mode = S_IFDIR | MODE;
265 		out.body.entry.nodeid = ino;
266 		out.body.entry.attr.nlink = 2;
267 	})));
268 
269 	setup_interruptor(self);
270 	EXPECT_EQ(0, mkdir(FULLDIRPATH0, MODE)) << strerror(errno);
271 	/*
272 	 * The final syscall simply ensures that the test's main thread doesn't
273 	 * end before the daemon finishes responding to the FUSE_INTERRUPT.
274 	 */
275 	EXPECT_EQ(0, access(FULLDIRPATH0, F_OK)) << strerror(errno);
276 }
277 
278 /*
279  * If a FUSE file system returns ENOSYS for a FUSE_INTERRUPT operation, the
280  * kernel should not attempt to interrupt any other operations on that mount
281  * point.
282  */
283 TEST_F(Intr, enosys)
284 {
285 	uint64_t ino0 = 42, ino1 = 43;;
286 	uint64_t mkdir_unique;
287 	pthread_t self, th0;
288 	sem_t sem0, sem1;
289 	void *thr0_value;
290 	Sequence seq;
291 
292 	self = pthread_self();
293 	ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno);
294 	ASSERT_EQ(0, sem_init(&sem1, 0, 0)) << strerror(errno);
295 
296 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH1)
297 	.WillOnce(Invoke(ReturnErrno(ENOENT)));
298 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0)
299 	.WillOnce(Invoke(ReturnErrno(ENOENT)));
300 	expect_mkdir(&mkdir_unique);
301 	EXPECT_CALL(*m_mock, process(
302 		ResultOf([&](auto in) {
303 			return (in.header.opcode == FUSE_INTERRUPT &&
304 				in.body.interrupt.unique == mkdir_unique);
305 		}, Eq(true)),
306 		_)
307 	).InSequence(seq)
308 	.WillOnce(Invoke([&](auto in, auto &out) {
309 		// reject FUSE_INTERRUPT and respond to the FUSE_MKDIR
310 		std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
311 		std::unique_ptr<mockfs_buf_out> out1(new mockfs_buf_out);
312 
313 		out0->header.unique = in.header.unique;
314 		out0->header.error = -ENOSYS;
315 		out0->header.len = sizeof(out0->header);
316 		out.push_back(std::move(out0));
317 
318 		SET_OUT_HEADER_LEN(*out1, entry);
319 		out1->body.create.entry.attr.mode = S_IFDIR | MODE;
320 		out1->body.create.entry.nodeid = ino1;
321 		out1->header.unique = mkdir_unique;
322 		out.push_back(std::move(out1));
323 	}));
324 	EXPECT_CALL(*m_mock, process(
325 		ResultOf([&](auto in) {
326 			return (in.header.opcode == FUSE_MKDIR);
327 		}, Eq(true)),
328 		_)
329 	).InSequence(seq)
330 	.WillOnce(Invoke([&](auto in, auto &out) {
331 		std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
332 
333 		sem_post(&sem0);
334 		sem_wait(&sem1);
335 
336 		SET_OUT_HEADER_LEN(*out0, entry);
337 		out0->body.create.entry.attr.mode = S_IFDIR | MODE;
338 		out0->body.create.entry.nodeid = ino0;
339 		out0->header.unique = in.header.unique;
340 		out.push_back(std::move(out0));
341 	}));
342 
343 	setup_interruptor(self);
344 	/* First mkdir operation should finish synchronously */
345 	ASSERT_EQ(0, mkdir(FULLDIRPATH1, MODE)) << strerror(errno);
346 
347 	ASSERT_EQ(0, pthread_create(&th0, NULL, mkdir0, NULL))
348 		<< strerror(errno);
349 
350 	sem_wait(&sem0);
351 	/*
352 	 * th0 should be blocked waiting for the fuse daemon thread.
353 	 * Signal it.  No FUSE_INTERRUPT should result
354 	 */
355 	pthread_kill(th0, SIGUSR1);
356 	/* Allow the daemon thread to proceed */
357 	sem_post(&sem1);
358 	pthread_join(th0, &thr0_value);
359 	/* Second mkdir should've finished without error */
360 	EXPECT_EQ(0, (intptr_t)thr0_value);
361 }
362 
363 /*
364  * A FUSE filesystem is legally allowed to ignore INTERRUPT operations, and
365  * complete the original operation whenever it damn well pleases.
366  */
367 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */
368 TEST_F(Intr, ignore)
369 {
370 	uint64_t ino = 42;
371 	pthread_t self;
372 	uint64_t mkdir_unique;
373 
374 	self = pthread_self();
375 
376 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0)
377 	.WillOnce(Invoke(ReturnErrno(ENOENT)));
378 	expect_mkdir(&mkdir_unique);
379 	EXPECT_CALL(*m_mock, process(
380 		ResultOf([&](auto in) {
381 			return (in.header.opcode == FUSE_INTERRUPT &&
382 				in.body.interrupt.unique == mkdir_unique);
383 		}, Eq(true)),
384 		_)
385 	).WillOnce(Invoke([&](auto in __unused, auto &out) {
386 		// Ignore FUSE_INTERRUPT; respond to the FUSE_MKDIR
387 		std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
388 		out0->header.unique = mkdir_unique;
389 		SET_OUT_HEADER_LEN(*out0, entry);
390 		out0->body.create.entry.attr.mode = S_IFDIR | MODE;
391 		out0->body.create.entry.nodeid = ino;
392 		out.push_back(std::move(out0));
393 	}));
394 
395 	setup_interruptor(self);
396 	ASSERT_EQ(0, mkdir(FULLDIRPATH0, MODE)) << strerror(errno);
397 }
398 
399 /*
400  * A restartable operation (basically, anything except write or setextattr)
401  * that hasn't yet been sent to userland can be interrupted without sending
402  * FUSE_INTERRUPT, and will be automatically restarted.
403  */
404 TEST_F(Intr, in_kernel_restartable)
405 {
406 	const char FULLPATH1[] = "mountpoint/other_file.txt";
407 	const char RELPATH1[] = "other_file.txt";
408 	uint64_t ino0 = 42, ino1 = 43;
409 	int fd1;
410 	pthread_t self, th0, th1;
411 	sem_t sem0, sem1;
412 	void *thr0_value, *thr1_value;
413 
414 	ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno);
415 	ASSERT_EQ(0, sem_init(&sem1, 0, 0)) << strerror(errno);
416 	self = pthread_self();
417 
418 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0)
419 	.WillOnce(Invoke(ReturnErrno(ENOENT)));
420 	expect_lookup(RELPATH1, ino1);
421 	expect_open(ino1, 0, 1);
422 	EXPECT_CALL(*m_mock, process(
423 		ResultOf([=](auto in) {
424 			return (in.header.opcode == FUSE_MKDIR);
425 		}, Eq(true)),
426 		_)
427 	).WillOnce(Invoke(ReturnImmediate([&](auto in __unused, auto& out) {
428 		/* Let the next write proceed */
429 		sem_post(&sem1);
430 		/* Pause the daemon thread so it won't read the next op */
431 		sem_wait(&sem0);
432 
433 		SET_OUT_HEADER_LEN(out, entry);
434 		out.body.create.entry.attr.mode = S_IFDIR | MODE;
435 		out.body.create.entry.nodeid = ino0;
436 	})));
437 	FuseTest::expect_read(ino1, 0, FILESIZE, 0, NULL);
438 
439 	fd1 = open(FULLPATH1, O_RDONLY);
440 	ASSERT_LE(0, fd1) << strerror(errno);
441 
442 	/* Use a separate thread for each operation */
443 	ASSERT_EQ(0, pthread_create(&th0, NULL, mkdir0, NULL))
444 		<< strerror(errno);
445 
446 	sem_wait(&sem1);	/* Sequence the two operations */
447 
448 	ASSERT_EQ(0, pthread_create(&th1, NULL, read1, (void*)(intptr_t)fd1))
449 		<< strerror(errno);
450 
451 	setup_interruptor(self, true);
452 
453 	pause();		/* Wait for signal */
454 
455 	/* Unstick the daemon */
456 	ASSERT_EQ(0, sem_post(&sem0)) << strerror(errno);
457 
458 	/* Wait awhile to make sure the signal generates no FUSE_INTERRUPT */
459 	nap();
460 
461 	pthread_join(th1, &thr1_value);
462 	pthread_join(th0, &thr0_value);
463 	EXPECT_EQ(0, (intptr_t)thr1_value);
464 	EXPECT_EQ(0, (intptr_t)thr0_value);
465 	sem_destroy(&sem1);
466 	sem_destroy(&sem0);
467 
468 	leak(fd1);
469 }
470 
471 /*
472  * An operation that hasn't yet been sent to userland can be interrupted
473  * without sending FUSE_INTERRUPT.  If it's a non-restartable operation (write
474  * or setextattr) it will return EINTR.
475  */
476 TEST_F(Intr, in_kernel_nonrestartable)
477 {
478 	const char FULLPATH1[] = "mountpoint/other_file.txt";
479 	const char RELPATH1[] = "other_file.txt";
480 	const char value[] = "whatever";
481 	ssize_t value_len = strlen(value) + 1;
482 	uint64_t ino0 = 42, ino1 = 43;
483 	int ns = EXTATTR_NAMESPACE_USER;
484 	int fd1;
485 	pthread_t self, th0;
486 	sem_t sem0, sem1;
487 	void *thr0_value;
488 	ssize_t r;
489 
490 	ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno);
491 	ASSERT_EQ(0, sem_init(&sem1, 0, 0)) << strerror(errno);
492 	self = pthread_self();
493 
494 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0)
495 	.WillOnce(Invoke(ReturnErrno(ENOENT)));
496 	expect_lookup(RELPATH1, ino1);
497 	expect_open(ino1, 0, 1);
498 	EXPECT_CALL(*m_mock, process(
499 		ResultOf([=](auto in) {
500 			return (in.header.opcode == FUSE_MKDIR);
501 		}, Eq(true)),
502 		_)
503 	).WillOnce(Invoke(ReturnImmediate([&](auto in __unused, auto& out) {
504 		/* Let the next write proceed */
505 		sem_post(&sem1);
506 		/* Pause the daemon thread so it won't read the next op */
507 		sem_wait(&sem0);
508 
509 		SET_OUT_HEADER_LEN(out, entry);
510 		out.body.create.entry.attr.mode = S_IFDIR | MODE;
511 		out.body.create.entry.nodeid = ino0;
512 	})));
513 
514 	fd1 = open(FULLPATH1, O_WRONLY);
515 	ASSERT_LE(0, fd1) << strerror(errno);
516 
517 	/* Use a separate thread for the first write */
518 	ASSERT_EQ(0, pthread_create(&th0, NULL, mkdir0, NULL))
519 		<< strerror(errno);
520 
521 	sem_wait(&sem1);	/* Sequence the two operations */
522 
523 	setup_interruptor(self, true);
524 
525 	r = extattr_set_fd(fd1, ns, "foo", (const void*)value, value_len);
526 	EXPECT_NE(0, r);
527 	EXPECT_EQ(EINTR, errno);
528 
529 	/* Unstick the daemon */
530 	ASSERT_EQ(0, sem_post(&sem0)) << strerror(errno);
531 
532 	/* Wait awhile to make sure the signal generates no FUSE_INTERRUPT */
533 	nap();
534 
535 	pthread_join(th0, &thr0_value);
536 	EXPECT_EQ(0, (intptr_t)thr0_value);
537 	sem_destroy(&sem1);
538 	sem_destroy(&sem0);
539 
540 	leak(fd1);
541 }
542 
543 /*
544  * A syscall that gets interrupted while blocking on FUSE I/O should send a
545  * FUSE_INTERRUPT command to the fuse filesystem, which should then send EINTR
546  * in response to the _original_ operation.  The kernel should ultimately
547  * return EINTR to userspace
548  */
549 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */
550 TEST_F(Intr, in_progress)
551 {
552 	pthread_t self;
553 	uint64_t mkdir_unique;
554 
555 	self = pthread_self();
556 
557 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0)
558 	.WillOnce(Invoke(ReturnErrno(ENOENT)));
559 	expect_mkdir(&mkdir_unique);
560 	EXPECT_CALL(*m_mock, process(
561 		ResultOf([&](auto in) {
562 			return (in.header.opcode == FUSE_INTERRUPT &&
563 				in.body.interrupt.unique == mkdir_unique);
564 		}, Eq(true)),
565 		_)
566 	).WillOnce(Invoke([&](auto in __unused, auto &out) {
567 		std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
568 		out0->header.error = -EINTR;
569 		out0->header.unique = mkdir_unique;
570 		out0->header.len = sizeof(out0->header);
571 		out.push_back(std::move(out0));
572 	}));
573 
574 	setup_interruptor(self);
575 	ASSERT_EQ(-1, mkdir(FULLDIRPATH0, MODE));
576 	EXPECT_EQ(EINTR, errno);
577 }
578 
579 /* Reads should also be interruptible */
580 TEST_F(Intr, in_progress_read)
581 {
582 	const char FULLPATH[] = "mountpoint/some_file.txt";
583 	const char RELPATH[] = "some_file.txt";
584 	const size_t bufsize = 80;
585 	char buf[bufsize];
586 	uint64_t ino = 42;
587 	int fd;
588 	pthread_t self;
589 	uint64_t read_unique;
590 
591 	self = pthread_self();
592 
593 	expect_lookup(RELPATH, ino);
594 	expect_open(ino, 0, 1);
595 	expect_read(ino, &read_unique);
596 	EXPECT_CALL(*m_mock, process(
597 		ResultOf([&](auto in) {
598 			return (in.header.opcode == FUSE_INTERRUPT &&
599 				in.body.interrupt.unique == read_unique);
600 		}, Eq(true)),
601 		_)
602 	).WillOnce(Invoke([&](auto in __unused, auto &out) {
603 		std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
604 		out0->header.error = -EINTR;
605 		out0->header.unique = read_unique;
606 		out0->header.len = sizeof(out0->header);
607 		out.push_back(std::move(out0));
608 	}));
609 
610 	fd = open(FULLPATH, O_RDONLY);
611 	ASSERT_LE(0, fd) << strerror(errno);
612 
613 	setup_interruptor(self);
614 	ASSERT_EQ(-1, read(fd, buf, bufsize));
615 	EXPECT_EQ(EINTR, errno);
616 
617 	leak(fd);
618 }
619 
620 /*
621  * When mounted with -o nointr, fusefs will block signals while waiting for the
622  * server.
623  */
624 TEST_F(Nointr, block)
625 {
626 	uint64_t ino = 42;
627 	pthread_t self;
628 	sem_t sem0;
629 
630 	ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno);
631 	signaled_semaphore = &sem0;
632 	self = pthread_self();
633 
634 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0)
635 	.WillOnce(Invoke(ReturnErrno(ENOENT)));
636 	EXPECT_CALL(*m_mock, process(
637 		ResultOf([=](auto in) {
638 			return (in.header.opcode == FUSE_MKDIR);
639 		}, Eq(true)),
640 		_)
641 	).WillOnce(Invoke(ReturnImmediate([&](auto in __unused, auto& out) {
642 		/* Let the killer proceed */
643 		sem_post(blocked_semaphore);
644 
645 		/* Wait until after the signal has been sent */
646 		sem_wait(signaled_semaphore);
647 		/* Allow time for the mkdir thread to receive the signal */
648 		nap();
649 
650 		/* Finally, complete the original op */
651 		SET_OUT_HEADER_LEN(out, entry);
652 		out.body.create.entry.attr.mode = S_IFDIR | MODE;
653 		out.body.create.entry.nodeid = ino;
654 	})));
655 	EXPECT_CALL(*m_mock, process(
656 		ResultOf([&](auto in) {
657 			return (in.header.opcode == FUSE_INTERRUPT);
658 		}, Eq(true)),
659 		_)
660 	).Times(0);
661 
662 	setup_interruptor(self);
663 	ASSERT_EQ(0, mkdir(FULLDIRPATH0, MODE)) << strerror(errno);
664 
665 	sem_destroy(&sem0);
666 }
667 
668 /* FUSE_INTERRUPT operations should take priority over other pending ops */
669 TEST_F(Intr, priority)
670 {
671 	Sequence seq;
672 	uint64_t ino1 = 43;
673 	uint64_t mkdir_unique;
674 	pthread_t th0;
675 	sem_t sem0, sem1;
676 
677 	ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno);
678 	ASSERT_EQ(0, sem_init(&sem1, 0, 0)) << strerror(errno);
679 
680 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0)
681 	.WillOnce(Invoke(ReturnErrno(ENOENT)));
682 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH1)
683 	.WillOnce(Invoke(ReturnErrno(ENOENT)));
684 	EXPECT_CALL(*m_mock, process(
685 		ResultOf([=](auto in) {
686 			return (in.header.opcode == FUSE_MKDIR);
687 		}, Eq(true)),
688 		_)
689 	).InSequence(seq)
690 	.WillOnce(Invoke(ReturnImmediate([&](auto in, auto& out) {
691 		mkdir_unique = in.header.unique;
692 
693 		/* Let the next mkdir proceed */
694 		sem_post(&sem1);
695 
696 		/* Pause the daemon thread so it won't read the next op */
697 		sem_wait(&sem0);
698 
699 		/* Finally, interrupt the original op */
700 		out.header.error = -EINTR;
701 		out.header.unique = mkdir_unique;
702 		out.header.len = sizeof(out.header);
703 	})));
704 	/*
705 	 * FUSE_INTERRUPT should be received before the second FUSE_MKDIR,
706 	 * even though it was generated later
707 	 */
708 	EXPECT_CALL(*m_mock, process(
709 		ResultOf([&](auto in) {
710 			return (in.header.opcode == FUSE_INTERRUPT &&
711 				in.body.interrupt.unique == mkdir_unique);
712 		}, Eq(true)),
713 		_)
714 	).InSequence(seq)
715 	.WillOnce(Invoke(ReturnErrno(EAGAIN)));
716 	EXPECT_CALL(*m_mock, process(
717 		ResultOf([&](auto in) {
718 			return (in.header.opcode == FUSE_MKDIR);
719 		}, Eq(true)),
720 		_)
721 	).InSequence(seq)
722 	.WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) {
723 		SET_OUT_HEADER_LEN(out, entry);
724 		out.body.create.entry.attr.mode = S_IFDIR | MODE;
725 		out.body.create.entry.nodeid = ino1;
726 	})));
727 
728 	/* Use a separate thread for the first mkdir */
729 	ASSERT_EQ(0, pthread_create(&th0, NULL, mkdir0, NULL))
730 		<< strerror(errno);
731 
732 	signaled_semaphore = &sem0;
733 
734 	sem_wait(&sem1);	/* Sequence the two mkdirs */
735 	setup_interruptor(th0, true);
736 	ASSERT_EQ(0, mkdir(FULLDIRPATH1, MODE)) << strerror(errno);
737 
738 	pthread_join(th0, NULL);
739 	sem_destroy(&sem1);
740 	sem_destroy(&sem0);
741 }
742 
743 /*
744  * If the FUSE filesystem receives the FUSE_INTERRUPT operation before
745  * processing the original, then it should wait for "some timeout" for the
746  * original operation to arrive.  If not, it should send EAGAIN to the
747  * INTERRUPT operation, and the kernel should requeue the INTERRUPT.
748  *
749  * In this test, we'll pretend that the INTERRUPT arrives too soon, gets
750  * EAGAINed, then the kernel requeues it, and the second time around it
751  * successfully interrupts the original
752  */
753 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */
754 TEST_F(Intr, too_soon)
755 {
756 	Sequence seq;
757 	pthread_t self;
758 	uint64_t mkdir_unique;
759 
760 	self = pthread_self();
761 
762 	EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0)
763 	.WillOnce(Invoke(ReturnErrno(ENOENT)));
764 	expect_mkdir(&mkdir_unique);
765 
766 	EXPECT_CALL(*m_mock, process(
767 		ResultOf([&](auto in) {
768 			return (in.header.opcode == FUSE_INTERRUPT &&
769 				in.body.interrupt.unique == mkdir_unique);
770 		}, Eq(true)),
771 		_)
772 	).InSequence(seq)
773 	.WillOnce(Invoke(ReturnErrno(EAGAIN)));
774 
775 	EXPECT_CALL(*m_mock, process(
776 		ResultOf([&](auto in) {
777 			return (in.header.opcode == FUSE_INTERRUPT &&
778 				in.body.interrupt.unique == mkdir_unique);
779 		}, Eq(true)),
780 		_)
781 	).InSequence(seq)
782 	.WillOnce(Invoke([&](auto in __unused, auto &out __unused) {
783 		std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
784 		out0->header.error = -EINTR;
785 		out0->header.unique = mkdir_unique;
786 		out0->header.len = sizeof(out0->header);
787 		out.push_back(std::move(out0));
788 	}));
789 
790 	setup_interruptor(self);
791 	ASSERT_EQ(-1, mkdir(FULLDIRPATH0, MODE));
792 	EXPECT_EQ(EINTR, errno);
793 }
794 
795 
796 // TODO: add a test where write returns EWOULDBLOCK
797