]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - tests/sys/fs/fuse/mockfs.cc
fuse(4): combine common code in the tests
[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, uint32_t flags) {
247         struct iovec *iov = NULL;
248         int iovlen = 0;
249         char fdstr[15];
250
251         m_daemon_id = NULL;
252         m_maxreadahead = max_readahead;
253         quit = 0;
254
255         /*
256          * Kyua sets pwd to a testcase-unique tempdir; no need to use
257          * mkdtemp
258          */
259         /*
260          * googletest doesn't allow ASSERT_ in constructors, so we must throw
261          * instead.
262          */
263         if (mkdir("mountpoint" , 0644) && errno != EEXIST)
264                 throw(std::system_error(errno, std::system_category(),
265                         "Couldn't make mountpoint directory"));
266
267         m_fuse_fd = open("/dev/fuse", O_RDWR);
268         if (m_fuse_fd < 0)
269                 throw(std::system_error(errno, std::system_category(),
270                         "Couldn't open /dev/fuse"));
271         sprintf(fdstr, "%d", m_fuse_fd);
272
273         m_pid = getpid();
274
275         build_iovec(&iov, &iovlen, "fstype", __DECONST(void *, "fusefs"), -1);
276         build_iovec(&iov, &iovlen, "fspath",
277                     __DECONST(void *, "mountpoint"), -1);
278         build_iovec(&iov, &iovlen, "from", __DECONST(void *, "/dev/fuse"), -1);
279         build_iovec(&iov, &iovlen, "fd", fdstr, -1);
280         if (nmount(iov, iovlen, 0))
281                 throw(std::system_error(errno, std::system_category(),
282                         "Couldn't mount filesystem"));
283
284         // Setup default handler
285         ON_CALL(*this, process(_, _))
286                 .WillByDefault(Invoke(this, &MockFS::process_default));
287
288         init(flags);
289         signal(SIGUSR1, sigint_handler);
290         if (pthread_create(&m_daemon_id, NULL, service, (void*)this))
291                 throw(std::system_error(errno, std::system_category(),
292                         "Couldn't Couldn't start fuse thread"));
293 }
294
295 MockFS::~MockFS() {
296         kill_daemon();
297         ::unmount("mountpoint", MNT_FORCE);
298         rmdir("mountpoint");
299 }
300
301 void MockFS::init(uint32_t flags) {
302         mockfs_buf_in *in;
303         mockfs_buf_out *out;
304
305         in = (mockfs_buf_in*) malloc(sizeof(*in));
306         ASSERT_TRUE(in != NULL);
307         out = (mockfs_buf_out*) malloc(sizeof(*out));
308         ASSERT_TRUE(out != NULL);
309
310         read_request(in);
311         ASSERT_EQ(FUSE_INIT, in->header.opcode);
312
313         memset(out, 0, sizeof(*out));
314         out->header.unique = in->header.unique;
315         out->header.error = 0;
316         out->body.init.major = FUSE_KERNEL_VERSION;
317         out->body.init.minor = FUSE_KERNEL_MINOR_VERSION;
318         out->body.init.flags = in->body.init.flags & flags;
319
320         /*
321          * The default max_write is set to this formula in libfuse, though
322          * individual filesystems can lower it.  The "- 4096" was added in
323          * commit 154ffe2, with the commit message "fix".
324          */
325         uint32_t default_max_write = 32 * getpagesize() + 0x1000 - 4096;
326         /* For testing purposes, it should be distinct from MAXPHYS */
327         m_max_write = MIN(default_max_write, MAXPHYS / 2);
328         out->body.init.max_write = m_max_write;
329
330         out->body.init.max_readahead = m_maxreadahead;
331         SET_OUT_HEADER_LEN(out, init);
332         write(m_fuse_fd, out, out->header.len);
333
334         free(in);
335 }
336
337 void MockFS::kill_daemon() {
338         if (m_daemon_id != NULL) {
339                 pthread_kill(m_daemon_id, SIGUSR1);
340                 // Closing the /dev/fuse file descriptor first allows unmount
341                 // to succeed even if the daemon doesn't correctly respond to
342                 // commands during the unmount sequence.
343                 close(m_fuse_fd);
344                 pthread_join(m_daemon_id, NULL);
345                 m_daemon_id = NULL;
346         }
347 }
348
349 void MockFS::loop() {
350         mockfs_buf_in *in;
351         std::vector<mockfs_buf_out*> out;
352
353         in = (mockfs_buf_in*) malloc(sizeof(*in));
354         ASSERT_TRUE(in != NULL);
355         while (!quit) {
356                 bzero(in, sizeof(*in));
357                 read_request(in);
358                 if (quit)
359                         break;
360                 if (verbosity > 0)
361                         debug_fuseop(in);
362                 if (pid_ok((pid_t)in->header.pid)) {
363                         process(in, out);
364                 } else {
365                         /* 
366                          * Reject any requests from unknown processes.  Because
367                          * we actually do mount a filesystem, plenty of
368                          * unrelated system daemons may try to access it.
369                          */
370                         process_default(in, out);
371                 }
372                 for (auto &it: out) {
373                         ASSERT_TRUE(write(m_fuse_fd, it, it->header.len) > 0 ||
374                                     errno == EAGAIN)
375                                 << strerror(errno);
376                         delete it;
377                 }
378                 out.clear();
379         }
380         free(in);
381 }
382
383 bool MockFS::pid_ok(pid_t pid) {
384         if (pid == m_pid) {
385                 return (true);
386         } else {
387                 struct kinfo_proc *ki;
388                 bool ok = false;
389
390                 ki = kinfo_getproc(pid);
391                 if (ki == NULL)
392                         return (false);
393                 /* 
394                  * Allow access by the aio daemon processes so that our tests
395                  * can use aio functions
396                  */
397                 if (0 == strncmp("aiod", ki->ki_comm, 4))
398                         ok = true;
399                 free(ki);
400                 return (ok);
401         }
402 }
403
404 void MockFS::process_default(const mockfs_buf_in *in,
405                 std::vector<mockfs_buf_out*> &out)
406 {
407         auto out0 = new mockfs_buf_out;
408         out0->header.unique = in->header.unique;
409         out0->header.error = -EOPNOTSUPP;
410         out0->header.len = sizeof(out0->header);
411         out.push_back(out0);
412 }
413
414 void MockFS::read_request(mockfs_buf_in *in) {
415         ssize_t res;
416
417         res = read(m_fuse_fd, in, sizeof(*in));
418         if (res < 0 && !quit)
419                 perror("read");
420         ASSERT_TRUE(res >= (ssize_t)sizeof(in->header) || quit);
421 }
422
423 void* MockFS::service(void *pthr_data) {
424         MockFS *mock_fs = (MockFS*)pthr_data;
425
426         mock_fs->loop();
427
428         return (NULL);
429 }
430
431 void MockFS::unmount() {
432         ::unmount("mountpoint", 0);
433 }