1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 3 * 4 * Copyright (c) 2019 The FreeBSD Foundation 5 * 6 * This software was developed by BFF Storage Systems, LLC under sponsorship 7 * from the FreeBSD Foundation. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 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. 17 * 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 28 * SUCH DAMAGE. 29 */ 30 31 extern "C" { 32 #include <fcntl.h> 33 #include <sys/socket.h> 34 #include <sys/un.h> 35 } 36 37 #include "mockfs.hh" 38 #include "utils.hh" 39 40 using namespace testing; 41 42 #ifndef VNOVAL 43 #define VNOVAL (-1) /* Defined in sys/vnode.h */ 44 #endif 45 46 const char FULLPATH[] = "mountpoint/some_file.txt"; 47 const char RELPATH[] = "some_file.txt"; 48 49 class Mknod: public FuseTest { 50 51 mode_t m_oldmask; 52 const static mode_t c_umask = 022; 53 54 public: 55 56 virtual void SetUp() { 57 m_oldmask = umask(c_umask); 58 if (geteuid() != 0) { 59 GTEST_SKIP() << "Only root may use most mknod(2) variations"; 60 } 61 FuseTest::SetUp(); 62 } 63 64 virtual void TearDown() { 65 FuseTest::TearDown(); 66 (void)umask(m_oldmask); 67 } 68 69 /* Test an OK creation of a file with the given mode and device number */ 70 void expect_mknod(mode_t mode, dev_t dev) { 71 uint64_t ino = 42; 72 73 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 74 .WillOnce(Invoke(ReturnErrno(ENOENT))); 75 76 EXPECT_CALL(*m_mock, process( 77 ResultOf([=](auto in) { 78 const char *name = (const char*)in.body.bytes + 79 sizeof(fuse_mknod_in); 80 return (in.header.opcode == FUSE_MKNOD && 81 in.body.mknod.mode == mode && 82 in.body.mknod.rdev == (uint32_t)dev && 83 in.body.mknod.umask == c_umask && 84 (0 == strcmp(RELPATH, name))); 85 }, Eq(true)), 86 _) 87 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 88 SET_OUT_HEADER_LEN(out, entry); 89 out.body.entry.attr.mode = mode; 90 out.body.entry.nodeid = ino; 91 out.body.entry.entry_valid = UINT64_MAX; 92 out.body.entry.attr_valid = UINT64_MAX; 93 out.body.entry.attr.rdev = dev; 94 }))); 95 } 96 97 }; 98 99 class Mknod_7_11: public FuseTest { 100 public: 101 virtual void SetUp() { 102 m_kernel_minor_version = 11; 103 if (geteuid() != 0) { 104 GTEST_SKIP() << "Only root may use most mknod(2) variations"; 105 } 106 FuseTest::SetUp(); 107 } 108 109 void expect_lookup(const char *relpath, uint64_t ino, uint64_t size) 110 { 111 FuseTest::expect_lookup_7_8(relpath, ino, S_IFREG | 0644, size, 1); 112 } 113 114 /* Test an OK creation of a file with the given mode and device number */ 115 void expect_mknod(mode_t mode, dev_t dev) { 116 uint64_t ino = 42; 117 118 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 119 .WillOnce(Invoke(ReturnErrno(ENOENT))); 120 121 EXPECT_CALL(*m_mock, process( 122 ResultOf([=](auto in) { 123 const char *name = (const char*)in.body.bytes + 124 FUSE_COMPAT_MKNOD_IN_SIZE; 125 return (in.header.opcode == FUSE_MKNOD && 126 in.body.mknod.mode == mode && 127 in.body.mknod.rdev == (uint32_t)dev && 128 (0 == strcmp(RELPATH, name))); 129 }, Eq(true)), 130 _) 131 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 132 SET_OUT_HEADER_LEN(out, entry); 133 out.body.entry.attr.mode = mode; 134 out.body.entry.nodeid = ino; 135 out.body.entry.entry_valid = UINT64_MAX; 136 out.body.entry.attr_valid = UINT64_MAX; 137 out.body.entry.attr.rdev = dev; 138 }))); 139 } 140 141 }; 142 143 /* 144 * mknod(2) should be able to create block devices on a FUSE filesystem. Even 145 * though FreeBSD doesn't use block devices, this is useful when copying media 146 * from or preparing media for other operating systems. 147 */ 148 TEST_F(Mknod, blk) 149 { 150 mode_t mode = S_IFBLK | 0755; 151 dev_t rdev = 0xfe00; /* /dev/vda's device number on Linux */ 152 expect_mknod(mode, rdev); 153 EXPECT_EQ(0, mknod(FULLPATH, mode, rdev)) << strerror(errno); 154 } 155 156 TEST_F(Mknod, chr) 157 { 158 mode_t mode = S_IFCHR | 0755; 159 dev_t rdev = 54; /* /dev/fuse's device number */ 160 expect_mknod(mode, rdev); 161 EXPECT_EQ(0, mknod(FULLPATH, mode, rdev)) << strerror(errno); 162 } 163 164 /* 165 * The daemon is responsible for checking file permissions (unless the 166 * default_permissions mount option was used) 167 */ 168 TEST_F(Mknod, eperm) 169 { 170 mode_t mode = S_IFIFO | 0755; 171 172 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 173 .WillOnce(Invoke(ReturnErrno(ENOENT))); 174 175 EXPECT_CALL(*m_mock, process( 176 ResultOf([=](auto in) { 177 const char *name = (const char*)in.body.bytes + 178 sizeof(fuse_mknod_in); 179 return (in.header.opcode == FUSE_MKNOD && 180 in.body.mknod.mode == mode && 181 (0 == strcmp(RELPATH, name))); 182 }, Eq(true)), 183 _) 184 ).WillOnce(Invoke(ReturnErrno(EPERM))); 185 EXPECT_NE(0, mkfifo(FULLPATH, mode)); 186 EXPECT_EQ(EPERM, errno); 187 } 188 189 TEST_F(Mknod, fifo) 190 { 191 mode_t mode = S_IFIFO | 0755; 192 dev_t rdev = VNOVAL; /* Fifos don't have device numbers */ 193 expect_mknod(mode, rdev); 194 EXPECT_EQ(0, mkfifo(FULLPATH, mode)) << strerror(errno); 195 } 196 197 /* 198 * Create a unix-domain socket. 199 * 200 * This test case doesn't actually need root privileges. 201 */ 202 TEST_F(Mknod, socket) 203 { 204 mode_t mode = S_IFSOCK | 0755; 205 struct sockaddr_un sa; 206 int fd; 207 dev_t rdev = -1; /* Really it's a don't care */ 208 209 expect_mknod(mode, rdev); 210 211 fd = socket(AF_UNIX, SOCK_STREAM, 0); 212 ASSERT_LE(0, fd) << strerror(errno); 213 sa.sun_family = AF_UNIX; 214 strlcpy(sa.sun_path, FULLPATH, sizeof(sa.sun_path)); 215 ASSERT_EQ(0, bind(fd, (struct sockaddr*)&sa, sizeof(sa))) 216 << strerror(errno); 217 } 218 219 /* 220 * fusefs(5) lacks VOP_WHITEOUT support. No bugzilla entry, because that's a 221 * feature, not a bug 222 */ 223 TEST_F(Mknod, DISABLED_whiteout) 224 { 225 mode_t mode = S_IFWHT | 0755; 226 dev_t rdev = VNOVAL; /* whiteouts don't have device numbers */ 227 expect_mknod(mode, rdev); 228 EXPECT_EQ(0, mknod(FULLPATH, mode, 0)) << strerror(errno); 229 } 230 231 /* A server built at protocol version 7.11 or earlier can still use mknod */ 232 TEST_F(Mknod_7_11, fifo) 233 { 234 mode_t mode = S_IFIFO | 0755; 235 dev_t rdev = VNOVAL; 236 expect_mknod(mode, rdev); 237 EXPECT_EQ(0, mkfifo(FULLPATH, mode)) << strerror(errno); 238 } 239