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