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
33extern "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
47using namespace testing;
48
49/* Initial size of files used by these tests */
50const off_t FILESIZE = 1000;
51/* Access mode used by all directories in these tests */
52const mode_t MODE = 0755;
53const char FULLDIRPATH0[] = "mountpoint/some_dir";
54const char RELDIRPATH0[] = "some_dir";
55const char FULLDIRPATH1[] = "mountpoint/other_dir";
56const char RELDIRPATH1[] = "other_dir";
57
58static sem_t *blocked_semaphore;
59static sem_t *signaled_semaphore;
60
61static bool killer_should_sleep = false;
62
63/* Don't do anything; all we care about is that the syscall gets interrupted */
64void sigusr2_handler(int __unused sig) {
65	if (verbosity > 1) {
66		printf("Signaled!  thread %p\n", pthread_self());
67	}
68
69}
70
71void* 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
86class Interrupt: public FuseTest {
87public:
88pthread_t m_child;
89
90Interrupt(): m_child(NULL) {};
91
92void 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 */
101void 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 */
118void 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 */
136void 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
150void 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
158void 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
173void 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
190class Intr: public Interrupt {};
191
192class Nointr: public Interrupt {
193	void SetUp() {
194		m_nointr = true;
195		Interrupt::SetUp();
196	}
197};
198
199static 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
209static 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 */
227TEST_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 */
285TEST_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 */
370TEST_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 */
406TEST_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 */
478TEST_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 */
552TEST_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 */
582TEST_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 */
626TEST_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 */
671TEST_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 */
756TEST_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