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/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); 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 out.header.len = sizeof(struct fuse_out_header) + osize; 404 memmove(out.body.bytes, contents, osize); 405 }))).RetiresOnSaturation(); 406 } 407 408 void FuseTest::expect_readdir(uint64_t ino, uint64_t off, 409 std::vector<struct dirent> &ents) 410 { 411 EXPECT_CALL(*m_mock, process( 412 ResultOf([=](auto in) { 413 return (in.header.opcode == FUSE_READDIR && 414 in.header.nodeid == ino && 415 in.body.readdir.fh == FH && 416 in.body.readdir.offset == off); 417 }, Eq(true)), 418 _) 419 ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in, auto& out) { 420 struct fuse_dirent *fde = (struct fuse_dirent*)&(out.body); 421 int i = 0; 422 423 out.header.error = 0; 424 out.header.len = 0; 425 426 for (const auto& it: ents) { 427 size_t entlen, entsize; 428 429 fde->ino = it.d_fileno; 430 fde->off = it.d_off; 431 fde->type = it.d_type; 432 fde->namelen = it.d_namlen; 433 strncpy(fde->name, it.d_name, it.d_namlen); 434 entlen = FUSE_NAME_OFFSET + fde->namelen; 435 entsize = FUSE_DIRENT_SIZE(fde); 436 /* 437 * The FUSE protocol does not require zeroing out the 438 * unused portion of the name. But it's a good 439 * practice to prevent information disclosure to the 440 * FUSE client, even though the client is usually the 441 * kernel 442 */ 443 memset(fde->name + fde->namelen, 0, entsize - entlen); 444 if (out.header.len + entsize > in.body.read.size) { 445 printf("Overflow in readdir expectation: i=%d\n" 446 , i); 447 break; 448 } 449 out.header.len += entsize; 450 fde = (struct fuse_dirent*) 451 ((intmax_t*)fde + entsize / sizeof(intmax_t)); 452 i++; 453 } 454 out.header.len += sizeof(out.header); 455 }))); 456 457 } 458 void FuseTest::expect_release(uint64_t ino, uint64_t fh) 459 { 460 EXPECT_CALL(*m_mock, process( 461 ResultOf([=](auto in) { 462 return (in.header.opcode == FUSE_RELEASE && 463 in.header.nodeid == ino && 464 in.body.release.fh == fh); 465 }, Eq(true)), 466 _) 467 ).WillOnce(Invoke(ReturnErrno(0))); 468 } 469 470 void FuseTest::expect_releasedir(uint64_t ino, ProcessMockerT r) 471 { 472 EXPECT_CALL(*m_mock, process( 473 ResultOf([=](auto in) { 474 return (in.header.opcode == FUSE_RELEASEDIR && 475 in.header.nodeid == ino && 476 in.body.release.fh == FH); 477 }, Eq(true)), 478 _) 479 ).WillOnce(Invoke(r)); 480 } 481 482 void FuseTest::expect_unlink(uint64_t parent, const char *path, int error) 483 { 484 EXPECT_CALL(*m_mock, process( 485 ResultOf([=](auto in) { 486 return (in.header.opcode == FUSE_UNLINK && 487 0 == strcmp(path, in.body.unlink) && 488 in.header.nodeid == parent); 489 }, Eq(true)), 490 _) 491 ).WillOnce(Invoke(ReturnErrno(error))); 492 } 493 494 void FuseTest::expect_write(uint64_t ino, uint64_t offset, uint64_t isize, 495 uint64_t osize, uint32_t flags_set, uint32_t flags_unset, 496 const void *contents) 497 { 498 EXPECT_CALL(*m_mock, process( 499 ResultOf([=](auto in) { 500 const char *buf = (const char*)in.body.bytes + 501 sizeof(struct fuse_write_in); 502 bool pid_ok; 503 uint32_t wf = in.body.write.write_flags; 504 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 return (in.header.opcode == FUSE_WRITE && 538 in.header.nodeid == ino && 539 in.body.write.fh == FH && 540 in.body.write.offset == offset && 541 in.body.write.size == isize && 542 pid_ok && 543 0 == bcmp(buf, contents, isize)); 544 }, Eq(true)), 545 _) 546 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 547 SET_OUT_HEADER_LEN(out, write); 548 out.body.write.size = osize; 549 }))); 550 } 551 552 void 553 get_unprivileged_id(uid_t *uid, gid_t *gid) 554 { 555 struct passwd *pw; 556 struct group *gr; 557 558 /* 559 * First try "tests", Kyua's default unprivileged user. XXX after 560 * GoogleTest gains a proper Kyua wrapper, get this with the Kyua API 561 */ 562 pw = getpwnam("tests"); 563 if (pw == NULL) { 564 /* Fall back to "nobody" */ 565 pw = getpwnam("nobody"); 566 } 567 if (pw == NULL) 568 GTEST_SKIP() << "Test requires an unprivileged user"; 569 /* Use group "nobody", which is Kyua's default unprivileged group */ 570 gr = getgrnam("nobody"); 571 if (gr == NULL) 572 GTEST_SKIP() << "Test requires an unprivileged group"; 573 *uid = pw->pw_uid; 574 *gid = gr->gr_gid; 575 } 576 577 void 578 FuseTest::fork(bool drop_privs, int *child_status, 579 std::function<void()> parent_func, 580 std::function<int()> child_func) 581 { 582 sem_t *sem; 583 int mprot = PROT_READ | PROT_WRITE; 584 int mflags = MAP_ANON | MAP_SHARED; 585 pid_t child; 586 uid_t uid; 587 gid_t gid; 588 589 if (drop_privs) { 590 get_unprivileged_id(&uid, &gid); 591 if (IsSkipped()) 592 return; 593 } 594 595 sem = (sem_t*)mmap(NULL, sizeof(*sem), mprot, mflags, -1, 0); 596 ASSERT_NE(MAP_FAILED, sem) << strerror(errno); 597 ASSERT_EQ(0, sem_init(sem, 1, 0)) << strerror(errno); 598 599 if ((child = ::fork()) == 0) { 600 /* In child */ 601 int err = 0; 602 603 if (sem_wait(sem)) { 604 perror("sem_wait"); 605 err = 1; 606 goto out; 607 } 608 609 if (drop_privs && 0 != setegid(gid)) { 610 perror("setegid"); 611 err = 1; 612 goto out; 613 } 614 if (drop_privs && 0 != setreuid(-1, uid)) { 615 perror("setreuid"); 616 err = 1; 617 goto out; 618 } 619 err = child_func(); 620 621 out: 622 sem_destroy(sem); 623 _exit(err); 624 } else if (child > 0) { 625 /* 626 * In parent. Cleanup must happen here, because it's still 627 * privileged. 628 */ 629 m_mock->m_child_pid = child; 630 ASSERT_NO_FATAL_FAILURE(parent_func()); 631 632 /* Signal the child process to go */ 633 ASSERT_EQ(0, sem_post(sem)) << strerror(errno); 634 635 ASSERT_LE(0, wait(child_status)) << strerror(errno); 636 } else { 637 FAIL() << strerror(errno); 638 } 639 munmap(sem, sizeof(*sem)); 640 return; 641 } 642 643 void 644 FuseTest::reclaim_vnode(const char *path) 645 { 646 int err; 647 648 err = sysctlbyname(reclaim_mib, NULL, 0, path, strlen(path) + 1); 649 ASSERT_EQ(0, err) << strerror(errno); 650 } 651 652 static void usage(char* progname) { 653 fprintf(stderr, "Usage: %s [-v]\n\t-v increase verbosity\n", progname); 654 exit(2); 655 } 656 657 int main(int argc, char **argv) { 658 int ch; 659 FuseEnv *fuse_env = new FuseEnv; 660 661 InitGoogleTest(&argc, argv); 662 AddGlobalTestEnvironment(fuse_env); 663 664 while ((ch = getopt(argc, argv, "v")) != -1) { 665 switch (ch) { 666 case 'v': 667 verbosity++; 668 break; 669 default: 670 usage(argv[0]); 671 break; 672 } 673 } 674 675 return (RUN_ALL_TESTS()); 676 } 677