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 31 extern "C" { 32 #include <fcntl.h> 33 } 34 35 #include "mockfs.hh" 36 #include "utils.hh" 37 38 using namespace testing; 39 40 class Create: public FuseTest {}; 41 42 /* 43 * If FUSE_CREATE sets the attr_valid, then subsequent GETATTRs should use the 44 * attribute cache 45 */ 46 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=235775 */ 47 TEST_F(Create, DISABLED_attr_cache) 48 { 49 const char FULLPATH[] = "mountpoint/some_file.txt"; 50 const char RELPATH[] = "some_file.txt"; 51 mode_t mode = 0755; 52 uint64_t ino = 42; 53 int fd; 54 55 EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); 56 57 EXPECT_CALL(*m_mock, process( 58 ResultOf([=](auto in) { 59 const char *name = (const char*)in->body.bytes + 60 sizeof(fuse_open_in); 61 return (in->header.opcode == FUSE_CREATE && 62 (0 == strcmp(RELPATH, name))); 63 }, Eq(true)), 64 _) 65 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { 66 SET_OUT_HEADER_LEN(out, create); 67 out->body.create.entry.attr.mode = S_IFREG | mode; 68 out->body.create.entry.nodeid = ino; 69 out->body.create.entry.entry_valid = UINT64_MAX; 70 out->body.create.entry.attr_valid = UINT64_MAX; 71 }))); 72 73 EXPECT_CALL(*m_mock, process( 74 ResultOf([=](auto in) { 75 return (in->header.opcode == FUSE_GETATTR && 76 in->header.nodeid == ino); 77 }, Eq(true)), 78 _) 79 ).Times(0); 80 81 fd = open(FULLPATH, O_CREAT | O_EXCL, mode); 82 EXPECT_LE(0, fd) << strerror(errno); 83 /* Deliberately leak fd. close(2) will be tested in release.cc */ 84 } 85 86 /* 87 * The fuse daemon fails the request with EEXIST. This usually indicates a 88 * race condition: some other FUSE client created the file in between when the 89 * kernel checked for it with lookup and tried to create it with create 90 */ 91 TEST_F(Create, eexist) 92 { 93 const char FULLPATH[] = "mountpoint/some_file.txt"; 94 const char RELPATH[] = "some_file.txt"; 95 mode_t mode = 0755; 96 97 EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); 98 99 EXPECT_CALL(*m_mock, process( 100 ResultOf([=](auto in) { 101 const char *name = (const char*)in->body.bytes + 102 sizeof(fuse_open_in); 103 return (in->header.opcode == FUSE_CREATE && 104 (0 == strcmp(RELPATH, name))); 105 }, Eq(true)), 106 _) 107 ).WillOnce(Invoke(ReturnErrno(EEXIST))); 108 EXPECT_NE(0, open(FULLPATH, O_CREAT | O_EXCL, mode)); 109 EXPECT_EQ(EEXIST, errno); 110 } 111 112 /* 113 * If the daemon doesn't implement FUSE_CREATE, then the kernel should fallback 114 * to FUSE_MKNOD/FUSE_OPEN 115 */ 116 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236236 */ 117 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236557 */ 118 TEST_F(Create, DISABLED_Enosys) 119 { 120 const char FULLPATH[] = "mountpoint/some_file.txt"; 121 const char RELPATH[] = "some_file.txt"; 122 mode_t mode = 0755; 123 uint64_t ino = 42; 124 int fd; 125 126 EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); 127 128 EXPECT_CALL(*m_mock, process( 129 ResultOf([=](auto in) { 130 const char *name = (const char*)in->body.bytes + 131 sizeof(fuse_open_in); 132 return (in->header.opcode == FUSE_CREATE && 133 (0 == strcmp(RELPATH, name))); 134 }, Eq(true)), 135 _) 136 ).WillOnce(Invoke(ReturnErrno(ENOSYS))); 137 138 EXPECT_CALL(*m_mock, process( 139 ResultOf([=](auto in) { 140 const char *name = (const char*)in->body.bytes + 141 sizeof(fuse_mknod_in); 142 return (in->header.opcode == FUSE_MKNOD && 143 in->body.mknod.mode == (S_IFREG | mode) && 144 in->body.mknod.rdev == 0 && 145 (0 == strcmp(RELPATH, name))); 146 }, Eq(true)), 147 _) 148 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto out) { 149 SET_OUT_HEADER_LEN(out, create); 150 out->body.create.entry.attr.mode = S_IFREG | mode; 151 out->body.create.entry.nodeid = ino; 152 out->body.create.entry.entry_valid = UINT64_MAX; 153 out->body.create.entry.attr_valid = UINT64_MAX; 154 }))); 155 156 EXPECT_CALL(*m_mock, process( 157 ResultOf([=](auto in) { 158 return (in->header.opcode == FUSE_OPEN && 159 in->header.nodeid == ino); 160 }, Eq(true)), 161 _) 162 ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto out) { 163 out->header.len = sizeof(out->header); 164 SET_OUT_HEADER_LEN(out, open); 165 }))); 166 167 /* Until the attr cache is working, we may send an additional GETATTR */ 168 EXPECT_CALL(*m_mock, process( 169 ResultOf([=](auto in) { 170 return (in->header.opcode == FUSE_GETATTR && 171 in->header.nodeid == ino); 172 }, Eq(true)), 173 _) 174 ).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto out) { 175 SET_OUT_HEADER_LEN(out, attr); 176 out->body.attr.attr.ino = ino; // Must match nodeid 177 out->body.attr.attr.mode = S_IFREG | 0644; 178 }))); 179 180 fd = open(FULLPATH, O_CREAT | O_EXCL, mode); 181 EXPECT_LE(0, fd) << strerror(errno); 182 /* Deliberately leak fd. close(2) will be tested in release.cc */ 183 } 184 185 /* 186 * Creating a new file after FUSE_LOOKUP returned a negative cache entry 187 */ 188 TEST_F(Create, entry_cache_negative) 189 { 190 const char FULLPATH[] = "mountpoint/some_file.txt"; 191 const char RELPATH[] = "some_file.txt"; 192 mode_t mode = 0755; 193 uint64_t ino = 42; 194 int fd; 195 /* 196 * Set entry_valid = 0 because this test isn't concerned with whether 197 * or not we actually cache negative entries, only with whether we 198 * interpret negative cache responses correctly. 199 */ 200 struct timespec entry_valid = {.tv_sec = 0, .tv_nsec = 0}; 201 202 /* create will first do a LOOKUP, adding a negative cache entry */ 203 EXPECT_LOOKUP(1, RELPATH).WillOnce(ReturnNegativeCache(&entry_valid)); 204 205 EXPECT_CALL(*m_mock, process( 206 ResultOf([=](auto in) { 207 const char *name = (const char*)in->body.bytes + 208 sizeof(fuse_open_in); 209 return (in->header.opcode == FUSE_CREATE && 210 (0 == strcmp(RELPATH, name))); 211 }, Eq(true)), 212 _) 213 ).WillOnce(Invoke(ReturnImmediate([=](auto i __unused, auto out) { 214 SET_OUT_HEADER_LEN(out, create); 215 out->body.create.entry.attr.mode = S_IFREG | mode; 216 out->body.create.entry.nodeid = ino; 217 out->body.create.entry.entry_valid = UINT64_MAX; 218 out->body.create.entry.attr_valid = UINT64_MAX; 219 }))); 220 221 /* Until the attr cache is working, we may send an additional GETATTR */ 222 EXPECT_CALL(*m_mock, process( 223 ResultOf([=](auto in) { 224 return (in->header.opcode == FUSE_GETATTR && 225 in->header.nodeid == ino); 226 }, Eq(true)), 227 _) 228 ).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto out) { 229 SET_OUT_HEADER_LEN(out, attr); 230 out->body.attr.attr.ino = ino; // Must match nodeid 231 out->body.attr.attr.mode = S_IFREG | 0644; 232 }))); 233 234 fd = open(FULLPATH, O_CREAT | O_EXCL, mode); 235 ASSERT_LE(0, fd) << strerror(errno); 236 /* Deliberately leak fd. close(2) will be tested in release.cc */ 237 } 238 239 /* 240 * Creating a new file should purge any negative namecache entries 241 */ 242 TEST_F(Create, entry_cache_negative_purge) 243 { 244 const char FULLPATH[] = "mountpoint/some_file.txt"; 245 const char RELPATH[] = "some_file.txt"; 246 mode_t mode = 0755; 247 uint64_t ino = 42; 248 int fd; 249 struct timespec entry_valid = {.tv_sec = TIME_T_MAX, .tv_nsec = 0}; 250 251 /* create will first do a LOOKUP, adding a negative cache entry */ 252 EXPECT_LOOKUP(1, RELPATH).Times(1) 253 .WillOnce(Invoke(ReturnNegativeCache(&entry_valid))) 254 .RetiresOnSaturation(); 255 256 /* Then the CREATE should purge the negative cache entry */ 257 EXPECT_CALL(*m_mock, process( 258 ResultOf([=](auto in) { 259 const char *name = (const char*)in->body.bytes + 260 sizeof(fuse_open_in); 261 return (in->header.opcode == FUSE_CREATE && 262 (0 == strcmp(RELPATH, name))); 263 }, Eq(true)), 264 _) 265 ).WillOnce(Invoke(ReturnImmediate([=](auto i __unused, auto out) { 266 SET_OUT_HEADER_LEN(out, create); 267 out->body.create.entry.attr.mode = S_IFREG | mode; 268 out->body.create.entry.nodeid = ino; 269 out->body.create.entry.attr_valid = UINT64_MAX; 270 }))); 271 272 /* Until the attr cache is working, we may send an additional GETATTR */ 273 EXPECT_CALL(*m_mock, process( 274 ResultOf([=](auto in) { 275 return (in->header.opcode == FUSE_GETATTR && 276 in->header.nodeid == ino); 277 }, Eq(true)), 278 _) 279 ).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto out) { 280 SET_OUT_HEADER_LEN(out, attr); 281 out->body.attr.attr.ino = ino; // Must match nodeid 282 out->body.attr.attr.mode = S_IFREG | 0644; 283 }))); 284 285 fd = open(FULLPATH, O_CREAT | O_EXCL, mode); 286 ASSERT_LE(0, fd) << strerror(errno); 287 288 /* Finally, a subsequent lookup should query the daemon */ 289 expect_lookup(RELPATH, ino, S_IFREG | mode, 0, 1); 290 291 ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); 292 /* Deliberately leak fd. close(2) will be tested in release.cc */ 293 } 294 295 /* 296 * The daemon is responsible for checking file permissions (unless the 297 * default_permissions mount option was used) 298 */ 299 TEST_F(Create, eperm) 300 { 301 const char FULLPATH[] = "mountpoint/some_file.txt"; 302 const char RELPATH[] = "some_file.txt"; 303 mode_t mode = 0755; 304 305 EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); 306 307 EXPECT_CALL(*m_mock, process( 308 ResultOf([=](auto in) { 309 const char *name = (const char*)in->body.bytes + 310 sizeof(fuse_open_in); 311 return (in->header.opcode == FUSE_CREATE && 312 (0 == strcmp(RELPATH, name))); 313 }, Eq(true)), 314 _) 315 ).WillOnce(Invoke(ReturnErrno(EPERM))); 316 EXPECT_NE(0, open(FULLPATH, O_CREAT | O_EXCL, mode)); 317 EXPECT_EQ(EPERM, errno); 318 } 319 320 TEST_F(Create, ok) 321 { 322 const char FULLPATH[] = "mountpoint/some_file.txt"; 323 const char RELPATH[] = "some_file.txt"; 324 mode_t mode = 0755; 325 uint64_t ino = 42; 326 int fd; 327 328 EXPECT_LOOKUP(1, RELPATH).WillOnce(Invoke(ReturnErrno(ENOENT))); 329 330 EXPECT_CALL(*m_mock, process( 331 ResultOf([=](auto in) { 332 const char *name = (const char*)in->body.bytes + 333 sizeof(fuse_open_in); 334 return (in->header.opcode == FUSE_CREATE && 335 (0 == strcmp(RELPATH, name))); 336 }, Eq(true)), 337 _) 338 ).WillOnce(Invoke(ReturnImmediate([=](auto i __unused, auto out) { 339 SET_OUT_HEADER_LEN(out, create); 340 out->body.create.entry.attr.mode = S_IFREG | mode; 341 out->body.create.entry.nodeid = ino; 342 out->body.create.entry.entry_valid = UINT64_MAX; 343 out->body.create.entry.attr_valid = UINT64_MAX; 344 }))); 345 346 /* Until the attr cache is working, we may send an additional GETATTR */ 347 EXPECT_CALL(*m_mock, process( 348 ResultOf([=](auto in) { 349 return (in->header.opcode == FUSE_GETATTR && 350 in->header.nodeid == ino); 351 }, Eq(true)), 352 _) 353 ).WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto out) { 354 SET_OUT_HEADER_LEN(out, attr); 355 out->body.attr.attr.ino = ino; // Must match nodeid 356 out->body.attr.attr.mode = S_IFREG | 0644; 357 }))); 358 359 fd = open(FULLPATH, O_CREAT | O_EXCL, mode); 360 EXPECT_LE(0, fd) << strerror(errno); 361 /* Deliberately leak fd. close(2) will be tested in release.cc */ 362 } 363