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