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
53 #include <gtest/gtest.h>
57 using namespace testing;
61 const char* opcode2opname(uint32_t opcode)
63 const int NUM_OPS = 39;
64 const char* table[NUM_OPS] = {
84 "Unknown (opcode 19)",
105 if (opcode >= NUM_OPS)
106 return ("Unknown (opcode > max)");
108 return (table[opcode]);
112 ReturnErrno(int error)
114 return([=](auto in, auto &out) {
115 std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
116 out0->header.unique = in.header.unique;
117 out0->header.error = -error;
118 out0->header.len = sizeof(out0->header);
119 out.push_back(std::move(out0));
123 /* Helper function used for returning negative cache entries for LOOKUP */
125 ReturnNegativeCache(const struct timespec *entry_valid)
127 return([=](auto in, auto &out) {
128 /* nodeid means ENOENT and cache it */
129 std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
130 out0->body.entry.nodeid = 0;
131 out0->header.unique = in.header.unique;
132 out0->header.error = 0;
133 out0->body.entry.entry_valid = entry_valid->tv_sec;
134 out0->body.entry.entry_valid_nsec = entry_valid->tv_nsec;
135 SET_OUT_HEADER_LEN(*out0, entry);
136 out.push_back(std::move(out0));
141 ReturnImmediate(std::function<void(const mockfs_buf_in& in,
142 struct mockfs_buf_out &out)> f)
144 return([=](auto& in, auto &out) {
145 std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
146 out0->header.unique = in.header.unique;
148 out.push_back(std::move(out0));
152 void sigint_handler(int __unused sig) {
153 // Don't do anything except interrupt the daemon's read(2) call
156 void MockFS::debug_request(const mockfs_buf_in &in)
158 printf("%-11s ino=%2" PRIu64, opcode2opname(in.header.opcode),
161 printf(" uid=%5u gid=%5u pid=%5u unique=%" PRIu64 " len=%u",
162 in.header.uid, in.header.gid, in.header.pid,
163 in.header.unique, in.header.len);
165 switch (in.header.opcode) {
166 const char *name, *value;
169 printf(" mask=%#x", in.body.access.mask);
172 if (m_kernel_minor_version >= 12)
173 name = (const char*)in.body.bytes +
174 sizeof(fuse_create_in);
176 name = (const char*)in.body.bytes +
177 sizeof(fuse_open_in);
178 printf(" flags=%#x name=%s",
179 in.body.open.flags, name);
182 printf(" fh=%#" PRIx64 " lock_owner=%" PRIu64,
184 in.body.flush.lock_owner);
187 printf(" nlookup=%" PRIu64, in.body.forget.nlookup);
190 printf(" flags=%#x", in.body.fsync.fsync_flags);
193 printf(" flags=%#x", in.body.fsyncdir.fsync_flags);
196 printf(" unique=%" PRIu64, in.body.interrupt.unique);
199 printf(" oldnodeid=%" PRIu64, in.body.link.oldnodeid);
202 printf(" %s", in.body.lookup);
205 name = (const char*)in.body.bytes +
206 sizeof(fuse_mkdir_in);
207 printf(" name=%s mode=%#o umask=%#o", name,
208 in.body.mkdir.mode, in.body.mkdir.umask);
211 if (m_kernel_minor_version >= 12)
212 name = (const char*)in.body.bytes +
213 sizeof(fuse_mknod_in);
215 name = (const char*)in.body.bytes +
216 FUSE_COMPAT_MKNOD_IN_SIZE;
217 printf(" mode=%#o rdev=%x umask=%#o name=%s",
218 in.body.mknod.mode, in.body.mknod.rdev,
219 in.body.mknod.umask, name);
222 printf(" flags=%#x", in.body.open.flags);
225 printf(" flags=%#x", in.body.opendir.flags);
228 printf(" offset=%" PRIu64 " size=%u",
232 printf(" flags=%#x", in.body.read.flags);
235 printf(" fh=%#" PRIx64 " offset=%" PRIu64 " size=%u",
236 in.body.readdir.fh, in.body.readdir.offset,
237 in.body.readdir.size);
240 printf(" fh=%#" PRIx64 " flags=%#x lock_owner=%" PRIu64,
242 in.body.release.flags,
243 in.body.release.lock_owner);
246 if (verbosity <= 1) {
247 printf(" valid=%#x", in.body.setattr.valid);
250 if (in.body.setattr.valid & FATTR_MODE)
251 printf(" mode=%#o", in.body.setattr.mode);
252 if (in.body.setattr.valid & FATTR_UID)
253 printf(" uid=%u", in.body.setattr.uid);
254 if (in.body.setattr.valid & FATTR_GID)
255 printf(" gid=%u", in.body.setattr.gid);
256 if (in.body.setattr.valid & FATTR_SIZE)
257 printf(" size=%" PRIu64, in.body.setattr.size);
258 if (in.body.setattr.valid & FATTR_ATIME)
259 printf(" atime=%" PRIu64 ".%u",
260 in.body.setattr.atime,
261 in.body.setattr.atimensec);
262 if (in.body.setattr.valid & FATTR_MTIME)
263 printf(" mtime=%" PRIu64 ".%u",
264 in.body.setattr.mtime,
265 in.body.setattr.mtimensec);
266 if (in.body.setattr.valid & FATTR_FH)
267 printf(" fh=%" PRIu64 "", in.body.setattr.fh);
270 printf(" fh=%#" PRIx64 " owner=%" PRIu64
272 in.body.setlk.fh, in.body.setlk.owner,
273 in.body.setlk.lk.type,
274 in.body.setlk.lk.pid);
275 if (verbosity >= 2) {
276 printf(" range=[%" PRIu64 "-%" PRIu64 "]",
277 in.body.setlk.lk.start,
278 in.body.setlk.lk.end);
283 * In theory neither the xattr name and value need be
284 * ASCII, but in this test suite they always are.
286 name = (const char*)in.body.bytes +
287 sizeof(fuse_setxattr_in);
288 value = name + strlen(name) + 1;
289 printf(" %s=%s", name, value);
292 printf(" fh=%#" PRIx64 " offset=%" PRIu64
293 " size=%u write_flags=%u",
295 in.body.write.offset, in.body.write.size,
296 in.body.write.write_flags);
298 printf(" flags=%#x", in.body.write.flags);
307 * Debug a FUSE response.
309 * This is mostly useful for asynchronous notifications, which don't correspond
312 void MockFS::debug_response(const mockfs_buf_out &out) {
318 switch (out.header.error) {
319 case FUSE_NOTIFY_INVAL_ENTRY:
320 name = (const char*)out.body.bytes +
321 sizeof(fuse_notify_inval_entry_out);
322 printf("<- INVAL_ENTRY parent=%" PRIu64 " %s\n",
323 out.body.inval_entry.parent, name);
325 case FUSE_NOTIFY_INVAL_INODE:
326 printf("<- INVAL_INODE ino=%" PRIu64 " off=%" PRIi64
327 " len=%" PRIi64 "\n",
328 out.body.inval_inode.ino,
329 out.body.inval_inode.off,
330 out.body.inval_inode.len);
337 MockFS::MockFS(int max_readahead, bool allow_other, bool default_permissions,
338 bool push_symlinks_in, bool ro, enum poll_method pm, uint32_t flags,
339 uint32_t kernel_minor_version)
342 struct iovec *iov = NULL;
345 const bool trueval = true;
348 m_kernel_minor_version = kernel_minor_version;
349 m_maxreadahead = max_readahead;
359 * Kyua sets pwd to a testcase-unique tempdir; no need to use
363 * googletest doesn't allow ASSERT_ in constructors, so we must throw
366 if (mkdir("mountpoint" , 0755) && errno != EEXIST)
367 throw(std::system_error(errno, std::system_category(),
368 "Couldn't make mountpoint directory"));
372 m_fuse_fd = open("/dev/fuse", O_CLOEXEC | O_RDWR);
375 m_fuse_fd = open("/dev/fuse", O_CLOEXEC | O_RDWR | O_NONBLOCK);
379 throw(std::system_error(errno, std::system_category(),
380 "Couldn't open /dev/fuse"));
385 build_iovec(&iov, &iovlen, "fstype", __DECONST(void *, "fusefs"), -1);
386 build_iovec(&iov, &iovlen, "fspath",
387 __DECONST(void *, "mountpoint"), -1);
388 build_iovec(&iov, &iovlen, "from", __DECONST(void *, "/dev/fuse"), -1);
389 sprintf(fdstr, "%d", m_fuse_fd);
390 build_iovec(&iov, &iovlen, "fd", fdstr, -1);
392 build_iovec(&iov, &iovlen, "allow_other",
393 __DECONST(void*, &trueval), sizeof(bool));
395 if (default_permissions) {
396 build_iovec(&iov, &iovlen, "default_permissions",
397 __DECONST(void*, &trueval), sizeof(bool));
399 if (push_symlinks_in) {
400 build_iovec(&iov, &iovlen, "push_symlinks_in",
401 __DECONST(void*, &trueval), sizeof(bool));
404 build_iovec(&iov, &iovlen, "ro",
405 __DECONST(void*, &trueval), sizeof(bool));
407 if (nmount(iov, iovlen, 0))
408 throw(std::system_error(errno, std::system_category(),
409 "Couldn't mount filesystem"));
411 // Setup default handler
412 ON_CALL(*this, process(_, _))
413 .WillByDefault(Invoke(this, &MockFS::process_default));
416 bzero(&sa, sizeof(sa));
417 sa.sa_handler = sigint_handler;
418 sa.sa_flags = 0; /* Don't set SA_RESTART! */
419 if (0 != sigaction(SIGUSR1, &sa, NULL))
420 throw(std::system_error(errno, std::system_category(),
421 "Couldn't handle SIGUSR1"));
422 if (pthread_create(&m_daemon_id, NULL, service, (void*)this))
423 throw(std::system_error(errno, std::system_category(),
424 "Couldn't Couldn't start fuse thread"));
429 if (m_daemon_id != NULL) {
430 pthread_join(m_daemon_id, NULL);
433 ::unmount("mountpoint", MNT_FORCE);
439 void MockFS::init(uint32_t flags) {
440 std::unique_ptr<mockfs_buf_in> in(new mockfs_buf_in);
441 std::unique_ptr<mockfs_buf_out> out(new mockfs_buf_out);
444 ASSERT_EQ(FUSE_INIT, in->header.opcode);
446 out->header.unique = in->header.unique;
447 out->header.error = 0;
448 out->body.init.major = FUSE_KERNEL_VERSION;
449 out->body.init.minor = m_kernel_minor_version;;
450 out->body.init.flags = in->body.init.flags & flags;
453 * The default max_write is set to this formula in libfuse, though
454 * individual filesystems can lower it. The "- 4096" was added in
455 * commit 154ffe2, with the commit message "fix".
457 uint32_t default_max_write = 32 * getpagesize() + 0x1000 - 4096;
458 /* For testing purposes, it should be distinct from MAXPHYS */
459 m_max_write = MIN(default_max_write, MAXPHYS / 2);
460 out->body.init.max_write = m_max_write;
462 out->body.init.max_readahead = m_maxreadahead;
463 SET_OUT_HEADER_LEN(*out, init);
464 write(m_fuse_fd, out.get(), out->header.len);
467 void MockFS::kill_daemon() {
469 if (m_daemon_id != NULL)
470 pthread_kill(m_daemon_id, SIGUSR1);
471 // Closing the /dev/fuse file descriptor first allows unmount to
472 // succeed even if the daemon doesn't correctly respond to commands
473 // during the unmount sequence.
478 void MockFS::loop() {
479 std::vector<std::unique_ptr<mockfs_buf_out>> out;
481 std::unique_ptr<mockfs_buf_in> in(new mockfs_buf_in);
482 ASSERT_TRUE(in != NULL);
484 bzero(in.get(), sizeof(*in));
490 if (pid_ok((pid_t)in->header.pid)) {
494 * Reject any requests from unknown processes. Because
495 * we actually do mount a filesystem, plenty of
496 * unrelated system daemons may try to access it.
499 printf("\tREJECTED (wrong pid %d)\n",
501 process_default(*in, out);
509 int MockFS::notify_inval_entry(ino_t parent, const char *name, size_t namelen)
511 std::unique_ptr<mockfs_buf_out> out(new mockfs_buf_out);
513 out->header.unique = 0; /* 0 means asynchronous notification */
514 out->header.error = FUSE_NOTIFY_INVAL_ENTRY;
515 out->body.inval_entry.parent = parent;
516 out->body.inval_entry.namelen = namelen;
517 strlcpy((char*)&out->body.bytes + sizeof(out->body.inval_entry),
518 name, sizeof(out->body.bytes) - sizeof(out->body.inval_entry));
519 out->header.len = sizeof(out->header) + sizeof(out->body.inval_entry) +
521 debug_response(*out);
522 write_response(*out);
526 int MockFS::notify_inval_inode(ino_t ino, off_t off, ssize_t len)
528 std::unique_ptr<mockfs_buf_out> out(new mockfs_buf_out);
530 out->header.unique = 0; /* 0 means asynchronous notification */
531 out->header.error = FUSE_NOTIFY_INVAL_INODE;
532 out->body.inval_inode.ino = ino;
533 out->body.inval_inode.off = off;
534 out->body.inval_inode.len = len;
535 out->header.len = sizeof(out->header) + sizeof(out->body.inval_inode);
536 debug_response(*out);
537 write_response(*out);
541 bool MockFS::pid_ok(pid_t pid) {
544 } else if (pid == m_child_pid) {
547 struct kinfo_proc *ki;
550 ki = kinfo_getproc(pid);
554 * Allow access by the aio daemon processes so that our tests
555 * can use aio functions
557 if (0 == strncmp("aiod", ki->ki_comm, 4))
564 void MockFS::process_default(const mockfs_buf_in& in,
565 std::vector<std::unique_ptr<mockfs_buf_out>> &out)
567 std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
568 out0->header.unique = in.header.unique;
569 out0->header.error = -EOPNOTSUPP;
570 out0->header.len = sizeof(out0->header);
571 out.push_back(std::move(out0));
574 void MockFS::read_request(mockfs_buf_in &in) {
579 struct kevent changes[1];
580 struct kevent events[1];
581 struct timespec timeout_ts;
582 struct timeval timeout_tv;
583 const int timeout_ms = 999;
584 int timeout_int, nfds;
590 timeout_ts.tv_sec = 0;
591 timeout_ts.tv_nsec = timeout_ms * 1'000'000;
592 while (nready == 0) {
593 EV_SET(&changes[0], m_fuse_fd, EVFILT_READ, EV_ADD, 0,
595 nready = kevent(m_kq, &changes[0], 1, &events[0], 1,
600 ASSERT_LE(0, nready) << strerror(errno);
601 ASSERT_EQ(events[0].ident, (uintptr_t)m_fuse_fd);
602 if (events[0].flags & EV_ERROR)
603 FAIL() << strerror(events[0].data);
604 else if (events[0].flags & EV_EOF)
605 FAIL() << strerror(events[0].fflags);
606 m_nready = events[0].data;
609 timeout_int = timeout_ms;
610 fds[0].fd = m_fuse_fd;
611 fds[0].events = POLLIN;
612 while (nready == 0) {
613 nready = poll(fds, 1, timeout_int);
617 ASSERT_LE(0, nready) << strerror(errno);
618 ASSERT_TRUE(fds[0].revents & POLLIN);
621 timeout_tv.tv_sec = 0;
622 timeout_tv.tv_usec = timeout_ms * 1'000;
623 nfds = m_fuse_fd + 1;
624 while (nready == 0) {
626 FD_SET(m_fuse_fd, &readfds);
627 nready = select(nfds, &readfds, NULL, NULL,
632 ASSERT_LE(0, nready) << strerror(errno);
633 ASSERT_TRUE(FD_ISSET(m_fuse_fd, &readfds));
636 FAIL() << "not yet implemented";
638 res = read(m_fuse_fd, &in, sizeof(in));
640 if (res < 0 && !m_quit) {
641 FAIL() << "read: " << strerror(errno);
644 ASSERT_TRUE(res >= static_cast<ssize_t>(sizeof(in.header)) || m_quit);
647 void MockFS::write_response(const mockfs_buf_out &out) {
655 case KQ: /* EVFILT_WRITE is not supported */
658 fds[0].fd = m_fuse_fd;
659 fds[0].events = POLLOUT;
660 nready = poll(fds, 1, INFTIM);
661 ASSERT_LE(0, nready) << strerror(errno);
662 ASSERT_EQ(1, nready) << "NULL timeout expired?";
663 ASSERT_TRUE(fds[0].revents & POLLOUT);
667 FD_SET(m_fuse_fd, &writefds);
668 nfds = m_fuse_fd + 1;
669 nready = select(nfds, NULL, &writefds, NULL, NULL);
670 ASSERT_LE(0, nready) << strerror(errno);
671 ASSERT_EQ(1, nready) << "NULL timeout expired?";
672 ASSERT_TRUE(FD_ISSET(m_fuse_fd, &writefds));
675 FAIL() << "not yet implemented";
677 r = write(m_fuse_fd, &out, out.header.len);
678 ASSERT_TRUE(r > 0 || errno == EAGAIN) << strerror(errno);
681 void* MockFS::service(void *pthr_data) {
682 MockFS *mock_fs = (MockFS*)pthr_data;
689 void MockFS::unmount() {
690 ::unmount("mountpoint", 0);