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