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 33extern "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 42using namespace testing; 43 44#ifndef VNOVAL 45#define VNOVAL (-1) /* Defined in sys/vnode.h */ 46#endif 47 48const char FULLPATH[] = "mountpoint/some_file.txt"; 49const char RELPATH[] = "some_file.txt"; 50 51class Mknod: public FuseTest { 52 53mode_t m_oldmask; 54const static mode_t c_umask = 022; 55 56public: 57 58Mknod() { 59 m_oldmask = umask(c_umask); 60} 61 62virtual void SetUp() { 63 if (geteuid() != 0) { 64 GTEST_SKIP() << "Only root may use most mknod(2) variations"; 65 } 66 FuseTest::SetUp(); 67} 68 69virtual 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 */ 75void 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 104class Mknod_7_11: public FuseTest { 105public: 106virtual 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 114void 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 */ 120void 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 */ 153TEST_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 161TEST_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 */ 173TEST_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 194TEST_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 */ 207TEST_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 */ 231TEST_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 */ 240TEST_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