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