]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - tests/sys/fs/fusefs/mockfs.hh
fusefs: implement VOP_BMAP
[FreeBSD/FreeBSD.git] / tests / sys / fs / fusefs / mockfs.hh
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         __u64   ino;
83         __u64   size;
84         __u64   blocks;
85         __u64   atime;
86         __u64   mtime;
87         __u64   ctime;
88         __u32   atimensec;
89         __u32   mtimensec;
90         __u32   ctimensec;
91         __u32   mode;
92         __u32   nlink;
93         __u32   uid;
94         __u32   gid;
95         __u32   rdev;
96 };
97
98 /* Protocol 7.8 version of struct fuse_attr_out */
99 struct fuse_attr_out_7_8
100 {
101         __u64   attr_valid;
102         __u32   attr_valid_nsec;
103         __u32   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         __u64   nodeid;         /* Inode ID */
110         __u64   generation;     /* Inode generation: nodeid:gen must
111                                    be unique for the fs's lifetime */
112         __u64   entry_valid;    /* Cache timeout for the name */
113         __u64   attr_valid;     /* Cache timeout for the attributes */
114         __u32   entry_valid_nsec;
115         __u32   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 union fuse_payloads_in {
126         fuse_access_in  access;
127         fuse_bmap_in    bmap;
128         /* value is from fuse_kern_chan.c in fusefs-libs */
129         uint8_t         bytes[0x21000 - sizeof(struct fuse_in_header)];
130         fuse_create_in  create;
131         fuse_flush_in   flush;
132         fuse_fsync_in   fsync;
133         fuse_fsync_in   fsyncdir;
134         fuse_forget_in  forget;
135         fuse_interrupt_in interrupt;
136         fuse_lk_in      getlk;
137         fuse_getxattr_in getxattr;
138         fuse_init_in    init;
139         fuse_link_in    link;
140         fuse_listxattr_in listxattr;
141         char            lookup[0];
142         fuse_mkdir_in   mkdir;
143         fuse_mknod_in   mknod;
144         fuse_open_in    open;
145         fuse_open_in    opendir;
146         fuse_read_in    read;
147         fuse_read_in    readdir;
148         fuse_release_in release;
149         fuse_release_in releasedir;
150         fuse_rename_in  rename;
151         char            rmdir[0];
152         fuse_setattr_in setattr;
153         fuse_setxattr_in setxattr;
154         fuse_lk_in      setlk;
155         fuse_lk_in      setlkw;
156         char            unlink[0];
157         fuse_write_in   write;
158 };
159
160 struct mockfs_buf_in {
161         fuse_in_header          header;
162         union fuse_payloads_in  body;
163 };
164
165 union fuse_payloads_out {
166         fuse_attr_out           attr;
167         fuse_attr_out_7_8       attr_7_8;
168         fuse_bmap_out           bmap;
169         fuse_create_out         create;
170         fuse_create_out_7_8     create_7_8;
171         /*
172          * The protocol places no limits on the size of bytes.  Choose
173          * a size big enough for anything we'll test.
174          */
175         uint8_t                 bytes[0x20000];
176         fuse_entry_out          entry;
177         fuse_entry_out_7_8      entry_7_8;
178         fuse_lk_out             getlk;
179         fuse_getxattr_out       getxattr;
180         fuse_init_out           init;
181         /* The inval_entry structure should be followed by the entry's name */
182         fuse_notify_inval_entry_out     inval_entry;
183         fuse_notify_inval_inode_out     inval_inode;
184         fuse_listxattr_out      listxattr;
185         fuse_open_out           open;
186         fuse_statfs_out         statfs;
187         /*
188          * The protocol places no limits on the length of the string.  This is
189          * merely convenient for testing.
190          */
191         char                    str[80];
192         fuse_write_out          write;
193 };
194
195 struct mockfs_buf_out {
196         fuse_out_header         header;
197         union fuse_payloads_out body;
198
199         /* Default constructor: zero everything */
200         mockfs_buf_out() {
201                 memset(this, 0, sizeof(*this));
202         }
203 };
204
205 /* A function that can be invoked in place of MockFS::process */
206 typedef std::function<void (const mockfs_buf_in& in,
207                             std::vector<std::unique_ptr<mockfs_buf_out>> &out)>
208 ProcessMockerT;
209
210 /*
211  * Helper function used for setting an error expectation for any fuse operation.
212  * The operation will return the supplied error
213  */
214 ProcessMockerT ReturnErrno(int error);
215
216 /* Helper function used for returning negative cache entries for LOOKUP */
217 ProcessMockerT ReturnNegativeCache(const struct timespec *entry_valid);
218
219 /* Helper function used for returning a single immediate response */
220 ProcessMockerT ReturnImmediate(
221         std::function<void(const mockfs_buf_in& in,
222                            struct mockfs_buf_out &out)> f);
223
224 /* How the daemon should check /dev/fuse for readiness */
225 enum poll_method {
226         BLOCKING,
227         SELECT,
228         POLL,
229         KQ
230 };
231
232 /*
233  * Fake FUSE filesystem
234  *
235  * "Mounts" a filesystem to a temporary directory and services requests
236  * according to the programmed expectations.
237  *
238  * Operates directly on the fusefs(4) kernel API, not the libfuse(3) user api.
239  */
240 class MockFS {
241         /*
242          * thread id of the fuse daemon thread
243          *
244          * It must run in a separate thread so it doesn't deadlock with the
245          * client test code.
246          */
247         pthread_t m_daemon_id;
248
249         /* file descriptor of /dev/fuse control device */
250         int m_fuse_fd;
251         
252         /* The minor version of the kernel API that this mock daemon targets */
253         uint32_t m_kernel_minor_version;
254
255         int m_kq;
256
257         /* The max_readahead file system option */
258         uint32_t m_maxreadahead;
259
260         /* pid of the test process */
261         pid_t m_pid;
262
263         /* Method the daemon should use for I/O to and from /dev/fuse */
264         enum poll_method m_pm;
265
266         void debug_request(const mockfs_buf_in&);
267         void debug_response(const mockfs_buf_out&);
268
269         /* Initialize a session after mounting */
270         void init(uint32_t flags);
271
272         /* Is pid from a process that might be involved in the test? */
273         bool pid_ok(pid_t pid);
274
275         /* Default request handler */
276         void process_default(const mockfs_buf_in&,
277                 std::vector<std::unique_ptr<mockfs_buf_out>>&);
278
279         /* Entry point for the daemon thread */
280         static void* service(void*);
281
282         /* Read, but do not process, a single request from the kernel */
283         void read_request(mockfs_buf_in& in);
284
285         /* Write a single response back to the kernel */
286         void write_response(const mockfs_buf_out &out);
287
288         public:
289         /* pid of child process, for two-process test cases */
290         pid_t m_child_pid;
291
292         /* Maximum size of a FUSE_WRITE write */
293         uint32_t m_maxwrite;
294
295         /* 
296          * Number of events that were available from /dev/fuse after the last
297          * kevent call.  Only valid when m_pm = KQ.
298          */
299         int m_nready;
300
301         /* Tell the daemon to shut down ASAP */
302         bool m_quit;
303
304         /* Create a new mockfs and mount it to a tempdir */
305         MockFS(int max_readahead, bool allow_other,
306                 bool default_permissions, bool push_symlinks_in, bool ro,
307                 enum poll_method pm, uint32_t flags,
308                 uint32_t kernel_minor_version, uint32_t max_write, bool async,
309                 bool no_clusterr);
310
311         virtual ~MockFS();
312
313         /* Kill the filesystem daemon without unmounting the filesystem */
314         void kill_daemon();
315
316         /* Process FUSE requests endlessly */
317         void loop();
318
319         /*
320          * Send an asynchronous notification to invalidate a directory entry.
321          * Similar to libfuse's fuse_lowlevel_notify_inval_entry
322          *
323          * This method will block until the client has responded, so it should
324          * generally be run in a separate thread from request processing.
325          *
326          * @param       parent  Parent directory's inode number
327          * @param       name    name of dirent to invalidate
328          * @param       namelen size of name, including the NUL
329          */
330         int notify_inval_entry(ino_t parent, const char *name, size_t namelen);
331
332         /*
333          * Send an asynchronous notification to invalidate an inode's cached
334          * data and/or attributes.  Similar to libfuse's
335          * fuse_lowlevel_notify_inval_inode.
336          *
337          * This method will block until the client has responded, so it should
338          * generally be run in a separate thread from request processing.
339          *
340          * @param       ino     File's inode number
341          * @param       off     offset at which to begin invalidation.  A
342          *                      negative offset means to invalidate attributes
343          *                      only.
344          * @param       len     Size of region of data to invalidate.  0 means
345          *                      to invalidate all cached data.
346          */
347         int notify_inval_inode(ino_t ino, off_t off, ssize_t len);
348
349         /* 
350          * Request handler
351          *
352          * This method is expected to provide the responses to each FUSE
353          * operation.  For an immediate response, push one buffer into out.
354          * For a delayed response, push nothing.  For an immediate response
355          * plus a delayed response to an earlier operation, push two bufs.
356          * Test cases must define each response using Googlemock expectations
357          */
358         MOCK_METHOD2(process, void(const mockfs_buf_in&,
359                                 std::vector<std::unique_ptr<mockfs_buf_out>>&));
360
361         /* Gracefully unmount */
362         void unmount();
363 };