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 debug_fuseop(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 name = (const char*)in.body.bytes +
173 sizeof(fuse_open_in);
174 printf(" flags=%#x name=%s",
175 in.body.open.flags, name);
178 printf(" fh=%#" PRIx64 " lock_owner=%" PRIu64,
180 in.body.flush.lock_owner);
183 printf(" nlookup=%" PRIu64, in.body.forget.nlookup);
186 printf(" flags=%#x", in.body.fsync.fsync_flags);
189 printf(" flags=%#x", in.body.fsyncdir.fsync_flags);
192 printf(" unique=%" PRIu64, in.body.interrupt.unique);
195 printf(" oldnodeid=%" PRIu64, in.body.link.oldnodeid);
198 printf(" %s", in.body.lookup);
201 name = (const char*)in.body.bytes +
202 sizeof(fuse_mkdir_in);
203 printf(" name=%s mode=%#o", name, in.body.mkdir.mode);
206 printf(" mode=%#o rdev=%x", in.body.mknod.mode,
210 printf(" flags=%#x mode=%#o",
211 in.body.open.flags, in.body.open.mode);
214 printf(" flags=%#x mode=%#o",
215 in.body.opendir.flags, in.body.opendir.mode);
218 printf(" offset=%" PRIu64 " size=%u",
222 printf(" flags=%#x", in.body.read.flags);
225 printf(" fh=%#" PRIx64 " offset=%" PRIu64 " size=%u",
226 in.body.readdir.fh, in.body.readdir.offset,
227 in.body.readdir.size);
230 printf(" fh=%#" PRIx64 " flags=%#x lock_owner=%" PRIu64,
232 in.body.release.flags,
233 in.body.release.lock_owner);
236 if (verbosity <= 1) {
237 printf(" valid=%#x", in.body.setattr.valid);
240 if (in.body.setattr.valid & FATTR_MODE)
241 printf(" mode=%#o", in.body.setattr.mode);
242 if (in.body.setattr.valid & FATTR_UID)
243 printf(" uid=%u", in.body.setattr.uid);
244 if (in.body.setattr.valid & FATTR_GID)
245 printf(" gid=%u", in.body.setattr.gid);
246 if (in.body.setattr.valid & FATTR_SIZE)
247 printf(" size=%" PRIu64, in.body.setattr.size);
248 if (in.body.setattr.valid & FATTR_ATIME)
249 printf(" atime=%" PRIu64 ".%u",
250 in.body.setattr.atime,
251 in.body.setattr.atimensec);
252 if (in.body.setattr.valid & FATTR_MTIME)
253 printf(" mtime=%" PRIu64 ".%u",
254 in.body.setattr.mtime,
255 in.body.setattr.mtimensec);
256 if (in.body.setattr.valid & FATTR_FH)
257 printf(" fh=%" PRIu64 "", in.body.setattr.fh);
260 printf(" fh=%#" PRIx64 " owner=%" PRIu64
262 in.body.setlk.fh, in.body.setlk.owner,
263 in.body.setlk.lk.type,
264 in.body.setlk.lk.pid);
265 if (verbosity >= 2) {
266 printf(" range=[%" PRIu64 "-%" PRIu64 "]",
267 in.body.setlk.lk.start,
268 in.body.setlk.lk.end);
273 * In theory neither the xattr name and value need be
274 * ASCII, but in this test suite they always are.
276 name = (const char*)in.body.bytes +
277 sizeof(fuse_setxattr_in);
278 value = name + strlen(name) + 1;
279 printf(" %s=%s", name, value);
282 printf(" fh=%#" PRIx64 " offset=%" PRIu64
283 " size=%u write_flags=%u",
285 in.body.write.offset, in.body.write.size,
286 in.body.write.write_flags);
288 printf(" flags=%#x", in.body.write.flags);
296 MockFS::MockFS(int max_readahead, bool allow_other, bool default_permissions,
297 bool push_symlinks_in, bool ro, enum poll_method pm, uint32_t flags,
298 uint32_t kernel_minor_version)
301 struct iovec *iov = NULL;
304 const bool trueval = true;
307 m_kernel_minor_version = kernel_minor_version;
308 m_maxreadahead = max_readahead;
318 * Kyua sets pwd to a testcase-unique tempdir; no need to use
322 * googletest doesn't allow ASSERT_ in constructors, so we must throw
325 if (mkdir("mountpoint" , 0755) && errno != EEXIST)
326 throw(std::system_error(errno, std::system_category(),
327 "Couldn't make mountpoint directory"));
331 m_fuse_fd = open("/dev/fuse", O_CLOEXEC | O_RDWR);
334 m_fuse_fd = open("/dev/fuse", O_CLOEXEC | O_RDWR | O_NONBLOCK);
338 throw(std::system_error(errno, std::system_category(),
339 "Couldn't open /dev/fuse"));
344 build_iovec(&iov, &iovlen, "fstype", __DECONST(void *, "fusefs"), -1);
345 build_iovec(&iov, &iovlen, "fspath",
346 __DECONST(void *, "mountpoint"), -1);
347 build_iovec(&iov, &iovlen, "from", __DECONST(void *, "/dev/fuse"), -1);
348 sprintf(fdstr, "%d", m_fuse_fd);
349 build_iovec(&iov, &iovlen, "fd", fdstr, -1);
351 build_iovec(&iov, &iovlen, "allow_other",
352 __DECONST(void*, &trueval), sizeof(bool));
354 if (default_permissions) {
355 build_iovec(&iov, &iovlen, "default_permissions",
356 __DECONST(void*, &trueval), sizeof(bool));
358 if (push_symlinks_in) {
359 build_iovec(&iov, &iovlen, "push_symlinks_in",
360 __DECONST(void*, &trueval), sizeof(bool));
363 build_iovec(&iov, &iovlen, "ro",
364 __DECONST(void*, &trueval), sizeof(bool));
366 if (nmount(iov, iovlen, 0))
367 throw(std::system_error(errno, std::system_category(),
368 "Couldn't mount filesystem"));
370 // Setup default handler
371 ON_CALL(*this, process(_, _))
372 .WillByDefault(Invoke(this, &MockFS::process_default));
375 bzero(&sa, sizeof(sa));
376 sa.sa_handler = sigint_handler;
377 sa.sa_flags = 0; /* Don't set SA_RESTART! */
378 if (0 != sigaction(SIGUSR1, &sa, NULL))
379 throw(std::system_error(errno, std::system_category(),
380 "Couldn't handle SIGUSR1"));
381 if (pthread_create(&m_daemon_id, NULL, service, (void*)this))
382 throw(std::system_error(errno, std::system_category(),
383 "Couldn't Couldn't start fuse thread"));
388 if (m_daemon_id != NULL) {
389 pthread_join(m_daemon_id, NULL);
392 ::unmount("mountpoint", MNT_FORCE);
398 void MockFS::init(uint32_t flags) {
399 std::unique_ptr<mockfs_buf_in> in(new mockfs_buf_in);
400 std::unique_ptr<mockfs_buf_out> out(new mockfs_buf_out);
403 ASSERT_EQ(FUSE_INIT, in->header.opcode);
405 out->header.unique = in->header.unique;
406 out->header.error = 0;
407 out->body.init.major = FUSE_KERNEL_VERSION;
408 out->body.init.minor = m_kernel_minor_version;;
409 out->body.init.flags = in->body.init.flags & flags;
412 * The default max_write is set to this formula in libfuse, though
413 * individual filesystems can lower it. The "- 4096" was added in
414 * commit 154ffe2, with the commit message "fix".
416 uint32_t default_max_write = 32 * getpagesize() + 0x1000 - 4096;
417 /* For testing purposes, it should be distinct from MAXPHYS */
418 m_max_write = MIN(default_max_write, MAXPHYS / 2);
419 out->body.init.max_write = m_max_write;
421 out->body.init.max_readahead = m_maxreadahead;
422 SET_OUT_HEADER_LEN(*out, init);
423 write(m_fuse_fd, out.get(), out->header.len);
426 void MockFS::kill_daemon() {
428 if (m_daemon_id != NULL)
429 pthread_kill(m_daemon_id, SIGUSR1);
430 // Closing the /dev/fuse file descriptor first allows unmount to
431 // succeed even if the daemon doesn't correctly respond to commands
432 // during the unmount sequence.
437 void MockFS::loop() {
438 std::vector<std::unique_ptr<mockfs_buf_out>> out;
440 std::unique_ptr<mockfs_buf_in> in(new mockfs_buf_in);
441 ASSERT_TRUE(in != NULL);
443 bzero(in.get(), sizeof(*in));
449 if (pid_ok((pid_t)in->header.pid)) {
453 * Reject any requests from unknown processes. Because
454 * we actually do mount a filesystem, plenty of
455 * unrelated system daemons may try to access it.
458 printf("\tREJECTED (wrong pid %d)\n",
460 process_default(*in, 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<std::unique_ptr<mockfs_buf_out>> &out)
494 std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out);
495 out0->header.unique = in.header.unique;
496 out0->header.error = -EOPNOTSUPP;
497 out0->header.len = sizeof(out0->header);
498 out.push_back(std::move(out0));
501 void MockFS::read_request(mockfs_buf_in &in) {
506 struct kevent changes[1];
507 struct kevent events[1];
508 struct timespec timeout_ts;
509 struct timeval timeout_tv;
510 const int timeout_ms = 999;
511 int timeout_int, nfds;
517 timeout_ts.tv_sec = 0;
518 timeout_ts.tv_nsec = timeout_ms * 1'000'000;
519 while (nready == 0) {
520 EV_SET(&changes[0], m_fuse_fd, EVFILT_READ, EV_ADD, 0,
522 nready = kevent(m_kq, &changes[0], 1, &events[0], 1,
527 ASSERT_LE(0, nready) << strerror(errno);
528 ASSERT_EQ(events[0].ident, (uintptr_t)m_fuse_fd);
529 if (events[0].flags & EV_ERROR)
530 FAIL() << strerror(events[0].data);
531 else if (events[0].flags & EV_EOF)
532 FAIL() << strerror(events[0].fflags);
533 m_nready = events[0].data;
536 timeout_int = timeout_ms;
537 fds[0].fd = m_fuse_fd;
538 fds[0].events = POLLIN;
539 while (nready == 0) {
540 nready = poll(fds, 1, timeout_int);
544 ASSERT_LE(0, nready) << strerror(errno);
545 ASSERT_TRUE(fds[0].revents & POLLIN);
548 timeout_tv.tv_sec = 0;
549 timeout_tv.tv_usec = timeout_ms * 1'000;
550 nfds = m_fuse_fd + 1;
551 while (nready == 0) {
553 FD_SET(m_fuse_fd, &readfds);
554 nready = select(nfds, &readfds, NULL, NULL,
559 ASSERT_LE(0, nready) << strerror(errno);
560 ASSERT_TRUE(FD_ISSET(m_fuse_fd, &readfds));
563 FAIL() << "not yet implemented";
565 res = read(m_fuse_fd, &in, sizeof(in));
567 if (res < 0 && !m_quit)
569 ASSERT_TRUE(res >= static_cast<ssize_t>(sizeof(in.header)) || m_quit);
572 void MockFS::write_response(const mockfs_buf_out &out) {
580 case KQ: /* EVFILT_WRITE is not supported */
583 fds[0].fd = m_fuse_fd;
584 fds[0].events = POLLOUT;
585 nready = poll(fds, 1, INFTIM);
586 ASSERT_LE(0, nready) << strerror(errno);
587 ASSERT_EQ(1, nready) << "NULL timeout expired?";
588 ASSERT_TRUE(fds[0].revents & POLLOUT);
592 FD_SET(m_fuse_fd, &writefds);
593 nfds = m_fuse_fd + 1;
594 nready = select(nfds, NULL, &writefds, NULL, NULL);
595 ASSERT_LE(0, nready) << strerror(errno);
596 ASSERT_EQ(1, nready) << "NULL timeout expired?";
597 ASSERT_TRUE(FD_ISSET(m_fuse_fd, &writefds));
600 FAIL() << "not yet implemented";
602 r = write(m_fuse_fd, &out, out.header.len);
603 ASSERT_TRUE(r > 0 || errno == EAGAIN) << strerror(errno);
606 void* MockFS::service(void *pthr_data) {
607 MockFS *mock_fs = (MockFS*)pthr_data;
614 void MockFS::unmount() {
615 ::unmount("mountpoint", 0);