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