1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 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 34 #include <fcntl.h> 35 #include <semaphore.h> 36 } 37 38 #include "mockfs.hh" 39 #include "utils.hh" 40 41 using namespace testing; 42 43 class Open: public FuseTest { 44 45 public: 46 47 /* Test an OK open of a file with the given flags */ 48 void test_ok(int os_flags, int fuse_flags) { 49 const char FULLPATH[] = "mountpoint/some_file.txt"; 50 const char RELPATH[] = "some_file.txt"; 51 uint64_t ino = 42; 52 int fd; 53 54 FuseTest::expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); 55 EXPECT_CALL(*m_mock, process( 56 ResultOf([=](auto in) { 57 return (in.header.opcode == FUSE_OPEN && 58 in.body.open.flags == (uint32_t)fuse_flags && 59 in.header.nodeid == ino); 60 }, Eq(true)), 61 _) 62 ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) { 63 out.header.len = sizeof(out.header); 64 SET_OUT_HEADER_LEN(out, open); 65 }))); 66 67 fd = open(FULLPATH, os_flags); 68 ASSERT_LE(0, fd) << strerror(errno); 69 leak(fd); 70 } 71 }; 72 73 74 class OpenNoOpenSupport: public FuseTest { 75 virtual void SetUp() { 76 m_init_flags = FUSE_NO_OPEN_SUPPORT; 77 FuseTest::SetUp(); 78 } 79 }; 80 81 /* 82 * fusefs(5) does not support I/O on device nodes (neither does UFS). But it 83 * shouldn't crash 84 */ 85 TEST_F(Open, chr) 86 { 87 const char FULLPATH[] = "mountpoint/zero"; 88 const char RELPATH[] = "zero"; 89 uint64_t ino = 42; 90 91 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 92 .WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 93 SET_OUT_HEADER_LEN(out, entry); 94 out.body.entry.attr.mode = S_IFCHR | 0644; 95 out.body.entry.nodeid = ino; 96 out.body.entry.attr.nlink = 1; 97 out.body.entry.attr_valid = UINT64_MAX; 98 out.body.entry.attr.rdev = 44; /* /dev/zero's rdev */ 99 }))); 100 101 ASSERT_EQ(-1, open(FULLPATH, O_RDONLY)); 102 EXPECT_EQ(EOPNOTSUPP, errno); 103 } 104 105 /* 106 * The fuse daemon fails the request with enoent. This usually indicates a 107 * race condition: some other FUSE client removed the file in between when the 108 * kernel checked for it with lookup and tried to open it 109 */ 110 TEST_F(Open, enoent) 111 { 112 const char FULLPATH[] = "mountpoint/some_file.txt"; 113 const char RELPATH[] = "some_file.txt"; 114 uint64_t ino = 42; 115 sem_t sem; 116 117 ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno); 118 119 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); 120 EXPECT_CALL(*m_mock, process( 121 ResultOf([=](auto in) { 122 return (in.header.opcode == FUSE_OPEN && 123 in.header.nodeid == ino); 124 }, Eq(true)), 125 _) 126 ).WillOnce(Invoke(ReturnErrno(ENOENT))); 127 // Since FUSE_OPEN returns ENOENT, the kernel will reclaim the vnode 128 // and send a FUSE_FORGET 129 expect_forget(ino, 1, &sem); 130 131 ASSERT_EQ(-1, open(FULLPATH, O_RDONLY)); 132 EXPECT_EQ(ENOENT, errno); 133 134 sem_wait(&sem); 135 sem_destroy(&sem); 136 } 137 138 /* 139 * The daemon is responsible for checking file permissions (unless the 140 * default_permissions mount option was used) 141 */ 142 TEST_F(Open, eperm) 143 { 144 const char FULLPATH[] = "mountpoint/some_file.txt"; 145 const char RELPATH[] = "some_file.txt"; 146 uint64_t ino = 42; 147 148 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); 149 EXPECT_CALL(*m_mock, process( 150 ResultOf([=](auto in) { 151 return (in.header.opcode == FUSE_OPEN && 152 in.header.nodeid == ino); 153 }, Eq(true)), 154 _) 155 ).WillOnce(Invoke(ReturnErrno(EPERM))); 156 ASSERT_EQ(-1, open(FULLPATH, O_RDONLY)); 157 EXPECT_EQ(EPERM, errno); 158 } 159 160 /* 161 * fusefs must issue multiple FUSE_OPEN operations if clients with different 162 * credentials open the same file, even if they use the same mode. This is 163 * necessary so that the daemon can validate each set of credentials. 164 */ 165 TEST_F(Open, multiple_creds) 166 { 167 const static char FULLPATH[] = "mountpoint/some_file.txt"; 168 const static char RELPATH[] = "some_file.txt"; 169 int fd1, status; 170 const static uint64_t ino = 42; 171 const static uint64_t fh0 = 100, fh1 = 200; 172 173 /* Fork a child to open the file with different credentials */ 174 fork(false, &status, [&] { 175 176 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 2); 177 EXPECT_CALL(*m_mock, process( 178 ResultOf([=](auto in) { 179 return (in.header.opcode == FUSE_OPEN && 180 in.header.pid == (uint32_t)getpid() && 181 in.header.nodeid == ino); 182 }, Eq(true)), 183 _) 184 ).WillOnce(Invoke( 185 ReturnImmediate([](auto in __unused, auto& out) { 186 out.body.open.fh = fh0; 187 out.header.len = sizeof(out.header); 188 SET_OUT_HEADER_LEN(out, open); 189 }))); 190 191 EXPECT_CALL(*m_mock, process( 192 ResultOf([=](auto in) { 193 return (in.header.opcode == FUSE_OPEN && 194 in.header.pid != (uint32_t)getpid() && 195 in.header.nodeid == ino); 196 }, Eq(true)), 197 _) 198 ).WillOnce(Invoke( 199 ReturnImmediate([](auto in __unused, auto& out) { 200 out.body.open.fh = fh1; 201 out.header.len = sizeof(out.header); 202 SET_OUT_HEADER_LEN(out, open); 203 }))); 204 expect_flush(ino, 2, ReturnErrno(0)); 205 expect_release(ino, fh0); 206 expect_release(ino, fh1); 207 208 fd1 = open(FULLPATH, O_RDONLY); 209 ASSERT_LE(0, fd1) << strerror(errno); 210 }, [] { 211 int fd0; 212 213 fd0 = open(FULLPATH, O_RDONLY); 214 if (fd0 < 0) { 215 perror("open"); 216 return(1); 217 } 218 leak(fd0); 219 return 0; 220 } 221 ); 222 ASSERT_EQ(0, WEXITSTATUS(status)); 223 224 close(fd1); 225 } 226 227 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236340 */ 228 TEST_F(Open, DISABLED_o_append) 229 { 230 test_ok(O_WRONLY | O_APPEND, O_WRONLY | O_APPEND); 231 } 232 233 /* The kernel is supposed to filter out this flag */ 234 TEST_F(Open, o_creat) 235 { 236 test_ok(O_WRONLY | O_CREAT, O_WRONLY); 237 } 238 239 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236340 */ 240 TEST_F(Open, DISABLED_o_direct) 241 { 242 test_ok(O_WRONLY | O_DIRECT, O_WRONLY | O_DIRECT); 243 } 244 245 /* The kernel is supposed to filter out this flag */ 246 TEST_F(Open, o_excl) 247 { 248 test_ok(O_WRONLY | O_EXCL, O_WRONLY); 249 } 250 251 TEST_F(Open, o_exec) 252 { 253 test_ok(O_EXEC, O_EXEC); 254 } 255 256 /* The kernel is supposed to filter out this flag */ 257 TEST_F(Open, o_noctty) 258 { 259 test_ok(O_WRONLY | O_NOCTTY, O_WRONLY); 260 } 261 262 TEST_F(Open, o_rdonly) 263 { 264 test_ok(O_RDONLY, O_RDONLY); 265 } 266 267 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236340 */ 268 TEST_F(Open, DISABLED_o_trunc) 269 { 270 test_ok(O_WRONLY | O_TRUNC, O_WRONLY | O_TRUNC); 271 } 272 273 TEST_F(Open, o_wronly) 274 { 275 test_ok(O_WRONLY, O_WRONLY); 276 } 277 278 TEST_F(Open, o_rdwr) 279 { 280 test_ok(O_RDWR, O_RDWR); 281 } 282 283 /* 284 * Without FUSE_NO_OPEN_SUPPORT, returning ENOSYS is an error 285 */ 286 TEST_F(Open, enosys) 287 { 288 const char FULLPATH[] = "mountpoint/some_file.txt"; 289 const char RELPATH[] = "some_file.txt"; 290 uint64_t ino = 42; 291 int fd; 292 293 FuseTest::expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); 294 EXPECT_CALL(*m_mock, process( 295 ResultOf([=](auto in) { 296 return (in.header.opcode == FUSE_OPEN && 297 in.body.open.flags == (uint32_t)O_RDONLY && 298 in.header.nodeid == ino); 299 }, Eq(true)), 300 _) 301 ).Times(1) 302 .WillOnce(Invoke(ReturnErrno(ENOSYS))); 303 304 fd = open(FULLPATH, O_RDONLY); 305 ASSERT_EQ(-1, fd) << strerror(errno); 306 EXPECT_EQ(ENOSYS, errno); 307 } 308 309 /* 310 * If a fuse server sets FUSE_NO_OPEN_SUPPORT and returns ENOSYS to a 311 * FUSE_OPEN, then it and subsequent FUSE_OPEN and FUSE_RELEASE operations will 312 * also succeed automatically without being sent to the server. 313 */ 314 TEST_F(OpenNoOpenSupport, enosys) 315 { 316 const char FULLPATH[] = "mountpoint/some_file.txt"; 317 const char RELPATH[] = "some_file.txt"; 318 uint64_t ino = 42; 319 int fd; 320 321 FuseTest::expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 2); 322 EXPECT_CALL(*m_mock, process( 323 ResultOf([=](auto in) { 324 return (in.header.opcode == FUSE_OPEN && 325 in.body.open.flags == (uint32_t)O_RDONLY && 326 in.header.nodeid == ino); 327 }, Eq(true)), 328 _) 329 ).Times(1) 330 .WillOnce(Invoke(ReturnErrno(ENOSYS))); 331 expect_flush(ino, 1, ReturnErrno(ENOSYS)); 332 333 fd = open(FULLPATH, O_RDONLY); 334 ASSERT_LE(0, fd) << strerror(errno); 335 close(fd); 336 337 fd = open(FULLPATH, O_RDONLY); 338 ASSERT_LE(0, fd) << strerror(errno); 339 340 leak(fd); 341 } 342