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