From 7f1aad139c51c33220d6f58c4584e1bf561b8eb0 Mon Sep 17 00:00:00 2001 From: asomers Date: Tue, 5 Mar 2019 00:27:54 +0000 Subject: [PATCH] fuse(4): add tests related to FUSE_MKNOD PR: 236236 Sponsored by: The FreeBSD Foundation --- sys/fs/fuse/fuse_kernel.h | 5 ++ tests/sys/fs/fuse/Makefile | 6 ++ tests/sys/fs/fuse/create.cc | 89 +++++++++++++++++++- tests/sys/fs/fuse/mknod.cc | 162 ++++++++++++++++++++++++++++++++++++ tests/sys/fs/fuse/mockfs.hh | 1 + tests/sys/fs/fuse/utils.hh | 2 +- 6 files changed, 263 insertions(+), 2 deletions(-) create mode 100644 tests/sys/fs/fuse/mknod.cc diff --git a/sys/fs/fuse/fuse_kernel.h b/sys/fs/fuse/fuse_kernel.h index 5ac9f21bf038..c77a4f32d818 100644 --- a/sys/fs/fuse/fuse_kernel.h +++ b/sys/fs/fuse/fuse_kernel.h @@ -194,6 +194,11 @@ struct fuse_attr_out { struct fuse_attr attr; }; +struct fuse_mknod_in { + __u32 mode; + __u32 rdev; +}; + struct fuse_mkdir_in { __u32 mode; __u32 padding; diff --git a/tests/sys/fs/fuse/Makefile b/tests/sys/fs/fuse/Makefile index ac2f2e2f2273..2506c14e687f 100644 --- a/tests/sys/fs/fuse/Makefile +++ b/tests/sys/fs/fuse/Makefile @@ -7,6 +7,7 @@ TESTSDIR= ${TESTSBASE}/sys/fs/fuse ATF_TESTS_CXX+= create ATF_TESTS_CXX+= getattr ATF_TESTS_CXX+= lookup +ATF_TESTS_CXX+= mknod ATF_TESTS_CXX+= open ATF_TESTS_CXX+= readlink ATF_TESTS_CXX+= setattr @@ -27,6 +28,11 @@ SRCS.lookup+= lookup.cc SRCS.lookup+= mockfs.cc SRCS.lookup+= utils.cc +SRCS.mknod+= getmntopts.c +SRCS.mknod+= mockfs.cc +SRCS.mknod+= mknod.cc +SRCS.mknod+= utils.cc + SRCS.open+= getmntopts.c SRCS.open+= mockfs.cc SRCS.open+= open.cc diff --git a/tests/sys/fs/fuse/create.cc b/tests/sys/fs/fuse/create.cc index 506850396642..8315075e943f 100644 --- a/tests/sys/fs/fuse/create.cc +++ b/tests/sys/fs/fuse/create.cc @@ -133,7 +133,94 @@ TEST_F(Create, eexist) EXPECT_EQ(EEXIST, errno); } -// TODO: enosys: kernel should fall back to mknod/open +/* + * If the daemon doesn't implement FUSE_CREATE, then the kernel should fallback + * to FUSE_MKNOD/FUSE_OPEN + */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236236 */ +TEST_F(Create, DISABLED_Enosys) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + mode_t mode = 0755; + uint64_t ino = 42; + int fd; + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_LOOKUP && + strcmp(in->body.lookup, RELPATH) == 0); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.error = -ENOENT; + out->header.len = sizeof(out->header); + })); + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + const char *name = (const char*)in->body.bytes + + sizeof(fuse_open_in); + return (in->header.opcode == FUSE_CREATE && + (0 == strcmp(RELPATH, name))); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.error = -ENOSYS; + out->header.len = sizeof(out->header); + })); + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + const char *name = (const char*)in->body.bytes + + sizeof(fuse_mknod_in); + return (in->header.opcode == FUSE_MKNOD && + in->body.mknod.mode == (S_IFREG | mode) && + in->body.mknod.rdev == 0 && + (0 == strcmp(RELPATH, name))); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, create); + out->body.create.entry.attr.mode = S_IFREG | mode; + out->body.create.entry.nodeid = ino; + out->body.create.entry.entry_valid = UINT64_MAX; + out->body.create.entry.attr_valid = UINT64_MAX; + })); + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_OPEN && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).WillOnce(Invoke([](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.len = sizeof(out->header); + SET_OUT_HEADER_LEN(out, open); + })); + + /* Until the attr cache is working, we may send an additional GETATTR */ + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_GETATTR && + in->header.nodeid == ino); + }, Eq(true)), + _) + ).WillRepeatedly(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, attr); + out->body.attr.attr.ino = ino; // Must match nodeid + out->body.attr.attr.mode = S_IFREG | 0644; + })); + + fd = open(FULLPATH, O_CREAT | O_EXCL, mode); + EXPECT_LE(0, fd) << strerror(errno); + /* Deliberately leak fd. close(2) will be tested in release.cc */ +} /* * Creating a new file after FUSE_LOOKUP returned a negative cache entry diff --git a/tests/sys/fs/fuse/mknod.cc b/tests/sys/fs/fuse/mknod.cc new file mode 100644 index 000000000000..1b205ac6baea --- /dev/null +++ b/tests/sys/fs/fuse/mknod.cc @@ -0,0 +1,162 @@ +/*- + * Copyright (c) 2019 The FreeBSD Foundation + * All rights reserved. + * + * This software was developed by BFF Storage Systems, LLC under sponsorship + * from the FreeBSD Foundation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +extern "C" { +#include +} + +#include "mockfs.hh" +#include "utils.hh" + +using namespace testing; + +class Mknod: public FuseTest { + +public: + +virtual void SetUp() { + if (geteuid() != 0) { + // TODO: With GoogleTest 1.8.2, use SKIP instead + FAIL() << "Only root may use most mknod(2) variations"; + } + FuseTest::SetUp(); +} + +/* Test an OK creation of a file with the given mode and device number */ +void test_ok(mode_t mode, dev_t dev) { + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + uint64_t ino = 42; + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_LOOKUP && + strcmp(in->body.lookup, RELPATH) == 0); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.error = -ENOENT; + out->header.len = sizeof(out->header); + })); + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + const char *name = (const char*)in->body.bytes + + sizeof(fuse_mknod_in); + return (in->header.opcode == FUSE_MKNOD && + in->body.mknod.mode == mode && + in->body.mknod.rdev == dev && + (0 == strcmp(RELPATH, name))); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + SET_OUT_HEADER_LEN(out, create); + out->body.create.entry.attr.mode = mode; + out->body.create.entry.nodeid = ino; + out->body.create.entry.entry_valid = UINT64_MAX; + out->body.create.entry.attr_valid = UINT64_MAX; + out->body.create.entry.attr.rdev = dev; + })); + EXPECT_EQ(0, mknod(FULLPATH, mode, dev)) << strerror(errno); +} + +}; + +/* + * mknod(2) should be able to create block devices on a FUSE filesystem. Even + * though FreeBSD doesn't use block devices, this is useful when copying media + * from or preparing media for other operating systems. + */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236236 */ +TEST_F(Mknod, DISABLED_blk) +{ + test_ok(S_IFBLK | 0755, 0xfe00); /* /dev/vda's device number on Linux */ +} + +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236236 */ +TEST_F(Mknod, DISABLED_chr) +{ + test_ok(S_IFCHR | 0755, 0x64); /* /dev/fuse's device number */ +} + +/* + * The daemon is responsible for checking file permissions (unless the + * default_permissions mount option was used) + */ +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236236 */ +TEST_F(Mknod, DISABLED_eperm) +{ + const char FULLPATH[] = "mountpoint/some_file.txt"; + const char RELPATH[] = "some_file.txt"; + mode_t mode = S_IFIFO | 0755; + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + return (in->header.opcode == FUSE_LOOKUP && + strcmp(in->body.lookup, RELPATH) == 0); + }, Eq(true)), + _) + ).WillOnce(Invoke([=](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.error = -ENOENT; + out->header.len = sizeof(out->header); + })); + + EXPECT_CALL(*m_mock, process( + ResultOf([=](auto in) { + const char *name = (const char*)in->body.bytes + + sizeof(fuse_mknod_in); + return (in->header.opcode == FUSE_MKNOD && + in->body.mknod.mode == mode && + (0 == strcmp(RELPATH, name))); + }, Eq(true)), + _) + ).WillOnce(Invoke([](auto in, auto out) { + out->header.unique = in->header.unique; + out->header.error = -EPERM; + out->header.len = sizeof(out->header); + })); + EXPECT_NE(0, mknod(FULLPATH, mode, 0)); + EXPECT_EQ(EPERM, errno); +} + + +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236236 */ +TEST_F(Mknod, DISABLED_fifo) +{ + test_ok(S_IFIFO | 0755, 0); +} + +/* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236236 */ +TEST_F(Mknod, DISABLED_whiteout) +{ + test_ok(S_IFWHT | 0755, 0); +} diff --git a/tests/sys/fs/fuse/mockfs.hh b/tests/sys/fs/fuse/mockfs.hh index 77e645951e8e..bbcf14b4aaaa 100644 --- a/tests/sys/fs/fuse/mockfs.hh +++ b/tests/sys/fs/fuse/mockfs.hh @@ -56,6 +56,7 @@ union fuse_payloads_in { fuse_forget_in forget; fuse_init_in init; char lookup[0]; + fuse_mknod_in mknod; fuse_open_in open; fuse_setattr_in setattr; }; diff --git a/tests/sys/fs/fuse/utils.hh b/tests/sys/fs/fuse/utils.hh index 0462af14fcbc..d3edc44febac 100644 --- a/tests/sys/fs/fuse/utils.hh +++ b/tests/sys/fs/fuse/utils.hh @@ -32,7 +32,7 @@ class FuseTest : public ::testing::Test { MockFS *m_mock = NULL; public: - void SetUp() { + virtual void SetUp() { try { m_mock = new MockFS{}; } catch (std::system_error err) {