]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - tests/sys/fs/fuse/mockfs.cc
fuse(4): add tests for FUSE_READ
[FreeBSD/FreeBSD.git] / tests / sys / fs / fuse / mockfs.cc
1 /*-
2  * Copyright (c) 2019 The FreeBSD Foundation
3  * All rights reserved.
4  *
5  * This software was developed by BFF Storage Systems, LLC under sponsorship
6  * from the FreeBSD Foundation.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  */
29
30 extern "C" {
31 #include <sys/param.h>
32
33 #include <sys/mount.h>
34 #include <sys/stat.h>
35 #include <sys/uio.h>
36 #include <sys/user.h>
37
38 #include <fcntl.h>
39 #include <libutil.h>
40 #include <pthread.h>
41 #include <signal.h>
42 #include <stdlib.h>
43 #include <unistd.h>
44
45 #include "mntopts.h"    // for build_iovec
46 }
47
48 #include <gtest/gtest.h>
49
50 #include "mockfs.hh"
51
52 using namespace testing;
53
54 int verbosity = 0;
55 static sig_atomic_t quit = 0;
56
57 const char* opcode2opname(uint32_t opcode)
58 {
59         const int NUM_OPS = 39;
60         const char* table[NUM_OPS] = {
61                 "Unknown (opcode 0)",
62                 "LOOKUP",
63                 "FORGET",
64                 "GETATTR",
65                 "SETATTR",
66                 "READLINK",
67                 "SYMLINK",
68                 "Unknown (opcode 7)",
69                 "MKNOD",
70                 "MKDIR",
71                 "UNLINK",
72                 "RMDIR",
73                 "RENAME",
74                 "LINK",
75                 "OPEN",
76                 "READ",
77                 "WRITE",
78                 "STATFS",
79                 "RELEASE",
80                 "Unknown (opcode 19)",
81                 "FSYNC",
82                 "SETXATTR",
83                 "GETXATTR",
84                 "LISTXATTR",
85                 "REMOVEXATTR",
86                 "FLUSH",
87                 "INIT",
88                 "OPENDIR",
89                 "READDIR",
90                 "RELEASEDIR",
91                 "FSYNCDIR",
92                 "GETLK",
93                 "SETLK",
94                 "SETLKW",
95                 "ACCESS",
96                 "CREATE",
97                 "INTERRUPT",
98                 "BMAP",
99                 "DESTROY"
100         };
101         if (opcode >= NUM_OPS)
102                 return ("Unknown (opcode > max)");
103         else
104                 return (table[opcode]);
105 }
106
107 std::function<void (const struct mockfs_buf_in *in, struct mockfs_buf_out *out)>
108 ReturnErrno(int error)
109 {
110         return([=](auto in, auto out) {
111                 out->header.unique = in->header.unique;
112                 out->header.error = -error;
113                 out->header.len = sizeof(out->header);
114         });
115 }
116
117 /* Helper function used for returning negative cache entries for LOOKUP */
118 std::function<void (const struct mockfs_buf_in *in, struct mockfs_buf_out *out)>
119 ReturnNegativeCache(const struct timespec *entry_valid)
120 {
121         return([=](auto in, auto out) {
122                 /* nodeid means ENOENT and cache it */
123                 out->body.entry.nodeid = 0;
124                 out->header.unique = in->header.unique;
125                 out->header.error = 0;
126                 out->body.entry.entry_valid = entry_valid->tv_sec;
127                 out->body.entry.entry_valid_nsec = entry_valid->tv_nsec;
128                 SET_OUT_HEADER_LEN(out, entry);
129         });
130 }
131
132 void sigint_handler(int __unused sig) {
133         quit = 1;
134 }
135
136 void debug_fuseop(const mockfs_buf_in *in)
137 {
138         printf("%-11s ino=%2lu", opcode2opname(in->header.opcode),
139                 in->header.nodeid);
140         if (verbosity > 1) {
141                 printf(" uid=%5u gid=%5u pid=%5u unique=%lu len=%u",
142                         in->header.uid, in->header.gid, in->header.pid,
143                         in->header.unique, in->header.len);
144         }
145         switch (in->header.opcode) {
146                 case FUSE_LOOKUP:
147                         printf(" %s", in->body.lookup);
148                         break;
149                 case FUSE_OPEN:
150                         printf(" flags=%#x mode=%#o",
151                                 in->body.open.flags, in->body.open.mode);
152                         break;
153                 case FUSE_READ:
154                         printf(" offset=%lu size=%u", in->body.read.offset,
155                                 in->body.read.size);
156                         break;
157                 case FUSE_WRITE:
158                         printf(" offset=%lu size=%u flags=%u",
159                                 in->body.write.offset, in->body.write.size,
160                                 in->body.write.write_flags);
161                         break;
162                 default:
163                         break;
164         }
165         printf("\n");
166 }
167
168 MockFS::MockFS(int max_readahead) {
169         struct iovec *iov = NULL;
170         int iovlen = 0;
171         char fdstr[15];
172
173         m_daemon_id = NULL;
174         m_maxreadahead = max_readahead;
175         quit = 0;
176
177         /*
178          * Kyua sets pwd to a testcase-unique tempdir; no need to use
179          * mkdtemp
180          */
181         /*
182          * googletest doesn't allow ASSERT_ in constructors, so we must throw
183          * instead.
184          */
185         if (mkdir("mountpoint" , 0644) && errno != EEXIST)
186                 throw(std::system_error(errno, std::system_category(),
187                         "Couldn't make mountpoint directory"));
188
189         m_fuse_fd = open("/dev/fuse", O_RDWR);
190         if (m_fuse_fd < 0)
191                 throw(std::system_error(errno, std::system_category(),
192                         "Couldn't open /dev/fuse"));
193         sprintf(fdstr, "%d", m_fuse_fd);
194
195         m_pid = getpid();
196
197         build_iovec(&iov, &iovlen, "fstype", __DECONST(void *, "fusefs"), -1);
198         build_iovec(&iov, &iovlen, "fspath",
199                     __DECONST(void *, "mountpoint"), -1);
200         build_iovec(&iov, &iovlen, "from", __DECONST(void *, "/dev/fuse"), -1);
201         build_iovec(&iov, &iovlen, "fd", fdstr, -1);
202         if (nmount(iov, iovlen, 0))
203                 throw(std::system_error(errno, std::system_category(),
204                         "Couldn't mount filesystem"));
205
206         // Setup default handler
207         ON_CALL(*this, process(_, _))
208                 .WillByDefault(Invoke(this, &MockFS::process_default));
209
210         init();
211         signal(SIGUSR1, sigint_handler);
212         if (pthread_create(&m_daemon_id, NULL, service, (void*)this))
213                 throw(std::system_error(errno, std::system_category(),
214                         "Couldn't Couldn't start fuse thread"));
215 }
216
217 MockFS::~MockFS() {
218         kill_daemon();
219         ::unmount("mountpoint", MNT_FORCE);
220         rmdir("mountpoint");
221 }
222
223 void MockFS::init() {
224         mockfs_buf_in *in;
225         mockfs_buf_out *out;
226
227         in = (mockfs_buf_in*) malloc(sizeof(*in));
228         ASSERT_TRUE(in != NULL);
229         out = (mockfs_buf_out*) malloc(sizeof(*out));
230         ASSERT_TRUE(out != NULL);
231
232         read_request(in);
233         ASSERT_EQ(FUSE_INIT, in->header.opcode);
234
235         memset(out, 0, sizeof(*out));
236         out->header.unique = in->header.unique;
237         out->header.error = 0;
238         out->body.init.major = FUSE_KERNEL_VERSION;
239         out->body.init.minor = FUSE_KERNEL_MINOR_VERSION;
240
241         /*
242          * The default max_write is set to this formula in libfuse, though
243          * individual filesystems can lower it.  The "- 4096" was added in
244          * commit 154ffe2, with the commit message "fix".
245          */
246         uint32_t default_max_write = 32 * getpagesize() + 0x1000 - 4096;
247         /* For testing purposes, it should be distinct from MAXPHYS */
248         m_max_write = MIN(default_max_write, MAXPHYS / 2);
249         out->body.init.max_write = m_max_write;
250
251         out->body.init.max_readahead = m_maxreadahead;
252         SET_OUT_HEADER_LEN(out, init);
253         write(m_fuse_fd, out, out->header.len);
254
255         free(in);
256 }
257
258 void MockFS::kill_daemon() {
259         if (m_daemon_id != NULL) {
260                 pthread_kill(m_daemon_id, SIGUSR1);
261                 // Closing the /dev/fuse file descriptor first allows unmount
262                 // to succeed even if the daemon doesn't correctly respond to
263                 // commands during the unmount sequence.
264                 close(m_fuse_fd);
265                 pthread_join(m_daemon_id, NULL);
266                 m_daemon_id = NULL;
267         }
268 }
269
270 void MockFS::loop() {
271         mockfs_buf_in *in;
272         mockfs_buf_out out;
273
274         in = (mockfs_buf_in*) malloc(sizeof(*in));
275         ASSERT_TRUE(in != NULL);
276         while (!quit) {
277                 bzero(in, sizeof(*in));
278                 bzero(&out, sizeof(out));
279                 read_request(in);
280                 if (quit)
281                         break;
282                 if (verbosity > 0)
283                         debug_fuseop(in);
284                 if (pid_ok((pid_t)in->header.pid)) {
285                         process(in, &out);
286                 } else {
287                         /* 
288                          * Reject any requests from unknown processes.  Because
289                          * we actually do mount a filesystem, plenty of
290                          * unrelated system daemons may try to access it.
291                          */
292                         process_default(in, &out);
293                 }
294                 if (in->header.opcode == FUSE_FORGET) {
295                         /*Alone among the opcodes, FORGET expects no response*/
296                         continue;
297                 }
298                 ASSERT_TRUE(write(m_fuse_fd, &out, out.header.len) > 0 ||
299                             errno == EAGAIN)
300                         << strerror(errno);
301         }
302         free(in);
303 }
304
305 bool MockFS::pid_ok(pid_t pid) {
306         if (pid == m_pid) {
307                 return (true);
308         } else {
309                 struct kinfo_proc *ki;
310                 bool ok = false;
311
312                 ki = kinfo_getproc(pid);
313                 if (ki == NULL)
314                         return (false);
315                 /* 
316                  * Allow access by the aio daemon processes so that our tests
317                  * can use aio functions
318                  */
319                 if (0 == strncmp("aiod", ki->ki_comm, 4))
320                         ok = true;
321                 free(ki);
322                 return (ok);
323         }
324 }
325
326 void MockFS::process_default(const mockfs_buf_in *in, mockfs_buf_out* out) {
327         out->header.unique = in->header.unique;
328         out->header.error = -EOPNOTSUPP;
329         out->header.len = sizeof(out->header);
330 }
331
332 void MockFS::read_request(mockfs_buf_in *in) {
333         ssize_t res;
334
335         res = read(m_fuse_fd, in, sizeof(*in));
336         if (res < 0 && !quit)
337                 perror("read");
338         ASSERT_TRUE(res >= (ssize_t)sizeof(in->header) || quit);
339 }
340
341 void* MockFS::service(void *pthr_data) {
342         MockFS *mock_fs = (MockFS*)pthr_data;
343
344         mock_fs->loop();
345
346         return (NULL);
347 }
348
349 void MockFS::unmount() {
350         ::unmount("mountpoint", 0);
351 }