xref: /freebsd/tests/sys/fs/fusefs/interrupt.cc (revision a154214620e341704be58566f85516b6b6b5bcf8)
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/wait.h>
33 #include <fcntl.h>
34 #include <pthread.h>
35 #include <signal.h>
36 }
37 
38 #include "mockfs.hh"
39 #include "utils.hh"
40 
41 using namespace testing;
42 
43 /* Don't do anything; all we care about is that the syscall gets interrupted */
44 void sigusr2_handler(int __unused sig) {
45 	if (verbosity > 1) {
46 		printf("Signaled!  thread %p\n", pthread_self());
47 	}
48 
49 }
50 
51 void* killer(void* target) {
52 	/*
53 	 * Sleep for awhile so we can be mostly confident that the main thread
54 	 * is already blocked in write(2)
55 	 */
56 	usleep(250'000);
57 	if (verbosity > 1)
58 		printf("Signalling!  thread %p\n", target);
59 	pthread_kill((pthread_t)target, SIGUSR2);
60 
61 	return(NULL);
62 }
63 
64 class Interrupt: public FuseTest {
65 public:
66 pthread_t m_child;
67 
68 Interrupt(): m_child(NULL) {};
69 
70 void expect_lookup(const char *relpath, uint64_t ino)
71 {
72 	FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 1000, 1);
73 }
74 
75 /*
76  * Expect a FUSE_READ but don't reply.  Instead, just record the unique value
77  * to the provided pointer
78  */
79 void expect_read(uint64_t ino, uint64_t *read_unique)
80 {
81 	EXPECT_CALL(*m_mock, process(
82 		ResultOf([=](auto in) {
83 			return (in->header.opcode == FUSE_READ &&
84 				in->header.nodeid == ino);
85 		}, Eq(true)),
86 		_)
87 	).WillOnce(Invoke([=](auto in, auto &out __unused) {
88 		*read_unique = in->header.unique;
89 	}));
90 }
91 
92 /*
93  * Expect a FUSE_WRITE but don't reply.  Instead, just record the unique value
94  * to the provided pointer
95  */
96 void expect_write(uint64_t ino, uint64_t *write_unique)
97 {
98 	EXPECT_CALL(*m_mock, process(
99 		ResultOf([=](auto in) {
100 			return (in->header.opcode == FUSE_WRITE &&
101 				in->header.nodeid == ino);
102 		}, Eq(true)),
103 		_)
104 	).WillOnce(Invoke([=](auto in, auto &out __unused) {
105 		*write_unique = in->header.unique;
106 	}));
107 }
108 
109 void setup_interruptor(pthread_t self)
110 {
111 	ASSERT_NE(SIG_ERR, signal(SIGUSR2, sigusr2_handler)) << strerror(errno);
112 	ASSERT_EQ(0, pthread_create(&m_child, NULL, killer, (void*)self))
113 		<< strerror(errno);
114 }
115 
116 void TearDown() {
117 	struct sigaction sa;
118 
119 	if (m_child != NULL) {
120 		pthread_join(m_child, NULL);
121 	}
122 	bzero(&sa, sizeof(sa));
123 	sa.sa_handler = SIG_DFL;
124 	sigaction(SIGUSR2, &sa, NULL);
125 
126 	FuseTest::TearDown();
127 }
128 };
129 
130 /*
131  * An interrupt operation that gets received after the original command is
132  * complete should generate an EAGAIN response.
133  */
134 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */
135 TEST_F(Interrupt, already_complete)
136 {
137 	const char FULLPATH[] = "mountpoint/some_file.txt";
138 	const char RELPATH[] = "some_file.txt";
139 	const char *CONTENTS = "abcdefgh";
140 	uint64_t ino = 42;
141 	int fd;
142 	ssize_t bufsize = strlen(CONTENTS);
143 	pthread_t self;
144 	uint64_t write_unique = 0;
145 
146 	self = pthread_self();
147 
148 	expect_lookup(RELPATH, ino);
149 	expect_open(ino, 0, 1);
150 	expect_write(ino, &write_unique);
151 	EXPECT_CALL(*m_mock, process(
152 		ResultOf([&](auto in) {
153 			return (in->header.opcode == FUSE_INTERRUPT &&
154 				in->body.interrupt.unique == write_unique);
155 		}, Eq(true)),
156 		_)
157 	).WillOnce(Invoke([&](auto in, auto &out) {
158 		// First complete the write request
159 		auto out0 = new mockfs_buf_out;
160 		out0->header.unique = write_unique;
161 		SET_OUT_HEADER_LEN(out0, write);
162 		out0->body.write.size = bufsize;
163 		out.push_back(out0);
164 
165 		// Then, respond EAGAIN to the interrupt request
166 		auto out1 = new mockfs_buf_out;
167 		out1->header.unique = in->header.unique;
168 		out1->header.error = -EAGAIN;
169 		out1->header.len = sizeof(out1->header);
170 		out.push_back(out1);
171 	}));
172 
173 	fd = open(FULLPATH, O_WRONLY);
174 	ASSERT_LE(0, fd) << strerror(errno);
175 
176 	setup_interruptor(self);
177 	EXPECT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
178 
179 	/* Deliberately leak fd.  close(2) will be tested in release.cc */
180 }
181 
182 /*
183  * Upon receipt of a fatal signal, fusefs should return ASAP after sending
184  * FUSE_INTERRUPT.
185  */
186 TEST_F(Interrupt, fatal_signal)
187 {
188 	const char FULLPATH[] = "mountpoint/some_file.txt";
189 	const char *CONTENTS = "abcdefgh";
190 	const char RELPATH[] = "some_file.txt";
191 	ssize_t bufsize = strlen(CONTENTS);
192 	uint64_t ino = 42;
193 	int status;
194 	pthread_t self;
195 	uint64_t write_unique;
196 
197 	self = pthread_self();
198 
199 	expect_lookup(RELPATH, ino);
200 	expect_open(ino, 0, 1);
201 	expect_write(ino, &write_unique);
202 	EXPECT_CALL(*m_mock, process(
203 		ResultOf([&](auto in) {
204 			return (in->header.opcode == FUSE_INTERRUPT &&
205 				in->body.interrupt.unique == write_unique);
206 		}, Eq(true)),
207 		_)
208 	).WillOnce(Invoke([&](auto in __unused, auto &out __unused) {
209 		/* Don't respond.  The process should exit anyway */
210 	}));
211 	expect_flush(ino, 1, ReturnErrno(0));
212 	expect_release(ino, FH);
213 
214 	fork(false, &status, [&] {
215 	}, [&]() {
216 		struct sigaction sa;
217 		int fd, r;
218 		pthread_t killer_th;
219 		pthread_t self;
220 
221 		fd = open(FULLPATH, O_WRONLY);
222 		if (fd < 0) {
223 			perror("open");
224 			return 1;
225 		}
226 
227 		/* SIGUSR2 terminates the process by default */
228 		bzero(&sa, sizeof(sa));
229 		sa.sa_handler = SIG_DFL;
230 		r = sigaction(SIGUSR2, &sa, NULL);
231 		if (r != 0) {
232 			perror("sigaction");
233 			return 1;
234 		}
235 		self = pthread_self();
236 		r = pthread_create(&killer_th, NULL, killer, (void*)self);
237 		if (r != 0) {
238 			perror("pthread_create");
239 			return 1;
240 		}
241 
242 		write(fd, CONTENTS, bufsize);
243 		return 1;
244 	});
245 	ASSERT_EQ(SIGUSR2, WTERMSIG(status));
246 
247 	/* Deliberately leak fd.  close(2) will be tested in release.cc */
248 }
249 
250 /*
251  * A FUSE filesystem is legally allowed to ignore INTERRUPT operations, and
252  * complete the original operation whenever it damn well pleases.
253  */
254 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */
255 TEST_F(Interrupt, ignore)
256 {
257 	const char FULLPATH[] = "mountpoint/some_file.txt";
258 	const char RELPATH[] = "some_file.txt";
259 	const char *CONTENTS = "abcdefgh";
260 	uint64_t ino = 42;
261 	int fd;
262 	ssize_t bufsize = strlen(CONTENTS);
263 	pthread_t self;
264 	uint64_t write_unique;
265 
266 	self = pthread_self();
267 
268 	expect_lookup(RELPATH, ino);
269 	expect_open(ino, 0, 1);
270 	expect_write(ino, &write_unique);
271 	EXPECT_CALL(*m_mock, process(
272 		ResultOf([&](auto in) {
273 			return (in->header.opcode == FUSE_INTERRUPT &&
274 				in->body.interrupt.unique == write_unique);
275 		}, Eq(true)),
276 		_)
277 	).WillOnce(Invoke([&](auto in __unused, auto &out) {
278 		// Ignore FUSE_INTERRUPT; respond to the FUSE_WRITE
279 		auto out0 = new mockfs_buf_out;
280 		out0->header.unique = write_unique;
281 		SET_OUT_HEADER_LEN(out0, write);
282 		out0->body.write.size = bufsize;
283 		out.push_back(out0);
284 	}));
285 
286 	fd = open(FULLPATH, O_WRONLY);
287 	ASSERT_LE(0, fd) << strerror(errno);
288 
289 	setup_interruptor(self);
290 	ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno);
291 
292 	/* Deliberately leak fd.  close(2) will be tested in release.cc */
293 }
294 
295 /*
296  * A syscall that gets interrupted while blocking on FUSE I/O should send a
297  * FUSE_INTERRUPT command to the fuse filesystem, which should then send EINTR
298  * in response to the _original_ operation.  The kernel should ultimately
299  * return EINTR to userspace
300  */
301 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */
302 TEST_F(Interrupt, in_progress)
303 {
304 	const char FULLPATH[] = "mountpoint/some_file.txt";
305 	const char RELPATH[] = "some_file.txt";
306 	const char *CONTENTS = "abcdefgh";
307 	uint64_t ino = 42;
308 	int fd;
309 	ssize_t bufsize = strlen(CONTENTS);
310 	pthread_t self;
311 	uint64_t write_unique;
312 
313 	self = pthread_self();
314 
315 	expect_lookup(RELPATH, ino);
316 	expect_open(ino, 0, 1);
317 	expect_write(ino, &write_unique);
318 	EXPECT_CALL(*m_mock, process(
319 		ResultOf([&](auto in) {
320 			return (in->header.opcode == FUSE_INTERRUPT &&
321 				in->body.interrupt.unique == write_unique);
322 		}, Eq(true)),
323 		_)
324 	).WillOnce(Invoke([&](auto in __unused, auto &out) {
325 		auto out0 = new mockfs_buf_out;
326 		out0->header.error = -EINTR;
327 		out0->header.unique = write_unique;
328 		out0->header.len = sizeof(out0->header);
329 		out.push_back(out0);
330 	}));
331 
332 	fd = open(FULLPATH, O_WRONLY);
333 	ASSERT_LE(0, fd) << strerror(errno);
334 
335 	setup_interruptor(self);
336 	ASSERT_EQ(-1, write(fd, CONTENTS, bufsize));
337 	EXPECT_EQ(EINTR, errno);
338 
339 	/* Deliberately leak fd.  close(2) will be tested in release.cc */
340 }
341 
342 /* Reads should also be interruptible */
343 TEST_F(Interrupt, in_progress_read)
344 {
345 	const char FULLPATH[] = "mountpoint/some_file.txt";
346 	const char RELPATH[] = "some_file.txt";
347 	const size_t bufsize = 80;
348 	char buf[bufsize];
349 	uint64_t ino = 42;
350 	int fd;
351 	pthread_t self;
352 	uint64_t read_unique;
353 
354 	self = pthread_self();
355 
356 	expect_lookup(RELPATH, ino);
357 	expect_open(ino, 0, 1);
358 	expect_read(ino, &read_unique);
359 	EXPECT_CALL(*m_mock, process(
360 		ResultOf([&](auto in) {
361 			return (in->header.opcode == FUSE_INTERRUPT &&
362 				in->body.interrupt.unique == read_unique);
363 		}, Eq(true)),
364 		_)
365 	).WillOnce(Invoke([&](auto in __unused, auto &out) {
366 		auto out0 = new mockfs_buf_out;
367 		out0->header.error = -EINTR;
368 		out0->header.unique = read_unique;
369 		out0->header.len = sizeof(out0->header);
370 		out.push_back(out0);
371 	}));
372 
373 	fd = open(FULLPATH, O_RDONLY);
374 	ASSERT_LE(0, fd) << strerror(errno);
375 
376 	setup_interruptor(self);
377 	ASSERT_EQ(-1, read(fd, buf, bufsize));
378 	EXPECT_EQ(EINTR, errno);
379 
380 	/* Deliberately leak fd.  close(2) will be tested in release.cc */
381 }
382 
383 /*
384  * If the FUSE filesystem receives the FUSE_INTERRUPT operation before
385  * processing the original, then it should wait for "some timeout" for the
386  * original operation to arrive.  If not, it should send EAGAIN to the
387  * INTERRUPT operation, and the kernel should requeue the INTERRUPT.
388  *
389  * In this test, we'll pretend that the INTERRUPT arrives too soon, gets
390  * EAGAINed, then the kernel requeues it, and the second time around it
391  * successfully interrupts the original
392  */
393 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */
394 TEST_F(Interrupt, too_soon)
395 {
396 	const char FULLPATH[] = "mountpoint/some_file.txt";
397 	const char RELPATH[] = "some_file.txt";
398 	const char *CONTENTS = "abcdefgh";
399 	Sequence seq;
400 	uint64_t ino = 42;
401 	int fd;
402 	ssize_t bufsize = strlen(CONTENTS);
403 	pthread_t self;
404 	uint64_t write_unique;
405 
406 	self = pthread_self();
407 
408 	expect_lookup(RELPATH, ino);
409 	expect_open(ino, 0, 1);
410 	expect_write(ino, &write_unique);
411 
412 	EXPECT_CALL(*m_mock, process(
413 		ResultOf([&](auto in) {
414 			return (in->header.opcode == FUSE_INTERRUPT &&
415 				in->body.interrupt.unique == write_unique);
416 		}, Eq(true)),
417 		_)
418 	).InSequence(seq)
419 	.WillOnce(Invoke(ReturnErrno(EAGAIN)));
420 
421 	EXPECT_CALL(*m_mock, process(
422 		ResultOf([&](auto in) {
423 			return (in->header.opcode == FUSE_INTERRUPT &&
424 				in->body.interrupt.unique == write_unique);
425 		}, Eq(true)),
426 		_)
427 	).InSequence(seq)
428 	.WillOnce(Invoke([&](auto in __unused, auto &out __unused) {
429 		auto out0 = new mockfs_buf_out;
430 		out0->header.error = -EINTR;
431 		out0->header.unique = write_unique;
432 		out0->header.len = sizeof(out0->header);
433 		out.push_back(out0);
434 	}));
435 
436 	fd = open(FULLPATH, O_WRONLY);
437 	ASSERT_LE(0, fd) << strerror(errno);
438 
439 	setup_interruptor(self);
440 	ASSERT_EQ(-1, write(fd, CONTENTS, bufsize));
441 	EXPECT_EQ(EINTR, errno);
442 
443 	/* Deliberately leak fd.  close(2) will be tested in release.cc */
444 }
445 
446 
447 // TODO: add a test that uses siginterrupt and an interruptible signal
448 // TODO: add a test that verifies a process can be cleanly killed even if a
449 // FUSE_WRITE command never returns.
450 // TODO: write in-progress tests for other operations
451 // TODO: add a test where write returns EWOULDBLOCK
452 // TODO: test that operations that haven't been received by the server can be
453 // interrupted without generating a FUSE_INTERRUPT.
454