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