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