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