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/module.h>
35 #include <sys/sysctl.h>
39 #include <semaphore.h>
43 #include <gtest/gtest.h>
48 using namespace testing;
50 /* Check that fusefs(4) is accessible and the current user can mount(2) */
51 void check_environment()
53 const char *devnode = "/dev/fuse";
54 const char *usermount_node = "vfs.usermount";
55 int usermount_val = 0;
56 size_t usermount_size = sizeof(usermount_val);
57 if (eaccess(devnode, R_OK | W_OK)) {
58 if (errno == ENOENT) {
59 GTEST_SKIP() << devnode << " does not exist";
60 } else if (errno == EACCES) {
61 GTEST_SKIP() << devnode <<
62 " is not accessible by the current user";
64 GTEST_SKIP() << strerror(errno);
67 sysctlbyname(usermount_node, &usermount_val, &usermount_size,
69 if (geteuid() != 0 && !usermount_val)
70 GTEST_SKIP() << "current user is not allowed to mount";
73 class FuseEnv: public Environment {
74 virtual void SetUp() {
78 void FuseTest::SetUp() {
79 const char *node = "vfs.maxbcachebuf";
81 size_t size = sizeof(val);
84 * XXX check_environment should be called from FuseEnv::SetUp, but
85 * can't due to https://github.com/google/googletest/issues/2189
91 ASSERT_EQ(0, sysctlbyname(node, &val, &size, NULL, 0))
96 m_mock = new MockFS(m_maxreadahead, m_allow_other,
97 m_default_permissions, m_push_symlinks_in, m_ro,
100 * FUSE_ACCESS is called almost universally. Expecting it in
101 * each test case would be super-annoying. Instead, set a
102 * default expectation for FUSE_ACCESS and return ENOSYS.
104 * Individual test cases can override this expectation since
105 * googlemock evaluates expectations in LIFO order.
107 EXPECT_CALL(*m_mock, process(
108 ResultOf([=](auto in) {
109 return (in->header.opcode == FUSE_ACCESS);
113 .WillRepeatedly(Invoke(ReturnErrno(ENOSYS)));
114 } catch (std::system_error err) {
115 FAIL() << err.what();
120 FuseTest::expect_access(uint64_t ino, mode_t access_mode, int error)
122 EXPECT_CALL(*m_mock, process(
123 ResultOf([=](auto in) {
124 return (in->header.opcode == FUSE_ACCESS &&
125 in->header.nodeid == ino &&
126 in->body.access.mask == access_mode);
129 ).WillOnce(Invoke(ReturnErrno(error)));
133 FuseTest::expect_flush(uint64_t ino, int times, ProcessMockerT r)
135 EXPECT_CALL(*m_mock, process(
136 ResultOf([=](auto in) {
137 return (in->header.opcode == FUSE_FLUSH &&
138 in->header.nodeid == ino);
142 .WillRepeatedly(Invoke(r));
146 FuseTest::expect_forget(uint64_t ino, uint64_t nlookup)
148 EXPECT_CALL(*m_mock, process(
149 ResultOf([=](auto in) {
150 return (in->header.opcode == FUSE_FORGET &&
151 in->header.nodeid == ino &&
152 in->body.forget.nlookup == nlookup);
155 ).WillOnce(Invoke([](auto in __unused, auto &out __unused) {
156 /* FUSE_FORGET has no response! */
160 void FuseTest::expect_getattr(uint64_t ino, uint64_t size)
162 EXPECT_CALL(*m_mock, process(
163 ResultOf([=](auto in) {
164 return (in->header.opcode == FUSE_GETATTR &&
165 in->header.nodeid == ino);
168 ).WillOnce(Invoke(ReturnImmediate([=](auto i __unused, auto out) {
169 SET_OUT_HEADER_LEN(out, attr);
170 out->body.attr.attr.ino = ino; // Must match nodeid
171 out->body.attr.attr.mode = S_IFREG | 0644;
172 out->body.attr.attr.size = size;
173 out->body.attr.attr_valid = UINT64_MAX;
177 void FuseTest::expect_lookup(const char *relpath, uint64_t ino, mode_t mode,
178 uint64_t size, int times, uint64_t attr_valid, uid_t uid, gid_t gid)
180 EXPECT_LOOKUP(1, relpath)
182 .WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
183 SET_OUT_HEADER_LEN(out, entry);
184 out->body.entry.attr.mode = mode;
185 out->body.entry.nodeid = ino;
186 out->body.entry.attr.nlink = 1;
187 out->body.entry.attr_valid = attr_valid;
188 out->body.entry.attr.size = size;
189 out->body.entry.attr.uid = uid;
190 out->body.entry.attr.gid = gid;
194 void FuseTest::expect_open(uint64_t ino, uint32_t flags, int times)
196 EXPECT_CALL(*m_mock, process(
197 ResultOf([=](auto in) {
198 return (in->header.opcode == FUSE_OPEN &&
199 in->header.nodeid == ino);
203 .WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
204 out->header.len = sizeof(out->header);
205 SET_OUT_HEADER_LEN(out, open);
206 out->body.open.fh = FH;
207 out->body.open.open_flags = flags;
211 void FuseTest::expect_opendir(uint64_t ino)
213 /* opendir(3) calls fstatfs */
214 EXPECT_CALL(*m_mock, process(
215 ResultOf([](auto in) {
216 return (in->header.opcode == FUSE_STATFS);
219 ).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto out) {
220 SET_OUT_HEADER_LEN(out, statfs);
223 EXPECT_CALL(*m_mock, process(
224 ResultOf([=](auto in) {
225 return (in->header.opcode == FUSE_OPENDIR &&
226 in->header.nodeid == ino);
229 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
230 out->header.len = sizeof(out->header);
231 SET_OUT_HEADER_LEN(out, open);
232 out->body.open.fh = FH;
236 void FuseTest::expect_read(uint64_t ino, uint64_t offset, uint64_t isize,
237 uint64_t osize, const void *contents)
239 EXPECT_CALL(*m_mock, process(
240 ResultOf([=](auto in) {
241 return (in->header.opcode == FUSE_READ &&
242 in->header.nodeid == ino &&
243 in->body.read.fh == FH &&
244 in->body.read.offset == offset &&
245 in->body.read.size == isize);
248 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
249 out->header.len = sizeof(struct fuse_out_header) + osize;
250 memmove(out->body.bytes, contents, osize);
251 }))).RetiresOnSaturation();
254 void FuseTest::expect_release(uint64_t ino, uint64_t fh)
256 EXPECT_CALL(*m_mock, process(
257 ResultOf([=](auto in) {
258 return (in->header.opcode == FUSE_RELEASE &&
259 in->header.nodeid == ino &&
260 in->body.release.fh == fh);
263 ).WillOnce(Invoke(ReturnErrno(0)));
266 void FuseTest::expect_releasedir(uint64_t ino, ProcessMockerT r)
268 EXPECT_CALL(*m_mock, process(
269 ResultOf([=](auto in) {
270 return (in->header.opcode == FUSE_RELEASEDIR &&
271 in->header.nodeid == ino &&
272 in->body.release.fh == FH);
275 ).WillOnce(Invoke(r));
278 void FuseTest::expect_unlink(uint64_t parent, const char *path, int error)
280 EXPECT_CALL(*m_mock, process(
281 ResultOf([=](auto in) {
282 return (in->header.opcode == FUSE_UNLINK &&
283 0 == strcmp(path, in->body.unlink) &&
284 in->header.nodeid == parent);
287 ).WillOnce(Invoke(ReturnErrno(error)));
290 void FuseTest::expect_write(uint64_t ino, uint64_t offset, uint64_t isize,
291 uint64_t osize, uint32_t flags, const void *contents)
293 EXPECT_CALL(*m_mock, process(
294 ResultOf([=](auto in) {
295 const char *buf = (const char*)in->body.bytes +
296 sizeof(struct fuse_write_in);
299 if (in->body.write.write_flags & FUSE_WRITE_CACHE)
302 pid_ok = (pid_t)in->header.pid == getpid();
304 return (in->header.opcode == FUSE_WRITE &&
305 in->header.nodeid == ino &&
306 in->body.write.fh == FH &&
307 in->body.write.offset == offset &&
308 in->body.write.size == isize &&
310 in->body.write.write_flags == flags &&
311 0 == bcmp(buf, contents, isize));
314 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) {
315 SET_OUT_HEADER_LEN(out, write);
316 out->body.write.size = osize;
321 get_unprivileged_uid(uid_t *uid)
326 * First try "tests", Kyua's default unprivileged user. XXX after
327 * GoogleTest gains a proper Kyua wrapper, get this with the Kyua API
329 pw = getpwnam("tests");
331 /* Fall back to "nobody" */
332 pw = getpwnam("nobody");
335 GTEST_SKIP() << "Test requires an unprivileged user";
340 FuseTest::fork(bool drop_privs, int *child_status,
341 std::function<void()> parent_func,
342 std::function<int()> child_func)
345 int mprot = PROT_READ | PROT_WRITE;
346 int mflags = MAP_ANON | MAP_SHARED;
351 get_unprivileged_uid(&uid);
356 sem = (sem_t*)mmap(NULL, sizeof(*sem), mprot, mflags, -1, 0);
357 ASSERT_NE(MAP_FAILED, sem) << strerror(errno);
358 ASSERT_EQ(0, sem_init(sem, 1, 0)) << strerror(errno);
360 if ((child = ::fork()) == 0) {
370 if (drop_privs && 0 != setreuid(-1, uid)) {
380 } else if (child > 0) {
382 * In parent. Cleanup must happen here, because it's still
385 m_mock->m_child_pid = child;
386 ASSERT_NO_FATAL_FAILURE(parent_func());
388 /* Signal the child process to go */
389 ASSERT_EQ(0, sem_post(sem)) << strerror(errno);
391 ASSERT_LE(0, wait(child_status)) << strerror(errno);
393 FAIL() << strerror(errno);
395 munmap(sem, sizeof(*sem));
399 static void usage(char* progname) {
400 fprintf(stderr, "Usage: %s [-v]\n\t-v increase verbosity\n", progname);
404 int main(int argc, char **argv) {
406 FuseEnv *fuse_env = new FuseEnv;
408 InitGoogleTest(&argc, argv);
409 AddGlobalTestEnvironment(fuse_env);
411 while ((ch = getopt(argc, argv, "v")) != -1) {
422 return (RUN_ALL_TESTS());