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