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 <sys/param.h> 33 #include <sys/mman.h> 34 #include <sys/module.h> 35 #include <sys/sysctl.h> 36 #include <sys/wait.h> 37 38 #include <dirent.h> 39 #include <fcntl.h> 40 #include <grp.h> 41 #include <pwd.h> 42 #include <semaphore.h> 43 #include <unistd.h> 44 } 45 46 #include <gtest/gtest.h> 47 48 #include "mockfs.hh" 49 #include "utils.hh" 50 51 using namespace testing; 52 53 /* 54 * The default max_write is set to this formula in libfuse, though 55 * individual filesystems can lower it. The "- 4096" was added in 56 * commit 154ffe2, with the commit message "fix". 57 */ 58 const uint32_t libfuse_max_write = 32 * getpagesize() + 0x1000 - 4096; 59 60 /* Check that fusefs(4) is accessible and the current user can mount(2) */ 61 void check_environment() 62 { 63 const char *devnode = "/dev/fuse"; 64 const char *bsdextended_node = "security.mac.bsdextended.enabled"; 65 int bsdextended_val = 0; 66 size_t bsdextended_size = sizeof(bsdextended_val); 67 int bsdextended_found; 68 const char *usermount_node = "vfs.usermount"; 69 int usermount_val = 0; 70 size_t usermount_size = sizeof(usermount_val); 71 if (eaccess(devnode, R_OK | W_OK)) { 72 if (errno == ENOENT) { 73 GTEST_SKIP() << devnode << " does not exist"; 74 } else if (errno == EACCES) { 75 GTEST_SKIP() << devnode << 76 " is not accessible by the current user"; 77 } else { 78 GTEST_SKIP() << strerror(errno); 79 } 80 } 81 // mac_bsdextended(4), when enabled, generates many more GETATTR 82 // operations. The fusefs tests' expectations don't account for those, 83 // and adding extra code to handle them obfuscates the real purpose of 84 // the tests. Better just to skip the fusefs tests if mac_bsdextended 85 // is enabled. 86 bsdextended_found = sysctlbyname(bsdextended_node, &bsdextended_val, 87 &bsdextended_size, NULL, 0); 88 if (bsdextended_found == 0 && bsdextended_val != 0) 89 GTEST_SKIP() << 90 "The fusefs tests are incompatible with mac_bsdextended."; 91 ASSERT_EQ(sysctlbyname(usermount_node, &usermount_val, &usermount_size, 92 NULL, 0), 93 0); 94 if (geteuid() != 0 && !usermount_val) 95 GTEST_SKIP() << "current user is not allowed to mount"; 96 } 97 98 const char *cache_mode_to_s(enum cache_mode cm) { 99 switch (cm) { 100 case Uncached: 101 return "Uncached"; 102 case Writethrough: 103 return "Writethrough"; 104 case Writeback: 105 return "Writeback"; 106 case WritebackAsync: 107 return "WritebackAsync"; 108 default: 109 return "Unknown"; 110 } 111 } 112 113 bool is_unsafe_aio_enabled(void) { 114 const char *node = "vfs.aio.enable_unsafe"; 115 int val = 0; 116 size_t size = sizeof(val); 117 118 if (sysctlbyname(node, &val, &size, NULL, 0)) { 119 perror("sysctlbyname"); 120 return (false); 121 } 122 return (val != 0); 123 } 124 125 class FuseEnv: public Environment { 126 virtual void SetUp() { 127 } 128 }; 129 130 void FuseTest::SetUp() { 131 const char *maxbcachebuf_node = "vfs.maxbcachebuf"; 132 const char *maxphys_node = "kern.maxphys"; 133 size_t size; 134 135 /* 136 * XXX check_environment should be called from FuseEnv::SetUp, but 137 * can't due to https://github.com/google/googletest/issues/2189 138 */ 139 check_environment(); 140 if (IsSkipped()) 141 return; 142 143 size = sizeof(m_maxbcachebuf); 144 ASSERT_EQ(0, sysctlbyname(maxbcachebuf_node, &m_maxbcachebuf, &size, 145 NULL, 0)) << strerror(errno); 146 size = sizeof(m_maxphys); 147 ASSERT_EQ(0, sysctlbyname(maxphys_node, &m_maxphys, &size, NULL, 0)) 148 << strerror(errno); 149 /* 150 * Set the default max_write to a distinct value from MAXPHYS to catch 151 * bugs that confuse the two. 152 */ 153 if (m_maxwrite == 0) 154 m_maxwrite = MIN(libfuse_max_write, (uint32_t)m_maxphys / 2); 155 156 try { 157 m_mock = new MockFS(m_maxreadahead, m_allow_other, 158 m_default_permissions, m_push_symlinks_in, m_ro, 159 m_pm, m_init_flags, m_kernel_minor_version, 160 m_maxwrite, m_async, m_noclusterr, m_time_gran, 161 m_nointr, m_noatime, m_fsname, m_subtype); 162 /* 163 * FUSE_ACCESS is called almost universally. Expecting it in 164 * each test case would be super-annoying. Instead, set a 165 * default expectation for FUSE_ACCESS and return ENOSYS. 166 * 167 * Individual test cases can override this expectation since 168 * googlemock evaluates expectations in LIFO order. 169 */ 170 EXPECT_CALL(*m_mock, process( 171 ResultOf([=](auto in) { 172 return (in.header.opcode == FUSE_ACCESS); 173 }, Eq(true)), 174 _) 175 ).Times(AnyNumber()) 176 .WillRepeatedly(Invoke(ReturnErrno(ENOSYS))); 177 /* 178 * FUSE_BMAP is called for most test cases that read data. Set 179 * a default expectation and return ENOSYS. 180 * 181 * Individual test cases can override this expectation since 182 * googlemock evaluates expectations in LIFO order. 183 */ 184 EXPECT_CALL(*m_mock, process( 185 ResultOf([=](auto in) { 186 return (in.header.opcode == FUSE_BMAP); 187 }, Eq(true)), 188 _) 189 ).Times(AnyNumber()) 190 .WillRepeatedly(Invoke(ReturnErrno(ENOSYS))); 191 } catch (std::system_error err) { 192 FAIL() << err.what(); 193 } 194 } 195 196 void 197 FuseTest::expect_access(uint64_t ino, mode_t access_mode, int error) 198 { 199 EXPECT_CALL(*m_mock, process( 200 ResultOf([=](auto in) { 201 return (in.header.opcode == FUSE_ACCESS && 202 in.header.nodeid == ino && 203 in.body.access.mask == access_mode); 204 }, Eq(true)), 205 _) 206 ).WillOnce(Invoke(ReturnErrno(error))); 207 } 208 209 void 210 FuseTest::expect_destroy(int error) 211 { 212 EXPECT_CALL(*m_mock, process( 213 ResultOf([=](auto in) { 214 return (in.header.opcode == FUSE_DESTROY); 215 }, Eq(true)), 216 _) 217 ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto& out) { 218 m_mock->m_quit = true; 219 out.header.len = sizeof(out.header); 220 out.header.unique = in.header.unique; 221 out.header.error = -error; 222 }))); 223 } 224 225 void 226 FuseTest::expect_fallocate(uint64_t ino, uint64_t offset, uint64_t length, 227 uint32_t mode, int error, int times) 228 { 229 EXPECT_CALL(*m_mock, process( 230 ResultOf([=](auto in) { 231 return (in.header.opcode == FUSE_FALLOCATE && 232 in.header.nodeid == ino && 233 in.body.fallocate.offset == offset && 234 in.body.fallocate.length == length && 235 in.body.fallocate.mode == mode); 236 }, Eq(true)), 237 _) 238 ).Times(times) 239 .WillRepeatedly(Invoke(ReturnErrno(error))); 240 } 241 242 void 243 FuseTest::expect_flush(uint64_t ino, int times, ProcessMockerT r) 244 { 245 EXPECT_CALL(*m_mock, process( 246 ResultOf([=](auto in) { 247 return (in.header.opcode == FUSE_FLUSH && 248 in.header.nodeid == ino); 249 }, Eq(true)), 250 _) 251 ).Times(times) 252 .WillRepeatedly(Invoke(r)); 253 } 254 255 void 256 FuseTest::expect_forget(uint64_t ino, uint64_t nlookup, sem_t *sem) 257 { 258 EXPECT_CALL(*m_mock, process( 259 ResultOf([=](auto in) { 260 return (in.header.opcode == FUSE_FORGET && 261 in.header.nodeid == ino && 262 in.body.forget.nlookup == nlookup); 263 }, Eq(true)), 264 _) 265 ).WillOnce(Invoke([=](auto in __unused, auto &out __unused) { 266 if (sem != NULL) 267 sem_post(sem); 268 /* FUSE_FORGET has no response! */ 269 })); 270 } 271 272 void FuseTest::expect_getattr(uint64_t ino, uint64_t size) 273 { 274 EXPECT_CALL(*m_mock, process( 275 ResultOf([=](auto in) { 276 return (in.header.opcode == FUSE_GETATTR && 277 in.header.nodeid == ino); 278 }, Eq(true)), 279 _) 280 ).WillOnce(Invoke(ReturnImmediate([=](auto i __unused, auto& out) { 281 SET_OUT_HEADER_LEN(out, attr); 282 out.body.attr.attr.ino = ino; // Must match nodeid 283 out.body.attr.attr.mode = S_IFREG | 0644; 284 out.body.attr.attr.size = size; 285 out.body.attr.attr_valid = UINT64_MAX; 286 }))); 287 } 288 289 void FuseTest::expect_getxattr(uint64_t ino, const char *attr, ProcessMockerT r) 290 { 291 EXPECT_CALL(*m_mock, process( 292 ResultOf([=](auto in) { 293 const char *a = (const char*)in.body.bytes + 294 sizeof(fuse_getxattr_in); 295 return (in.header.opcode == FUSE_GETXATTR && 296 in.header.nodeid == ino && 297 0 == strcmp(attr, a)); 298 }, Eq(true)), 299 _) 300 ).WillOnce(Invoke(r)); 301 } 302 303 void FuseTest::expect_lookup(const char *relpath, uint64_t ino, mode_t mode, 304 uint64_t size, int times, uint64_t attr_valid, uid_t uid, gid_t gid) 305 { 306 EXPECT_LOOKUP(FUSE_ROOT_ID, relpath) 307 .Times(times) 308 .WillRepeatedly(Invoke( 309 ReturnImmediate([=](auto in __unused, auto& out) { 310 SET_OUT_HEADER_LEN(out, entry); 311 out.body.entry.attr.mode = mode; 312 out.body.entry.nodeid = ino; 313 out.body.entry.attr.nlink = 1; 314 out.body.entry.attr_valid = attr_valid; 315 out.body.entry.attr.size = size; 316 out.body.entry.attr.uid = uid; 317 out.body.entry.attr.gid = gid; 318 }))); 319 } 320 321 void FuseTest::expect_lookup_7_8(const char *relpath, uint64_t ino, mode_t mode, 322 uint64_t size, int times, uint64_t attr_valid, uid_t uid, gid_t gid) 323 { 324 EXPECT_LOOKUP(FUSE_ROOT_ID, relpath) 325 .Times(times) 326 .WillRepeatedly(Invoke( 327 ReturnImmediate([=](auto in __unused, auto& out) { 328 SET_OUT_HEADER_LEN(out, entry_7_8); 329 out.body.entry.attr.mode = mode; 330 out.body.entry.nodeid = ino; 331 out.body.entry.attr.nlink = 1; 332 out.body.entry.attr_valid = attr_valid; 333 out.body.entry.attr.size = size; 334 out.body.entry.attr.uid = uid; 335 out.body.entry.attr.gid = gid; 336 }))); 337 } 338 339 void FuseTest::expect_open(uint64_t ino, uint32_t flags, int times) 340 { 341 EXPECT_CALL(*m_mock, process( 342 ResultOf([=](auto in) { 343 return (in.header.opcode == FUSE_OPEN && 344 in.header.nodeid == ino); 345 }, Eq(true)), 346 _) 347 ).Times(times) 348 .WillRepeatedly(Invoke( 349 ReturnImmediate([=](auto in __unused, auto& out) { 350 out.header.len = sizeof(out.header); 351 SET_OUT_HEADER_LEN(out, open); 352 out.body.open.fh = FH; 353 out.body.open.open_flags = flags; 354 }))); 355 } 356 357 void FuseTest::expect_opendir(uint64_t ino) 358 { 359 /* opendir(3) calls fstatfs */ 360 EXPECT_CALL(*m_mock, process( 361 ResultOf([](auto in) { 362 return (in.header.opcode == FUSE_STATFS); 363 }, Eq(true)), 364 _) 365 ).WillRepeatedly(Invoke( 366 ReturnImmediate([=](auto i __unused, auto& out) { 367 SET_OUT_HEADER_LEN(out, statfs); 368 }))); 369 370 EXPECT_CALL(*m_mock, process( 371 ResultOf([=](auto in) { 372 return (in.header.opcode == FUSE_OPENDIR && 373 in.header.nodeid == ino); 374 }, Eq(true)), 375 _) 376 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 377 out.header.len = sizeof(out.header); 378 SET_OUT_HEADER_LEN(out, open); 379 out.body.open.fh = FH; 380 }))); 381 } 382 383 void FuseTest::expect_read(uint64_t ino, uint64_t offset, uint64_t isize, 384 uint64_t osize, const void *contents, int flags, uint64_t fh) 385 { 386 EXPECT_CALL(*m_mock, process( 387 ResultOf([=](auto in) { 388 return (in.header.opcode == FUSE_READ && 389 in.header.nodeid == ino && 390 in.body.read.fh == fh && 391 in.body.read.offset == offset && 392 in.body.read.size == isize && 393 (flags == -1 ? 394 (in.body.read.flags == O_RDONLY || 395 in.body.read.flags == O_RDWR) 396 : in.body.read.flags == (uint32_t)flags)); 397 }, Eq(true)), 398 _) 399 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 400 assert(osize <= sizeof(out.body.bytes)); 401 out.header.len = sizeof(struct fuse_out_header) + osize; 402 memmove(out.body.bytes, contents, osize); 403 }))).RetiresOnSaturation(); 404 } 405 406 void FuseTest::expect_readdir(uint64_t ino, uint64_t off, 407 std::vector<struct dirent> &ents) 408 { 409 EXPECT_CALL(*m_mock, process( 410 ResultOf([=](auto in) { 411 return (in.header.opcode == FUSE_READDIR && 412 in.header.nodeid == ino && 413 in.body.readdir.fh == FH && 414 in.body.readdir.offset == off); 415 }, Eq(true)), 416 _) 417 ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto& out) { 418 struct fuse_dirent *fde = (struct fuse_dirent*)&(out.body); 419 int i = 0; 420 421 out.header.error = 0; 422 out.header.len = 0; 423 424 for (const auto& it: ents) { 425 size_t entlen, entsize; 426 427 fde->ino = it.d_fileno; 428 fde->off = it.d_off; 429 fde->type = it.d_type; 430 fde->namelen = it.d_namlen; 431 strncpy(fde->name, it.d_name, it.d_namlen); 432 entlen = FUSE_NAME_OFFSET + fde->namelen; 433 entsize = FUSE_DIRENT_SIZE(fde); 434 /* 435 * The FUSE protocol does not require zeroing out the 436 * unused portion of the name. But it's a good 437 * practice to prevent information disclosure to the 438 * FUSE client, even though the client is usually the 439 * kernel 440 */ 441 memset(fde->name + fde->namelen, 0, entsize - entlen); 442 if (out.header.len + entsize > in.body.read.size) { 443 printf("Overflow in readdir expectation: i=%d\n" 444 , i); 445 break; 446 } 447 out.header.len += entsize; 448 fde = (struct fuse_dirent*) 449 ((intmax_t*)fde + entsize / sizeof(intmax_t)); 450 i++; 451 } 452 out.header.len += sizeof(out.header); 453 }))); 454 455 } 456 void FuseTest::expect_release(uint64_t ino, uint64_t fh) 457 { 458 EXPECT_CALL(*m_mock, process( 459 ResultOf([=](auto in) { 460 return (in.header.opcode == FUSE_RELEASE && 461 in.header.nodeid == ino && 462 in.body.release.fh == fh); 463 }, Eq(true)), 464 _) 465 ).WillOnce(Invoke(ReturnErrno(0))); 466 } 467 468 void FuseTest::expect_releasedir(uint64_t ino, ProcessMockerT r) 469 { 470 EXPECT_CALL(*m_mock, process( 471 ResultOf([=](auto in) { 472 return (in.header.opcode == FUSE_RELEASEDIR && 473 in.header.nodeid == ino && 474 in.body.release.fh == FH); 475 }, Eq(true)), 476 _) 477 ).WillOnce(Invoke(r)); 478 } 479 480 void FuseTest::expect_unlink(uint64_t parent, const char *path, int error) 481 { 482 EXPECT_CALL(*m_mock, process( 483 ResultOf([=](auto in) { 484 return (in.header.opcode == FUSE_UNLINK && 485 0 == strcmp(path, in.body.unlink) && 486 in.header.nodeid == parent); 487 }, Eq(true)), 488 _) 489 ).WillOnce(Invoke(ReturnErrno(error))); 490 } 491 492 void FuseTest::expect_write(uint64_t ino, uint64_t offset, uint64_t isize, 493 uint64_t osize, uint32_t flags_set, uint32_t flags_unset, 494 const void *contents) 495 { 496 EXPECT_CALL(*m_mock, process( 497 ResultOf([=](auto in) { 498 const char *buf = (const char*)in.body.bytes + 499 sizeof(struct fuse_write_in); 500 bool pid_ok; 501 uint32_t wf = in.body.write.write_flags; 502 503 assert(isize <= sizeof(in.body.bytes) - 504 sizeof(struct fuse_write_in)); 505 if (wf & FUSE_WRITE_CACHE) 506 pid_ok = true; 507 else 508 pid_ok = (pid_t)in.header.pid == getpid(); 509 510 return (in.header.opcode == FUSE_WRITE && 511 in.header.nodeid == ino && 512 in.body.write.fh == FH && 513 in.body.write.offset == offset && 514 in.body.write.size == isize && 515 pid_ok && 516 (wf & flags_set) == flags_set && 517 (wf & flags_unset) == 0 && 518 (in.body.write.flags == O_WRONLY || 519 in.body.write.flags == O_RDWR) && 520 0 == bcmp(buf, contents, isize)); 521 }, Eq(true)), 522 _) 523 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 524 SET_OUT_HEADER_LEN(out, write); 525 out.body.write.size = osize; 526 }))); 527 } 528 529 void FuseTest::expect_write_7_8(uint64_t ino, uint64_t offset, uint64_t isize, 530 uint64_t osize, const void *contents) 531 { 532 EXPECT_CALL(*m_mock, process( 533 ResultOf([=](auto in) { 534 const char *buf = (const char*)in.body.bytes + 535 FUSE_COMPAT_WRITE_IN_SIZE; 536 bool pid_ok = (pid_t)in.header.pid == getpid(); 537 538 assert(isize <= sizeof(in.body.bytes) - 539 FUSE_COMPAT_WRITE_IN_SIZE); 540 return (in.header.opcode == FUSE_WRITE && 541 in.header.nodeid == ino && 542 in.body.write.fh == FH && 543 in.body.write.offset == offset && 544 in.body.write.size == isize && 545 pid_ok && 546 0 == bcmp(buf, contents, isize)); 547 }, Eq(true)), 548 _) 549 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 550 SET_OUT_HEADER_LEN(out, write); 551 out.body.write.size = osize; 552 }))); 553 } 554 555 void 556 get_unprivileged_id(uid_t *uid, gid_t *gid) 557 { 558 struct passwd *pw; 559 struct group *gr; 560 561 /* 562 * First try "tests", Kyua's default unprivileged user. XXX after 563 * GoogleTest gains a proper Kyua wrapper, get this with the Kyua API 564 */ 565 pw = getpwnam("tests"); 566 if (pw == NULL) { 567 /* Fall back to "nobody" */ 568 pw = getpwnam("nobody"); 569 } 570 if (pw == NULL) 571 GTEST_SKIP() << "Test requires an unprivileged user"; 572 /* Use group "nobody", which is Kyua's default unprivileged group */ 573 gr = getgrnam("nobody"); 574 if (gr == NULL) 575 GTEST_SKIP() << "Test requires an unprivileged group"; 576 *uid = pw->pw_uid; 577 *gid = gr->gr_gid; 578 } 579 580 void 581 FuseTest::fork(bool drop_privs, int *child_status, 582 std::function<void()> parent_func, 583 std::function<int()> child_func) 584 { 585 sem_t *sem; 586 int mprot = PROT_READ | PROT_WRITE; 587 int mflags = MAP_ANON | MAP_SHARED; 588 pid_t child; 589 uid_t uid; 590 gid_t gid; 591 592 if (drop_privs) { 593 get_unprivileged_id(&uid, &gid); 594 if (IsSkipped()) 595 return; 596 } 597 598 sem = (sem_t*)mmap(NULL, sizeof(*sem), mprot, mflags, -1, 0); 599 ASSERT_NE(MAP_FAILED, sem) << strerror(errno); 600 ASSERT_EQ(0, sem_init(sem, 1, 0)) << strerror(errno); 601 602 if ((child = ::fork()) == 0) { 603 /* In child */ 604 int err = 0; 605 606 if (sem_wait(sem)) { 607 perror("sem_wait"); 608 err = 1; 609 goto out; 610 } 611 612 if (drop_privs && 0 != setegid(gid)) { 613 perror("setegid"); 614 err = 1; 615 goto out; 616 } 617 if (drop_privs && 0 != setreuid(-1, uid)) { 618 perror("setreuid"); 619 err = 1; 620 goto out; 621 } 622 err = child_func(); 623 624 out: 625 sem_destroy(sem); 626 _exit(err); 627 } else if (child > 0) { 628 /* 629 * In parent. Cleanup must happen here, because it's still 630 * privileged. 631 */ 632 m_mock->m_child_pid = child; 633 ASSERT_NO_FATAL_FAILURE(parent_func()); 634 635 /* Signal the child process to go */ 636 ASSERT_EQ(0, sem_post(sem)) << strerror(errno); 637 638 ASSERT_LE(0, wait(child_status)) << strerror(errno); 639 } else { 640 FAIL() << strerror(errno); 641 } 642 munmap(sem, sizeof(*sem)); 643 return; 644 } 645 646 void 647 FuseTest::reclaim_vnode(const char *path) 648 { 649 int err; 650 651 err = sysctlbyname(reclaim_mib, NULL, 0, path, strlen(path) + 1); 652 ASSERT_EQ(0, err) << strerror(errno); 653 } 654 655 static void usage(char* progname) { 656 fprintf(stderr, "Usage: %s [-v]\n\t-v increase verbosity\n", progname); 657 exit(2); 658 } 659 660 int main(int argc, char **argv) { 661 int ch; 662 FuseEnv *fuse_env = new FuseEnv; 663 664 InitGoogleTest(&argc, argv); 665 AddGlobalTestEnvironment(fuse_env); 666 667 while ((ch = getopt(argc, argv, "v")) != -1) { 668 switch (ch) { 669 case 'v': 670 verbosity++; 671 break; 672 default: 673 usage(argv[0]); 674 break; 675 } 676 } 677 678 return (RUN_ALL_TESTS()); 679 } 680