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 <sys/wait.h> 33 #include <fcntl.h> 34 } 35 36 #include "mockfs.hh" 37 #include "utils.hh" 38 39 using namespace testing; 40 41 class Open: public FuseTest { 42 43 public: 44 45 /* Test an OK open of a file with the given flags */ 46 void test_ok(int os_flags, int fuse_flags) { 47 const char FULLPATH[] = "mountpoint/some_file.txt"; 48 const char RELPATH[] = "some_file.txt"; 49 uint64_t ino = 42; 50 int fd; 51 52 FuseTest::expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); 53 EXPECT_CALL(*m_mock, process( 54 ResultOf([=](auto in) { 55 return (in.header.opcode == FUSE_OPEN && 56 in.body.open.flags == (uint32_t)fuse_flags && 57 in.header.nodeid == ino); 58 }, Eq(true)), 59 _) 60 ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) { 61 out.header.len = sizeof(out.header); 62 SET_OUT_HEADER_LEN(out, open); 63 }))); 64 65 fd = open(FULLPATH, os_flags); 66 EXPECT_LE(0, fd) << strerror(errno); 67 leak(fd); 68 } 69 }; 70 71 72 /* 73 * fusefs(5) does not support I/O on device nodes (neither does UFS). But it 74 * shouldn't crash 75 */ 76 TEST_F(Open, chr) 77 { 78 const char FULLPATH[] = "mountpoint/zero"; 79 const char RELPATH[] = "zero"; 80 uint64_t ino = 42; 81 82 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 83 .WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 84 SET_OUT_HEADER_LEN(out, entry); 85 out.body.entry.attr.mode = S_IFCHR | 0644; 86 out.body.entry.nodeid = ino; 87 out.body.entry.attr.nlink = 1; 88 out.body.entry.attr_valid = UINT64_MAX; 89 out.body.entry.attr.rdev = 44; /* /dev/zero's rdev */ 90 }))); 91 92 ASSERT_EQ(-1, open(FULLPATH, O_RDONLY)); 93 EXPECT_EQ(EOPNOTSUPP, errno); 94 } 95 96 /* 97 * The fuse daemon fails the request with enoent. This usually indicates a 98 * race condition: some other FUSE client removed the file in between when the 99 * kernel checked for it with lookup and tried to open it 100 */ 101 TEST_F(Open, enoent) 102 { 103 const char FULLPATH[] = "mountpoint/some_file.txt"; 104 const char RELPATH[] = "some_file.txt"; 105 uint64_t ino = 42; 106 107 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); 108 EXPECT_CALL(*m_mock, process( 109 ResultOf([=](auto in) { 110 return (in.header.opcode == FUSE_OPEN && 111 in.header.nodeid == ino); 112 }, Eq(true)), 113 _) 114 ).WillOnce(Invoke(ReturnErrno(ENOENT))); 115 EXPECT_NE(0, open(FULLPATH, O_RDONLY)); 116 EXPECT_EQ(ENOENT, errno); 117 } 118 119 /* 120 * The daemon is responsible for checking file permissions (unless the 121 * default_permissions mount option was used) 122 */ 123 TEST_F(Open, eperm) 124 { 125 const char FULLPATH[] = "mountpoint/some_file.txt"; 126 const char RELPATH[] = "some_file.txt"; 127 uint64_t ino = 42; 128 129 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); 130 EXPECT_CALL(*m_mock, process( 131 ResultOf([=](auto in) { 132 return (in.header.opcode == FUSE_OPEN && 133 in.header.nodeid == ino); 134 }, Eq(true)), 135 _) 136 ).WillOnce(Invoke(ReturnErrno(EPERM))); 137 EXPECT_NE(0, open(FULLPATH, O_RDONLY)); 138 EXPECT_EQ(EPERM, errno); 139 } 140 141 /* 142 * fusefs must issue multiple FUSE_OPEN operations if clients with different 143 * credentials open the same file, even if they use the same mode. This is 144 * necessary so that the daemon can validate each set of credentials. 145 */ 146 TEST_F(Open, multiple_creds) 147 { 148 const static char FULLPATH[] = "mountpoint/some_file.txt"; 149 const static char RELPATH[] = "some_file.txt"; 150 int fd1, status; 151 const static uint64_t ino = 42; 152 const static uint64_t fh0 = 100, fh1 = 200; 153 154 /* Fork a child to open the file with different credentials */ 155 fork(false, &status, [&] { 156 157 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 2); 158 EXPECT_CALL(*m_mock, process( 159 ResultOf([=](auto in) { 160 return (in.header.opcode == FUSE_OPEN && 161 in.header.pid == (uint32_t)getpid() && 162 in.header.nodeid == ino); 163 }, Eq(true)), 164 _) 165 ).WillOnce(Invoke( 166 ReturnImmediate([](auto in __unused, auto& out) { 167 out.body.open.fh = fh0; 168 out.header.len = sizeof(out.header); 169 SET_OUT_HEADER_LEN(out, open); 170 }))); 171 172 EXPECT_CALL(*m_mock, process( 173 ResultOf([=](auto in) { 174 return (in.header.opcode == FUSE_OPEN && 175 in.header.pid != (uint32_t)getpid() && 176 in.header.nodeid == ino); 177 }, Eq(true)), 178 _) 179 ).WillOnce(Invoke( 180 ReturnImmediate([](auto in __unused, auto& out) { 181 out.body.open.fh = fh1; 182 out.header.len = sizeof(out.header); 183 SET_OUT_HEADER_LEN(out, open); 184 }))); 185 expect_flush(ino, 2, ReturnErrno(0)); 186 expect_release(ino, fh0); 187 expect_release(ino, fh1); 188 189 fd1 = open(FULLPATH, O_RDONLY); 190 EXPECT_LE(0, fd1) << strerror(errno); 191 }, [] { 192 int fd0; 193 194 fd0 = open(FULLPATH, O_RDONLY); 195 if (fd0 < 0) { 196 perror("open"); 197 return(1); 198 } 199 return 0; 200 } 201 ); 202 ASSERT_EQ(0, WEXITSTATUS(status)); 203 204 close(fd1); 205 } 206 207 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236340 */ 208 TEST_F(Open, DISABLED_o_append) 209 { 210 test_ok(O_WRONLY | O_APPEND, O_WRONLY | O_APPEND); 211 } 212 213 /* The kernel is supposed to filter out this flag */ 214 TEST_F(Open, o_creat) 215 { 216 test_ok(O_WRONLY | O_CREAT, O_WRONLY); 217 } 218 219 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236340 */ 220 TEST_F(Open, DISABLED_o_direct) 221 { 222 test_ok(O_WRONLY | O_DIRECT, O_WRONLY | O_DIRECT); 223 } 224 225 /* The kernel is supposed to filter out this flag */ 226 TEST_F(Open, o_excl) 227 { 228 test_ok(O_WRONLY | O_EXCL, O_WRONLY); 229 } 230 231 TEST_F(Open, o_exec) 232 { 233 test_ok(O_EXEC, O_EXEC); 234 } 235 236 /* The kernel is supposed to filter out this flag */ 237 TEST_F(Open, o_noctty) 238 { 239 test_ok(O_WRONLY | O_NOCTTY, O_WRONLY); 240 } 241 242 TEST_F(Open, o_rdonly) 243 { 244 test_ok(O_RDONLY, O_RDONLY); 245 } 246 247 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236340 */ 248 TEST_F(Open, DISABLED_o_trunc) 249 { 250 test_ok(O_WRONLY | O_TRUNC, O_WRONLY | O_TRUNC); 251 } 252 253 TEST_F(Open, o_wronly) 254 { 255 test_ok(O_WRONLY, O_WRONLY); 256 } 257 258 TEST_F(Open, o_rdwr) 259 { 260 test_ok(O_RDWR, O_RDWR); 261 } 262 263