]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - tests/sys/fs/fuse/mockfs.cc
fuse(4): add tests for some mount options.
[FreeBSD/FreeBSD.git] / tests / sys / fs / fuse / 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         rmdir("mountpoint");
311 }
312
313 void MockFS::init(uint32_t flags) {
314         mockfs_buf_in *in;
315         mockfs_buf_out *out;
316
317         in = (mockfs_buf_in*) malloc(sizeof(*in));
318         ASSERT_TRUE(in != NULL);
319         out = (mockfs_buf_out*) malloc(sizeof(*out));
320         ASSERT_TRUE(out != NULL);
321
322         read_request(in);
323         ASSERT_EQ(FUSE_INIT, in->header.opcode);
324
325         memset(out, 0, sizeof(*out));
326         out->header.unique = in->header.unique;
327         out->header.error = 0;
328         out->body.init.major = FUSE_KERNEL_VERSION;
329         out->body.init.minor = FUSE_KERNEL_MINOR_VERSION;
330         out->body.init.flags = in->body.init.flags & flags;
331
332         /*
333          * The default max_write is set to this formula in libfuse, though
334          * individual filesystems can lower it.  The "- 4096" was added in
335          * commit 154ffe2, with the commit message "fix".
336          */
337         uint32_t default_max_write = 32 * getpagesize() + 0x1000 - 4096;
338         /* For testing purposes, it should be distinct from MAXPHYS */
339         m_max_write = MIN(default_max_write, MAXPHYS / 2);
340         out->body.init.max_write = m_max_write;
341
342         out->body.init.max_readahead = m_maxreadahead;
343         SET_OUT_HEADER_LEN(out, init);
344         write(m_fuse_fd, out, out->header.len);
345
346         free(in);
347 }
348
349 void MockFS::kill_daemon() {
350         if (m_daemon_id != NULL) {
351                 pthread_kill(m_daemon_id, SIGUSR1);
352                 // Closing the /dev/fuse file descriptor first allows unmount
353                 // to succeed even if the daemon doesn't correctly respond to
354                 // commands during the unmount sequence.
355                 close(m_fuse_fd);
356                 pthread_join(m_daemon_id, NULL);
357                 m_daemon_id = NULL;
358         }
359 }
360
361 void MockFS::loop() {
362         mockfs_buf_in *in;
363         std::vector<mockfs_buf_out*> out;
364
365         in = (mockfs_buf_in*) malloc(sizeof(*in));
366         ASSERT_TRUE(in != NULL);
367         while (!quit) {
368                 bzero(in, sizeof(*in));
369                 read_request(in);
370                 if (quit)
371                         break;
372                 if (verbosity > 0)
373                         debug_fuseop(in);
374                 if (pid_ok((pid_t)in->header.pid)) {
375                         process(in, out);
376                 } else {
377                         /* 
378                          * Reject any requests from unknown processes.  Because
379                          * we actually do mount a filesystem, plenty of
380                          * unrelated system daemons may try to access it.
381                          */
382                         process_default(in, out);
383                 }
384                 for (auto &it: out) {
385                         ASSERT_TRUE(write(m_fuse_fd, it, it->header.len) > 0 ||
386                                     errno == EAGAIN)
387                                 << strerror(errno);
388                         delete it;
389                 }
390                 out.clear();
391         }
392         free(in);
393 }
394
395 bool MockFS::pid_ok(pid_t pid) {
396         if (pid == m_pid) {
397                 return (true);
398         } else {
399                 struct kinfo_proc *ki;
400                 bool ok = false;
401
402                 ki = kinfo_getproc(pid);
403                 if (ki == NULL)
404                         return (false);
405                 /* 
406                  * Allow access by the aio daemon processes so that our tests
407                  * can use aio functions
408                  */
409                 if (0 == strncmp("aiod", ki->ki_comm, 4))
410                         ok = true;
411                 free(ki);
412                 return (ok);
413         }
414 }
415
416 void MockFS::process_default(const mockfs_buf_in *in,
417                 std::vector<mockfs_buf_out*> &out)
418 {
419         auto out0 = new mockfs_buf_out;
420         out0->header.unique = in->header.unique;
421         out0->header.error = -EOPNOTSUPP;
422         out0->header.len = sizeof(out0->header);
423         out.push_back(out0);
424 }
425
426 void MockFS::read_request(mockfs_buf_in *in) {
427         ssize_t res;
428
429         res = read(m_fuse_fd, in, sizeof(*in));
430         if (res < 0 && !quit)
431                 perror("read");
432         ASSERT_TRUE(res >= (ssize_t)sizeof(in->header) || quit);
433 }
434
435 void* MockFS::service(void *pthr_data) {
436         MockFS *mock_fs = (MockFS*)pthr_data;
437
438         mock_fs->loop();
439
440         return (NULL);
441 }
442
443 void MockFS::unmount() {
444         ::unmount("mountpoint", 0);
445 }