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