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 <fcntl.h> 33 } 34 35 #include "mockfs.hh" 36 #include "utils.hh" 37 38 using namespace testing; 39 40 class Create: public FuseTest { 41 public: 42 43 void expect_create(const char *relpath, mode_t mode, ProcessMockerT r) 44 { 45 mode_t mask = umask(0); 46 (void)umask(mask); 47 48 EXPECT_CALL(*m_mock, process( 49 ResultOf([=](auto in) { 50 const char *name = (const char*)in.body.bytes + 51 sizeof(fuse_create_in); 52 return (in.header.opcode == FUSE_CREATE && 53 in.body.create.mode == mode && 54 in.body.create.umask == mask && 55 (0 == strcmp(relpath, name))); 56 }, Eq(true)), 57 _) 58 ).WillOnce(Invoke(r)); 59 } 60 61 }; 62 63 /* FUSE_CREATE operations for a protocol 7.8 server */ 64 class Create_7_8: public Create { 65 public: 66 virtual void SetUp() { 67 m_kernel_minor_version = 8; 68 Create::SetUp(); 69 } 70 71 void expect_create(const char *relpath, mode_t mode, ProcessMockerT r) 72 { 73 EXPECT_CALL(*m_mock, process( 74 ResultOf([=](auto in) { 75 const char *name = (const char*)in.body.bytes + 76 sizeof(fuse_open_in); 77 return (in.header.opcode == FUSE_CREATE && 78 in.body.create.mode == mode && 79 (0 == strcmp(relpath, name))); 80 }, Eq(true)), 81 _) 82 ).WillOnce(Invoke(r)); 83 } 84 85 }; 86 87 /* FUSE_CREATE operations for a server built at protocol <= 7.11 */ 88 class Create_7_11: public FuseTest { 89 public: 90 virtual void SetUp() { 91 m_kernel_minor_version = 11; 92 FuseTest::SetUp(); 93 } 94 95 void expect_create(const char *relpath, mode_t mode, ProcessMockerT r) 96 { 97 EXPECT_CALL(*m_mock, process( 98 ResultOf([=](auto in) { 99 const char *name = (const char*)in.body.bytes + 100 sizeof(fuse_open_in); 101 return (in.header.opcode == FUSE_CREATE && 102 in.body.create.mode == mode && 103 (0 == strcmp(relpath, name))); 104 }, Eq(true)), 105 _) 106 ).WillOnce(Invoke(r)); 107 } 108 109 }; 110 111 112 /* 113 * If FUSE_CREATE sets attr_valid, then subsequent GETATTRs should use the 114 * attribute cache 115 */ 116 TEST_F(Create, attr_cache) 117 { 118 const char FULLPATH[] = "mountpoint/some_file.txt"; 119 const char RELPATH[] = "some_file.txt"; 120 mode_t mode = S_IFREG | 0755; 121 uint64_t ino = 42; 122 int fd; 123 124 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 125 .WillOnce(Invoke(ReturnErrno(ENOENT))); 126 expect_create(RELPATH, mode, 127 ReturnImmediate([=](auto in __unused, auto& out) { 128 SET_OUT_HEADER_LEN(out, create); 129 out.body.create.entry.attr.mode = mode; 130 out.body.create.entry.nodeid = ino; 131 out.body.create.entry.entry_valid = UINT64_MAX; 132 out.body.create.entry.attr_valid = UINT64_MAX; 133 })); 134 135 EXPECT_CALL(*m_mock, process( 136 ResultOf([=](auto in) { 137 return (in.header.opcode == FUSE_GETATTR && 138 in.header.nodeid == ino); 139 }, Eq(true)), 140 _) 141 ).Times(0); 142 143 fd = open(FULLPATH, O_CREAT | O_EXCL, mode); 144 ASSERT_LE(0, fd) << strerror(errno); 145 leak(fd); 146 } 147 148 /* A successful CREATE operation should purge the parent dir's attr cache */ 149 TEST_F(Create, clear_attr_cache) 150 { 151 const char FULLPATH[] = "mountpoint/src"; 152 const char RELPATH[] = "src"; 153 mode_t mode = S_IFREG | 0755; 154 uint64_t ino = 42; 155 int fd; 156 struct stat sb; 157 158 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 159 .WillOnce(Invoke(ReturnErrno(ENOENT))); 160 EXPECT_CALL(*m_mock, process( 161 ResultOf([=](auto in) { 162 return (in.header.opcode == FUSE_GETATTR && 163 in.header.nodeid == FUSE_ROOT_ID); 164 }, Eq(true)), 165 _) 166 ).Times(2) 167 .WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) { 168 SET_OUT_HEADER_LEN(out, attr); 169 out.body.attr.attr.ino = FUSE_ROOT_ID; 170 out.body.attr.attr.mode = S_IFDIR | 0755; 171 out.body.attr.attr_valid = UINT64_MAX; 172 }))); 173 174 expect_create(RELPATH, mode, 175 ReturnImmediate([=](auto in __unused, auto& out) { 176 SET_OUT_HEADER_LEN(out, create); 177 out.body.create.entry.attr.mode = mode; 178 out.body.create.entry.nodeid = ino; 179 out.body.create.entry.entry_valid = UINT64_MAX; 180 out.body.create.entry.attr_valid = UINT64_MAX; 181 })); 182 183 EXPECT_EQ(0, stat("mountpoint", &sb)) << strerror(errno); 184 fd = open(FULLPATH, O_CREAT | O_EXCL, mode); 185 ASSERT_LE(0, fd) << strerror(errno); 186 EXPECT_EQ(0, stat("mountpoint", &sb)) << strerror(errno); 187 188 leak(fd); 189 } 190 191 /* 192 * The fuse daemon fails the request with EEXIST. This usually indicates a 193 * race condition: some other FUSE client created the file in between when the 194 * kernel checked for it with lookup and tried to create it with create 195 */ 196 TEST_F(Create, eexist) 197 { 198 const char FULLPATH[] = "mountpoint/some_file.txt"; 199 const char RELPATH[] = "some_file.txt"; 200 mode_t mode = S_IFREG | 0755; 201 202 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 203 .WillOnce(Invoke(ReturnErrno(ENOENT))); 204 expect_create(RELPATH, mode, ReturnErrno(EEXIST)); 205 EXPECT_EQ(-1, open(FULLPATH, O_CREAT | O_EXCL, mode)); 206 EXPECT_EQ(EEXIST, errno); 207 } 208 209 /* 210 * If the daemon doesn't implement FUSE_CREATE, then the kernel should fallback 211 * to FUSE_MKNOD/FUSE_OPEN 212 */ 213 TEST_F(Create, Enosys) 214 { 215 const char FULLPATH[] = "mountpoint/some_file.txt"; 216 const char RELPATH[] = "some_file.txt"; 217 mode_t mode = S_IFREG | 0755; 218 uint64_t ino = 42; 219 int fd; 220 221 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 222 .WillOnce(Invoke(ReturnErrno(ENOENT))); 223 expect_create(RELPATH, mode, ReturnErrno(ENOSYS)); 224 225 EXPECT_CALL(*m_mock, process( 226 ResultOf([=](auto in) { 227 const char *name = (const char*)in.body.bytes + 228 sizeof(fuse_mknod_in); 229 return (in.header.opcode == FUSE_MKNOD && 230 in.body.mknod.mode == (S_IFREG | mode) && 231 in.body.mknod.rdev == 0 && 232 (0 == strcmp(RELPATH, name))); 233 }, Eq(true)), 234 _) 235 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 236 SET_OUT_HEADER_LEN(out, entry); 237 out.body.entry.attr.mode = mode; 238 out.body.entry.nodeid = ino; 239 out.body.entry.entry_valid = UINT64_MAX; 240 out.body.entry.attr_valid = UINT64_MAX; 241 }))); 242 243 EXPECT_CALL(*m_mock, process( 244 ResultOf([=](auto in) { 245 return (in.header.opcode == FUSE_OPEN && 246 in.header.nodeid == ino); 247 }, Eq(true)), 248 _) 249 ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) { 250 out.header.len = sizeof(out.header); 251 SET_OUT_HEADER_LEN(out, open); 252 }))); 253 254 fd = open(FULLPATH, O_CREAT | O_EXCL, mode); 255 ASSERT_LE(0, fd) << strerror(errno); 256 leak(fd); 257 } 258 259 /* 260 * Creating a new file after FUSE_LOOKUP returned a negative cache entry 261 */ 262 TEST_F(Create, entry_cache_negative) 263 { 264 const char FULLPATH[] = "mountpoint/some_file.txt"; 265 const char RELPATH[] = "some_file.txt"; 266 mode_t mode = S_IFREG | 0755; 267 uint64_t ino = 42; 268 int fd; 269 /* 270 * Set entry_valid = 0 because this test isn't concerned with whether 271 * or not we actually cache negative entries, only with whether we 272 * interpret negative cache responses correctly. 273 */ 274 struct timespec entry_valid = {.tv_sec = 0, .tv_nsec = 0}; 275 276 /* create will first do a LOOKUP, adding a negative cache entry */ 277 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 278 .WillOnce(ReturnNegativeCache(&entry_valid)); 279 expect_create(RELPATH, mode, 280 ReturnImmediate([=](auto in __unused, auto& out) { 281 SET_OUT_HEADER_LEN(out, create); 282 out.body.create.entry.attr.mode = mode; 283 out.body.create.entry.nodeid = ino; 284 out.body.create.entry.entry_valid = UINT64_MAX; 285 out.body.create.entry.attr_valid = UINT64_MAX; 286 })); 287 288 fd = open(FULLPATH, O_CREAT | O_EXCL, mode); 289 ASSERT_LE(0, fd) << strerror(errno); 290 leak(fd); 291 } 292 293 /* 294 * Creating a new file should purge any negative namecache entries 295 */ 296 TEST_F(Create, entry_cache_negative_purge) 297 { 298 const char FULLPATH[] = "mountpoint/some_file.txt"; 299 const char RELPATH[] = "some_file.txt"; 300 mode_t mode = S_IFREG | 0755; 301 uint64_t ino = 42; 302 int fd; 303 struct timespec entry_valid = {.tv_sec = TIME_T_MAX, .tv_nsec = 0}; 304 305 /* create will first do a LOOKUP, adding a negative cache entry */ 306 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH).Times(1) 307 .WillOnce(Invoke(ReturnNegativeCache(&entry_valid))) 308 .RetiresOnSaturation(); 309 310 /* Then the CREATE should purge the negative cache entry */ 311 expect_create(RELPATH, mode, 312 ReturnImmediate([=](auto in __unused, auto& out) { 313 SET_OUT_HEADER_LEN(out, create); 314 out.body.create.entry.attr.mode = mode; 315 out.body.create.entry.nodeid = ino; 316 out.body.create.entry.attr_valid = UINT64_MAX; 317 })); 318 319 fd = open(FULLPATH, O_CREAT | O_EXCL, mode); 320 ASSERT_LE(0, fd) << strerror(errno); 321 322 /* Finally, a subsequent lookup should query the daemon */ 323 expect_lookup(RELPATH, ino, S_IFREG | mode, 0, 1); 324 325 ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); 326 leak(fd); 327 } 328 329 /* 330 * The daemon is responsible for checking file permissions (unless the 331 * default_permissions mount option was used) 332 */ 333 TEST_F(Create, eperm) 334 { 335 const char FULLPATH[] = "mountpoint/some_file.txt"; 336 const char RELPATH[] = "some_file.txt"; 337 mode_t mode = S_IFREG | 0755; 338 339 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 340 .WillOnce(Invoke(ReturnErrno(ENOENT))); 341 expect_create(RELPATH, mode, ReturnErrno(EPERM)); 342 343 EXPECT_EQ(-1, open(FULLPATH, O_CREAT | O_EXCL, mode)); 344 EXPECT_EQ(EPERM, errno); 345 } 346 347 TEST_F(Create, ok) 348 { 349 const char FULLPATH[] = "mountpoint/some_file.txt"; 350 const char RELPATH[] = "some_file.txt"; 351 mode_t mode = S_IFREG | 0755; 352 uint64_t ino = 42; 353 int fd; 354 355 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 356 .WillOnce(Invoke(ReturnErrno(ENOENT))); 357 expect_create(RELPATH, mode, 358 ReturnImmediate([=](auto in __unused, auto& out) { 359 SET_OUT_HEADER_LEN(out, create); 360 out.body.create.entry.attr.mode = mode; 361 out.body.create.entry.nodeid = ino; 362 out.body.create.entry.entry_valid = UINT64_MAX; 363 out.body.create.entry.attr_valid = UINT64_MAX; 364 })); 365 366 fd = open(FULLPATH, O_CREAT | O_EXCL, mode); 367 ASSERT_LE(0, fd) << strerror(errno); 368 leak(fd); 369 } 370 371 /* 372 * Nothing bad should happen if the server returns the parent's inode number 373 * for the newly created file. Regression test for bug 263662 374 * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=263662 375 */ 376 TEST_F(Create, parent_inode) 377 { 378 const char FULLPATH[] = "mountpoint/some_dir/some_file.txt"; 379 const char RELDIRPATH[] = "some_dir"; 380 const char RELPATH[] = "some_file.txt"; 381 mode_t mode = 0755; 382 uint64_t ino = 42; 383 int fd; 384 385 expect_lookup(RELDIRPATH, ino, S_IFDIR | mode, 0, 1); 386 EXPECT_LOOKUP(ino, RELPATH) 387 .WillOnce(Invoke(ReturnErrno(ENOENT))); 388 expect_create(RELPATH, S_IFREG | mode, 389 ReturnImmediate([=](auto in __unused, auto& out) { 390 SET_OUT_HEADER_LEN(out, create); 391 out.body.create.entry.attr.mode = S_IFREG | mode; 392 /* Return the same inode as the parent dir */ 393 out.body.create.entry.nodeid = ino; 394 out.body.create.entry.entry_valid = UINT64_MAX; 395 out.body.create.entry.attr_valid = UINT64_MAX; 396 })); 397 // FUSE_RELEASE happens asynchronously, so it may or may not arrive 398 // before the test completes. 399 EXPECT_CALL(*m_mock, process( 400 ResultOf([=](auto in) { 401 return (in.header.opcode == FUSE_RELEASE); 402 }, Eq(true)), 403 _) 404 ).Times(AtMost(1)) 405 .WillOnce(Invoke([=](auto in __unused, auto &out __unused) { })); 406 407 fd = open(FULLPATH, O_CREAT | O_EXCL, mode); 408 ASSERT_EQ(-1, fd); 409 EXPECT_EQ(EIO, errno); 410 } 411 412 /* 413 * A regression test for a bug that affected old FUSE implementations: 414 * open(..., O_WRONLY | O_CREAT, 0444) should work despite the seeming 415 * contradiction between O_WRONLY and 0444 416 * 417 * For example: 418 * https://bugs.launchpad.net/ubuntu/+source/sshfs-fuse/+bug/44886 419 */ 420 TEST_F(Create, wronly_0444) 421 { 422 const char FULLPATH[] = "mountpoint/some_file.txt"; 423 const char RELPATH[] = "some_file.txt"; 424 mode_t mode = S_IFREG | 0444; 425 uint64_t ino = 42; 426 int fd; 427 428 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 429 .WillOnce(Invoke(ReturnErrno(ENOENT))); 430 expect_create(RELPATH, mode, 431 ReturnImmediate([=](auto in __unused, auto& out) { 432 SET_OUT_HEADER_LEN(out, create); 433 out.body.create.entry.attr.mode = mode; 434 out.body.create.entry.nodeid = ino; 435 out.body.create.entry.entry_valid = UINT64_MAX; 436 out.body.create.entry.attr_valid = UINT64_MAX; 437 })); 438 439 fd = open(FULLPATH, O_CREAT | O_WRONLY, mode); 440 ASSERT_LE(0, fd) << strerror(errno); 441 leak(fd); 442 } 443 444 TEST_F(Create_7_8, ok) 445 { 446 const char FULLPATH[] = "mountpoint/some_file.txt"; 447 const char RELPATH[] = "some_file.txt"; 448 mode_t mode = S_IFREG | 0755; 449 uint64_t ino = 42; 450 int fd; 451 452 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 453 .WillOnce(Invoke(ReturnErrno(ENOENT))); 454 expect_create(RELPATH, mode, 455 ReturnImmediate([=](auto in __unused, auto& out) { 456 SET_OUT_HEADER_LEN(out, create_7_8); 457 out.body.create_7_8.entry.attr.mode = mode; 458 out.body.create_7_8.entry.nodeid = ino; 459 out.body.create_7_8.entry.entry_valid = UINT64_MAX; 460 out.body.create_7_8.entry.attr_valid = UINT64_MAX; 461 out.body.create_7_8.open.fh = FH; 462 })); 463 expect_flush(ino, 1, ReturnErrno(0)); 464 expect_release(ino, FH); 465 466 fd = open(FULLPATH, O_CREAT | O_EXCL, mode); 467 ASSERT_LE(0, fd) << strerror(errno); 468 close(fd); 469 } 470 471 TEST_F(Create_7_11, ok) 472 { 473 const char FULLPATH[] = "mountpoint/some_file.txt"; 474 const char RELPATH[] = "some_file.txt"; 475 mode_t mode = S_IFREG | 0755; 476 uint64_t ino = 42; 477 int fd; 478 479 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 480 .WillOnce(Invoke(ReturnErrno(ENOENT))); 481 expect_create(RELPATH, mode, 482 ReturnImmediate([=](auto in __unused, auto& out) { 483 SET_OUT_HEADER_LEN(out, create); 484 out.body.create.entry.attr.mode = mode; 485 out.body.create.entry.nodeid = ino; 486 out.body.create.entry.entry_valid = UINT64_MAX; 487 out.body.create.entry.attr_valid = UINT64_MAX; 488 })); 489 490 fd = open(FULLPATH, O_CREAT | O_EXCL, mode); 491 ASSERT_LE(0, fd) << strerror(errno); 492 leak(fd); 493 } 494