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=%#lx blocksize=%#x", in.body.bmap.block,
173 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,
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;
372 * Kyua sets pwd to a testcase-unique tempdir; no need to use
376 * googletest doesn't allow ASSERT_ in constructors, so we must throw
379 if (mkdir("mountpoint" , 0755) && errno != EEXIST)
380 throw(std::system_error(errno, std::system_category(),
381 "Couldn't make mountpoint directory"));
385 m_fuse_fd = open("/dev/fuse", O_CLOEXEC | O_RDWR);
388 m_fuse_fd = open("/dev/fuse", O_CLOEXEC | O_RDWR | O_NONBLOCK);
392 throw(std::system_error(errno, std::system_category(),
393 "Couldn't open /dev/fuse"));
398 build_iovec(&iov, &iovlen, "fstype", __DECONST(void *, "fusefs"), -1);
399 build_iovec(&iov, &iovlen, "fspath",
400 __DECONST(void *, "mountpoint"), -1);
401 build_iovec(&iov, &iovlen, "from", __DECONST(void *, "/dev/fuse"), -1);
402 sprintf(fdstr, "%d", m_fuse_fd);
403 build_iovec(&iov, &iovlen, "fd", fdstr, -1);
405 build_iovec(&iov, &iovlen, "allow_other",
406 __DECONST(void*, &trueval), sizeof(bool));
408 if (default_permissions) {
409 build_iovec(&iov, &iovlen, "default_permissions",
410 __DECONST(void*, &trueval), sizeof(bool));
412 if (push_symlinks_in) {
413 build_iovec(&iov, &iovlen, "push_symlinks_in",
414 __DECONST(void*, &trueval), sizeof(bool));
417 build_iovec(&iov, &iovlen, "ro",
418 __DECONST(void*, &trueval), sizeof(bool));
421 build_iovec(&iov, &iovlen, "async", __DECONST(void*, &trueval),
425 build_iovec(&iov, &iovlen, "noclusterr",
426 __DECONST(void*, &trueval), sizeof(bool));
428 if (nmount(iov, iovlen, 0))
429 throw(std::system_error(errno, std::system_category(),
430 "Couldn't mount filesystem"));
432 // Setup default handler
433 ON_CALL(*this, process(_, _))
434 .WillByDefault(Invoke(this, &MockFS::process_default));
437 bzero(&sa, sizeof(sa));
438 sa.sa_handler = sigint_handler;
439 sa.sa_flags = 0; /* Don't set SA_RESTART! */
440 if (0 != sigaction(SIGUSR1, &sa, NULL))
441 throw(std::system_error(errno, std::system_category(),
442 "Couldn't handle SIGUSR1"));
443 if (pthread_create(&m_daemon_id, NULL, service, (void*)this))
444 throw(std::system_error(errno, std::system_category(),
445 "Couldn't Couldn't start fuse thread"));
450 if (m_daemon_id != NULL) {
451 pthread_join(m_daemon_id, NULL);
454 ::unmount("mountpoint", MNT_FORCE);
460 void MockFS::init(uint32_t flags) {
461 std::unique_ptr<mockfs_buf_in> in(new mockfs_buf_in);
462 std::unique_ptr<mockfs_buf_out> out(new mockfs_buf_out);
465 ASSERT_EQ(FUSE_INIT, in->header.opcode);
467 out->header.unique = in->header.unique;
468 out->header.error = 0;
469 out->body.init.major = FUSE_KERNEL_VERSION;
470 out->body.init.minor = m_kernel_minor_version;;
471 out->body.init.flags = in->body.init.flags & flags;
473 out->body.init.max_write = m_maxwrite;
475 out->body.init.max_readahead = m_maxreadahead;
476 SET_OUT_HEADER_LEN(*out, init);
477 write(m_fuse_fd, out.get(), out->header.len);
480 void MockFS::kill_daemon() {
482 if (m_daemon_id != NULL)
483 pthread_kill(m_daemon_id, SIGUSR1);
484 // Closing the /dev/fuse file descriptor first allows unmount to
485 // succeed even if the daemon doesn't correctly respond to commands
486 // during the unmount sequence.
491 void MockFS::loop() {
492 std::vector<std::unique_ptr<mockfs_buf_out>> out;
494 std::unique_ptr<mockfs_buf_in> in(new mockfs_buf_in);
495 ASSERT_TRUE(in != NULL);
497 bzero(in.get(), sizeof(*in));
503 if (pid_ok((pid_t)in->header.pid)) {
507 * Reject any requests from unknown processes. Because
508 * we actually do mount a filesystem, plenty of
509 * unrelated system daemons may try to access it.
512 printf("\tREJECTED (wrong pid %d)\n",
514 process_default(*in, out);
522 int MockFS::notify_inval_entry(ino_t parent, const char *name, size_t namelen)
524 std::unique_ptr<mockfs_buf_out> out(new mockfs_buf_out);
526 out->header.unique = 0; /* 0 means asynchronous notification */
527 out->header.error = FUSE_NOTIFY_INVAL_ENTRY;
528 out->body.inval_entry.parent = parent;
529 out->body.inval_entry.namelen = namelen;
530 strlcpy((char*)&out->body.bytes + sizeof(out->body.inval_entry),
531 name, sizeof(out->body.bytes) - sizeof(out->body.inval_entry));
532 out->header.len = sizeof(out->header) + sizeof(out->body.inval_entry) +
534 debug_response(*out);
535 write_response(*out);
539 int MockFS::notify_inval_inode(ino_t ino, off_t off, ssize_t len)
541 std::unique_ptr<mockfs_buf_out> out(new mockfs_buf_out);
543 out->header.unique = 0; /* 0 means asynchronous notification */
544 out->header.error = FUSE_NOTIFY_INVAL_INODE;
545 out->body.inval_inode.ino = ino;
546 out->body.inval_inode.off = off;
547 out->body.inval_inode.len = len;
548 out->header.len = sizeof(out->header) + sizeof(out->body.inval_inode);
549 debug_response(*out);
550 write_response(*out);
554 int MockFS::notify_store(ino_t ino, off_t off, void* data, ssize_t size)
556 std::unique_ptr<mockfs_buf_out> out(new mockfs_buf_out);
558 out->header.unique = 0; /* 0 means asynchronous notification */
559 out->header.error = FUSE_NOTIFY_STORE;
560 out->body.store.nodeid = ino;
561 out->body.store.offset = off;
562 out->body.store.size = size;
563 bcopy(data, (char*)&out->body.bytes + sizeof(out->body.store), size);
564 out->header.len = sizeof(out->header) + sizeof(out->body.store) + size;
565 debug_response(*out);
566 write_response(*out);
570 bool MockFS::pid_ok(pid_t pid) {
573 } else if (pid == m_child_pid) {
576 struct kinfo_proc *ki;
579 ki = kinfo_getproc(pid);
583 * Allow access by the aio daemon processes so that our tests
584 * can use aio functions
586 if (0 == strncmp("aiod", ki->ki_comm, 4))
593 void MockFS::process_default(const mockfs_buf_in& in,
594 std::vector<std::unique_ptr<mockfs_buf_out>> &out)
596 std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
597 out0->header.unique = in.header.unique;
598 out0->header.error = -EOPNOTSUPP;
599 out0->header.len = sizeof(out0->header);
600 out.push_back(std::move(out0));
603 void MockFS::read_request(mockfs_buf_in &in) {
608 struct kevent changes[1];
609 struct kevent events[1];
610 struct timespec timeout_ts;
611 struct timeval timeout_tv;
612 const int timeout_ms = 999;
613 int timeout_int, nfds;
619 timeout_ts.tv_sec = 0;
620 timeout_ts.tv_nsec = timeout_ms * 1'000'000;
621 while (nready == 0) {
622 EV_SET(&changes[0], m_fuse_fd, EVFILT_READ, EV_ADD, 0,
624 nready = kevent(m_kq, &changes[0], 1, &events[0], 1,
629 ASSERT_LE(0, nready) << strerror(errno);
630 ASSERT_EQ(events[0].ident, (uintptr_t)m_fuse_fd);
631 if (events[0].flags & EV_ERROR)
632 FAIL() << strerror(events[0].data);
633 else if (events[0].flags & EV_EOF)
634 FAIL() << strerror(events[0].fflags);
635 m_nready = events[0].data;
638 timeout_int = timeout_ms;
639 fds[0].fd = m_fuse_fd;
640 fds[0].events = POLLIN;
641 while (nready == 0) {
642 nready = poll(fds, 1, timeout_int);
646 ASSERT_LE(0, nready) << strerror(errno);
647 ASSERT_TRUE(fds[0].revents & POLLIN);
650 timeout_tv.tv_sec = 0;
651 timeout_tv.tv_usec = timeout_ms * 1'000;
652 nfds = m_fuse_fd + 1;
653 while (nready == 0) {
655 FD_SET(m_fuse_fd, &readfds);
656 nready = select(nfds, &readfds, NULL, NULL,
661 ASSERT_LE(0, nready) << strerror(errno);
662 ASSERT_TRUE(FD_ISSET(m_fuse_fd, &readfds));
665 FAIL() << "not yet implemented";
667 res = read(m_fuse_fd, &in, sizeof(in));
669 if (res < 0 && !m_quit) {
670 FAIL() << "read: " << strerror(errno);
673 ASSERT_TRUE(res >= static_cast<ssize_t>(sizeof(in.header)) || m_quit);
676 void MockFS::write_response(const mockfs_buf_out &out) {
684 case KQ: /* EVFILT_WRITE is not supported */
687 fds[0].fd = m_fuse_fd;
688 fds[0].events = POLLOUT;
689 nready = poll(fds, 1, INFTIM);
690 ASSERT_LE(0, nready) << strerror(errno);
691 ASSERT_EQ(1, nready) << "NULL timeout expired?";
692 ASSERT_TRUE(fds[0].revents & POLLOUT);
696 FD_SET(m_fuse_fd, &writefds);
697 nfds = m_fuse_fd + 1;
698 nready = select(nfds, NULL, &writefds, NULL, NULL);
699 ASSERT_LE(0, nready) << strerror(errno);
700 ASSERT_EQ(1, nready) << "NULL timeout expired?";
701 ASSERT_TRUE(FD_ISSET(m_fuse_fd, &writefds));
704 FAIL() << "not yet implemented";
706 r = write(m_fuse_fd, &out, out.header.len);
707 ASSERT_TRUE(r > 0 || errno == EAGAIN) << strerror(errno);
710 void* MockFS::service(void *pthr_data) {
711 MockFS *mock_fs = (MockFS*)pthr_data;
718 void MockFS::unmount() {
719 ::unmount("mountpoint", 0);