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