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