]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - tests/sys/fs/fusefs/mockfs.hh
fusefs: only root may set the sticky bit on a non-directory
[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 union fuse_payloads_in {
80         fuse_access_in  access;
81         /* value is from fuse_kern_chan.c in fusefs-libs */
82         uint8_t         bytes[0x21000 - sizeof(struct fuse_in_header)];
83         fuse_flush_in   flush;
84         fuse_fsync_in   fsync;
85         fuse_fsync_in   fsyncdir;
86         fuse_forget_in  forget;
87         fuse_interrupt_in interrupt;
88         fuse_lk_in      getlk;
89         fuse_getxattr_in getxattr;
90         fuse_init_in    init;
91         fuse_link_in    link;
92         fuse_listxattr_in listxattr;
93         char            lookup[0];
94         fuse_mkdir_in   mkdir;
95         fuse_mknod_in   mknod;
96         fuse_open_in    open;
97         fuse_open_in    opendir;
98         fuse_read_in    read;
99         fuse_read_in    readdir;
100         fuse_release_in release;
101         fuse_release_in releasedir;
102         fuse_rename_in  rename;
103         char            rmdir[0];
104         fuse_setattr_in setattr;
105         fuse_setxattr_in setxattr;
106         fuse_lk_in      setlk;
107         fuse_lk_in      setlkw;
108         char            unlink[0];
109         fuse_write_in   write;
110 };
111
112 struct mockfs_buf_in {
113         fuse_in_header          header;
114         union fuse_payloads_in  body;
115 };
116
117 union fuse_payloads_out {
118         fuse_attr_out           attr;
119         fuse_create_out         create;
120         /* The protocol places no limits on the size of bytes */
121         uint8_t                 bytes[0x20000];
122         fuse_entry_out          entry;
123         fuse_lk_out             getlk;
124         fuse_getxattr_out       getxattr;
125         fuse_init_out           init;
126         fuse_listxattr_out      listxattr;
127         fuse_open_out           open;
128         fuse_statfs_out         statfs;
129         /*
130          * The protocol places no limits on the length of the string.  This is
131          * merely convenient for testing.
132          */
133         char                    str[80];
134         fuse_write_out          write;
135 };
136
137 struct mockfs_buf_out {
138         fuse_out_header         header;
139         union fuse_payloads_out body;
140
141         /* Default constructor: zero everything */
142         mockfs_buf_out() {
143                 memset(this, 0, sizeof(*this));
144         }
145 };
146
147 /* A function that can be invoked in place of MockFS::process */
148 typedef std::function<void (const struct mockfs_buf_in *in,
149                             std::vector<struct mockfs_buf_out*> &out)>
150 ProcessMockerT;
151
152 /*
153  * Helper function used for setting an error expectation for any fuse operation.
154  * The operation will return the supplied error
155  */
156 ProcessMockerT ReturnErrno(int error);
157
158 /* Helper function used for returning negative cache entries for LOOKUP */
159 ProcessMockerT ReturnNegativeCache(const struct timespec *entry_valid);
160
161 /* Helper function used for returning a single immediate response */
162 ProcessMockerT ReturnImmediate(
163         std::function<void(const struct mockfs_buf_in *in,
164                            struct mockfs_buf_out *out)> f);
165
166 /*
167  * Fake FUSE filesystem
168  *
169  * "Mounts" a filesystem to a temporary directory and services requests
170  * according to the programmed expectations.
171  *
172  * Operates directly on the fusefs(4) kernel API, not the libfuse(3) user api.
173  */
174 class MockFS {
175         /*
176          * thread id of the fuse daemon thread
177          *
178          * It must run in a separate thread so it doesn't deadlock with the
179          * client test code.
180          */
181         pthread_t m_daemon_id;
182
183         /* file descriptor of /dev/fuse control device */
184         int m_fuse_fd;
185         
186         /* The max_readahead filesystem option */
187         uint32_t m_maxreadahead;
188
189         /* pid of the test process */
190         pid_t m_pid;
191
192         /* Initialize a session after mounting */
193         void init(uint32_t flags);
194
195         /* Is pid from a process that might be involved in the test? */
196         bool pid_ok(pid_t pid);
197
198         /* Default request handler */
199         void process_default(const mockfs_buf_in*,
200                 std::vector<mockfs_buf_out*>&);
201
202         /* Entry point for the daemon thread */
203         static void* service(void*);
204
205         /* Read, but do not process, a single request from the kernel */
206         void read_request(mockfs_buf_in*);
207
208         public:
209         /* pid of child process, for two-process test cases */
210         pid_t m_child_pid;
211
212         /* Maximum size of a FUSE_WRITE write */
213         uint32_t m_max_write;
214
215         /* Create a new mockfs and mount it to a tempdir */
216         MockFS(int max_readahead, bool allow_other,
217                 bool default_permissions, bool push_symlinks_in, bool ro,
218                 uint32_t flags);
219         virtual ~MockFS();
220
221         /* Kill the filesystem daemon without unmounting the filesystem */
222         void kill_daemon();
223
224         /* Process FUSE requests endlessly */
225         void loop();
226
227         /* 
228          * Request handler
229          *
230          * This method is expected to provide the responses to each FUSE
231          * operation.  For an immediate response, push one buffer into out.
232          * For a delayed response, push nothing.  For an immediate response
233          * plus a delayed response to an earlier operation, push two bufs.
234          * Test cases must define each response using Googlemock expectations
235          */
236         MOCK_METHOD2(process, void(const mockfs_buf_in*,
237                                 std::vector<mockfs_buf_out*>&));
238
239         /* Gracefully unmount */
240         void unmount();
241 };