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/param.h> 35 #include <sys/mount.h> 36 37 #include <fcntl.h> 38 #include <unistd.h> 39 } 40 41 #include "mockfs.hh" 42 #include "utils.hh" 43 44 using namespace testing; 45 46 class Lookup: public FuseTest {}; 47 48 class Lookup_7_8: public Lookup { 49 public: 50 virtual void SetUp() { 51 m_kernel_minor_version = 8; 52 Lookup::SetUp(); 53 } 54 }; 55 56 class LookupExportable: public Lookup { 57 public: 58 virtual void SetUp() { 59 m_init_flags = FUSE_EXPORT_SUPPORT; 60 Lookup::SetUp(); 61 } 62 }; 63 64 /* 65 * If lookup returns a non-zero cache timeout, then subsequent VOP_GETATTRs 66 * should use the cached attributes, rather than query the daemon 67 */ 68 TEST_F(Lookup, attr_cache) 69 { 70 const char FULLPATH[] = "mountpoint/some_file.txt"; 71 const char RELPATH[] = "some_file.txt"; 72 const uint64_t ino = 42; 73 const uint64_t generation = 13; 74 struct stat sb; 75 76 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 77 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 78 SET_OUT_HEADER_LEN(out, entry); 79 out.body.entry.nodeid = ino; 80 out.body.entry.attr_valid = UINT64_MAX; 81 out.body.entry.attr.ino = ino; // Must match nodeid 82 out.body.entry.attr.mode = S_IFREG | 0644; 83 out.body.entry.attr.size = 1; 84 out.body.entry.attr.blocks = 2; 85 out.body.entry.attr.atime = 3; 86 out.body.entry.attr.mtime = 4; 87 out.body.entry.attr.ctime = 5; 88 out.body.entry.attr.atimensec = 6; 89 out.body.entry.attr.mtimensec = 7; 90 out.body.entry.attr.ctimensec = 8; 91 out.body.entry.attr.nlink = 9; 92 out.body.entry.attr.uid = 10; 93 out.body.entry.attr.gid = 11; 94 out.body.entry.attr.rdev = 12; 95 out.body.entry.generation = generation; 96 }))); 97 /* stat(2) issues a VOP_LOOKUP followed by a VOP_GETATTR */ 98 ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno); 99 EXPECT_EQ(1, sb.st_size); 100 EXPECT_EQ(2, sb.st_blocks); 101 EXPECT_EQ(3, sb.st_atim.tv_sec); 102 EXPECT_EQ(6, sb.st_atim.tv_nsec); 103 EXPECT_EQ(4, sb.st_mtim.tv_sec); 104 EXPECT_EQ(7, sb.st_mtim.tv_nsec); 105 EXPECT_EQ(5, sb.st_ctim.tv_sec); 106 EXPECT_EQ(8, sb.st_ctim.tv_nsec); 107 EXPECT_EQ(9ull, sb.st_nlink); 108 EXPECT_EQ(10ul, sb.st_uid); 109 EXPECT_EQ(11ul, sb.st_gid); 110 EXPECT_EQ(12ul, sb.st_rdev); 111 EXPECT_EQ(ino, sb.st_ino); 112 EXPECT_EQ(S_IFREG | 0644, sb.st_mode); 113 114 // fuse(4) does not _yet_ support inode generations 115 //EXPECT_EQ(generation, sb.st_gen); 116 117 //st_birthtim and st_flags are not supported by protocol 7.8. They're 118 //only supported as OS-specific extensions to OSX. 119 //EXPECT_EQ(, sb.st_birthtim); 120 //EXPECT_EQ(, sb.st_flags); 121 122 //FUSE can't set st_blksize until protocol 7.9 123 } 124 125 /* 126 * If lookup returns a finite but non-zero cache timeout, then we should discard 127 * the cached attributes and requery the daemon. 128 */ 129 TEST_F(Lookup, attr_cache_timeout) 130 { 131 const char FULLPATH[] = "mountpoint/some_file.txt"; 132 const char RELPATH[] = "some_file.txt"; 133 const uint64_t ino = 42; 134 struct stat sb; 135 136 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 137 .Times(2) 138 .WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 139 SET_OUT_HEADER_LEN(out, entry); 140 out.body.entry.nodeid = ino; 141 out.body.entry.attr_valid_nsec = NAP_NS / 2; 142 out.body.entry.attr.ino = ino; // Must match nodeid 143 out.body.entry.attr.mode = S_IFREG | 0644; 144 }))); 145 146 /* access(2) will issue a VOP_LOOKUP and fill the attr cache */ 147 ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); 148 /* Next access(2) will use the cached attributes */ 149 nap(); 150 /* The cache has timed out; VOP_GETATTR should query the daemon*/ 151 ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno); 152 } 153 154 TEST_F(Lookup, dot) 155 { 156 const char FULLPATH[] = "mountpoint/some_dir/."; 157 const char RELDIRPATH[] = "some_dir"; 158 uint64_t ino = 42; 159 160 EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH) 161 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 162 SET_OUT_HEADER_LEN(out, entry); 163 out.body.entry.attr.mode = S_IFDIR | 0755; 164 out.body.entry.nodeid = ino; 165 out.body.entry.attr_valid = UINT64_MAX; 166 out.body.entry.entry_valid = UINT64_MAX; 167 }))); 168 169 /* 170 * access(2) is one of the few syscalls that will not (always) follow 171 * up a successful VOP_LOOKUP with another VOP. 172 */ 173 ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); 174 } 175 176 TEST_F(Lookup, dotdot) 177 { 178 const char FULLPATH[] = "mountpoint/some_dir/.."; 179 const char RELDIRPATH[] = "some_dir"; 180 181 EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH) 182 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 183 SET_OUT_HEADER_LEN(out, entry); 184 out.body.entry.attr.mode = S_IFDIR | 0755; 185 out.body.entry.nodeid = 14; 186 out.body.entry.attr_valid = UINT64_MAX; 187 out.body.entry.entry_valid = UINT64_MAX; 188 }))); 189 190 /* 191 * access(2) is one of the few syscalls that will not (always) follow 192 * up a successful VOP_LOOKUP with another VOP. 193 */ 194 ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); 195 } 196 197 /* 198 * Lookup ".." when that vnode's entry cache has timed out, but its child's 199 * hasn't. Since this file system doesn't set FUSE_EXPORT_SUPPORT, we have no 200 * choice but to use the cached entry, even though it expired. 201 */ 202 TEST_F(Lookup, dotdot_entry_cache_timeout) 203 { 204 uint64_t foo_ino = 42; 205 uint64_t bar_ino = 43; 206 207 EXPECT_LOOKUP(FUSE_ROOT_ID, "foo") 208 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 209 SET_OUT_HEADER_LEN(out, entry); 210 out.body.entry.attr.mode = S_IFDIR | 0755; 211 out.body.entry.nodeid = foo_ino; 212 out.body.entry.attr_valid = UINT64_MAX; 213 out.body.entry.entry_valid = 0; // immediate timeout 214 }))); 215 EXPECT_LOOKUP(foo_ino, "bar") 216 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 217 SET_OUT_HEADER_LEN(out, entry); 218 out.body.entry.attr.mode = S_IFDIR | 0755; 219 out.body.entry.nodeid = bar_ino; 220 out.body.entry.attr_valid = UINT64_MAX; 221 out.body.entry.entry_valid = UINT64_MAX; 222 }))); 223 expect_opendir(bar_ino); 224 225 int fd = open("mountpoint/foo/bar", O_EXEC| O_DIRECTORY); 226 ASSERT_LE(0, fd) << strerror(errno); 227 EXPECT_EQ(0, faccessat(fd, "../..", F_OK, 0)) << strerror(errno); 228 } 229 230 /* 231 * Lookup ".." for a vnode with no valid parent nid 232 * Regression test for https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=259974 233 * Since the file system is not exportable, we have no choice but to return an 234 * error. 235 */ 236 TEST_F(Lookup, dotdot_no_parent_nid) 237 { 238 uint64_t foo_ino = 42; 239 uint64_t bar_ino = 43; 240 int fd; 241 242 EXPECT_LOOKUP(FUSE_ROOT_ID, "foo") 243 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 244 SET_OUT_HEADER_LEN(out, entry); 245 out.body.entry.attr.mode = S_IFDIR | 0755; 246 out.body.entry.nodeid = foo_ino; 247 out.body.entry.attr_valid = UINT64_MAX; 248 out.body.entry.entry_valid = UINT64_MAX; 249 }))); 250 EXPECT_LOOKUP(foo_ino, "bar") 251 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 252 SET_OUT_HEADER_LEN(out, entry); 253 out.body.entry.attr.mode = S_IFDIR | 0755; 254 out.body.entry.nodeid = bar_ino; 255 out.body.entry.attr_valid = UINT64_MAX; 256 out.body.entry.entry_valid = UINT64_MAX; 257 }))); 258 EXPECT_CALL(*m_mock, process( 259 ResultOf([=](auto in) { 260 return (in.header.opcode == FUSE_OPENDIR); 261 }, Eq(true)), 262 _) 263 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 264 SET_OUT_HEADER_LEN(out, open); 265 }))); 266 expect_forget(FUSE_ROOT_ID, 1, NULL); 267 expect_forget(foo_ino, 1, NULL); 268 269 fd = open("mountpoint/foo/bar", O_EXEC| O_DIRECTORY); 270 ASSERT_LE(0, fd) << strerror(errno); 271 // Try (and fail) to unmount the file system, to reclaim the mountpoint 272 // and foo vnodes. 273 ASSERT_NE(0, unmount("mountpoint", 0)); 274 EXPECT_EQ(EBUSY, errno); 275 nap(); // Because vnode reclamation is asynchronous 276 EXPECT_NE(0, faccessat(fd, "../..", F_OK, 0)); 277 EXPECT_EQ(ESTALE, errno); 278 } 279 280 TEST_F(Lookup, enoent) 281 { 282 const char FULLPATH[] = "mountpoint/does_not_exist"; 283 const char RELPATH[] = "does_not_exist"; 284 285 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 286 .WillOnce(Invoke(ReturnErrno(ENOENT))); 287 EXPECT_NE(0, access(FULLPATH, F_OK)); 288 EXPECT_EQ(ENOENT, errno); 289 } 290 291 TEST_F(Lookup, enotdir) 292 { 293 const char FULLPATH[] = "mountpoint/not_a_dir/some_file.txt"; 294 const char RELPATH[] = "not_a_dir"; 295 296 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 297 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 298 SET_OUT_HEADER_LEN(out, entry); 299 out.body.entry.entry_valid = UINT64_MAX; 300 out.body.entry.attr.mode = S_IFREG | 0644; 301 out.body.entry.nodeid = 42; 302 }))); 303 304 ASSERT_EQ(-1, access(FULLPATH, F_OK)); 305 ASSERT_EQ(ENOTDIR, errno); 306 } 307 308 /* 309 * If lookup returns a non-zero entry timeout, then subsequent VOP_LOOKUPs 310 * should use the cached inode rather than requery the daemon 311 */ 312 TEST_F(Lookup, entry_cache) 313 { 314 const char FULLPATH[] = "mountpoint/some_file.txt"; 315 const char RELPATH[] = "some_file.txt"; 316 317 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 318 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 319 SET_OUT_HEADER_LEN(out, entry); 320 out.body.entry.entry_valid = UINT64_MAX; 321 out.body.entry.attr.mode = S_IFREG | 0644; 322 out.body.entry.nodeid = 14; 323 }))); 324 ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); 325 /* The second access(2) should use the cache */ 326 ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); 327 } 328 329 /* 330 * If the daemon returns an error of 0 and an inode of 0, that's a flag for 331 * "ENOENT and cache it" with the given entry_timeout 332 */ 333 TEST_F(Lookup, entry_cache_negative) 334 { 335 struct timespec entry_valid = {.tv_sec = TIME_T_MAX, .tv_nsec = 0}; 336 337 EXPECT_LOOKUP(FUSE_ROOT_ID, "does_not_exist") 338 .Times(1) 339 .WillOnce(Invoke(ReturnNegativeCache(&entry_valid))); 340 341 EXPECT_NE(0, access("mountpoint/does_not_exist", F_OK)); 342 EXPECT_EQ(ENOENT, errno); 343 EXPECT_NE(0, access("mountpoint/does_not_exist", F_OK)); 344 EXPECT_EQ(ENOENT, errno); 345 } 346 347 /* Negative entry caches should timeout, too */ 348 TEST_F(Lookup, entry_cache_negative_timeout) 349 { 350 const char *RELPATH = "does_not_exist"; 351 const char *FULLPATH = "mountpoint/does_not_exist"; 352 struct timespec entry_valid = {.tv_sec = 0, .tv_nsec = NAP_NS / 2}; 353 354 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 355 .Times(2) 356 .WillRepeatedly(Invoke(ReturnNegativeCache(&entry_valid))); 357 358 EXPECT_NE(0, access(FULLPATH, F_OK)); 359 EXPECT_EQ(ENOENT, errno); 360 361 nap(); 362 363 /* The cache has timed out; VOP_LOOKUP should requery the daemon*/ 364 EXPECT_NE(0, access(FULLPATH, F_OK)); 365 EXPECT_EQ(ENOENT, errno); 366 } 367 368 /* 369 * If lookup returns a finite but non-zero entry cache timeout, then we should 370 * discard the cached inode and requery the daemon 371 */ 372 TEST_F(Lookup, entry_cache_timeout) 373 { 374 const char FULLPATH[] = "mountpoint/some_file.txt"; 375 const char RELPATH[] = "some_file.txt"; 376 377 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 378 .Times(2) 379 .WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 380 SET_OUT_HEADER_LEN(out, entry); 381 out.body.entry.entry_valid_nsec = NAP_NS / 2; 382 out.body.entry.attr.mode = S_IFREG | 0644; 383 out.body.entry.nodeid = 14; 384 }))); 385 386 /* access(2) will issue a VOP_LOOKUP and fill the entry cache */ 387 ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); 388 /* Next access(2) will use the cached entry */ 389 ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); 390 nap(); 391 /* The cache has timed out; VOP_LOOKUP should requery the daemon*/ 392 ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); 393 } 394 395 TEST_F(Lookup, ok) 396 { 397 const char FULLPATH[] = "mountpoint/some_file.txt"; 398 const char RELPATH[] = "some_file.txt"; 399 400 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 401 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 402 SET_OUT_HEADER_LEN(out, entry); 403 out.body.entry.attr.mode = S_IFREG | 0644; 404 out.body.entry.nodeid = 14; 405 }))); 406 /* 407 * access(2) is one of the few syscalls that will not (always) follow 408 * up a successful VOP_LOOKUP with another VOP. 409 */ 410 ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); 411 } 412 413 // Lookup in a subdirectory of the fuse mount 414 TEST_F(Lookup, subdir) 415 { 416 const char FULLPATH[] = "mountpoint/some_dir/some_file.txt"; 417 const char DIRPATH[] = "some_dir"; 418 const char RELPATH[] = "some_file.txt"; 419 uint64_t dir_ino = 2; 420 uint64_t file_ino = 3; 421 422 EXPECT_LOOKUP(FUSE_ROOT_ID, DIRPATH) 423 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 424 SET_OUT_HEADER_LEN(out, entry); 425 out.body.entry.attr.mode = S_IFDIR | 0755; 426 out.body.entry.nodeid = dir_ino; 427 }))); 428 EXPECT_LOOKUP(dir_ino, RELPATH) 429 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 430 SET_OUT_HEADER_LEN(out, entry); 431 out.body.entry.attr.mode = S_IFREG | 0644; 432 out.body.entry.nodeid = file_ino; 433 }))); 434 /* 435 * access(2) is one of the few syscalls that will not (always) follow 436 * up a successful VOP_LOOKUP with another VOP. 437 */ 438 ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); 439 } 440 441 /* 442 * The server returns two different vtypes for the same nodeid. This is 443 * technically allowed if the entry's cache has already expired. 444 * https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=258022 445 */ 446 TEST_F(Lookup, vtype_conflict) 447 { 448 const char FIRSTFULLPATH[] = "mountpoint/foo"; 449 const char SECONDFULLPATH[] = "mountpoint/bar"; 450 const char FIRSTRELPATH[] = "foo"; 451 const char SECONDRELPATH[] = "bar"; 452 uint64_t ino = 42; 453 454 EXPECT_LOOKUP(FUSE_ROOT_ID, FIRSTRELPATH) 455 .WillOnce(Invoke( 456 ReturnImmediate([=](auto in __unused, auto& out) { 457 SET_OUT_HEADER_LEN(out, entry); 458 out.body.entry.attr.mode = S_IFDIR | 0644; 459 out.body.entry.nodeid = ino; 460 out.body.entry.attr.nlink = 1; 461 }))); 462 expect_lookup(SECONDRELPATH, ino, S_IFREG | 0755, 0, 1, UINT64_MAX); 463 // VOP_FORGET happens asynchronously, so it may or may not arrive 464 // before the test completes. 465 EXPECT_CALL(*m_mock, process( 466 ResultOf([=](auto in) { 467 return (in.header.opcode == FUSE_FORGET && 468 in.header.nodeid == ino && 469 in.body.forget.nlookup == 1); 470 }, Eq(true)), 471 _) 472 ).Times(AtMost(1)) 473 .WillOnce(Invoke([=](auto in __unused, auto &out __unused) { })); 474 475 ASSERT_EQ(0, access(FIRSTFULLPATH, F_OK)) << strerror(errno); 476 EXPECT_EQ(0, access(SECONDFULLPATH, F_OK)) << strerror(errno); 477 } 478 479 TEST_F(Lookup_7_8, ok) 480 { 481 const char FULLPATH[] = "mountpoint/some_file.txt"; 482 const char RELPATH[] = "some_file.txt"; 483 484 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 485 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 486 SET_OUT_HEADER_LEN(out, entry_7_8); 487 out.body.entry.attr.mode = S_IFREG | 0644; 488 out.body.entry.nodeid = 14; 489 }))); 490 /* 491 * access(2) is one of the few syscalls that will not (always) follow 492 * up a successful VOP_LOOKUP with another VOP. 493 */ 494 ASSERT_EQ(0, access(FULLPATH, F_OK)) << strerror(errno); 495 } 496 497 /* 498 * Lookup ".." when that vnode's entry cache has timed out, but its child's 499 * hasn't. 500 */ 501 TEST_F(LookupExportable, dotdot_entry_cache_timeout) 502 { 503 uint64_t foo_ino = 42; 504 uint64_t bar_ino = 43; 505 506 EXPECT_LOOKUP(FUSE_ROOT_ID, "foo") 507 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 508 SET_OUT_HEADER_LEN(out, entry); 509 out.body.entry.attr.mode = S_IFDIR | 0755; 510 out.body.entry.nodeid = foo_ino; 511 out.body.entry.attr_valid = UINT64_MAX; 512 out.body.entry.entry_valid = 0; // immediate timeout 513 }))); 514 EXPECT_LOOKUP(foo_ino, "bar") 515 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 516 SET_OUT_HEADER_LEN(out, entry); 517 out.body.entry.attr.mode = S_IFDIR | 0755; 518 out.body.entry.nodeid = bar_ino; 519 out.body.entry.attr_valid = UINT64_MAX; 520 out.body.entry.entry_valid = UINT64_MAX; 521 }))); 522 expect_opendir(bar_ino); 523 EXPECT_LOOKUP(foo_ino, "..") 524 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 525 SET_OUT_HEADER_LEN(out, entry); 526 out.body.entry.attr.mode = S_IFDIR | 0755; 527 out.body.entry.nodeid = FUSE_ROOT_ID; 528 out.body.entry.attr_valid = UINT64_MAX; 529 out.body.entry.entry_valid = UINT64_MAX; 530 }))); 531 532 int fd = open("mountpoint/foo/bar", O_EXEC| O_DIRECTORY); 533 ASSERT_LE(0, fd) << strerror(errno); 534 /* FreeBSD's fusefs driver always uses the same cache expiration time 535 * for ".." as for the directory itself. So we need to look up two 536 * levels to find an expired ".." cache entry. 537 */ 538 EXPECT_EQ(0, faccessat(fd, "../..", F_OK, 0)) << strerror(errno); 539 } 540 541 /* 542 * Lookup ".." for a vnode with no valid parent nid 543 * Regression test for https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=259974 544 * Since the file system is exportable, we should resolve the problem by 545 * sending a FUSE_LOOKUP for "..". 546 */ 547 TEST_F(LookupExportable, dotdot_no_parent_nid) 548 { 549 uint64_t foo_ino = 42; 550 uint64_t bar_ino = 43; 551 int fd; 552 553 EXPECT_LOOKUP(FUSE_ROOT_ID, "foo") 554 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 555 SET_OUT_HEADER_LEN(out, entry); 556 out.body.entry.attr.mode = S_IFDIR | 0755; 557 out.body.entry.nodeid = foo_ino; 558 out.body.entry.attr_valid = UINT64_MAX; 559 out.body.entry.entry_valid = UINT64_MAX; 560 }))); 561 EXPECT_LOOKUP(foo_ino, "bar") 562 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 563 SET_OUT_HEADER_LEN(out, entry); 564 out.body.entry.attr.mode = S_IFDIR | 0755; 565 out.body.entry.nodeid = bar_ino; 566 out.body.entry.attr_valid = UINT64_MAX; 567 out.body.entry.entry_valid = UINT64_MAX; 568 }))); 569 EXPECT_CALL(*m_mock, process( 570 ResultOf([=](auto in) { 571 return (in.header.opcode == FUSE_OPENDIR); 572 }, Eq(true)), 573 _) 574 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 575 SET_OUT_HEADER_LEN(out, open); 576 }))); 577 expect_forget(FUSE_ROOT_ID, 1, NULL); 578 expect_forget(foo_ino, 1, NULL); 579 EXPECT_LOOKUP(bar_ino, "..") 580 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 581 SET_OUT_HEADER_LEN(out, entry); 582 out.body.entry.attr.mode = S_IFDIR | 0755; 583 out.body.entry.nodeid = foo_ino; 584 out.body.entry.attr_valid = UINT64_MAX; 585 out.body.entry.entry_valid = UINT64_MAX; 586 }))); 587 EXPECT_LOOKUP(foo_ino, "..") 588 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 589 SET_OUT_HEADER_LEN(out, entry); 590 out.body.entry.attr.mode = S_IFDIR | 0755; 591 out.body.entry.nodeid = FUSE_ROOT_ID; 592 out.body.entry.attr_valid = UINT64_MAX; 593 out.body.entry.entry_valid = UINT64_MAX; 594 }))); 595 596 fd = open("mountpoint/foo/bar", O_EXEC| O_DIRECTORY); 597 ASSERT_LE(0, fd) << strerror(errno); 598 // Try (and fail) to unmount the file system, to reclaim the mountpoint 599 // and foo vnodes. 600 ASSERT_NE(0, unmount("mountpoint", 0)); 601 EXPECT_EQ(EBUSY, errno); 602 nap(); // Because vnode reclamation is asynchronous 603 EXPECT_EQ(0, faccessat(fd, "../..", F_OK, 0)) << strerror(errno); 604 } 605