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/types.h> 35 #include <sys/sysctl.h> 36 37 #include <fcntl.h> 38 #include <pthread.h> 39 } 40 41 #include "mockfs.hh" 42 #include "utils.hh" 43 44 using namespace testing; 45 46 /* 47 * FUSE asynchonous notification 48 * 49 * FUSE servers can send unprompted notification messages for things like cache 50 * invalidation. This file tests our client's handling of those messages. 51 */ 52 53 class Notify: public FuseTest { 54 public: 55 /* Ignore an optional FUSE_FSYNC */ 56 void maybe_expect_fsync(uint64_t ino) 57 { 58 EXPECT_CALL(*m_mock, process( 59 ResultOf([=](auto in) { 60 return (in.header.opcode == FUSE_FSYNC && 61 in.header.nodeid == ino); 62 }, Eq(true)), 63 _) 64 ).WillOnce(Invoke(ReturnErrno(0))); 65 } 66 67 void expect_lookup(uint64_t parent, const char *relpath, uint64_t ino, 68 off_t size, Sequence &seq) 69 { 70 EXPECT_LOOKUP(parent, relpath) 71 .InSequence(seq) 72 .WillOnce(Invoke( 73 ReturnImmediate([=](auto in __unused, auto& out) { 74 SET_OUT_HEADER_LEN(out, entry); 75 out.body.entry.attr.mode = S_IFREG | 0644; 76 out.body.entry.nodeid = ino; 77 out.body.entry.attr.ino = ino; 78 out.body.entry.attr.nlink = 1; 79 out.body.entry.attr.size = size; 80 out.body.entry.attr_valid = UINT64_MAX; 81 out.body.entry.entry_valid = UINT64_MAX; 82 }))); 83 } 84 }; 85 86 class NotifyWriteback: public Notify { 87 public: 88 virtual void SetUp() { 89 m_init_flags |= FUSE_WRITEBACK_CACHE; 90 m_async = true; 91 Notify::SetUp(); 92 if (IsSkipped()) 93 return; 94 } 95 96 void expect_write(uint64_t ino, uint64_t offset, uint64_t size, 97 const void *contents) 98 { 99 FuseTest::expect_write(ino, offset, size, size, 0, 0, contents); 100 } 101 102 }; 103 104 struct inval_entry_args { 105 MockFS *mock; 106 ino_t parent; 107 const char *name; 108 size_t namelen; 109 }; 110 111 static void* inval_entry(void* arg) { 112 const struct inval_entry_args *iea = (struct inval_entry_args*)arg; 113 ssize_t r; 114 115 r = iea->mock->notify_inval_entry(iea->parent, iea->name, iea->namelen); 116 if (r >= 0) 117 return 0; 118 else 119 return (void*)(intptr_t)errno; 120 } 121 122 struct inval_inode_args { 123 MockFS *mock; 124 ino_t ino; 125 off_t off; 126 ssize_t len; 127 }; 128 129 struct store_args { 130 MockFS *mock; 131 ino_t nodeid; 132 off_t offset; 133 ssize_t size; 134 const void* data; 135 }; 136 137 static void* inval_inode(void* arg) { 138 const struct inval_inode_args *iia = (struct inval_inode_args*)arg; 139 ssize_t r; 140 141 r = iia->mock->notify_inval_inode(iia->ino, iia->off, iia->len); 142 if (r >= 0) 143 return 0; 144 else 145 return (void*)(intptr_t)errno; 146 } 147 148 static void* store(void* arg) { 149 const struct store_args *sa = (struct store_args*)arg; 150 ssize_t r; 151 152 r = sa->mock->notify_store(sa->nodeid, sa->offset, sa->data, sa->size); 153 if (r >= 0) 154 return 0; 155 else 156 return (void*)(intptr_t)errno; 157 } 158 159 /* Invalidate a nonexistent entry */ 160 TEST_F(Notify, inval_entry_nonexistent) 161 { 162 const static char *name = "foo"; 163 struct inval_entry_args iea; 164 void *thr0_value; 165 pthread_t th0; 166 167 iea.mock = m_mock; 168 iea.parent = FUSE_ROOT_ID; 169 iea.name = name; 170 iea.namelen = strlen(name); 171 ASSERT_EQ(0, pthread_create(&th0, NULL, inval_entry, &iea)) 172 << strerror(errno); 173 pthread_join(th0, &thr0_value); 174 /* It's not an error for an entry to not be cached */ 175 EXPECT_EQ(0, (intptr_t)thr0_value); 176 } 177 178 /* Invalidate a cached entry */ 179 TEST_F(Notify, inval_entry) 180 { 181 const static char FULLPATH[] = "mountpoint/foo"; 182 const static char RELPATH[] = "foo"; 183 struct inval_entry_args iea; 184 struct stat sb; 185 void *thr0_value; 186 uint64_t ino0 = 42; 187 uint64_t ino1 = 43; 188 Sequence seq; 189 pthread_t th0; 190 191 expect_lookup(FUSE_ROOT_ID, RELPATH, ino0, 0, seq); 192 expect_lookup(FUSE_ROOT_ID, RELPATH, ino1, 0, seq); 193 194 /* Fill the entry cache */ 195 ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno); 196 EXPECT_EQ(ino0, sb.st_ino); 197 198 /* Now invalidate the entry */ 199 iea.mock = m_mock; 200 iea.parent = FUSE_ROOT_ID; 201 iea.name = RELPATH; 202 iea.namelen = strlen(RELPATH); 203 ASSERT_EQ(0, pthread_create(&th0, NULL, inval_entry, &iea)) 204 << strerror(errno); 205 pthread_join(th0, &thr0_value); 206 EXPECT_EQ(0, (intptr_t)thr0_value); 207 208 /* The second lookup should return the alternate ino */ 209 ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno); 210 EXPECT_EQ(ino1, sb.st_ino); 211 } 212 213 /* 214 * Invalidate a cached entry beneath the root, which uses a slightly different 215 * code path. 216 */ 217 TEST_F(Notify, inval_entry_below_root) 218 { 219 const static char FULLPATH[] = "mountpoint/some_dir/foo"; 220 const static char DNAME[] = "some_dir"; 221 const static char FNAME[] = "foo"; 222 struct inval_entry_args iea; 223 struct stat sb; 224 void *thr0_value; 225 uint64_t dir_ino = 41; 226 uint64_t ino0 = 42; 227 uint64_t ino1 = 43; 228 Sequence seq; 229 pthread_t th0; 230 231 EXPECT_LOOKUP(FUSE_ROOT_ID, DNAME) 232 .WillOnce(Invoke( 233 ReturnImmediate([=](auto in __unused, auto& out) { 234 SET_OUT_HEADER_LEN(out, entry); 235 out.body.entry.attr.mode = S_IFDIR | 0755; 236 out.body.entry.nodeid = dir_ino; 237 out.body.entry.attr.nlink = 2; 238 out.body.entry.attr_valid = UINT64_MAX; 239 out.body.entry.entry_valid = UINT64_MAX; 240 }))); 241 expect_lookup(dir_ino, FNAME, ino0, 0, seq); 242 expect_lookup(dir_ino, FNAME, ino1, 0, seq); 243 244 /* Fill the entry cache */ 245 ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno); 246 EXPECT_EQ(ino0, sb.st_ino); 247 248 /* Now invalidate the entry */ 249 iea.mock = m_mock; 250 iea.parent = dir_ino; 251 iea.name = FNAME; 252 iea.namelen = strlen(FNAME); 253 ASSERT_EQ(0, pthread_create(&th0, NULL, inval_entry, &iea)) 254 << strerror(errno); 255 pthread_join(th0, &thr0_value); 256 EXPECT_EQ(0, (intptr_t)thr0_value); 257 258 /* The second lookup should return the alternate ino */ 259 ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno); 260 EXPECT_EQ(ino1, sb.st_ino); 261 } 262 263 /* Invalidating an entry invalidates the parent directory's attributes */ 264 TEST_F(Notify, inval_entry_invalidates_parent_attrs) 265 { 266 const static char FULLPATH[] = "mountpoint/foo"; 267 const static char RELPATH[] = "foo"; 268 struct inval_entry_args iea; 269 struct stat sb; 270 void *thr0_value; 271 uint64_t ino = 42; 272 Sequence seq; 273 pthread_t th0; 274 275 expect_lookup(FUSE_ROOT_ID, RELPATH, ino, 0, seq); 276 EXPECT_CALL(*m_mock, process( 277 ResultOf([=](auto in) { 278 return (in.header.opcode == FUSE_GETATTR && 279 in.header.nodeid == FUSE_ROOT_ID); 280 }, Eq(true)), 281 _) 282 ).Times(2) 283 .WillRepeatedly(Invoke(ReturnImmediate([=](auto i __unused, auto& out) { 284 SET_OUT_HEADER_LEN(out, attr); 285 out.body.attr.attr.mode = S_IFDIR | 0755; 286 out.body.attr.attr_valid = UINT64_MAX; 287 }))); 288 289 /* Fill the attr and entry cache */ 290 ASSERT_EQ(0, stat("mountpoint", &sb)) << strerror(errno); 291 ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno); 292 293 /* Now invalidate the entry */ 294 iea.mock = m_mock; 295 iea.parent = FUSE_ROOT_ID; 296 iea.name = RELPATH; 297 iea.namelen = strlen(RELPATH); 298 ASSERT_EQ(0, pthread_create(&th0, NULL, inval_entry, &iea)) 299 << strerror(errno); 300 pthread_join(th0, &thr0_value); 301 EXPECT_EQ(0, (intptr_t)thr0_value); 302 303 /* /'s attribute cache should be cleared */ 304 ASSERT_EQ(0, stat("mountpoint", &sb)) << strerror(errno); 305 } 306 307 308 TEST_F(Notify, inval_inode_nonexistent) 309 { 310 struct inval_inode_args iia; 311 ino_t ino = 42; 312 void *thr0_value; 313 pthread_t th0; 314 315 iia.mock = m_mock; 316 iia.ino = ino; 317 iia.off = 0; 318 iia.len = 0; 319 ASSERT_EQ(0, pthread_create(&th0, NULL, inval_inode, &iia)) 320 << strerror(errno); 321 pthread_join(th0, &thr0_value); 322 /* It's not an error for an inode to not be cached */ 323 EXPECT_EQ(0, (intptr_t)thr0_value); 324 } 325 326 TEST_F(Notify, inval_inode_with_clean_cache) 327 { 328 const static char FULLPATH[] = "mountpoint/foo"; 329 const static char RELPATH[] = "foo"; 330 const char CONTENTS0[] = "abcdefgh"; 331 const char CONTENTS1[] = "ijklmnopqrstuvwxyz"; 332 struct inval_inode_args iia; 333 struct stat sb; 334 ino_t ino = 42; 335 void *thr0_value; 336 Sequence seq; 337 uid_t uid = 12345; 338 pthread_t th0; 339 ssize_t size0 = sizeof(CONTENTS0); 340 ssize_t size1 = sizeof(CONTENTS1); 341 char buf[80]; 342 int fd; 343 344 expect_lookup(FUSE_ROOT_ID, RELPATH, ino, size0, seq); 345 expect_open(ino, 0, 1); 346 EXPECT_CALL(*m_mock, process( 347 ResultOf([=](auto in) { 348 return (in.header.opcode == FUSE_GETATTR && 349 in.header.nodeid == ino); 350 }, Eq(true)), 351 _) 352 ).WillOnce(Invoke(ReturnImmediate([=](auto i __unused, auto& out) { 353 SET_OUT_HEADER_LEN(out, attr); 354 out.body.attr.attr.mode = S_IFREG | 0644; 355 out.body.attr.attr_valid = UINT64_MAX; 356 out.body.attr.attr.size = size1; 357 out.body.attr.attr.uid = uid; 358 }))); 359 expect_read(ino, 0, size0, size0, CONTENTS0); 360 expect_read(ino, 0, size1, size1, CONTENTS1); 361 362 /* Fill the data cache */ 363 fd = open(FULLPATH, O_RDWR); 364 ASSERT_LE(0, fd) << strerror(errno); 365 ASSERT_EQ(size0, read(fd, buf, size0)) << strerror(errno); 366 EXPECT_EQ(0, memcmp(buf, CONTENTS0, size0)); 367 368 /* Evict the data cache */ 369 iia.mock = m_mock; 370 iia.ino = ino; 371 iia.off = 0; 372 iia.len = 0; 373 ASSERT_EQ(0, pthread_create(&th0, NULL, inval_inode, &iia)) 374 << strerror(errno); 375 pthread_join(th0, &thr0_value); 376 EXPECT_EQ(0, (intptr_t)thr0_value); 377 378 /* cache attributes were purged; this will trigger a new GETATTR */ 379 ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno); 380 EXPECT_EQ(uid, sb.st_uid); 381 EXPECT_EQ(size1, sb.st_size); 382 383 /* This read should not be serviced by cache */ 384 ASSERT_EQ(0, lseek(fd, 0, SEEK_SET)) << strerror(errno); 385 ASSERT_EQ(size1, read(fd, buf, size1)) << strerror(errno); 386 EXPECT_EQ(0, memcmp(buf, CONTENTS1, size1)); 387 388 leak(fd); 389 } 390 391 /* FUSE_NOTIFY_STORE with a file that's not in the entry cache */ 392 /* disabled because FUSE_NOTIFY_STORE is not yet implemented */ 393 TEST_F(Notify, DISABLED_store_nonexistent) 394 { 395 struct store_args sa; 396 ino_t ino = 42; 397 void *thr0_value; 398 pthread_t th0; 399 400 sa.mock = m_mock; 401 sa.nodeid = ino; 402 sa.offset = 0; 403 sa.size = 0; 404 ASSERT_EQ(0, pthread_create(&th0, NULL, store, &sa)) << strerror(errno); 405 pthread_join(th0, &thr0_value); 406 /* It's not an error for a file to be unknown to the kernel */ 407 EXPECT_EQ(0, (intptr_t)thr0_value); 408 } 409 410 /* Store data into for a file that does not yet have anything cached */ 411 /* disabled because FUSE_NOTIFY_STORE is not yet implemented */ 412 TEST_F(Notify, DISABLED_store_with_blank_cache) 413 { 414 const static char FULLPATH[] = "mountpoint/foo"; 415 const static char RELPATH[] = "foo"; 416 const char CONTENTS1[] = "ijklmnopqrstuvwxyz"; 417 struct store_args sa; 418 ino_t ino = 42; 419 void *thr0_value; 420 Sequence seq; 421 pthread_t th0; 422 ssize_t size1 = sizeof(CONTENTS1); 423 char buf[80]; 424 int fd; 425 426 expect_lookup(FUSE_ROOT_ID, RELPATH, ino, size1, seq); 427 expect_open(ino, 0, 1); 428 429 /* Fill the data cache */ 430 fd = open(FULLPATH, O_RDWR); 431 ASSERT_LE(0, fd) << strerror(errno); 432 433 /* Evict the data cache */ 434 sa.mock = m_mock; 435 sa.nodeid = ino; 436 sa.offset = 0; 437 sa.size = size1; 438 sa.data = (const void*)CONTENTS1; 439 ASSERT_EQ(0, pthread_create(&th0, NULL, store, &sa)) << strerror(errno); 440 pthread_join(th0, &thr0_value); 441 EXPECT_EQ(0, (intptr_t)thr0_value); 442 443 /* This read should be serviced by cache */ 444 ASSERT_EQ(size1, read(fd, buf, size1)) << strerror(errno); 445 EXPECT_EQ(0, memcmp(buf, CONTENTS1, size1)); 446 447 leak(fd); 448 } 449 450 TEST_F(NotifyWriteback, inval_inode_with_dirty_cache) 451 { 452 const static char FULLPATH[] = "mountpoint/foo"; 453 const static char RELPATH[] = "foo"; 454 const char CONTENTS[] = "abcdefgh"; 455 struct inval_inode_args iia; 456 ino_t ino = 42; 457 void *thr0_value; 458 Sequence seq; 459 pthread_t th0; 460 ssize_t bufsize = sizeof(CONTENTS); 461 int fd; 462 463 expect_lookup(FUSE_ROOT_ID, RELPATH, ino, 0, seq); 464 expect_open(ino, 0, 1); 465 466 /* Fill the data cache */ 467 fd = open(FULLPATH, O_RDWR); 468 ASSERT_LE(0, fd); 469 ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno); 470 471 expect_write(ino, 0, bufsize, CONTENTS); 472 /* 473 * The FUSE protocol does not require an fsync here, but FreeBSD's 474 * bufobj_invalbuf sends it anyway 475 */ 476 maybe_expect_fsync(ino); 477 478 /* Evict the data cache */ 479 iia.mock = m_mock; 480 iia.ino = ino; 481 iia.off = 0; 482 iia.len = 0; 483 ASSERT_EQ(0, pthread_create(&th0, NULL, inval_inode, &iia)) 484 << strerror(errno); 485 pthread_join(th0, &thr0_value); 486 EXPECT_EQ(0, (intptr_t)thr0_value); 487 488 leak(fd); 489 } 490 491 TEST_F(NotifyWriteback, inval_inode_attrs_only) 492 { 493 const static char FULLPATH[] = "mountpoint/foo"; 494 const static char RELPATH[] = "foo"; 495 const char CONTENTS[] = "abcdefgh"; 496 struct inval_inode_args iia; 497 struct stat sb; 498 uid_t uid = 12345; 499 ino_t ino = 42; 500 void *thr0_value; 501 Sequence seq; 502 pthread_t th0; 503 ssize_t bufsize = sizeof(CONTENTS); 504 int fd; 505 506 expect_lookup(FUSE_ROOT_ID, RELPATH, ino, 0, seq); 507 expect_open(ino, 0, 1); 508 EXPECT_CALL(*m_mock, process( 509 ResultOf([=](auto in) { 510 return (in.header.opcode == FUSE_WRITE); 511 }, Eq(true)), 512 _) 513 ).Times(0); 514 EXPECT_CALL(*m_mock, process( 515 ResultOf([=](auto in) { 516 return (in.header.opcode == FUSE_GETATTR && 517 in.header.nodeid == ino); 518 }, Eq(true)), 519 _) 520 ).WillOnce(Invoke(ReturnImmediate([=](auto i __unused, auto& out) { 521 SET_OUT_HEADER_LEN(out, attr); 522 out.body.attr.attr.mode = S_IFREG | 0644; 523 out.body.attr.attr_valid = UINT64_MAX; 524 out.body.attr.attr.size = bufsize; 525 out.body.attr.attr.uid = uid; 526 }))); 527 528 /* Fill the data cache */ 529 fd = open(FULLPATH, O_RDWR); 530 ASSERT_LE(0, fd) << strerror(errno); 531 ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno); 532 533 /* Evict the attributes, but not data cache */ 534 iia.mock = m_mock; 535 iia.ino = ino; 536 iia.off = -1; 537 iia.len = 0; 538 ASSERT_EQ(0, pthread_create(&th0, NULL, inval_inode, &iia)) 539 << strerror(errno); 540 pthread_join(th0, &thr0_value); 541 EXPECT_EQ(0, (intptr_t)thr0_value); 542 543 /* cache attributes were been purged; this will trigger a new GETATTR */ 544 ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno); 545 EXPECT_EQ(uid, sb.st_uid); 546 EXPECT_EQ(bufsize, sb.st_size); 547 548 leak(fd); 549 } 550