2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
4 * Copyright (c) 2019 The FreeBSD Foundation
6 * This software was developed by BFF Storage Systems, LLC under sponsorship
7 * from the FreeBSD Foundation.
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
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.
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
32 #include <sys/param.h>
34 #include <sys/mount.h>
35 #include <sys/select.h>
48 #include "mntopts.h" // for build_iovec
51 #include <gtest/gtest.h>
55 using namespace testing;
59 const char* opcode2opname(uint32_t opcode)
61 const int NUM_OPS = 39;
62 const char* table[NUM_OPS] = {
82 "Unknown (opcode 19)",
103 if (opcode >= NUM_OPS)
104 return ("Unknown (opcode > max)");
106 return (table[opcode]);
110 ReturnErrno(int error)
112 return([=](auto in, auto &out) {
113 auto out0 = new mockfs_buf_out;
114 out0->header.unique = in->header.unique;
115 out0->header.error = -error;
116 out0->header.len = sizeof(out0->header);
121 /* Helper function used for returning negative cache entries for LOOKUP */
123 ReturnNegativeCache(const struct timespec *entry_valid)
125 return([=](auto in, auto &out) {
126 /* nodeid means ENOENT and cache it */
127 auto out0 = new mockfs_buf_out;
128 out0->body.entry.nodeid = 0;
129 out0->header.unique = in->header.unique;
130 out0->header.error = 0;
131 out0->body.entry.entry_valid = entry_valid->tv_sec;
132 out0->body.entry.entry_valid_nsec = entry_valid->tv_nsec;
133 SET_OUT_HEADER_LEN(out0, entry);
139 ReturnImmediate(std::function<void(const struct mockfs_buf_in *in,
140 struct mockfs_buf_out *out)> f)
142 return([=](auto in, auto &out) {
143 auto out0 = new mockfs_buf_out;
144 out0->header.unique = in->header.unique;
150 void sigint_handler(int __unused sig) {
151 // Don't do anything except interrupt the daemon's read(2) call
154 void debug_fuseop(const mockfs_buf_in *in)
156 printf("%-11s ino=%2lu", opcode2opname(in->header.opcode),
159 printf(" uid=%5u gid=%5u pid=%5u unique=%lu len=%u",
160 in->header.uid, in->header.gid, in->header.pid,
161 in->header.unique, in->header.len);
163 switch (in->header.opcode) {
164 const char *name, *value;
167 printf(" mask=%#x", in->body.access.mask);
170 name = (const char*)in->body.bytes +
171 sizeof(fuse_open_in);
172 printf(" flags=%#x name=%s",
173 in->body.open.flags, name);
176 printf(" fh=%#lx lock_owner=%lu", in->body.flush.fh,
177 in->body.flush.lock_owner);
180 printf(" nlookup=%lu", in->body.forget.nlookup);
183 printf(" flags=%#x", in->body.fsync.fsync_flags);
186 printf(" flags=%#x", in->body.fsyncdir.fsync_flags);
189 printf(" unique=%lu", in->body.interrupt.unique);
192 printf(" oldnodeid=%lu", in->body.link.oldnodeid);
195 printf(" %s", in->body.lookup);
198 name = (const char*)in->body.bytes +
199 sizeof(fuse_mkdir_in);
200 printf(" name=%s mode=%#o", name, in->body.mkdir.mode);
203 printf(" mode=%#o rdev=%x", in->body.mknod.mode,
204 in->body.mknod.rdev);
207 printf(" flags=%#x mode=%#o",
208 in->body.open.flags, in->body.open.mode);
211 printf(" flags=%#x mode=%#o",
212 in->body.opendir.flags, in->body.opendir.mode);
215 printf(" offset=%lu size=%u", in->body.read.offset,
219 printf(" fh=%#lx offset=%lu size=%u",
220 in->body.readdir.fh, in->body.readdir.offset,
221 in->body.readdir.size);
224 printf(" fh=%#lx flags=%#x lock_owner=%lu",
226 in->body.release.flags,
227 in->body.release.lock_owner);
230 if (verbosity <= 1) {
231 printf(" valid=%#x", in->body.setattr.valid);
234 if (in->body.setattr.valid & FATTR_MODE)
235 printf(" mode=%#o", in->body.setattr.mode);
236 if (in->body.setattr.valid & FATTR_UID)
237 printf(" uid=%u", in->body.setattr.uid);
238 if (in->body.setattr.valid & FATTR_GID)
239 printf(" gid=%u", in->body.setattr.gid);
240 if (in->body.setattr.valid & FATTR_SIZE)
241 printf(" size=%zu", in->body.setattr.size);
242 if (in->body.setattr.valid & FATTR_ATIME)
243 printf(" atime=%zu.%u",
244 in->body.setattr.atime,
245 in->body.setattr.atimensec);
246 if (in->body.setattr.valid & FATTR_MTIME)
247 printf(" mtime=%zu.%u",
248 in->body.setattr.mtime,
249 in->body.setattr.mtimensec);
250 if (in->body.setattr.valid & FATTR_FH)
251 printf(" fh=%zu", in->body.setattr.fh);
254 printf(" fh=%#lx owner=%lu type=%u pid=%u",
255 in->body.setlk.fh, in->body.setlk.owner,
256 in->body.setlk.lk.type,
257 in->body.setlk.lk.pid);
258 if (verbosity >= 2) {
259 printf(" range=[%lu-%lu]",
260 in->body.setlk.lk.start,
261 in->body.setlk.lk.end);
266 * In theory neither the xattr name and value need be
267 * ASCII, but in this test suite they always are.
269 name = (const char*)in->body.bytes +
270 sizeof(fuse_setxattr_in);
271 value = name + strlen(name) + 1;
272 printf(" %s=%s", name, value);
275 printf(" fh=%#lx offset=%lu size=%u flags=%u",
277 in->body.write.offset, in->body.write.size,
278 in->body.write.write_flags);
286 MockFS::MockFS(int max_readahead, bool allow_other, bool default_permissions,
287 bool push_symlinks_in, bool ro, enum poll_method pm, uint32_t flags)
290 struct iovec *iov = NULL;
293 const bool trueval = true;
296 m_maxreadahead = max_readahead;
306 * Kyua sets pwd to a testcase-unique tempdir; no need to use
310 * googletest doesn't allow ASSERT_ in constructors, so we must throw
313 if (mkdir("mountpoint" , 0755) && errno != EEXIST)
314 throw(std::system_error(errno, std::system_category(),
315 "Couldn't make mountpoint directory"));
319 m_fuse_fd = open("/dev/fuse", O_CLOEXEC | O_RDWR);
322 m_fuse_fd = open("/dev/fuse", O_CLOEXEC | O_RDWR | O_NONBLOCK);
326 throw(std::system_error(errno, std::system_category(),
327 "Couldn't open /dev/fuse"));
332 build_iovec(&iov, &iovlen, "fstype", __DECONST(void *, "fusefs"), -1);
333 build_iovec(&iov, &iovlen, "fspath",
334 __DECONST(void *, "mountpoint"), -1);
335 build_iovec(&iov, &iovlen, "from", __DECONST(void *, "/dev/fuse"), -1);
336 sprintf(fdstr, "%d", m_fuse_fd);
337 build_iovec(&iov, &iovlen, "fd", fdstr, -1);
339 build_iovec(&iov, &iovlen, "allow_other",
340 __DECONST(void*, &trueval), sizeof(bool));
342 if (default_permissions) {
343 build_iovec(&iov, &iovlen, "default_permissions",
344 __DECONST(void*, &trueval), sizeof(bool));
346 if (push_symlinks_in) {
347 build_iovec(&iov, &iovlen, "push_symlinks_in",
348 __DECONST(void*, &trueval), sizeof(bool));
351 build_iovec(&iov, &iovlen, "ro",
352 __DECONST(void*, &trueval), sizeof(bool));
354 if (nmount(iov, iovlen, 0))
355 throw(std::system_error(errno, std::system_category(),
356 "Couldn't mount filesystem"));
358 // Setup default handler
359 ON_CALL(*this, process(_, _))
360 .WillByDefault(Invoke(this, &MockFS::process_default));
363 bzero(&sa, sizeof(sa));
364 sa.sa_handler = sigint_handler;
365 sa.sa_flags = 0; /* Don't set SA_RESTART! */
366 if (0 != sigaction(SIGUSR1, &sa, NULL))
367 throw(std::system_error(errno, std::system_category(),
368 "Couldn't handle SIGUSR1"));
369 if (pthread_create(&m_daemon_id, NULL, service, (void*)this))
370 throw(std::system_error(errno, std::system_category(),
371 "Couldn't Couldn't start fuse thread"));
376 if (m_daemon_id != NULL) {
377 pthread_join(m_daemon_id, NULL);
380 ::unmount("mountpoint", MNT_FORCE);
386 void MockFS::init(uint32_t flags) {
390 in = (mockfs_buf_in*) malloc(sizeof(*in));
391 ASSERT_TRUE(in != NULL);
392 out = (mockfs_buf_out*) malloc(sizeof(*out));
393 ASSERT_TRUE(out != NULL);
396 ASSERT_EQ(FUSE_INIT, in->header.opcode);
398 memset(out, 0, sizeof(*out));
399 out->header.unique = in->header.unique;
400 out->header.error = 0;
401 out->body.init.major = FUSE_KERNEL_VERSION;
402 out->body.init.minor = FUSE_KERNEL_MINOR_VERSION;
403 out->body.init.flags = in->body.init.flags & flags;
406 * The default max_write is set to this formula in libfuse, though
407 * individual filesystems can lower it. The "- 4096" was added in
408 * commit 154ffe2, with the commit message "fix".
410 uint32_t default_max_write = 32 * getpagesize() + 0x1000 - 4096;
411 /* For testing purposes, it should be distinct from MAXPHYS */
412 m_max_write = MIN(default_max_write, MAXPHYS / 2);
413 out->body.init.max_write = m_max_write;
415 out->body.init.max_readahead = m_maxreadahead;
416 SET_OUT_HEADER_LEN(out, init);
417 write(m_fuse_fd, out, out->header.len);
422 void MockFS::kill_daemon() {
424 if (m_daemon_id != NULL)
425 pthread_kill(m_daemon_id, SIGUSR1);
426 // Closing the /dev/fuse file descriptor first allows unmount to
427 // succeed even if the daemon doesn't correctly respond to commands
428 // during the unmount sequence.
433 void MockFS::loop() {
435 std::vector<mockfs_buf_out*> out;
437 in = (mockfs_buf_in*) malloc(sizeof(*in));
438 ASSERT_TRUE(in != NULL);
440 bzero(in, sizeof(*in));
446 if (pid_ok((pid_t)in->header.pid)) {
450 * Reject any requests from unknown processes. Because
451 * we actually do mount a filesystem, plenty of
452 * unrelated system daemons may try to access it.
455 printf("\tREJECTED (wrong pid %d)\n",
457 process_default(in, out);
459 for (auto &it: out) {
468 bool MockFS::pid_ok(pid_t pid) {
471 } else if (pid == m_child_pid) {
474 struct kinfo_proc *ki;
477 ki = kinfo_getproc(pid);
481 * Allow access by the aio daemon processes so that our tests
482 * can use aio functions
484 if (0 == strncmp("aiod", ki->ki_comm, 4))
491 void MockFS::process_default(const mockfs_buf_in *in,
492 std::vector<mockfs_buf_out*> &out)
494 auto out0 = new mockfs_buf_out;
495 out0->header.unique = in->header.unique;
496 out0->header.error = -EOPNOTSUPP;
497 out0->header.len = sizeof(out0->header);
501 void MockFS::read_request(mockfs_buf_in *in) {
506 struct kevent changes[1];
507 struct kevent events[1];
514 EV_SET(&changes[0], m_fuse_fd, EVFILT_READ, EV_ADD, 0, 0, 0);
515 nready = kevent(m_kq, &changes[0], 1, &events[0], 1, NULL);
518 ASSERT_LE(0, nready) << strerror(errno);
519 ASSERT_EQ(1, nready) << "NULL timeout expired?";
520 ASSERT_EQ(events[0].ident, (uintptr_t)m_fuse_fd);
521 if (events[0].flags & EV_ERROR)
522 FAIL() << strerror(events[0].data);
523 else if (events[0].flags & EV_EOF)
524 FAIL() << strerror(events[0].fflags);
525 m_nready = events[0].data;
528 fds[0].fd = m_fuse_fd;
529 fds[0].events = POLLIN;
530 nready = poll(fds, 1, INFTIM);
533 ASSERT_LE(0, nready) << strerror(errno);
534 ASSERT_EQ(1, nready) << "NULL timeout expired?";
535 ASSERT_TRUE(fds[0].revents & POLLIN);
539 FD_SET(m_fuse_fd, &readfds);
540 nfds = m_fuse_fd + 1;
541 nready = select(nfds, &readfds, NULL, NULL, NULL);
544 ASSERT_LE(0, nready) << strerror(errno);
545 ASSERT_EQ(1, nready) << "NULL timeout expired?";
546 ASSERT_TRUE(FD_ISSET(m_fuse_fd, &readfds));
549 FAIL() << "not yet implemented";
551 res = read(m_fuse_fd, in, sizeof(*in));
553 if (res < 0 && !m_quit)
555 ASSERT_TRUE(res >= (ssize_t)sizeof(in->header) || m_quit);
558 void MockFS::write_response(mockfs_buf_out *out) {
566 case KQ: /* EVFILT_WRITE is not supported */
569 fds[0].fd = m_fuse_fd;
570 fds[0].events = POLLOUT;
571 nready = poll(fds, 1, INFTIM);
572 ASSERT_LE(0, nready) << strerror(errno);
573 ASSERT_EQ(1, nready) << "NULL timeout expired?";
574 ASSERT_TRUE(fds[0].revents & POLLOUT);
578 FD_SET(m_fuse_fd, &writefds);
579 nfds = m_fuse_fd + 1;
580 nready = select(nfds, NULL, &writefds, NULL, NULL);
581 ASSERT_LE(0, nready) << strerror(errno);
582 ASSERT_EQ(1, nready) << "NULL timeout expired?";
583 ASSERT_TRUE(FD_ISSET(m_fuse_fd, &writefds));
586 FAIL() << "not yet implemented";
588 r = write(m_fuse_fd, out, out->header.len);
589 ASSERT_TRUE(r > 0 || errno == EAGAIN) << strerror(errno);
592 void* MockFS::service(void *pthr_data) {
593 MockFS *mock_fs = (MockFS*)pthr_data;
600 void MockFS::unmount() {
601 ::unmount("mountpoint", 0);