]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - tests/sys/fs/fusefs/mockfs.cc
MFHead @345353
[FreeBSD/FreeBSD.git] / tests / sys / fs / fusefs / mockfs.cc
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/param.h>
33
34 #include <sys/mount.h>
35 #include <sys/stat.h>
36 #include <sys/uio.h>
37 #include <sys/user.h>
38
39 #include <fcntl.h>
40 #include <libutil.h>
41 #include <pthread.h>
42 #include <signal.h>
43 #include <stdlib.h>
44 #include <unistd.h>
45
46 #include "mntopts.h"    // for build_iovec
47 }
48
49 #include <gtest/gtest.h>
50
51 #include "mockfs.hh"
52
53 using namespace testing;
54
55 int verbosity = 0;
56 static sig_atomic_t quit = 0;
57
58 const char* opcode2opname(uint32_t opcode)
59 {
60         const int NUM_OPS = 39;
61         const char* table[NUM_OPS] = {
62                 "Unknown (opcode 0)",
63                 "LOOKUP",
64                 "FORGET",
65                 "GETATTR",
66                 "SETATTR",
67                 "READLINK",
68                 "SYMLINK",
69                 "Unknown (opcode 7)",
70                 "MKNOD",
71                 "MKDIR",
72                 "UNLINK",
73                 "RMDIR",
74                 "RENAME",
75                 "LINK",
76                 "OPEN",
77                 "READ",
78                 "WRITE",
79                 "STATFS",
80                 "RELEASE",
81                 "Unknown (opcode 19)",
82                 "FSYNC",
83                 "SETXATTR",
84                 "GETXATTR",
85                 "LISTXATTR",
86                 "REMOVEXATTR",
87                 "FLUSH",
88                 "INIT",
89                 "OPENDIR",
90                 "READDIR",
91                 "RELEASEDIR",
92                 "FSYNCDIR",
93                 "GETLK",
94                 "SETLK",
95                 "SETLKW",
96                 "ACCESS",
97                 "CREATE",
98                 "INTERRUPT",
99                 "BMAP",
100                 "DESTROY"
101         };
102         if (opcode >= NUM_OPS)
103                 return ("Unknown (opcode > max)");
104         else
105                 return (table[opcode]);
106 }
107
108 ProcessMockerT
109 ReturnErrno(int error)
110 {
111         return([=](auto in, auto &out) {
112                 auto out0 = new mockfs_buf_out;
113                 out0->header.unique = in->header.unique;
114                 out0->header.error = -error;
115                 out0->header.len = sizeof(out0->header);
116                 out.push_back(out0);
117         });
118 }
119
120 /* Helper function used for returning negative cache entries for LOOKUP */
121 ProcessMockerT
122 ReturnNegativeCache(const struct timespec *entry_valid)
123 {
124         return([=](auto in, auto &out) {
125                 /* nodeid means ENOENT and cache it */
126                 auto out0 = new mockfs_buf_out;
127                 out0->body.entry.nodeid = 0;
128                 out0->header.unique = in->header.unique;
129                 out0->header.error = 0;
130                 out0->body.entry.entry_valid = entry_valid->tv_sec;
131                 out0->body.entry.entry_valid_nsec = entry_valid->tv_nsec;
132                 SET_OUT_HEADER_LEN(out0, entry);
133                 out.push_back(out0);
134         });
135 }
136
137 ProcessMockerT
138 ReturnImmediate(std::function<void(const struct mockfs_buf_in *in,
139                                    struct mockfs_buf_out *out)> f)
140 {
141         return([=](auto in, auto &out) {
142                 auto out0 = new mockfs_buf_out;
143                 out0->header.unique = in->header.unique;
144                 f(in, out0);
145                 out.push_back(out0);
146         });
147 }
148
149 void sigint_handler(int __unused sig) {
150         quit = 1;
151 }
152
153 void debug_fuseop(const mockfs_buf_in *in)
154 {
155         printf("%-11s ino=%2lu", opcode2opname(in->header.opcode),
156                 in->header.nodeid);
157         if (verbosity > 1) {
158                 printf(" uid=%5u gid=%5u pid=%5u unique=%lu len=%u",
159                         in->header.uid, in->header.gid, in->header.pid,
160                         in->header.unique, in->header.len);
161         }
162         switch (in->header.opcode) {
163                 case FUSE_FLUSH:
164                         printf(" lock_owner=%lu", in->body.flush.lock_owner);
165                         break;
166                 case FUSE_FORGET:
167                         printf(" nlookup=%lu", in->body.forget.nlookup);
168                         break;
169                 case FUSE_FSYNC:
170                         printf(" flags=%#x", in->body.fsync.fsync_flags);
171                         break;
172                 case FUSE_FSYNCDIR:
173                         printf(" flags=%#x", in->body.fsyncdir.fsync_flags);
174                         break;
175                 case FUSE_LOOKUP:
176                         printf(" %s", in->body.lookup);
177                         break;
178                 case FUSE_OPEN:
179                         printf(" flags=%#x mode=%#o",
180                                 in->body.open.flags, in->body.open.mode);
181                         break;
182                 case FUSE_OPENDIR:
183                         printf(" flags=%#x mode=%#o",
184                                 in->body.opendir.flags, in->body.opendir.mode);
185                         break;
186                 case FUSE_READ:
187                         printf(" offset=%lu size=%u", in->body.read.offset,
188                                 in->body.read.size);
189                         break;
190                 case FUSE_READDIR:
191                         printf(" offset=%lu size=%u", in->body.readdir.offset,
192                                 in->body.readdir.size);
193                         break;
194                 case FUSE_RELEASE:
195                         printf(" flags=%#x lock_owner=%lu",
196                                 in->body.release.flags,
197                                 in->body.release.lock_owner);
198                         break;
199                 case FUSE_SETATTR:
200                         if (verbosity <= 1) {
201                                 printf(" valid=%#x", in->body.setattr.valid);
202                                 break;
203                         }
204                         if (in->body.setattr.valid & FATTR_MODE)
205                                 printf(" mode=%#o", in->body.setattr.mode);
206                         if (in->body.setattr.valid & FATTR_UID)
207                                 printf(" uid=%u", in->body.setattr.uid);
208                         if (in->body.setattr.valid & FATTR_GID)
209                                 printf(" gid=%u", in->body.setattr.gid);
210                         if (in->body.setattr.valid & FATTR_SIZE)
211                                 printf(" size=%zu", in->body.setattr.size);
212                         if (in->body.setattr.valid & FATTR_ATIME)
213                                 printf(" atime=%zu.%u",
214                                         in->body.setattr.atime,
215                                         in->body.setattr.atimensec);
216                         if (in->body.setattr.valid & FATTR_MTIME)
217                                 printf(" mtime=%zu.%u",
218                                         in->body.setattr.mtime,
219                                         in->body.setattr.mtimensec);
220                         if (in->body.setattr.valid & FATTR_FH)
221                                 printf(" fh=%zu", in->body.setattr.fh);
222                         break;
223                 case FUSE_SETXATTR:
224                         /* 
225                          * In theory neither the xattr name and value need be
226                          * ASCII, but in this test suite they always are.
227                          */
228                         {
229                                 const char *attr = (const char*)in->body.bytes +
230                                         sizeof(fuse_setxattr_in);
231                                 const char *v = attr + strlen(attr) + 1;
232                                 printf(" %s=%s", attr, v);
233                         }
234                         break;
235                 case FUSE_WRITE:
236                         printf(" offset=%lu size=%u flags=%u",
237                                 in->body.write.offset, in->body.write.size,
238                                 in->body.write.write_flags);
239                         break;
240                 default:
241                         break;
242         }
243         printf("\n");
244 }
245
246 MockFS::MockFS(int max_readahead, bool push_symlinks_in,
247         bool default_permissions, uint32_t flags)
248 {
249         struct iovec *iov = NULL;
250         int iovlen = 0;
251         char fdstr[15];
252
253         m_daemon_id = NULL;
254         m_maxreadahead = max_readahead;
255         quit = 0;
256
257         /*
258          * Kyua sets pwd to a testcase-unique tempdir; no need to use
259          * mkdtemp
260          */
261         /*
262          * googletest doesn't allow ASSERT_ in constructors, so we must throw
263          * instead.
264          */
265         if (mkdir("mountpoint" , 0644) && errno != EEXIST)
266                 throw(std::system_error(errno, std::system_category(),
267                         "Couldn't make mountpoint directory"));
268
269         m_fuse_fd = open("/dev/fuse", O_RDWR);
270         if (m_fuse_fd < 0)
271                 throw(std::system_error(errno, std::system_category(),
272                         "Couldn't open /dev/fuse"));
273         sprintf(fdstr, "%d", m_fuse_fd);
274
275         m_pid = getpid();
276
277         build_iovec(&iov, &iovlen, "fstype", __DECONST(void *, "fusefs"), -1);
278         build_iovec(&iov, &iovlen, "fspath",
279                     __DECONST(void *, "mountpoint"), -1);
280         build_iovec(&iov, &iovlen, "from", __DECONST(void *, "/dev/fuse"), -1);
281         build_iovec(&iov, &iovlen, "fd", fdstr, -1);
282         if (push_symlinks_in) {
283                 const bool trueval = true;
284                 build_iovec(&iov, &iovlen, "push_symlinks_in",
285                         __DECONST(void*, &trueval), sizeof(bool));
286         }
287         if (default_permissions) {
288                 const bool trueval = true;
289                 build_iovec(&iov, &iovlen, "default_permissions",
290                         __DECONST(void*, &trueval), sizeof(bool));
291         }
292         if (nmount(iov, iovlen, 0))
293                 throw(std::system_error(errno, std::system_category(),
294                         "Couldn't mount filesystem"));
295
296         // Setup default handler
297         ON_CALL(*this, process(_, _))
298                 .WillByDefault(Invoke(this, &MockFS::process_default));
299
300         init(flags);
301         signal(SIGUSR1, sigint_handler);
302         if (pthread_create(&m_daemon_id, NULL, service, (void*)this))
303                 throw(std::system_error(errno, std::system_category(),
304                         "Couldn't Couldn't start fuse thread"));
305 }
306
307 MockFS::~MockFS() {
308         kill_daemon();
309         ::unmount("mountpoint", MNT_FORCE);
310         if (m_daemon_id != NULL) {
311                 pthread_join(m_daemon_id, NULL);
312                 m_daemon_id = NULL;
313         }
314         rmdir("mountpoint");
315 }
316
317 void MockFS::init(uint32_t flags) {
318         mockfs_buf_in *in;
319         mockfs_buf_out *out;
320
321         in = (mockfs_buf_in*) malloc(sizeof(*in));
322         ASSERT_TRUE(in != NULL);
323         out = (mockfs_buf_out*) malloc(sizeof(*out));
324         ASSERT_TRUE(out != NULL);
325
326         read_request(in);
327         ASSERT_EQ(FUSE_INIT, in->header.opcode);
328
329         memset(out, 0, sizeof(*out));
330         out->header.unique = in->header.unique;
331         out->header.error = 0;
332         out->body.init.major = FUSE_KERNEL_VERSION;
333         out->body.init.minor = FUSE_KERNEL_MINOR_VERSION;
334         out->body.init.flags = in->body.init.flags & flags;
335
336         /*
337          * The default max_write is set to this formula in libfuse, though
338          * individual filesystems can lower it.  The "- 4096" was added in
339          * commit 154ffe2, with the commit message "fix".
340          */
341         uint32_t default_max_write = 32 * getpagesize() + 0x1000 - 4096;
342         /* For testing purposes, it should be distinct from MAXPHYS */
343         m_max_write = MIN(default_max_write, MAXPHYS / 2);
344         out->body.init.max_write = m_max_write;
345
346         out->body.init.max_readahead = m_maxreadahead;
347         SET_OUT_HEADER_LEN(out, init);
348         write(m_fuse_fd, out, out->header.len);
349
350         free(in);
351 }
352
353 void MockFS::kill_daemon() {
354         if (m_daemon_id != NULL) {
355                 pthread_kill(m_daemon_id, SIGUSR1);
356                 // Closing the /dev/fuse file descriptor first allows unmount
357                 // to succeed even if the daemon doesn't correctly respond to
358                 // commands during the unmount sequence.
359                 close(m_fuse_fd);
360         }
361 }
362
363 void MockFS::loop() {
364         mockfs_buf_in *in;
365         std::vector<mockfs_buf_out*> out;
366
367         in = (mockfs_buf_in*) malloc(sizeof(*in));
368         ASSERT_TRUE(in != NULL);
369         while (!quit) {
370                 bzero(in, sizeof(*in));
371                 read_request(in);
372                 if (quit)
373                         break;
374                 if (verbosity > 0)
375                         debug_fuseop(in);
376                 if (pid_ok((pid_t)in->header.pid)) {
377                         process(in, out);
378                 } else {
379                         /* 
380                          * Reject any requests from unknown processes.  Because
381                          * we actually do mount a filesystem, plenty of
382                          * unrelated system daemons may try to access it.
383                          */
384                         process_default(in, out);
385                 }
386                 for (auto &it: out) {
387                         ASSERT_TRUE(write(m_fuse_fd, it, it->header.len) > 0 ||
388                                     errno == EAGAIN)
389                                 << strerror(errno);
390                         delete it;
391                 }
392                 out.clear();
393         }
394         free(in);
395 }
396
397 bool MockFS::pid_ok(pid_t pid) {
398         if (pid == m_pid) {
399                 return (true);
400         } else {
401                 struct kinfo_proc *ki;
402                 bool ok = false;
403
404                 ki = kinfo_getproc(pid);
405                 if (ki == NULL)
406                         return (false);
407                 /* 
408                  * Allow access by the aio daemon processes so that our tests
409                  * can use aio functions
410                  */
411                 if (0 == strncmp("aiod", ki->ki_comm, 4))
412                         ok = true;
413                 free(ki);
414                 return (ok);
415         }
416 }
417
418 void MockFS::process_default(const mockfs_buf_in *in,
419                 std::vector<mockfs_buf_out*> &out)
420 {
421         auto out0 = new mockfs_buf_out;
422         out0->header.unique = in->header.unique;
423         out0->header.error = -EOPNOTSUPP;
424         out0->header.len = sizeof(out0->header);
425         out.push_back(out0);
426 }
427
428 void MockFS::read_request(mockfs_buf_in *in) {
429         ssize_t res;
430
431         res = read(m_fuse_fd, in, sizeof(*in));
432         if (res < 0 && !quit)
433                 perror("read");
434         ASSERT_TRUE(res >= (ssize_t)sizeof(in->header) || quit);
435 }
436
437 void* MockFS::service(void *pthr_data) {
438         MockFS *mock_fs = (MockFS*)pthr_data;
439
440         mock_fs->loop();
441
442         return (NULL);
443 }
444
445 void MockFS::unmount() {
446         ::unmount("mountpoint", 0);
447 }