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 printf(" block=%" PRIx64 " blocksize=%#x",
173 in.body.bmap.block, in.body.bmap.blocksize);
176 if (m_kernel_minor_version >= 12)
177 name = (const char*)in.body.bytes +
178 sizeof(fuse_create_in);
180 name = (const char*)in.body.bytes +
181 sizeof(fuse_open_in);
182 printf(" flags=%#x name=%s",
183 in.body.open.flags, name);
186 printf(" fh=%#" PRIx64 " lock_owner=%" PRIu64,
188 in.body.flush.lock_owner);
191 printf(" nlookup=%" PRIu64, in.body.forget.nlookup);
194 printf(" flags=%#x", in.body.fsync.fsync_flags);
197 printf(" flags=%#x", in.body.fsyncdir.fsync_flags);
200 printf(" unique=%" PRIu64, in.body.interrupt.unique);
203 printf(" oldnodeid=%" PRIu64, in.body.link.oldnodeid);
206 printf(" %s", in.body.lookup);
209 name = (const char*)in.body.bytes +
210 sizeof(fuse_mkdir_in);
211 printf(" name=%s mode=%#o umask=%#o", name,
212 in.body.mkdir.mode, in.body.mkdir.umask);
215 if (m_kernel_minor_version >= 12)
216 name = (const char*)in.body.bytes +
217 sizeof(fuse_mknod_in);
219 name = (const char*)in.body.bytes +
220 FUSE_COMPAT_MKNOD_IN_SIZE;
221 printf(" mode=%#o rdev=%x umask=%#o name=%s",
222 in.body.mknod.mode, in.body.mknod.rdev,
223 in.body.mknod.umask, name);
226 printf(" flags=%#x", in.body.open.flags);
229 printf(" flags=%#x", in.body.opendir.flags);
232 printf(" offset=%" PRIu64 " size=%u",
236 printf(" flags=%#x", in.body.read.flags);
239 printf(" fh=%#" PRIx64 " offset=%" PRIu64 " size=%u",
240 in.body.readdir.fh, in.body.readdir.offset,
241 in.body.readdir.size);
244 printf(" fh=%#" PRIx64 " flags=%#x lock_owner=%" PRIu64,
246 in.body.release.flags,
247 in.body.release.lock_owner);
250 if (verbosity <= 1) {
251 printf(" valid=%#x", in.body.setattr.valid);
254 if (in.body.setattr.valid & FATTR_MODE)
255 printf(" mode=%#o", in.body.setattr.mode);
256 if (in.body.setattr.valid & FATTR_UID)
257 printf(" uid=%u", in.body.setattr.uid);
258 if (in.body.setattr.valid & FATTR_GID)
259 printf(" gid=%u", in.body.setattr.gid);
260 if (in.body.setattr.valid & FATTR_SIZE)
261 printf(" size=%" PRIu64, in.body.setattr.size);
262 if (in.body.setattr.valid & FATTR_ATIME)
263 printf(" atime=%" PRIu64 ".%u",
264 in.body.setattr.atime,
265 in.body.setattr.atimensec);
266 if (in.body.setattr.valid & FATTR_MTIME)
267 printf(" mtime=%" PRIu64 ".%u",
268 in.body.setattr.mtime,
269 in.body.setattr.mtimensec);
270 if (in.body.setattr.valid & FATTR_FH)
271 printf(" fh=%" PRIu64 "", in.body.setattr.fh);
274 printf(" fh=%#" PRIx64 " owner=%" PRIu64
276 in.body.setlk.fh, in.body.setlk.owner,
277 in.body.setlk.lk.type,
278 in.body.setlk.lk.pid);
279 if (verbosity >= 2) {
280 printf(" range=[%" PRIu64 "-%" PRIu64 "]",
281 in.body.setlk.lk.start,
282 in.body.setlk.lk.end);
287 * In theory neither the xattr name and value need be
288 * ASCII, but in this test suite they always are.
290 name = (const char*)in.body.bytes +
291 sizeof(fuse_setxattr_in);
292 value = name + strlen(name) + 1;
293 printf(" %s=%s", name, value);
296 printf(" fh=%#" PRIx64 " offset=%" PRIu64
297 " size=%u write_flags=%u",
299 in.body.write.offset, in.body.write.size,
300 in.body.write.write_flags);
302 printf(" flags=%#x", in.body.write.flags);
311 * Debug a FUSE response.
313 * This is mostly useful for asynchronous notifications, which don't correspond
316 void MockFS::debug_response(const mockfs_buf_out &out) {
322 switch (out.header.error) {
323 case FUSE_NOTIFY_INVAL_ENTRY:
324 name = (const char*)out.body.bytes +
325 sizeof(fuse_notify_inval_entry_out);
326 printf("<- INVAL_ENTRY parent=%" PRIu64 " %s\n",
327 out.body.inval_entry.parent, name);
329 case FUSE_NOTIFY_INVAL_INODE:
330 printf("<- INVAL_INODE ino=%" PRIu64 " off=%" PRIi64
331 " len=%" PRIi64 "\n",
332 out.body.inval_inode.ino,
333 out.body.inval_inode.off,
334 out.body.inval_inode.len);
336 case FUSE_NOTIFY_STORE:
337 printf("<- STORE ino=%" PRIu64 " off=%" PRIu64
338 " size=%" PRIu32 "\n",
339 out.body.store.nodeid,
340 out.body.store.offset,
341 out.body.store.size);
348 MockFS::MockFS(int max_readahead, bool allow_other, bool default_permissions,
349 bool push_symlinks_in, bool ro, enum poll_method pm, uint32_t flags,
350 uint32_t kernel_minor_version, uint32_t max_write, bool async,
351 bool noclusterr, unsigned time_gran, bool nointr)
354 struct iovec *iov = NULL;
357 const bool trueval = true;
360 m_kernel_minor_version = kernel_minor_version;
361 m_maxreadahead = max_readahead;
362 m_maxwrite = max_write;
365 m_time_gran = time_gran;
373 * Kyua sets pwd to a testcase-unique tempdir; no need to use
377 * googletest doesn't allow ASSERT_ in constructors, so we must throw
380 if (mkdir("mountpoint" , 0755) && errno != EEXIST)
381 throw(std::system_error(errno, std::system_category(),
382 "Couldn't make mountpoint directory"));
386 m_fuse_fd = open("/dev/fuse", O_CLOEXEC | O_RDWR);
389 m_fuse_fd = open("/dev/fuse", O_CLOEXEC | O_RDWR | O_NONBLOCK);
393 throw(std::system_error(errno, std::system_category(),
394 "Couldn't open /dev/fuse"));
399 build_iovec(&iov, &iovlen, "fstype", __DECONST(void *, "fusefs"), -1);
400 build_iovec(&iov, &iovlen, "fspath",
401 __DECONST(void *, "mountpoint"), -1);
402 build_iovec(&iov, &iovlen, "from", __DECONST(void *, "/dev/fuse"), -1);
403 sprintf(fdstr, "%d", m_fuse_fd);
404 build_iovec(&iov, &iovlen, "fd", fdstr, -1);
406 build_iovec(&iov, &iovlen, "allow_other",
407 __DECONST(void*, &trueval), sizeof(bool));
409 if (default_permissions) {
410 build_iovec(&iov, &iovlen, "default_permissions",
411 __DECONST(void*, &trueval), sizeof(bool));
413 if (push_symlinks_in) {
414 build_iovec(&iov, &iovlen, "push_symlinks_in",
415 __DECONST(void*, &trueval), sizeof(bool));
418 build_iovec(&iov, &iovlen, "ro",
419 __DECONST(void*, &trueval), sizeof(bool));
422 build_iovec(&iov, &iovlen, "async", __DECONST(void*, &trueval),
426 build_iovec(&iov, &iovlen, "noclusterr",
427 __DECONST(void*, &trueval), sizeof(bool));
430 build_iovec(&iov, &iovlen, "nointr",
431 __DECONST(void*, &trueval), sizeof(bool));
433 build_iovec(&iov, &iovlen, "intr",
434 __DECONST(void*, &trueval), sizeof(bool));
436 if (nmount(iov, iovlen, 0))
437 throw(std::system_error(errno, std::system_category(),
438 "Couldn't mount filesystem"));
440 // Setup default handler
441 ON_CALL(*this, process(_, _))
442 .WillByDefault(Invoke(this, &MockFS::process_default));
445 bzero(&sa, sizeof(sa));
446 sa.sa_handler = sigint_handler;
447 sa.sa_flags = 0; /* Don't set SA_RESTART! */
448 if (0 != sigaction(SIGUSR1, &sa, NULL))
449 throw(std::system_error(errno, std::system_category(),
450 "Couldn't handle SIGUSR1"));
451 if (pthread_create(&m_daemon_id, NULL, service, (void*)this))
452 throw(std::system_error(errno, std::system_category(),
453 "Couldn't Couldn't start fuse thread"));
458 if (m_daemon_id != NULL) {
459 pthread_join(m_daemon_id, NULL);
462 ::unmount("mountpoint", MNT_FORCE);
468 void MockFS::init(uint32_t flags) {
469 std::unique_ptr<mockfs_buf_in> in(new mockfs_buf_in);
470 std::unique_ptr<mockfs_buf_out> out(new mockfs_buf_out);
473 ASSERT_EQ(FUSE_INIT, in->header.opcode);
475 out->header.unique = in->header.unique;
476 out->header.error = 0;
477 out->body.init.major = FUSE_KERNEL_VERSION;
478 out->body.init.minor = m_kernel_minor_version;;
479 out->body.init.flags = in->body.init.flags & flags;
480 out->body.init.max_write = m_maxwrite;
481 out->body.init.max_readahead = m_maxreadahead;
483 if (m_kernel_minor_version < 23) {
484 SET_OUT_HEADER_LEN(*out, init_7_22);
486 out->body.init.time_gran = m_time_gran;
487 SET_OUT_HEADER_LEN(*out, init);
490 write(m_fuse_fd, out.get(), out->header.len);
493 void MockFS::kill_daemon() {
495 if (m_daemon_id != NULL)
496 pthread_kill(m_daemon_id, SIGUSR1);
497 // Closing the /dev/fuse file descriptor first allows unmount to
498 // succeed even if the daemon doesn't correctly respond to commands
499 // during the unmount sequence.
504 void MockFS::loop() {
505 std::vector<std::unique_ptr<mockfs_buf_out>> out;
507 std::unique_ptr<mockfs_buf_in> in(new mockfs_buf_in);
508 ASSERT_TRUE(in != NULL);
510 bzero(in.get(), sizeof(*in));
516 if (pid_ok((pid_t)in->header.pid)) {
520 * Reject any requests from unknown processes. Because
521 * we actually do mount a filesystem, plenty of
522 * unrelated system daemons may try to access it.
525 printf("\tREJECTED (wrong pid %d)\n",
527 process_default(*in, out);
535 int MockFS::notify_inval_entry(ino_t parent, const char *name, size_t namelen)
537 std::unique_ptr<mockfs_buf_out> out(new mockfs_buf_out);
539 out->header.unique = 0; /* 0 means asynchronous notification */
540 out->header.error = FUSE_NOTIFY_INVAL_ENTRY;
541 out->body.inval_entry.parent = parent;
542 out->body.inval_entry.namelen = namelen;
543 strlcpy((char*)&out->body.bytes + sizeof(out->body.inval_entry),
544 name, sizeof(out->body.bytes) - sizeof(out->body.inval_entry));
545 out->header.len = sizeof(out->header) + sizeof(out->body.inval_entry) +
547 debug_response(*out);
548 write_response(*out);
552 int MockFS::notify_inval_inode(ino_t ino, off_t off, ssize_t len)
554 std::unique_ptr<mockfs_buf_out> out(new mockfs_buf_out);
556 out->header.unique = 0; /* 0 means asynchronous notification */
557 out->header.error = FUSE_NOTIFY_INVAL_INODE;
558 out->body.inval_inode.ino = ino;
559 out->body.inval_inode.off = off;
560 out->body.inval_inode.len = len;
561 out->header.len = sizeof(out->header) + sizeof(out->body.inval_inode);
562 debug_response(*out);
563 write_response(*out);
567 int MockFS::notify_store(ino_t ino, off_t off, const void* data, ssize_t size)
569 std::unique_ptr<mockfs_buf_out> out(new mockfs_buf_out);
571 out->header.unique = 0; /* 0 means asynchronous notification */
572 out->header.error = FUSE_NOTIFY_STORE;
573 out->body.store.nodeid = ino;
574 out->body.store.offset = off;
575 out->body.store.size = size;
576 bcopy(data, (char*)&out->body.bytes + sizeof(out->body.store), size);
577 out->header.len = sizeof(out->header) + sizeof(out->body.store) + size;
578 debug_response(*out);
579 write_response(*out);
583 bool MockFS::pid_ok(pid_t pid) {
586 } else if (pid == m_child_pid) {
589 struct kinfo_proc *ki;
592 ki = kinfo_getproc(pid);
596 * Allow access by the aio daemon processes so that our tests
597 * can use aio functions
599 if (0 == strncmp("aiod", ki->ki_comm, 4))
606 void MockFS::process_default(const mockfs_buf_in& in,
607 std::vector<std::unique_ptr<mockfs_buf_out>> &out)
609 std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
610 out0->header.unique = in.header.unique;
611 out0->header.error = -EOPNOTSUPP;
612 out0->header.len = sizeof(out0->header);
613 out.push_back(std::move(out0));
616 void MockFS::read_request(mockfs_buf_in &in) {
621 struct kevent changes[1];
622 struct kevent events[1];
623 struct timespec timeout_ts;
624 struct timeval timeout_tv;
625 const int timeout_ms = 999;
626 int timeout_int, nfds;
632 timeout_ts.tv_sec = 0;
633 timeout_ts.tv_nsec = timeout_ms * 1'000'000;
634 while (nready == 0) {
635 EV_SET(&changes[0], m_fuse_fd, EVFILT_READ, EV_ADD, 0,
637 nready = kevent(m_kq, &changes[0], 1, &events[0], 1,
642 ASSERT_LE(0, nready) << strerror(errno);
643 ASSERT_EQ(events[0].ident, (uintptr_t)m_fuse_fd);
644 if (events[0].flags & EV_ERROR)
645 FAIL() << strerror(events[0].data);
646 else if (events[0].flags & EV_EOF)
647 FAIL() << strerror(events[0].fflags);
648 m_nready = events[0].data;
651 timeout_int = timeout_ms;
652 fds[0].fd = m_fuse_fd;
653 fds[0].events = POLLIN;
654 while (nready == 0) {
655 nready = poll(fds, 1, timeout_int);
659 ASSERT_LE(0, nready) << strerror(errno);
660 ASSERT_TRUE(fds[0].revents & POLLIN);
663 timeout_tv.tv_sec = 0;
664 timeout_tv.tv_usec = timeout_ms * 1'000;
665 nfds = m_fuse_fd + 1;
666 while (nready == 0) {
668 FD_SET(m_fuse_fd, &readfds);
669 nready = select(nfds, &readfds, NULL, NULL,
674 ASSERT_LE(0, nready) << strerror(errno);
675 ASSERT_TRUE(FD_ISSET(m_fuse_fd, &readfds));
678 FAIL() << "not yet implemented";
680 res = read(m_fuse_fd, &in, sizeof(in));
682 if (res < 0 && !m_quit) {
683 FAIL() << "read: " << strerror(errno);
686 ASSERT_TRUE(res >= static_cast<ssize_t>(sizeof(in.header)) || m_quit);
689 void MockFS::write_response(const mockfs_buf_out &out) {
697 case KQ: /* EVFILT_WRITE is not supported */
700 fds[0].fd = m_fuse_fd;
701 fds[0].events = POLLOUT;
702 nready = poll(fds, 1, INFTIM);
703 ASSERT_LE(0, nready) << strerror(errno);
704 ASSERT_EQ(1, nready) << "NULL timeout expired?";
705 ASSERT_TRUE(fds[0].revents & POLLOUT);
709 FD_SET(m_fuse_fd, &writefds);
710 nfds = m_fuse_fd + 1;
711 nready = select(nfds, NULL, &writefds, NULL, NULL);
712 ASSERT_LE(0, nready) << strerror(errno);
713 ASSERT_EQ(1, nready) << "NULL timeout expired?";
714 ASSERT_TRUE(FD_ISSET(m_fuse_fd, &writefds));
717 FAIL() << "not yet implemented";
719 r = write(m_fuse_fd, &out, out.header.len);
720 ASSERT_TRUE(r > 0 || errno == EAGAIN) << strerror(errno);
723 void* MockFS::service(void *pthr_data) {
724 MockFS *mock_fs = (MockFS*)pthr_data;
731 void MockFS::unmount() {
732 ::unmount("mountpoint", 0);