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