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>
46 #include "mntopts.h" // for build_iovec
49 #include <gtest/gtest.h>
53 using namespace testing;
56 static sig_atomic_t quit = 0;
58 const char* opcode2opname(uint32_t opcode)
60 const int NUM_OPS = 39;
61 const char* table[NUM_OPS] = {
81 "Unknown (opcode 19)",
102 if (opcode >= NUM_OPS)
103 return ("Unknown (opcode > max)");
105 return (table[opcode]);
109 ReturnErrno(int error)
111 return([=](auto in, auto &out) {
112 auto out0 = new mockfs_buf_out;
113 out0->header.unique = in->header.unique;
114 out0->header.error = -error;
115 out0->header.len = sizeof(out0->header);
120 /* Helper function used for returning negative cache entries for LOOKUP */
122 ReturnNegativeCache(const struct timespec *entry_valid)
124 return([=](auto in, auto &out) {
125 /* nodeid means ENOENT and cache it */
126 auto out0 = new mockfs_buf_out;
127 out0->body.entry.nodeid = 0;
128 out0->header.unique = in->header.unique;
129 out0->header.error = 0;
130 out0->body.entry.entry_valid = entry_valid->tv_sec;
131 out0->body.entry.entry_valid_nsec = entry_valid->tv_nsec;
132 SET_OUT_HEADER_LEN(out0, entry);
138 ReturnImmediate(std::function<void(const struct mockfs_buf_in *in,
139 struct mockfs_buf_out *out)> f)
141 return([=](auto in, auto &out) {
142 auto out0 = new mockfs_buf_out;
143 out0->header.unique = in->header.unique;
149 void sigint_handler(int __unused sig) {
153 void debug_fuseop(const mockfs_buf_in *in)
155 printf("%-11s ino=%2lu", opcode2opname(in->header.opcode),
158 printf(" uid=%5u gid=%5u pid=%5u unique=%lu len=%u",
159 in->header.uid, in->header.gid, in->header.pid,
160 in->header.unique, in->header.len);
162 switch (in->header.opcode) {
163 const char *name, *value;
166 name = (const char*)in->body.bytes +
167 sizeof(fuse_open_in);
168 printf(" flags=%#x name=%s",
169 in->body.open.flags, name);
172 printf(" fh=%#lx lock_owner=%lu", in->body.flush.fh,
173 in->body.flush.lock_owner);
176 printf(" nlookup=%lu", in->body.forget.nlookup);
179 printf(" flags=%#x", in->body.fsync.fsync_flags);
182 printf(" flags=%#x", in->body.fsyncdir.fsync_flags);
185 printf(" %s", in->body.lookup);
188 printf(" mode=%#o rdev=%x", in->body.mknod.mode,
189 in->body.mknod.rdev);
192 printf(" flags=%#x mode=%#o",
193 in->body.open.flags, in->body.open.mode);
196 printf(" flags=%#x mode=%#o",
197 in->body.opendir.flags, in->body.opendir.mode);
200 printf(" offset=%lu size=%u", in->body.read.offset,
204 printf(" fh=%#lx offset=%lu size=%u",
205 in->body.readdir.fh, in->body.readdir.offset,
206 in->body.readdir.size);
209 printf(" fh=%#lx flags=%#x lock_owner=%lu",
211 in->body.release.flags,
212 in->body.release.lock_owner);
215 if (verbosity <= 1) {
216 printf(" valid=%#x", in->body.setattr.valid);
219 if (in->body.setattr.valid & FATTR_MODE)
220 printf(" mode=%#o", in->body.setattr.mode);
221 if (in->body.setattr.valid & FATTR_UID)
222 printf(" uid=%u", in->body.setattr.uid);
223 if (in->body.setattr.valid & FATTR_GID)
224 printf(" gid=%u", in->body.setattr.gid);
225 if (in->body.setattr.valid & FATTR_SIZE)
226 printf(" size=%zu", in->body.setattr.size);
227 if (in->body.setattr.valid & FATTR_ATIME)
228 printf(" atime=%zu.%u",
229 in->body.setattr.atime,
230 in->body.setattr.atimensec);
231 if (in->body.setattr.valid & FATTR_MTIME)
232 printf(" mtime=%zu.%u",
233 in->body.setattr.mtime,
234 in->body.setattr.mtimensec);
235 if (in->body.setattr.valid & FATTR_FH)
236 printf(" fh=%zu", in->body.setattr.fh);
240 * In theory neither the xattr name and value need be
241 * ASCII, but in this test suite they always are.
243 name = (const char*)in->body.bytes +
244 sizeof(fuse_setxattr_in);
245 value = name + strlen(name) + 1;
246 printf(" %s=%s", name, value);
249 printf(" offset=%lu size=%u flags=%u",
250 in->body.write.offset, in->body.write.size,
251 in->body.write.write_flags);
259 MockFS::MockFS(int max_readahead, bool allow_other, bool default_permissions,
260 bool push_symlinks_in, uint32_t flags)
262 struct iovec *iov = NULL;
265 const bool trueval = true;
268 m_maxreadahead = max_readahead;
272 * Kyua sets pwd to a testcase-unique tempdir; no need to use
276 * googletest doesn't allow ASSERT_ in constructors, so we must throw
279 if (mkdir("mountpoint" , 0755) && errno != EEXIST)
280 throw(std::system_error(errno, std::system_category(),
281 "Couldn't make mountpoint directory"));
283 m_fuse_fd = open("/dev/fuse", O_CLOEXEC | O_RDWR);
285 throw(std::system_error(errno, std::system_category(),
286 "Couldn't open /dev/fuse"));
287 sprintf(fdstr, "%d", m_fuse_fd);
292 build_iovec(&iov, &iovlen, "fstype", __DECONST(void *, "fusefs"), -1);
293 build_iovec(&iov, &iovlen, "fspath",
294 __DECONST(void *, "mountpoint"), -1);
295 build_iovec(&iov, &iovlen, "from", __DECONST(void *, "/dev/fuse"), -1);
296 build_iovec(&iov, &iovlen, "fd", fdstr, -1);
298 build_iovec(&iov, &iovlen, "allow_other",
299 __DECONST(void*, &trueval), sizeof(bool));
301 if (default_permissions) {
302 build_iovec(&iov, &iovlen, "default_permissions",
303 __DECONST(void*, &trueval), sizeof(bool));
305 if (push_symlinks_in) {
306 build_iovec(&iov, &iovlen, "push_symlinks_in",
307 __DECONST(void*, &trueval), sizeof(bool));
309 if (nmount(iov, iovlen, 0))
310 throw(std::system_error(errno, std::system_category(),
311 "Couldn't mount filesystem"));
313 // Setup default handler
314 ON_CALL(*this, process(_, _))
315 .WillByDefault(Invoke(this, &MockFS::process_default));
318 signal(SIGUSR1, sigint_handler);
319 if (pthread_create(&m_daemon_id, NULL, service, (void*)this))
320 throw(std::system_error(errno, std::system_category(),
321 "Couldn't Couldn't start fuse thread"));
326 ::unmount("mountpoint", MNT_FORCE);
327 if (m_daemon_id != NULL) {
328 pthread_join(m_daemon_id, NULL);
334 void MockFS::init(uint32_t flags) {
338 in = (mockfs_buf_in*) malloc(sizeof(*in));
339 ASSERT_TRUE(in != NULL);
340 out = (mockfs_buf_out*) malloc(sizeof(*out));
341 ASSERT_TRUE(out != NULL);
344 ASSERT_EQ(FUSE_INIT, in->header.opcode);
346 memset(out, 0, sizeof(*out));
347 out->header.unique = in->header.unique;
348 out->header.error = 0;
349 out->body.init.major = FUSE_KERNEL_VERSION;
350 out->body.init.minor = FUSE_KERNEL_MINOR_VERSION;
351 out->body.init.flags = in->body.init.flags & flags;
354 * The default max_write is set to this formula in libfuse, though
355 * individual filesystems can lower it. The "- 4096" was added in
356 * commit 154ffe2, with the commit message "fix".
358 uint32_t default_max_write = 32 * getpagesize() + 0x1000 - 4096;
359 /* For testing purposes, it should be distinct from MAXPHYS */
360 m_max_write = MIN(default_max_write, MAXPHYS / 2);
361 out->body.init.max_write = m_max_write;
363 out->body.init.max_readahead = m_maxreadahead;
364 SET_OUT_HEADER_LEN(out, init);
365 write(m_fuse_fd, out, out->header.len);
370 void MockFS::kill_daemon() {
371 if (m_daemon_id != NULL) {
372 pthread_kill(m_daemon_id, SIGUSR1);
373 // Closing the /dev/fuse file descriptor first allows unmount
374 // to succeed even if the daemon doesn't correctly respond to
375 // commands during the unmount sequence.
380 void MockFS::loop() {
382 std::vector<mockfs_buf_out*> out;
384 in = (mockfs_buf_in*) malloc(sizeof(*in));
385 ASSERT_TRUE(in != NULL);
387 bzero(in, sizeof(*in));
393 if (pid_ok((pid_t)in->header.pid)) {
397 * Reject any requests from unknown processes. Because
398 * we actually do mount a filesystem, plenty of
399 * unrelated system daemons may try to access it.
401 process_default(in, out);
403 for (auto &it: out) {
404 ASSERT_TRUE(write(m_fuse_fd, it, it->header.len) > 0 ||
414 bool MockFS::pid_ok(pid_t pid) {
417 } else if (pid == m_child_pid) {
420 struct kinfo_proc *ki;
423 ki = kinfo_getproc(pid);
427 * Allow access by the aio daemon processes so that our tests
428 * can use aio functions
430 if (0 == strncmp("aiod", ki->ki_comm, 4))
437 void MockFS::process_default(const mockfs_buf_in *in,
438 std::vector<mockfs_buf_out*> &out)
440 auto out0 = new mockfs_buf_out;
441 out0->header.unique = in->header.unique;
442 out0->header.error = -EOPNOTSUPP;
443 out0->header.len = sizeof(out0->header);
447 void MockFS::read_request(mockfs_buf_in *in) {
450 res = read(m_fuse_fd, in, sizeof(*in));
451 if (res < 0 && !quit)
453 ASSERT_TRUE(res >= (ssize_t)sizeof(in->header) || quit);
456 void* MockFS::service(void *pthr_data) {
457 MockFS *mock_fs = (MockFS*)pthr_data;
464 void MockFS::unmount() {
465 ::unmount("mountpoint", 0);