xref: /freebsd/tests/sys/fs/fusefs/mockfs.cc (revision 0bf48626aaa33768078f5872b922b1487b3a9296)
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/param.h>
33 
34 #include <sys/mount.h>
35 #include <sys/select.h>
36 #include <sys/stat.h>
37 #include <sys/uio.h>
38 #include <sys/user.h>
39 
40 #include <fcntl.h>
41 #include <libutil.h>
42 #include <poll.h>
43 #include <pthread.h>
44 #include <signal.h>
45 #include <stdlib.h>
46 #include <unistd.h>
47 
48 #include "mntopts.h"	// for build_iovec
49 }
50 
51 #include <cinttypes>
52 
53 #include <gtest/gtest.h>
54 
55 #include "mockfs.hh"
56 
57 using namespace testing;
58 
59 int verbosity = 0;
60 
61 const char* opcode2opname(uint32_t opcode)
62 {
63 	const int NUM_OPS = 39;
64 	const char* table[NUM_OPS] = {
65 		"Unknown (opcode 0)",
66 		"LOOKUP",
67 		"FORGET",
68 		"GETATTR",
69 		"SETATTR",
70 		"READLINK",
71 		"SYMLINK",
72 		"Unknown (opcode 7)",
73 		"MKNOD",
74 		"MKDIR",
75 		"UNLINK",
76 		"RMDIR",
77 		"RENAME",
78 		"LINK",
79 		"OPEN",
80 		"READ",
81 		"WRITE",
82 		"STATFS",
83 		"RELEASE",
84 		"Unknown (opcode 19)",
85 		"FSYNC",
86 		"SETXATTR",
87 		"GETXATTR",
88 		"LISTXATTR",
89 		"REMOVEXATTR",
90 		"FLUSH",
91 		"INIT",
92 		"OPENDIR",
93 		"READDIR",
94 		"RELEASEDIR",
95 		"FSYNCDIR",
96 		"GETLK",
97 		"SETLK",
98 		"SETLKW",
99 		"ACCESS",
100 		"CREATE",
101 		"INTERRUPT",
102 		"BMAP",
103 		"DESTROY"
104 	};
105 	if (opcode >= NUM_OPS)
106 		return ("Unknown (opcode > max)");
107 	else
108 		return (table[opcode]);
109 }
110 
111 ProcessMockerT
112 ReturnErrno(int error)
113 {
114 	return([=](auto in, auto &out) {
115 		std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
116 		out0->header.unique = in.header.unique;
117 		out0->header.error = -error;
118 		out0->header.len = sizeof(out0->header);
119 		out.push_back(std::move(out0));
120 	});
121 }
122 
123 /* Helper function used for returning negative cache entries for LOOKUP */
124 ProcessMockerT
125 ReturnNegativeCache(const struct timespec *entry_valid)
126 {
127 	return([=](auto in, auto &out) {
128 		/* nodeid means ENOENT and cache it */
129 		std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
130 		out0->body.entry.nodeid = 0;
131 		out0->header.unique = in.header.unique;
132 		out0->header.error = 0;
133 		out0->body.entry.entry_valid = entry_valid->tv_sec;
134 		out0->body.entry.entry_valid_nsec = entry_valid->tv_nsec;
135 		SET_OUT_HEADER_LEN(*out0, entry);
136 		out.push_back(std::move(out0));
137 	});
138 }
139 
140 ProcessMockerT
141 ReturnImmediate(std::function<void(const mockfs_buf_in& in,
142 				   struct mockfs_buf_out &out)> f)
143 {
144 	return([=](auto& in, auto &out) {
145 		std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
146 		out0->header.unique = in.header.unique;
147 		f(in, *out0);
148 		out.push_back(std::move(out0));
149 	});
150 }
151 
152 void sigint_handler(int __unused sig) {
153 	// Don't do anything except interrupt the daemon's read(2) call
154 }
155 
156 void MockFS::debug_request(const mockfs_buf_in &in)
157 {
158 	printf("%-11s ino=%2" PRIu64, opcode2opname(in.header.opcode),
159 		in.header.nodeid);
160 	if (verbosity > 1) {
161 		printf(" uid=%5u gid=%5u pid=%5u unique=%" PRIu64 " len=%u",
162 			in.header.uid, in.header.gid, in.header.pid,
163 			in.header.unique, in.header.len);
164 	}
165 	switch (in.header.opcode) {
166 		const char *name, *value;
167 
168 		case FUSE_ACCESS:
169 			printf(" mask=%#x", in.body.access.mask);
170 			break;
171 		case FUSE_BMAP:
172 			printf(" block=%" PRIx64 " blocksize=%#x",
173 				in.body.bmap.block, in.body.bmap.blocksize);
174 			break;
175 		case FUSE_CREATE:
176 			if (m_kernel_minor_version >= 12)
177 				name = (const char*)in.body.bytes +
178 					sizeof(fuse_create_in);
179 			else
180 				name = (const char*)in.body.bytes +
181 					sizeof(fuse_open_in);
182 			printf(" flags=%#x name=%s",
183 				in.body.open.flags, name);
184 			break;
185 		case FUSE_FLUSH:
186 			printf(" fh=%#" PRIx64 " lock_owner=%" PRIu64,
187 				in.body.flush.fh,
188 				in.body.flush.lock_owner);
189 			break;
190 		case FUSE_FORGET:
191 			printf(" nlookup=%" PRIu64, in.body.forget.nlookup);
192 			break;
193 		case FUSE_FSYNC:
194 			printf(" flags=%#x", in.body.fsync.fsync_flags);
195 			break;
196 		case FUSE_FSYNCDIR:
197 			printf(" flags=%#x", in.body.fsyncdir.fsync_flags);
198 			break;
199 		case FUSE_INTERRUPT:
200 			printf(" unique=%" PRIu64, in.body.interrupt.unique);
201 			break;
202 		case FUSE_LINK:
203 			printf(" oldnodeid=%" PRIu64, in.body.link.oldnodeid);
204 			break;
205 		case FUSE_LOOKUP:
206 			printf(" %s", in.body.lookup);
207 			break;
208 		case FUSE_MKDIR:
209 			name = (const char*)in.body.bytes +
210 				sizeof(fuse_mkdir_in);
211 			printf(" name=%s mode=%#o umask=%#o", name,
212 				in.body.mkdir.mode, in.body.mkdir.umask);
213 			break;
214 		case FUSE_MKNOD:
215 			if (m_kernel_minor_version >= 12)
216 				name = (const char*)in.body.bytes +
217 					sizeof(fuse_mknod_in);
218 			else
219 				name = (const char*)in.body.bytes +
220 					FUSE_COMPAT_MKNOD_IN_SIZE;
221 			printf(" mode=%#o rdev=%x umask=%#o name=%s",
222 				in.body.mknod.mode, in.body.mknod.rdev,
223 				in.body.mknod.umask, name);
224 			break;
225 		case FUSE_OPEN:
226 			printf(" flags=%#x", in.body.open.flags);
227 			break;
228 		case FUSE_OPENDIR:
229 			printf(" flags=%#x", in.body.opendir.flags);
230 			break;
231 		case FUSE_READ:
232 			printf(" offset=%" PRIu64 " size=%u",
233 				in.body.read.offset,
234 				in.body.read.size);
235 			if (verbosity > 1)
236 				printf(" flags=%#x", in.body.read.flags);
237 			break;
238 		case FUSE_READDIR:
239 			printf(" fh=%#" PRIx64 " offset=%" PRIu64 " size=%u",
240 				in.body.readdir.fh, in.body.readdir.offset,
241 				in.body.readdir.size);
242 			break;
243 		case FUSE_RELEASE:
244 			printf(" fh=%#" PRIx64 " flags=%#x lock_owner=%" PRIu64,
245 				in.body.release.fh,
246 				in.body.release.flags,
247 				in.body.release.lock_owner);
248 			break;
249 		case FUSE_SETATTR:
250 			if (verbosity <= 1) {
251 				printf(" valid=%#x", in.body.setattr.valid);
252 				break;
253 			}
254 			if (in.body.setattr.valid & FATTR_MODE)
255 				printf(" mode=%#o", in.body.setattr.mode);
256 			if (in.body.setattr.valid & FATTR_UID)
257 				printf(" uid=%u", in.body.setattr.uid);
258 			if (in.body.setattr.valid & FATTR_GID)
259 				printf(" gid=%u", in.body.setattr.gid);
260 			if (in.body.setattr.valid & FATTR_SIZE)
261 				printf(" size=%" PRIu64, in.body.setattr.size);
262 			if (in.body.setattr.valid & FATTR_ATIME)
263 				printf(" atime=%" PRIu64 ".%u",
264 					in.body.setattr.atime,
265 					in.body.setattr.atimensec);
266 			if (in.body.setattr.valid & FATTR_MTIME)
267 				printf(" mtime=%" PRIu64 ".%u",
268 					in.body.setattr.mtime,
269 					in.body.setattr.mtimensec);
270 			if (in.body.setattr.valid & FATTR_FH)
271 				printf(" fh=%" PRIu64 "", in.body.setattr.fh);
272 			break;
273 		case FUSE_SETLK:
274 			printf(" fh=%#" PRIx64 " owner=%" PRIu64
275 				" type=%u pid=%u",
276 				in.body.setlk.fh, in.body.setlk.owner,
277 				in.body.setlk.lk.type,
278 				in.body.setlk.lk.pid);
279 			if (verbosity >= 2) {
280 				printf(" range=[%" PRIu64 "-%" PRIu64 "]",
281 					in.body.setlk.lk.start,
282 					in.body.setlk.lk.end);
283 			}
284 			break;
285 		case FUSE_SETXATTR:
286 			/*
287 			 * In theory neither the xattr name and value need be
288 			 * ASCII, but in this test suite they always are.
289 			 */
290 			name = (const char*)in.body.bytes +
291 				sizeof(fuse_setxattr_in);
292 			value = name + strlen(name) + 1;
293 			printf(" %s=%s", name, value);
294 			break;
295 		case FUSE_WRITE:
296 			printf(" fh=%#" PRIx64 " offset=%" PRIu64
297 				" size=%u write_flags=%u",
298 				in.body.write.fh,
299 				in.body.write.offset, in.body.write.size,
300 				in.body.write.write_flags);
301 			if (verbosity > 1)
302 				printf(" flags=%#x", in.body.write.flags);
303 			break;
304 		default:
305 			break;
306 	}
307 	printf("\n");
308 }
309 
310 /*
311  * Debug a FUSE response.
312  *
313  * This is mostly useful for asynchronous notifications, which don't correspond
314  * to any request
315  */
316 void MockFS::debug_response(const mockfs_buf_out &out) {
317 	const char *name;
318 
319 	if (verbosity == 0)
320 		return;
321 
322 	switch (out.header.error) {
323 		case FUSE_NOTIFY_INVAL_ENTRY:
324 			name = (const char*)out.body.bytes +
325 				sizeof(fuse_notify_inval_entry_out);
326 			printf("<- INVAL_ENTRY parent=%" PRIu64 " %s\n",
327 				out.body.inval_entry.parent, name);
328 			break;
329 		case FUSE_NOTIFY_INVAL_INODE:
330 			printf("<- INVAL_INODE ino=%" PRIu64 " off=%" PRIi64
331 				" len=%" PRIi64 "\n",
332 				out.body.inval_inode.ino,
333 				out.body.inval_inode.off,
334 				out.body.inval_inode.len);
335 			break;
336 		case FUSE_NOTIFY_STORE:
337 			printf("<- STORE ino=%" PRIu64 " off=%" PRIu64
338 				" size=%" PRIu32 "\n",
339 				out.body.store.nodeid,
340 				out.body.store.offset,
341 				out.body.store.size);
342 			break;
343 		default:
344 			break;
345 	}
346 }
347 
348 MockFS::MockFS(int max_readahead, bool allow_other, bool default_permissions,
349 	bool push_symlinks_in, bool ro, enum poll_method pm, uint32_t flags,
350 	uint32_t kernel_minor_version, uint32_t max_write, bool async,
351 	bool noclusterr, unsigned time_gran, bool nointr)
352 {
353 	struct sigaction sa;
354 	struct iovec *iov = NULL;
355 	int iovlen = 0;
356 	char fdstr[15];
357 	const bool trueval = true;
358 
359 	m_daemon_id = NULL;
360 	m_kernel_minor_version = kernel_minor_version;
361 	m_maxreadahead = max_readahead;
362 	m_maxwrite = max_write;
363 	m_nready = -1;
364 	m_pm = pm;
365 	m_time_gran = time_gran;
366 	m_quit = false;
367 	if (m_pm == KQ)
368 		m_kq = kqueue();
369 	else
370 		m_kq = -1;
371 
372 	/*
373 	 * Kyua sets pwd to a testcase-unique tempdir; no need to use
374 	 * mkdtemp
375 	 */
376 	/*
377 	 * googletest doesn't allow ASSERT_ in constructors, so we must throw
378 	 * instead.
379 	 */
380 	if (mkdir("mountpoint" , 0755) && errno != EEXIST)
381 		throw(std::system_error(errno, std::system_category(),
382 			"Couldn't make mountpoint directory"));
383 
384 	switch (m_pm) {
385 	case BLOCKING:
386 		m_fuse_fd = open("/dev/fuse", O_CLOEXEC | O_RDWR);
387 		break;
388 	default:
389 		m_fuse_fd = open("/dev/fuse", O_CLOEXEC | O_RDWR | O_NONBLOCK);
390 		break;
391 	}
392 	if (m_fuse_fd < 0)
393 		throw(std::system_error(errno, std::system_category(),
394 			"Couldn't open /dev/fuse"));
395 
396 	m_pid = getpid();
397 	m_child_pid = -1;
398 
399 	build_iovec(&iov, &iovlen, "fstype", __DECONST(void *, "fusefs"), -1);
400 	build_iovec(&iov, &iovlen, "fspath",
401 		    __DECONST(void *, "mountpoint"), -1);
402 	build_iovec(&iov, &iovlen, "from", __DECONST(void *, "/dev/fuse"), -1);
403 	sprintf(fdstr, "%d", m_fuse_fd);
404 	build_iovec(&iov, &iovlen, "fd", fdstr, -1);
405 	if (allow_other) {
406 		build_iovec(&iov, &iovlen, "allow_other",
407 			__DECONST(void*, &trueval), sizeof(bool));
408 	}
409 	if (default_permissions) {
410 		build_iovec(&iov, &iovlen, "default_permissions",
411 			__DECONST(void*, &trueval), sizeof(bool));
412 	}
413 	if (push_symlinks_in) {
414 		build_iovec(&iov, &iovlen, "push_symlinks_in",
415 			__DECONST(void*, &trueval), sizeof(bool));
416 	}
417 	if (ro) {
418 		build_iovec(&iov, &iovlen, "ro",
419 			__DECONST(void*, &trueval), sizeof(bool));
420 	}
421 	if (async) {
422 		build_iovec(&iov, &iovlen, "async", __DECONST(void*, &trueval),
423 			sizeof(bool));
424 	}
425 	if (noclusterr) {
426 		build_iovec(&iov, &iovlen, "noclusterr",
427 			__DECONST(void*, &trueval), sizeof(bool));
428 	}
429 	if (nointr) {
430 		build_iovec(&iov, &iovlen, "nointr",
431 			__DECONST(void*, &trueval), sizeof(bool));
432 	} else {
433 		build_iovec(&iov, &iovlen, "intr",
434 			__DECONST(void*, &trueval), sizeof(bool));
435 	}
436 	if (nmount(iov, iovlen, 0))
437 		throw(std::system_error(errno, std::system_category(),
438 			"Couldn't mount filesystem"));
439 
440 	// Setup default handler
441 	ON_CALL(*this, process(_, _))
442 		.WillByDefault(Invoke(this, &MockFS::process_default));
443 
444 	init(flags);
445 	bzero(&sa, sizeof(sa));
446 	sa.sa_handler = sigint_handler;
447 	sa.sa_flags = 0;	/* Don't set SA_RESTART! */
448 	if (0 != sigaction(SIGUSR1, &sa, NULL))
449 		throw(std::system_error(errno, std::system_category(),
450 			"Couldn't handle SIGUSR1"));
451 	if (pthread_create(&m_daemon_id, NULL, service, (void*)this))
452 		throw(std::system_error(errno, std::system_category(),
453 			"Couldn't Couldn't start fuse thread"));
454 }
455 
456 MockFS::~MockFS() {
457 	kill_daemon();
458 	if (m_daemon_id != NULL) {
459 		pthread_join(m_daemon_id, NULL);
460 		m_daemon_id = NULL;
461 	}
462 	::unmount("mountpoint", MNT_FORCE);
463 	rmdir("mountpoint");
464 	if (m_kq >= 0)
465 		close(m_kq);
466 }
467 
468 void MockFS::init(uint32_t flags) {
469 	std::unique_ptr<mockfs_buf_in> in(new mockfs_buf_in);
470 	std::unique_ptr<mockfs_buf_out> out(new mockfs_buf_out);
471 
472 	read_request(*in);
473 	ASSERT_EQ(FUSE_INIT, in->header.opcode);
474 
475 	out->header.unique = in->header.unique;
476 	out->header.error = 0;
477 	out->body.init.major = FUSE_KERNEL_VERSION;
478 	out->body.init.minor = m_kernel_minor_version;;
479 	out->body.init.flags = in->body.init.flags & flags;
480 	out->body.init.max_write = m_maxwrite;
481 	out->body.init.max_readahead = m_maxreadahead;
482 
483 	if (m_kernel_minor_version < 23) {
484 		SET_OUT_HEADER_LEN(*out, init_7_22);
485 	} else {
486 		out->body.init.time_gran = m_time_gran;
487 		SET_OUT_HEADER_LEN(*out, init);
488 	}
489 
490 	write(m_fuse_fd, out.get(), out->header.len);
491 }
492 
493 void MockFS::kill_daemon() {
494 	m_quit = true;
495 	if (m_daemon_id != NULL)
496 		pthread_kill(m_daemon_id, SIGUSR1);
497 	// Closing the /dev/fuse file descriptor first allows unmount to
498 	// succeed even if the daemon doesn't correctly respond to commands
499 	// during the unmount sequence.
500 	close(m_fuse_fd);
501 	m_fuse_fd = -1;
502 }
503 
504 void MockFS::loop() {
505 	std::vector<std::unique_ptr<mockfs_buf_out>> out;
506 
507 	std::unique_ptr<mockfs_buf_in> in(new mockfs_buf_in);
508 	ASSERT_TRUE(in != NULL);
509 	while (!m_quit) {
510 		bzero(in.get(), sizeof(*in));
511 		read_request(*in);
512 		if (m_quit)
513 			break;
514 		if (verbosity > 0)
515 			debug_request(*in);
516 		if (pid_ok((pid_t)in->header.pid)) {
517 			process(*in, out);
518 		} else {
519 			/*
520 			 * Reject any requests from unknown processes.  Because
521 			 * we actually do mount a filesystem, plenty of
522 			 * unrelated system daemons may try to access it.
523 			 */
524 			if (verbosity > 1)
525 				printf("\tREJECTED (wrong pid %d)\n",
526 					in->header.pid);
527 			process_default(*in, out);
528 		}
529 		for (auto &it: out)
530 			write_response(*it);
531 		out.clear();
532 	}
533 }
534 
535 int MockFS::notify_inval_entry(ino_t parent, const char *name, size_t namelen)
536 {
537 	std::unique_ptr<mockfs_buf_out> out(new mockfs_buf_out);
538 
539 	out->header.unique = 0;	/* 0 means asynchronous notification */
540 	out->header.error = FUSE_NOTIFY_INVAL_ENTRY;
541 	out->body.inval_entry.parent = parent;
542 	out->body.inval_entry.namelen = namelen;
543 	strlcpy((char*)&out->body.bytes + sizeof(out->body.inval_entry),
544 		name, sizeof(out->body.bytes) - sizeof(out->body.inval_entry));
545 	out->header.len = sizeof(out->header) + sizeof(out->body.inval_entry) +
546 		namelen;
547 	debug_response(*out);
548 	write_response(*out);
549 	return 0;
550 }
551 
552 int MockFS::notify_inval_inode(ino_t ino, off_t off, ssize_t len)
553 {
554 	std::unique_ptr<mockfs_buf_out> out(new mockfs_buf_out);
555 
556 	out->header.unique = 0;	/* 0 means asynchronous notification */
557 	out->header.error = FUSE_NOTIFY_INVAL_INODE;
558 	out->body.inval_inode.ino = ino;
559 	out->body.inval_inode.off = off;
560 	out->body.inval_inode.len = len;
561 	out->header.len = sizeof(out->header) + sizeof(out->body.inval_inode);
562 	debug_response(*out);
563 	write_response(*out);
564 	return 0;
565 }
566 
567 int MockFS::notify_store(ino_t ino, off_t off, const void* data, ssize_t size)
568 {
569 	std::unique_ptr<mockfs_buf_out> out(new mockfs_buf_out);
570 
571 	out->header.unique = 0;	/* 0 means asynchronous notification */
572 	out->header.error = FUSE_NOTIFY_STORE;
573 	out->body.store.nodeid = ino;
574 	out->body.store.offset = off;
575 	out->body.store.size = size;
576 	bcopy(data, (char*)&out->body.bytes + sizeof(out->body.store), size);
577 	out->header.len = sizeof(out->header) + sizeof(out->body.store) + size;
578 	debug_response(*out);
579 	write_response(*out);
580 	return 0;
581 }
582 
583 bool MockFS::pid_ok(pid_t pid) {
584 	if (pid == m_pid) {
585 		return (true);
586 	} else if (pid == m_child_pid) {
587 		return (true);
588 	} else {
589 		struct kinfo_proc *ki;
590 		bool ok = false;
591 
592 		ki = kinfo_getproc(pid);
593 		if (ki == NULL)
594 			return (false);
595 		/*
596 		 * Allow access by the aio daemon processes so that our tests
597 		 * can use aio functions
598 		 */
599 		if (0 == strncmp("aiod", ki->ki_comm, 4))
600 			ok = true;
601 		free(ki);
602 		return (ok);
603 	}
604 }
605 
606 void MockFS::process_default(const mockfs_buf_in& in,
607 		std::vector<std::unique_ptr<mockfs_buf_out>> &out)
608 {
609 	std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
610 	out0->header.unique = in.header.unique;
611 	out0->header.error = -EOPNOTSUPP;
612 	out0->header.len = sizeof(out0->header);
613 	out.push_back(std::move(out0));
614 }
615 
616 void MockFS::read_request(mockfs_buf_in &in) {
617 	ssize_t res;
618 	int nready = 0;
619 	fd_set readfds;
620 	pollfd fds[1];
621 	struct kevent changes[1];
622 	struct kevent events[1];
623 	struct timespec timeout_ts;
624 	struct timeval timeout_tv;
625 	const int timeout_ms = 999;
626 	int timeout_int, nfds;
627 
628 	switch (m_pm) {
629 	case BLOCKING:
630 		break;
631 	case KQ:
632 		timeout_ts.tv_sec = 0;
633 		timeout_ts.tv_nsec = timeout_ms * 1'000'000;
634 		while (nready == 0) {
635 			EV_SET(&changes[0], m_fuse_fd, EVFILT_READ, EV_ADD, 0,
636 				0, 0);
637 			nready = kevent(m_kq, &changes[0], 1, &events[0], 1,
638 				&timeout_ts);
639 			if (m_quit)
640 				return;
641 		}
642 		ASSERT_LE(0, nready) << strerror(errno);
643 		ASSERT_EQ(events[0].ident, (uintptr_t)m_fuse_fd);
644 		if (events[0].flags & EV_ERROR)
645 			FAIL() << strerror(events[0].data);
646 		else if (events[0].flags & EV_EOF)
647 			FAIL() << strerror(events[0].fflags);
648 		m_nready = events[0].data;
649 		break;
650 	case POLL:
651 		timeout_int = timeout_ms;
652 		fds[0].fd = m_fuse_fd;
653 		fds[0].events = POLLIN;
654 		while (nready == 0) {
655 			nready = poll(fds, 1, timeout_int);
656 			if (m_quit)
657 				return;
658 		}
659 		ASSERT_LE(0, nready) << strerror(errno);
660 		ASSERT_TRUE(fds[0].revents & POLLIN);
661 		break;
662 	case SELECT:
663 		timeout_tv.tv_sec = 0;
664 		timeout_tv.tv_usec = timeout_ms * 1'000;
665 		nfds = m_fuse_fd + 1;
666 		while (nready == 0) {
667 			FD_ZERO(&readfds);
668 			FD_SET(m_fuse_fd, &readfds);
669 			nready = select(nfds, &readfds, NULL, NULL,
670 				&timeout_tv);
671 			if (m_quit)
672 				return;
673 		}
674 		ASSERT_LE(0, nready) << strerror(errno);
675 		ASSERT_TRUE(FD_ISSET(m_fuse_fd, &readfds));
676 		break;
677 	default:
678 		FAIL() << "not yet implemented";
679 	}
680 	res = read(m_fuse_fd, &in, sizeof(in));
681 
682 	if (res < 0 && !m_quit) {
683 		FAIL() << "read: " << strerror(errno);
684 		m_quit = true;
685 	}
686 	ASSERT_TRUE(res >= static_cast<ssize_t>(sizeof(in.header)) || m_quit);
687 }
688 
689 void MockFS::write_response(const mockfs_buf_out &out) {
690 	fd_set writefds;
691 	pollfd fds[1];
692 	int nready, nfds;
693 	ssize_t r;
694 
695 	switch (m_pm) {
696 	case BLOCKING:
697 	case KQ:	/* EVFILT_WRITE is not supported */
698 		break;
699 	case POLL:
700 		fds[0].fd = m_fuse_fd;
701 		fds[0].events = POLLOUT;
702 		nready = poll(fds, 1, INFTIM);
703 		ASSERT_LE(0, nready) << strerror(errno);
704 		ASSERT_EQ(1, nready) << "NULL timeout expired?";
705 		ASSERT_TRUE(fds[0].revents & POLLOUT);
706 		break;
707 	case SELECT:
708 		FD_ZERO(&writefds);
709 		FD_SET(m_fuse_fd, &writefds);
710 		nfds = m_fuse_fd + 1;
711 		nready = select(nfds, NULL, &writefds, NULL, NULL);
712 		ASSERT_LE(0, nready) << strerror(errno);
713 		ASSERT_EQ(1, nready) << "NULL timeout expired?";
714 		ASSERT_TRUE(FD_ISSET(m_fuse_fd, &writefds));
715 		break;
716 	default:
717 		FAIL() << "not yet implemented";
718 	}
719 	r = write(m_fuse_fd, &out, out.header.len);
720 	ASSERT_TRUE(r > 0 || errno == EAGAIN) << strerror(errno);
721 }
722 
723 void* MockFS::service(void *pthr_data) {
724 	MockFS *mock_fs = (MockFS*)pthr_data;
725 
726 	mock_fs->loop();
727 
728 	return (NULL);
729 }
730 
731 void MockFS::unmount() {
732 	::unmount("mountpoint", 0);
733 }
734