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 return 0; 221 } 222 ); 223 ASSERT_EQ(0, WEXITSTATUS(status)); 224 225 close(fd1); 226 } 227 228 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236340 */ 229 TEST_F(Open, DISABLED_o_append) 230 { 231 test_ok(O_WRONLY | O_APPEND, O_WRONLY | O_APPEND); 232 } 233 234 /* The kernel is supposed to filter out this flag */ 235 TEST_F(Open, o_creat) 236 { 237 test_ok(O_WRONLY | O_CREAT, O_WRONLY); 238 } 239 240 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236340 */ 241 TEST_F(Open, DISABLED_o_direct) 242 { 243 test_ok(O_WRONLY | O_DIRECT, O_WRONLY | O_DIRECT); 244 } 245 246 /* The kernel is supposed to filter out this flag */ 247 TEST_F(Open, o_excl) 248 { 249 test_ok(O_WRONLY | O_EXCL, O_WRONLY); 250 } 251 252 TEST_F(Open, o_exec) 253 { 254 test_ok(O_EXEC, O_EXEC); 255 } 256 257 /* The kernel is supposed to filter out this flag */ 258 TEST_F(Open, o_noctty) 259 { 260 test_ok(O_WRONLY | O_NOCTTY, O_WRONLY); 261 } 262 263 TEST_F(Open, o_rdonly) 264 { 265 test_ok(O_RDONLY, O_RDONLY); 266 } 267 268 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236340 */ 269 TEST_F(Open, DISABLED_o_trunc) 270 { 271 test_ok(O_WRONLY | O_TRUNC, O_WRONLY | O_TRUNC); 272 } 273 274 TEST_F(Open, o_wronly) 275 { 276 test_ok(O_WRONLY, O_WRONLY); 277 } 278 279 TEST_F(Open, o_rdwr) 280 { 281 test_ok(O_RDWR, O_RDWR); 282 } 283 284 /* 285 * Without FUSE_NO_OPEN_SUPPORT, returning ENOSYS is an error 286 */ 287 TEST_F(Open, enosys) 288 { 289 const char FULLPATH[] = "mountpoint/some_file.txt"; 290 const char RELPATH[] = "some_file.txt"; 291 uint64_t ino = 42; 292 int fd; 293 294 FuseTest::expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); 295 EXPECT_CALL(*m_mock, process( 296 ResultOf([=](auto in) { 297 return (in.header.opcode == FUSE_OPEN && 298 in.body.open.flags == (uint32_t)O_RDONLY && 299 in.header.nodeid == ino); 300 }, Eq(true)), 301 _) 302 ).Times(1) 303 .WillOnce(Invoke(ReturnErrno(ENOSYS))); 304 305 fd = open(FULLPATH, O_RDONLY); 306 ASSERT_EQ(-1, fd) << strerror(errno); 307 EXPECT_EQ(ENOSYS, errno); 308 } 309 310 /* 311 * If a fuse server sets FUSE_NO_OPEN_SUPPORT and returns ENOSYS to a 312 * FUSE_OPEN, then it and subsequent FUSE_OPEN and FUSE_RELEASE operations will 313 * also succeed automatically without being sent to the server. 314 */ 315 TEST_F(OpenNoOpenSupport, enosys) 316 { 317 const char FULLPATH[] = "mountpoint/some_file.txt"; 318 const char RELPATH[] = "some_file.txt"; 319 uint64_t ino = 42; 320 int fd; 321 322 FuseTest::expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 2); 323 EXPECT_CALL(*m_mock, process( 324 ResultOf([=](auto in) { 325 return (in.header.opcode == FUSE_OPEN && 326 in.body.open.flags == (uint32_t)O_RDONLY && 327 in.header.nodeid == ino); 328 }, Eq(true)), 329 _) 330 ).Times(1) 331 .WillOnce(Invoke(ReturnErrno(ENOSYS))); 332 expect_flush(ino, 1, ReturnErrno(ENOSYS)); 333 334 fd = open(FULLPATH, O_RDONLY); 335 ASSERT_LE(0, fd) << strerror(errno); 336 close(fd); 337 338 fd = open(FULLPATH, O_RDONLY); 339 ASSERT_LE(0, fd) << strerror(errno); 340 341 leak(fd); 342 } 343