xref: /freebsd/tests/sys/fs/fusefs/mockfs.hh (revision ffb747d587bf09a982df67fba322b91d02f70be6)
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 
34 #include <pthread.h>
35 
36 #include "fuse_kernel.h"
37 }
38 
39 #include <unordered_set>
40 
41 #include <gmock/gmock.h>
42 
43 #define TIME_T_MAX (std::numeric_limits<time_t>::max())
44 
45 /*
46  * A pseudo-fuse errno used indicate that a fuse operation should have no
47  * response, at least not immediately
48  */
49 #define FUSE_NORESPONSE 9999
50 
51 #define SET_OUT_HEADER_LEN(out, variant) { \
52 	(out).header.len = (sizeof((out).header) + \
53 			    sizeof((out).body.variant)); \
54 }
55 
56 /*
57  * Create an expectation on FUSE_LOOKUP and return it so the caller can set
58  * actions.
59  *
60  * This must be a macro instead of a method because EXPECT_CALL returns a type
61  * with a deleted constructor.
62  */
63 #define EXPECT_LOOKUP(parent, path)					\
64 	EXPECT_CALL(*m_mock, process(					\
65 		ResultOf([=](auto in) {					\
66 			return (in.header.opcode == FUSE_LOOKUP &&	\
67 				in.header.nodeid == (parent) &&	\
68 				strcmp(in.body.lookup, (path)) == 0);	\
69 		}, Eq(true)),						\
70 		_)							\
71 	)
72 
73 extern int verbosity;
74 
75 /*
76  * The maximum that a test case can set max_write, limited by the buffer
77  * supplied when reading from /dev/fuse.  This limitation is imposed by
78  * fusefs-libs, but not by the FUSE protocol.
79  */
80 const uint32_t max_max_write = 0x20000;
81 
82 
83 /* This struct isn't defined by fuse_kernel.h or libfuse, but it should be */
84 struct fuse_create_out {
85 	struct fuse_entry_out	entry;
86 	struct fuse_open_out	open;
87 };
88 
89 /* Protocol 7.8 version of struct fuse_attr */
90 struct fuse_attr_7_8
91 {
92 	uint64_t	ino;
93 	uint64_t	size;
94 	uint64_t	blocks;
95 	uint64_t	atime;
96 	uint64_t	mtime;
97 	uint64_t	ctime;
98 	uint32_t	atimensec;
99 	uint32_t	mtimensec;
100 	uint32_t	ctimensec;
101 	uint32_t	mode;
102 	uint32_t	nlink;
103 	uint32_t	uid;
104 	uint32_t	gid;
105 	uint32_t	rdev;
106 };
107 
108 /* Protocol 7.8 version of struct fuse_attr_out */
109 struct fuse_attr_out_7_8
110 {
111 	uint64_t	attr_valid;
112 	uint32_t	attr_valid_nsec;
113 	uint32_t	dummy;
114 	struct fuse_attr_7_8 attr;
115 };
116 
117 /* Protocol 7.8 version of struct fuse_entry_out */
118 struct fuse_entry_out_7_8 {
119 	uint64_t	nodeid;		/* Inode ID */
120 	uint64_t	generation;	/* Inode generation: nodeid:gen must
121 				   be unique for the fs's lifetime */
122 	uint64_t	entry_valid;	/* Cache timeout for the name */
123 	uint64_t	attr_valid;	/* Cache timeout for the attributes */
124 	uint32_t	entry_valid_nsec;
125 	uint32_t	attr_valid_nsec;
126 	struct fuse_attr_7_8 attr;
127 };
128 
129 /* Output struct for FUSE_CREATE for protocol 7.8 servers */
130 struct fuse_create_out_7_8 {
131 	struct fuse_entry_out_7_8	entry;
132 	struct fuse_open_out	open;
133 };
134 
135 /* Output struct for FUSE_INIT for protocol 7.22 and earlier servers */
136 struct fuse_init_out_7_22 {
137 	uint32_t	major;
138 	uint32_t	minor;
139 	uint32_t	max_readahead;
140 	uint32_t	flags;
141 	uint16_t	max_background;
142 	uint16_t	congestion_threshold;
143 	uint32_t	max_write;
144 };
145 
146 union fuse_payloads_in {
147 	fuse_access_in	access;
148 	fuse_bmap_in	bmap;
149 	/*
150 	 * In fusefs-libs 3.4.2 and below the buffer size is fixed at 0x21000
151 	 * minus the header sizes.  fusefs-libs 3.4.3 (and FUSE Protocol 7.29)
152 	 * add a FUSE_MAX_PAGES option that allows it to be greater.
153 	 *
154 	 * See fuse_kern_chan.c in fusefs-libs 2.9.9 and below, or
155 	 * FUSE_DEFAULT_MAX_PAGES_PER_REQ in fusefs-libs 3.4.3 and above.
156 	 */
157 	uint8_t		bytes[
158 	    max_max_write + 0x1000 - sizeof(struct fuse_in_header)
159 	];
160 	fuse_copy_file_range_in	copy_file_range;
161 	fuse_create_in	create;
162 	fuse_fallocate_in fallocate;
163 	fuse_flush_in	flush;
164 	fuse_fsync_in	fsync;
165 	fuse_fsync_in	fsyncdir;
166 	fuse_forget_in	forget;
167 	fuse_getattr_in	getattr;
168 	fuse_interrupt_in interrupt;
169 	fuse_ioctl_in	ioctl;
170 	fuse_lk_in	getlk;
171 	fuse_getxattr_in getxattr;
172 	fuse_init_in	init;
173 	fuse_link_in	link;
174 	fuse_listxattr_in listxattr;
175 	char		lookup[0];
176 	fuse_lseek_in	lseek;
177 	fuse_mkdir_in	mkdir;
178 	fuse_mknod_in	mknod;
179 	fuse_open_in	open;
180 	fuse_open_in	opendir;
181 	fuse_read_in	read;
182 	fuse_read_in	readdir;
183 	fuse_release_in	release;
184 	fuse_release_in	releasedir;
185 	fuse_rename_in	rename;
186 	char		rmdir[0];
187 	fuse_setattr_in	setattr;
188 	fuse_setxattr_in setxattr;
189 	fuse_lk_in	setlk;
190 	fuse_lk_in	setlkw;
191 	char		unlink[0];
192 	fuse_write_in	write;
193 };
194 
195 struct mockfs_buf_in {
196 	fuse_in_header		header;
197 	union fuse_payloads_in	body;
198 };
199 
200 union fuse_payloads_out {
201 	fuse_attr_out		attr;
202 	fuse_attr_out_7_8	attr_7_8;
203 	fuse_bmap_out		bmap;
204 	fuse_create_out		create;
205 	fuse_create_out_7_8	create_7_8;
206 	/*
207 	 * The protocol places no limits on the size of bytes.  Choose
208 	 * a size big enough for anything we'll test.
209 	 */
210 	uint8_t			bytes[0x40000];
211 	fuse_entry_out		entry;
212 	fuse_entry_out_7_8	entry_7_8;
213 	fuse_lk_out		getlk;
214 	fuse_getxattr_out	getxattr;
215 	fuse_init_out		init;
216 	fuse_init_out_7_22	init_7_22;
217 	fuse_lseek_out		lseek;
218 	/* The inval_entry structure should be followed by the entry's name */
219 	fuse_notify_inval_entry_out	inval_entry;
220 	fuse_notify_inval_inode_out	inval_inode;
221 	/* The store structure should be followed by the data to store */
222 	fuse_notify_store_out		store;
223 	fuse_listxattr_out	listxattr;
224 	fuse_open_out		open;
225 	fuse_statfs_out		statfs;
226 	fuse_ioctl_out		ioctl;
227 	/*
228 	 * The protocol places no limits on the length of the string.  This is
229 	 * merely convenient for testing.
230 	 */
231 	char			str[80];
232 	fuse_write_out		write;
233 };
234 
235 struct mockfs_buf_out {
236 	fuse_out_header		header;
237 	union fuse_payloads_out	body;
238 	/* the expected errno of the write to /dev/fuse */
239 	int			expected_errno;
240 
241 	/* Default constructor: zero everything */
mockfs_buf_outmockfs_buf_out242 	mockfs_buf_out() {
243 		memset(this, 0, sizeof(*this));
244 	}
245 };
246 
247 /* A function that can be invoked in place of MockFS::process */
248 typedef std::function<void (const mockfs_buf_in& in,
249 			    std::vector<std::unique_ptr<mockfs_buf_out>> &out)>
250 ProcessMockerT;
251 
252 /*
253  * Helper function used for setting an error expectation for any fuse operation.
254  * The operation will return the supplied error
255  */
256 ProcessMockerT ReturnErrno(int error);
257 
258 /* Helper function used for returning negative cache entries for LOOKUP */
259 ProcessMockerT ReturnNegativeCache(const struct timespec *entry_valid);
260 
261 /* Helper function used for returning a single immediate response */
262 ProcessMockerT ReturnImmediate(
263 	std::function<void(const mockfs_buf_in& in,
264 			   struct mockfs_buf_out &out)> f);
265 
266 /* How the daemon should check /dev/fuse for readiness */
267 enum poll_method {
268 	BLOCKING,
269 	SELECT,
270 	POLL,
271 	KQ
272 };
273 
274 /*
275  * Fake FUSE filesystem
276  *
277  * "Mounts" a filesystem to a temporary directory and services requests
278  * according to the programmed expectations.
279  *
280  * Operates directly on the fusefs(4) kernel API, not the libfuse(3) user api.
281  */
282 class MockFS {
283 	/*
284 	 * thread id of the fuse daemon thread
285 	 *
286 	 * It must run in a separate thread so it doesn't deadlock with the
287 	 * client test code.
288 	 */
289 	pthread_t m_daemon_id;
290 
291 	/* file descriptor of /dev/fuse control device */
292 	volatile int m_fuse_fd;
293 
294 	/* The minor version of the kernel API that this mock daemon targets */
295 	uint32_t m_kernel_minor_version;
296 
297 	int m_kq;
298 
299 	/*
300 	 * If nonzero, the maximum size in bytes of a read that the kernel will
301 	 * send to the server.
302 	 */
303 	int m_maxread;
304 
305 	/* The max_readahead file system option */
306 	uint32_t m_maxreadahead;
307 
308 	/* pid of the test process */
309 	pid_t m_pid;
310 
311 	/* Every "unique" value of a fuse ticket seen so far */
312 	std::unique_ptr<std::unordered_set<uint64_t>> m_uniques;
313 
314 	/* Method the daemon should use for I/O to and from /dev/fuse */
315 	enum poll_method m_pm;
316 
317 	/* Timestamp granularity in nanoseconds */
318 	unsigned m_time_gran;
319 
320 	void audit_request(const mockfs_buf_in &in, ssize_t buflen);
321 	void debug_request(const mockfs_buf_in&, ssize_t buflen);
322 	void debug_response(const mockfs_buf_out&);
323 
324 	/* Initialize a session after mounting */
325 	void init(uint32_t flags);
326 
327 	/* Is pid from a process that might be involved in the test? */
328 	bool pid_ok(pid_t pid);
329 
330 	/* Default request handler */
331 	void process_default(const mockfs_buf_in&,
332 		std::vector<std::unique_ptr<mockfs_buf_out>>&);
333 
334 	/* Entry point for the daemon thread */
335 	static void* service(void*);
336 
337 	/*
338 	 * Read, but do not process, a single request from the kernel
339 	 *
340 	 * @param in	Return storage for the FUSE request
341 	 * @param res	Return value of read(2).  If positive, the amount of
342 	 *		data read from the fuse device.
343 	 */
344 	void read_request(mockfs_buf_in& in, ssize_t& res);
345 
346 	public:
347 	/* Write a single response back to the kernel */
348 	void write_response(const mockfs_buf_out &out);
349 
350 	/* pid of child process, for two-process test cases */
351 	pid_t m_child_pid;
352 
353 	/* Maximum size of a FUSE_WRITE write */
354 	uint32_t m_maxwrite;
355 
356 	/*
357 	 * Number of events that were available from /dev/fuse after the last
358 	 * kevent call.  Only valid when m_pm = KQ.
359 	 */
360 	int m_nready;
361 
362 	/* Tell the daemon to shut down ASAP */
363 	bool m_quit;
364 
365 	/* Tell the daemon that the server might forcibly unmount us */
366 	bool m_expect_unmount;
367 
368 	/* Create a new mockfs and mount it to a tempdir */
369 	MockFS(int max_read, int max_readahead, bool allow_other,
370 		bool default_permissions, bool push_symlinks_in, bool ro,
371 		enum poll_method pm, uint32_t flags,
372 		uint32_t kernel_minor_version, uint32_t max_write, bool async,
373 		bool no_clusterr, unsigned time_gran, bool nointr,
374 		bool noatime, const char *fsname, const char *subtype,
375 		bool no_auto_init, bool auto_unmount);
376 
377 	virtual ~MockFS();
378 
379 	/* Duplicate the /dev/fuse file descriptor, and return the duplicate */
380 	int dup_dev_fuse();
381 
382 	/* Kill the filesystem daemon without unmounting the filesystem */
383 	void kill_daemon();
384 
385 	/* Wait until the daemon thread terminates */
386 	void join_daemon();
387 
388 	/* Process FUSE requests endlessly */
389 	void loop();
390 
391 	/*
392 	 * Send an asynchronous notification to invalidate a directory entry.
393 	 * Similar to libfuse's fuse_lowlevel_notify_inval_entry
394 	 *
395 	 * This method will block until the client has responded, so it should
396 	 * generally be run in a separate thread from request processing.
397 	 *
398 	 * @param	parent	Parent directory's inode number
399 	 * @param	name	name of dirent to invalidate
400 	 * @param	namelen	size of name, including the NUL
401 	 * @param	expected_errno The error that write() should return
402 	 */
403 	int notify_inval_entry(ino_t parent, const char *name, size_t namelen,
404 			int expected_errno = 0);
405 
406 	/*
407 	 * Send an asynchronous notification to invalidate an inode's cached
408 	 * data and/or attributes.  Similar to libfuse's
409 	 * fuse_lowlevel_notify_inval_inode.
410 	 *
411 	 * This method will block until the client has responded, so it should
412 	 * generally be run in a separate thread from request processing.
413 	 *
414 	 * @param	ino	File's inode number
415 	 * @param	off	offset at which to begin invalidation.  A
416 	 * 			negative offset means to invalidate attributes
417 	 * 			only.
418 	 * @param	len	Size of region of data to invalidate.  0 means
419 	 * 			to invalidate all cached data.
420 	 */
421 	int notify_inval_inode(ino_t ino, off_t off, ssize_t len);
422 
423 	/*
424 	 * Send an asynchronous notification to store data directly into an
425 	 * inode's cache.  Similar to libfuse's fuse_lowlevel_notify_store.
426 	 *
427 	 * This method will block until the client has responded, so it should
428 	 * generally be run in a separate thread from request processing.
429 	 *
430 	 * @param	ino	File's inode number
431 	 * @param	off	Offset at which to store data
432 	 * @param	data	Pointer to the data to cache
433 	 * @param	len	Size of data
434 	 */
435 	int notify_store(ino_t ino, off_t off, const void* data, ssize_t size);
436 
437 	/*
438 	 * Request handler
439 	 *
440 	 * This method is expected to provide the responses to each FUSE
441 	 * operation.  For an immediate response, push one buffer into out.
442 	 * For a delayed response, push nothing.  For an immediate response
443 	 * plus a delayed response to an earlier operation, push two bufs.
444 	 * Test cases must define each response using Googlemock expectations
445 	 */
446 	MOCK_METHOD2(process, void(const mockfs_buf_in&,
447 				std::vector<std::unique_ptr<mockfs_buf_out>>&));
448 
449 	/* Gracefully unmount */
450 	void unmount();
451 };
452