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