2 * Copyright (c) 2019 The FreeBSD Foundation
5 * This software was developed by BFF Storage Systems, LLC under sponsorship
6 * from the FreeBSD Foundation.
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31 #include <sys/param.h>
33 #include <sys/mount.h>
45 #include "mntopts.h" // for build_iovec
48 #include <gtest/gtest.h>
52 using namespace testing;
55 static sig_atomic_t quit = 0;
57 const char* opcode2opname(uint32_t opcode)
59 const int NUM_OPS = 39;
60 const char* table[NUM_OPS] = {
80 "Unknown (opcode 19)",
101 if (opcode >= NUM_OPS)
102 return ("Unknown (opcode > max)");
104 return (table[opcode]);
107 std::function<void (const struct mockfs_buf_in *in, struct mockfs_buf_out *out)>
108 ReturnErrno(int error)
110 return([=](auto in, auto out) {
111 out->header.unique = in->header.unique;
112 out->header.error = -error;
113 out->header.len = sizeof(out->header);
117 /* Helper function used for returning negative cache entries for LOOKUP */
118 std::function<void (const struct mockfs_buf_in *in, struct mockfs_buf_out *out)>
119 ReturnNegativeCache(const struct timespec *entry_valid)
121 return([=](auto in, auto out) {
122 /* nodeid means ENOENT and cache it */
123 out->body.entry.nodeid = 0;
124 out->header.unique = in->header.unique;
125 out->header.error = 0;
126 out->body.entry.entry_valid = entry_valid->tv_sec;
127 out->body.entry.entry_valid_nsec = entry_valid->tv_nsec;
128 SET_OUT_HEADER_LEN(out, entry);
132 void sigint_handler(int __unused sig) {
136 void debug_fuseop(const mockfs_buf_in *in)
138 printf("%-11s ino=%2lu", opcode2opname(in->header.opcode),
141 printf(" uid=%5u gid=%5u pid=%5u unique=%lu len=%u",
142 in->header.uid, in->header.gid, in->header.pid,
143 in->header.unique, in->header.len);
145 switch (in->header.opcode) {
147 printf(" %s", in->body.lookup);
150 printf(" flags=%#x mode=%#o",
151 in->body.open.flags, in->body.open.mode);
154 printf(" offset=%lu size=%u", in->body.read.offset,
158 printf(" offset=%lu size=%u flags=%u",
159 in->body.write.offset, in->body.write.size,
160 in->body.write.write_flags);
168 MockFS::MockFS(int max_readahead) {
169 struct iovec *iov = NULL;
174 m_maxreadahead = max_readahead;
178 * Kyua sets pwd to a testcase-unique tempdir; no need to use
182 * googletest doesn't allow ASSERT_ in constructors, so we must throw
185 if (mkdir("mountpoint" , 0644) && errno != EEXIST)
186 throw(std::system_error(errno, std::system_category(),
187 "Couldn't make mountpoint directory"));
189 m_fuse_fd = open("/dev/fuse", O_RDWR);
191 throw(std::system_error(errno, std::system_category(),
192 "Couldn't open /dev/fuse"));
193 sprintf(fdstr, "%d", m_fuse_fd);
197 build_iovec(&iov, &iovlen, "fstype", __DECONST(void *, "fusefs"), -1);
198 build_iovec(&iov, &iovlen, "fspath",
199 __DECONST(void *, "mountpoint"), -1);
200 build_iovec(&iov, &iovlen, "from", __DECONST(void *, "/dev/fuse"), -1);
201 build_iovec(&iov, &iovlen, "fd", fdstr, -1);
202 if (nmount(iov, iovlen, 0))
203 throw(std::system_error(errno, std::system_category(),
204 "Couldn't mount filesystem"));
206 // Setup default handler
207 ON_CALL(*this, process(_, _))
208 .WillByDefault(Invoke(this, &MockFS::process_default));
211 signal(SIGUSR1, sigint_handler);
212 if (pthread_create(&m_daemon_id, NULL, service, (void*)this))
213 throw(std::system_error(errno, std::system_category(),
214 "Couldn't Couldn't start fuse thread"));
219 ::unmount("mountpoint", MNT_FORCE);
223 void MockFS::init() {
227 in = (mockfs_buf_in*) malloc(sizeof(*in));
228 ASSERT_TRUE(in != NULL);
229 out = (mockfs_buf_out*) malloc(sizeof(*out));
230 ASSERT_TRUE(out != NULL);
233 ASSERT_EQ(FUSE_INIT, in->header.opcode);
235 memset(out, 0, sizeof(*out));
236 out->header.unique = in->header.unique;
237 out->header.error = 0;
238 out->body.init.major = FUSE_KERNEL_VERSION;
239 out->body.init.minor = FUSE_KERNEL_MINOR_VERSION;
242 * The default max_write is set to this formula in libfuse, though
243 * individual filesystems can lower it. The "- 4096" was added in
244 * commit 154ffe2, with the commit message "fix".
246 uint32_t default_max_write = 32 * getpagesize() + 0x1000 - 4096;
247 /* For testing purposes, it should be distinct from MAXPHYS */
248 m_max_write = MIN(default_max_write, MAXPHYS / 2);
249 out->body.init.max_write = m_max_write;
251 out->body.init.max_readahead = m_maxreadahead;
252 SET_OUT_HEADER_LEN(out, init);
253 write(m_fuse_fd, out, out->header.len);
258 void MockFS::kill_daemon() {
259 if (m_daemon_id != NULL) {
260 pthread_kill(m_daemon_id, SIGUSR1);
261 // Closing the /dev/fuse file descriptor first allows unmount
262 // to succeed even if the daemon doesn't correctly respond to
263 // commands during the unmount sequence.
265 pthread_join(m_daemon_id, NULL);
270 void MockFS::loop() {
274 in = (mockfs_buf_in*) malloc(sizeof(*in));
275 ASSERT_TRUE(in != NULL);
277 bzero(in, sizeof(*in));
278 bzero(&out, sizeof(out));
284 if (pid_ok((pid_t)in->header.pid)) {
288 * Reject any requests from unknown processes. Because
289 * we actually do mount a filesystem, plenty of
290 * unrelated system daemons may try to access it.
292 process_default(in, &out);
294 if (in->header.opcode == FUSE_FORGET) {
295 /*Alone among the opcodes, FORGET expects no response*/
298 ASSERT_TRUE(write(m_fuse_fd, &out, out.header.len) > 0 ||
305 bool MockFS::pid_ok(pid_t pid) {
309 struct kinfo_proc *ki;
312 ki = kinfo_getproc(pid);
316 * Allow access by the aio daemon processes so that our tests
317 * can use aio functions
319 if (0 == strncmp("aiod", ki->ki_comm, 4))
326 void MockFS::process_default(const mockfs_buf_in *in, mockfs_buf_out* out) {
327 out->header.unique = in->header.unique;
328 out->header.error = -EOPNOTSUPP;
329 out->header.len = sizeof(out->header);
332 void MockFS::read_request(mockfs_buf_in *in) {
335 res = read(m_fuse_fd, in, sizeof(*in));
336 if (res < 0 && !quit)
338 ASSERT_TRUE(res >= (ssize_t)sizeof(in->header) || quit);
341 void* MockFS::service(void *pthr_data) {
342 MockFS *mock_fs = (MockFS*)pthr_data;
349 void MockFS::unmount() {
350 ::unmount("mountpoint", 0);