]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - tests/sys/fs/fusefs/mockfs.cc
fusefs: fallback to MKNOD/OPEN if a filesystem doesn't support CREATE
[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                 const char *name, *value;
164
165                 case FUSE_CREATE:
166                         name = (const char*)in->body.bytes +
167                                 sizeof(fuse_open_in);
168                         printf(" flags=%#x name=%s",
169                                 in->body.open.flags, name);
170                         break;
171                 case FUSE_FLUSH:
172                         printf(" lock_owner=%lu", in->body.flush.lock_owner);
173                         break;
174                 case FUSE_FORGET:
175                         printf(" nlookup=%lu", in->body.forget.nlookup);
176                         break;
177                 case FUSE_FSYNC:
178                         printf(" flags=%#x", in->body.fsync.fsync_flags);
179                         break;
180                 case FUSE_FSYNCDIR:
181                         printf(" flags=%#x", in->body.fsyncdir.fsync_flags);
182                         break;
183                 case FUSE_LOOKUP:
184                         printf(" %s", in->body.lookup);
185                         break;
186                 case FUSE_MKNOD:
187                         printf(" mode=%#o rdev=%x", in->body.mknod.mode,
188                                 in->body.mknod.rdev);
189                         break;
190                 case FUSE_OPEN:
191                         printf(" flags=%#x mode=%#o",
192                                 in->body.open.flags, in->body.open.mode);
193                         break;
194                 case FUSE_OPENDIR:
195                         printf(" flags=%#x mode=%#o",
196                                 in->body.opendir.flags, in->body.opendir.mode);
197                         break;
198                 case FUSE_READ:
199                         printf(" offset=%lu size=%u", in->body.read.offset,
200                                 in->body.read.size);
201                         break;
202                 case FUSE_READDIR:
203                         printf(" offset=%lu size=%u", in->body.readdir.offset,
204                                 in->body.readdir.size);
205                         break;
206                 case FUSE_RELEASE:
207                         printf(" flags=%#x lock_owner=%lu",
208                                 in->body.release.flags,
209                                 in->body.release.lock_owner);
210                         break;
211                 case FUSE_SETATTR:
212                         if (verbosity <= 1) {
213                                 printf(" valid=%#x", in->body.setattr.valid);
214                                 break;
215                         }
216                         if (in->body.setattr.valid & FATTR_MODE)
217                                 printf(" mode=%#o", in->body.setattr.mode);
218                         if (in->body.setattr.valid & FATTR_UID)
219                                 printf(" uid=%u", in->body.setattr.uid);
220                         if (in->body.setattr.valid & FATTR_GID)
221                                 printf(" gid=%u", in->body.setattr.gid);
222                         if (in->body.setattr.valid & FATTR_SIZE)
223                                 printf(" size=%zu", in->body.setattr.size);
224                         if (in->body.setattr.valid & FATTR_ATIME)
225                                 printf(" atime=%zu.%u",
226                                         in->body.setattr.atime,
227                                         in->body.setattr.atimensec);
228                         if (in->body.setattr.valid & FATTR_MTIME)
229                                 printf(" mtime=%zu.%u",
230                                         in->body.setattr.mtime,
231                                         in->body.setattr.mtimensec);
232                         if (in->body.setattr.valid & FATTR_FH)
233                                 printf(" fh=%zu", in->body.setattr.fh);
234                         break;
235                 case FUSE_SETXATTR:
236                         /* 
237                          * In theory neither the xattr name and value need be
238                          * ASCII, but in this test suite they always are.
239                          */
240                         name = (const char*)in->body.bytes +
241                                 sizeof(fuse_setxattr_in);
242                         value = name + strlen(name) + 1;
243                         printf(" %s=%s", name, value);
244                         break;
245                 case FUSE_WRITE:
246                         printf(" offset=%lu size=%u flags=%u",
247                                 in->body.write.offset, in->body.write.size,
248                                 in->body.write.write_flags);
249                         break;
250                 default:
251                         break;
252         }
253         printf("\n");
254 }
255
256 MockFS::MockFS(int max_readahead, bool allow_other, bool default_permissions,
257         bool push_symlinks_in, uint32_t flags)
258 {
259         struct iovec *iov = NULL;
260         int iovlen = 0;
261         char fdstr[15];
262         const bool trueval = true;
263
264         m_daemon_id = NULL;
265         m_maxreadahead = max_readahead;
266         quit = 0;
267
268         /*
269          * Kyua sets pwd to a testcase-unique tempdir; no need to use
270          * mkdtemp
271          */
272         /*
273          * googletest doesn't allow ASSERT_ in constructors, so we must throw
274          * instead.
275          */
276         if (mkdir("mountpoint" , 0755) && errno != EEXIST)
277                 throw(std::system_error(errno, std::system_category(),
278                         "Couldn't make mountpoint directory"));
279
280         m_fuse_fd = open("/dev/fuse", O_CLOEXEC | O_RDWR);
281         if (m_fuse_fd < 0)
282                 throw(std::system_error(errno, std::system_category(),
283                         "Couldn't open /dev/fuse"));
284         sprintf(fdstr, "%d", m_fuse_fd);
285
286         m_pid = getpid();
287         m_child_pid = -1;
288
289         build_iovec(&iov, &iovlen, "fstype", __DECONST(void *, "fusefs"), -1);
290         build_iovec(&iov, &iovlen, "fspath",
291                     __DECONST(void *, "mountpoint"), -1);
292         build_iovec(&iov, &iovlen, "from", __DECONST(void *, "/dev/fuse"), -1);
293         build_iovec(&iov, &iovlen, "fd", fdstr, -1);
294         if (allow_other) {
295                 build_iovec(&iov, &iovlen, "allow_other",
296                         __DECONST(void*, &trueval), sizeof(bool));
297         }
298         if (default_permissions) {
299                 build_iovec(&iov, &iovlen, "default_permissions",
300                         __DECONST(void*, &trueval), sizeof(bool));
301         }
302         if (push_symlinks_in) {
303                 build_iovec(&iov, &iovlen, "push_symlinks_in",
304                         __DECONST(void*, &trueval), sizeof(bool));
305         }
306         if (nmount(iov, iovlen, 0))
307                 throw(std::system_error(errno, std::system_category(),
308                         "Couldn't mount filesystem"));
309
310         // Setup default handler
311         ON_CALL(*this, process(_, _))
312                 .WillByDefault(Invoke(this, &MockFS::process_default));
313
314         init(flags);
315         signal(SIGUSR1, sigint_handler);
316         if (pthread_create(&m_daemon_id, NULL, service, (void*)this))
317                 throw(std::system_error(errno, std::system_category(),
318                         "Couldn't Couldn't start fuse thread"));
319 }
320
321 MockFS::~MockFS() {
322         kill_daemon();
323         ::unmount("mountpoint", MNT_FORCE);
324         if (m_daemon_id != NULL) {
325                 pthread_join(m_daemon_id, NULL);
326                 m_daemon_id = NULL;
327         }
328         rmdir("mountpoint");
329 }
330
331 void MockFS::init(uint32_t flags) {
332         mockfs_buf_in *in;
333         mockfs_buf_out *out;
334
335         in = (mockfs_buf_in*) malloc(sizeof(*in));
336         ASSERT_TRUE(in != NULL);
337         out = (mockfs_buf_out*) malloc(sizeof(*out));
338         ASSERT_TRUE(out != NULL);
339
340         read_request(in);
341         ASSERT_EQ(FUSE_INIT, in->header.opcode);
342
343         memset(out, 0, sizeof(*out));
344         out->header.unique = in->header.unique;
345         out->header.error = 0;
346         out->body.init.major = FUSE_KERNEL_VERSION;
347         out->body.init.minor = FUSE_KERNEL_MINOR_VERSION;
348         out->body.init.flags = in->body.init.flags & flags;
349
350         /*
351          * The default max_write is set to this formula in libfuse, though
352          * individual filesystems can lower it.  The "- 4096" was added in
353          * commit 154ffe2, with the commit message "fix".
354          */
355         uint32_t default_max_write = 32 * getpagesize() + 0x1000 - 4096;
356         /* For testing purposes, it should be distinct from MAXPHYS */
357         m_max_write = MIN(default_max_write, MAXPHYS / 2);
358         out->body.init.max_write = m_max_write;
359
360         out->body.init.max_readahead = m_maxreadahead;
361         SET_OUT_HEADER_LEN(out, init);
362         write(m_fuse_fd, out, out->header.len);
363
364         free(in);
365 }
366
367 void MockFS::kill_daemon() {
368         if (m_daemon_id != NULL) {
369                 pthread_kill(m_daemon_id, SIGUSR1);
370                 // Closing the /dev/fuse file descriptor first allows unmount
371                 // to succeed even if the daemon doesn't correctly respond to
372                 // commands during the unmount sequence.
373                 close(m_fuse_fd);
374         }
375 }
376
377 void MockFS::loop() {
378         mockfs_buf_in *in;
379         std::vector<mockfs_buf_out*> out;
380
381         in = (mockfs_buf_in*) malloc(sizeof(*in));
382         ASSERT_TRUE(in != NULL);
383         while (!quit) {
384                 bzero(in, sizeof(*in));
385                 read_request(in);
386                 if (quit)
387                         break;
388                 if (verbosity > 0)
389                         debug_fuseop(in);
390                 if (pid_ok((pid_t)in->header.pid)) {
391                         process(in, out);
392                 } else {
393                         /* 
394                          * Reject any requests from unknown processes.  Because
395                          * we actually do mount a filesystem, plenty of
396                          * unrelated system daemons may try to access it.
397                          */
398                         process_default(in, out);
399                 }
400                 for (auto &it: out) {
401                         ASSERT_TRUE(write(m_fuse_fd, it, it->header.len) > 0 ||
402                                     errno == EAGAIN)
403                                 << strerror(errno);
404                         delete it;
405                 }
406                 out.clear();
407         }
408         free(in);
409 }
410
411 bool MockFS::pid_ok(pid_t pid) {
412         if (pid == m_pid) {
413                 return (true);
414         } else if (pid == m_child_pid) {
415                 return (true);
416         } else {
417                 struct kinfo_proc *ki;
418                 bool ok = false;
419
420                 ki = kinfo_getproc(pid);
421                 if (ki == NULL)
422                         return (false);
423                 /* 
424                  * Allow access by the aio daemon processes so that our tests
425                  * can use aio functions
426                  */
427                 if (0 == strncmp("aiod", ki->ki_comm, 4))
428                         ok = true;
429                 free(ki);
430                 return (ok);
431         }
432 }
433
434 void MockFS::process_default(const mockfs_buf_in *in,
435                 std::vector<mockfs_buf_out*> &out)
436 {
437         auto out0 = new mockfs_buf_out;
438         out0->header.unique = in->header.unique;
439         out0->header.error = -EOPNOTSUPP;
440         out0->header.len = sizeof(out0->header);
441         out.push_back(out0);
442 }
443
444 void MockFS::read_request(mockfs_buf_in *in) {
445         ssize_t res;
446
447         res = read(m_fuse_fd, in, sizeof(*in));
448         if (res < 0 && !quit)
449                 perror("read");
450         ASSERT_TRUE(res >= (ssize_t)sizeof(in->header) || quit);
451 }
452
453 void* MockFS::service(void *pthr_data) {
454         MockFS *mock_fs = (MockFS*)pthr_data;
455
456         mock_fs->loop();
457
458         return (NULL);
459 }
460
461 void MockFS::unmount() {
462         ::unmount("mountpoint", 0);
463 }