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/types.h> 35 #include <sys/extattr.h> 36 #include <sys/mman.h> 37 #include <sys/wait.h> 38 #include <fcntl.h> 39 #include <pthread.h> 40 #include <semaphore.h> 41 #include <signal.h> 42 } 43 44 #include "mockfs.hh" 45 #include "utils.hh" 46 47 using namespace testing; 48 49 /* Initial size of files used by these tests */ 50 const off_t FILESIZE = 1000; 51 /* Access mode used by all directories in these tests */ 52 const mode_t MODE = 0755; 53 const char FULLDIRPATH0[] = "mountpoint/some_dir"; 54 const char RELDIRPATH0[] = "some_dir"; 55 const char FULLDIRPATH1[] = "mountpoint/other_dir"; 56 const char RELDIRPATH1[] = "other_dir"; 57 58 static sem_t *blocked_semaphore; 59 static sem_t *signaled_semaphore; 60 61 static bool killer_should_sleep = false; 62 63 /* Don't do anything; all we care about is that the syscall gets interrupted */ 64 void sigusr2_handler(int __unused sig) { 65 if (verbosity > 1) { 66 printf("Signaled! thread %p\n", pthread_self()); 67 } 68 69 } 70 71 void* killer(void* target) { 72 /* Wait until the main thread is blocked in fdisp_wait_answ */ 73 if (killer_should_sleep) 74 nap(); 75 else 76 sem_wait(blocked_semaphore); 77 if (verbosity > 1) 78 printf("Signalling! thread %p\n", target); 79 pthread_kill((pthread_t)target, SIGUSR2); 80 if (signaled_semaphore != NULL) 81 sem_post(signaled_semaphore); 82 83 return(NULL); 84 } 85 86 class Interrupt: public FuseTest { 87 public: 88 pthread_t m_child; 89 90 Interrupt(): m_child(NULL) {}; 91 92 void expect_lookup(const char *relpath, uint64_t ino) 93 { 94 FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, FILESIZE, 1); 95 } 96 97 /* 98 * Expect a FUSE_MKDIR but don't reply. Instead, just record the unique value 99 * to the provided pointer 100 */ 101 void expect_mkdir(uint64_t *mkdir_unique) 102 { 103 EXPECT_CALL(*m_mock, process( 104 ResultOf([=](auto in) { 105 return (in.header.opcode == FUSE_MKDIR); 106 }, Eq(true)), 107 _) 108 ).WillOnce(Invoke([=](auto in, auto &out __unused) { 109 *mkdir_unique = in.header.unique; 110 sem_post(blocked_semaphore); 111 })); 112 } 113 114 /* 115 * Expect a FUSE_READ but don't reply. Instead, just record the unique value 116 * to the provided pointer 117 */ 118 void expect_read(uint64_t ino, uint64_t *read_unique) 119 { 120 EXPECT_CALL(*m_mock, process( 121 ResultOf([=](auto in) { 122 return (in.header.opcode == FUSE_READ && 123 in.header.nodeid == ino); 124 }, Eq(true)), 125 _) 126 ).WillOnce(Invoke([=](auto in, auto &out __unused) { 127 *read_unique = in.header.unique; 128 sem_post(blocked_semaphore); 129 })); 130 } 131 132 /* 133 * Expect a FUSE_WRITE but don't reply. Instead, just record the unique value 134 * to the provided pointer 135 */ 136 void expect_write(uint64_t ino, uint64_t *write_unique) 137 { 138 EXPECT_CALL(*m_mock, process( 139 ResultOf([=](auto in) { 140 return (in.header.opcode == FUSE_WRITE && 141 in.header.nodeid == ino); 142 }, Eq(true)), 143 _) 144 ).WillOnce(Invoke([=](auto in, auto &out __unused) { 145 *write_unique = in.header.unique; 146 sem_post(blocked_semaphore); 147 })); 148 } 149 150 void setup_interruptor(pthread_t target, bool sleep = false) 151 { 152 ASSERT_NE(SIG_ERR, signal(SIGUSR2, sigusr2_handler)) << strerror(errno); 153 killer_should_sleep = sleep; 154 ASSERT_EQ(0, pthread_create(&m_child, NULL, killer, (void*)target)) 155 << strerror(errno); 156 } 157 158 void SetUp() { 159 const int mprot = PROT_READ | PROT_WRITE; 160 const int mflags = MAP_ANON | MAP_SHARED; 161 162 signaled_semaphore = NULL; 163 164 blocked_semaphore = (sem_t*)mmap(NULL, sizeof(*blocked_semaphore), 165 mprot, mflags, -1, 0); 166 ASSERT_NE(MAP_FAILED, blocked_semaphore) << strerror(errno); 167 ASSERT_EQ(0, sem_init(blocked_semaphore, 1, 0)) << strerror(errno); 168 ASSERT_EQ(0, siginterrupt(SIGUSR2, 1)); 169 170 FuseTest::SetUp(); 171 } 172 173 void TearDown() { 174 struct sigaction sa; 175 176 if (m_child != NULL) { 177 pthread_join(m_child, NULL); 178 } 179 bzero(&sa, sizeof(sa)); 180 sa.sa_handler = SIG_DFL; 181 sigaction(SIGUSR2, &sa, NULL); 182 183 sem_destroy(blocked_semaphore); 184 munmap(blocked_semaphore, sizeof(*blocked_semaphore)); 185 186 FuseTest::TearDown(); 187 } 188 }; 189 190 class Intr: public Interrupt {}; 191 192 class Nointr: public Interrupt { 193 void SetUp() { 194 m_nointr = true; 195 Interrupt::SetUp(); 196 } 197 }; 198 199 static void* mkdir0(void* arg __unused) { 200 ssize_t r; 201 202 r = mkdir(FULLDIRPATH0, MODE); 203 if (r >= 0) 204 return 0; 205 else 206 return (void*)(intptr_t)errno; 207 } 208 209 static void* read1(void* arg) { 210 const size_t bufsize = FILESIZE; 211 char buf[bufsize]; 212 int fd = (int)(intptr_t)arg; 213 ssize_t r; 214 215 r = read(fd, buf, bufsize); 216 if (r >= 0) 217 return 0; 218 else 219 return (void*)(intptr_t)errno; 220 } 221 222 /* 223 * An interrupt operation that gets received after the original command is 224 * complete should generate an EAGAIN response. 225 */ 226 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */ 227 TEST_F(Intr, already_complete) 228 { 229 uint64_t ino = 42; 230 pthread_t self; 231 uint64_t mkdir_unique = 0; 232 Sequence seq; 233 234 self = pthread_self(); 235 236 EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0) 237 .InSequence(seq) 238 .WillOnce(Invoke(ReturnErrno(ENOENT))); 239 expect_mkdir(&mkdir_unique); 240 EXPECT_CALL(*m_mock, process( 241 ResultOf([&](auto in) { 242 return (in.header.opcode == FUSE_INTERRUPT && 243 in.body.interrupt.unique == mkdir_unique); 244 }, Eq(true)), 245 _) 246 ).WillOnce(Invoke([&](auto in, auto &out) { 247 // First complete the mkdir request 248 std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out); 249 out0->header.unique = mkdir_unique; 250 SET_OUT_HEADER_LEN(*out0, entry); 251 out0->body.create.entry.attr.mode = S_IFDIR | MODE; 252 out0->body.create.entry.nodeid = ino; 253 out.push_back(std::move(out0)); 254 255 // Then, respond EAGAIN to the interrupt request 256 std::unique_ptr<mockfs_buf_out> out1(new mockfs_buf_out); 257 out1->header.unique = in.header.unique; 258 out1->header.error = -EAGAIN; 259 out1->header.len = sizeof(out1->header); 260 out.push_back(std::move(out1)); 261 })); 262 EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0) 263 .InSequence(seq) 264 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 265 SET_OUT_HEADER_LEN(out, entry); 266 out.body.entry.attr.mode = S_IFDIR | MODE; 267 out.body.entry.nodeid = ino; 268 out.body.entry.attr.nlink = 2; 269 }))); 270 271 setup_interruptor(self); 272 EXPECT_EQ(0, mkdir(FULLDIRPATH0, MODE)) << strerror(errno); 273 /* 274 * The final syscall simply ensures that the test's main thread doesn't 275 * end before the daemon finishes responding to the FUSE_INTERRUPT. 276 */ 277 EXPECT_EQ(0, access(FULLDIRPATH0, F_OK)) << strerror(errno); 278 } 279 280 /* 281 * If a FUSE file system returns ENOSYS for a FUSE_INTERRUPT operation, the 282 * kernel should not attempt to interrupt any other operations on that mount 283 * point. 284 */ 285 TEST_F(Intr, enosys) 286 { 287 uint64_t ino0 = 42, ino1 = 43;; 288 uint64_t mkdir_unique; 289 pthread_t self, th0; 290 sem_t sem0, sem1; 291 void *thr0_value; 292 Sequence seq; 293 294 self = pthread_self(); 295 ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno); 296 ASSERT_EQ(0, sem_init(&sem1, 0, 0)) << strerror(errno); 297 298 EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH1) 299 .WillOnce(Invoke(ReturnErrno(ENOENT))); 300 EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0) 301 .WillOnce(Invoke(ReturnErrno(ENOENT))); 302 expect_mkdir(&mkdir_unique); 303 EXPECT_CALL(*m_mock, process( 304 ResultOf([&](auto in) { 305 return (in.header.opcode == FUSE_INTERRUPT && 306 in.body.interrupt.unique == mkdir_unique); 307 }, Eq(true)), 308 _) 309 ).InSequence(seq) 310 .WillOnce(Invoke([&](auto in, auto &out) { 311 // reject FUSE_INTERRUPT and respond to the FUSE_MKDIR 312 std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out); 313 std::unique_ptr<mockfs_buf_out> out1(new mockfs_buf_out); 314 315 out0->header.unique = in.header.unique; 316 out0->header.error = -ENOSYS; 317 out0->header.len = sizeof(out0->header); 318 out.push_back(std::move(out0)); 319 320 SET_OUT_HEADER_LEN(*out1, entry); 321 out1->body.create.entry.attr.mode = S_IFDIR | MODE; 322 out1->body.create.entry.nodeid = ino1; 323 out1->header.unique = mkdir_unique; 324 out.push_back(std::move(out1)); 325 })); 326 EXPECT_CALL(*m_mock, process( 327 ResultOf([&](auto in) { 328 return (in.header.opcode == FUSE_MKDIR); 329 }, Eq(true)), 330 _) 331 ).InSequence(seq) 332 .WillOnce(Invoke([&](auto in, auto &out) { 333 std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out); 334 335 sem_post(&sem0); 336 sem_wait(&sem1); 337 338 SET_OUT_HEADER_LEN(*out0, entry); 339 out0->body.create.entry.attr.mode = S_IFDIR | MODE; 340 out0->body.create.entry.nodeid = ino0; 341 out0->header.unique = in.header.unique; 342 out.push_back(std::move(out0)); 343 })); 344 345 setup_interruptor(self); 346 /* First mkdir operation should finish synchronously */ 347 ASSERT_EQ(0, mkdir(FULLDIRPATH1, MODE)) << strerror(errno); 348 349 ASSERT_EQ(0, pthread_create(&th0, NULL, mkdir0, NULL)) 350 << strerror(errno); 351 352 sem_wait(&sem0); 353 /* 354 * th0 should be blocked waiting for the fuse daemon thread. 355 * Signal it. No FUSE_INTERRUPT should result 356 */ 357 pthread_kill(th0, SIGUSR1); 358 /* Allow the daemon thread to proceed */ 359 sem_post(&sem1); 360 pthread_join(th0, &thr0_value); 361 /* Second mkdir should've finished without error */ 362 EXPECT_EQ(0, (intptr_t)thr0_value); 363 } 364 365 /* 366 * A FUSE filesystem is legally allowed to ignore INTERRUPT operations, and 367 * complete the original operation whenever it damn well pleases. 368 */ 369 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */ 370 TEST_F(Intr, ignore) 371 { 372 uint64_t ino = 42; 373 pthread_t self; 374 uint64_t mkdir_unique; 375 376 self = pthread_self(); 377 378 EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0) 379 .WillOnce(Invoke(ReturnErrno(ENOENT))); 380 expect_mkdir(&mkdir_unique); 381 EXPECT_CALL(*m_mock, process( 382 ResultOf([&](auto in) { 383 return (in.header.opcode == FUSE_INTERRUPT && 384 in.body.interrupt.unique == mkdir_unique); 385 }, Eq(true)), 386 _) 387 ).WillOnce(Invoke([&](auto in __unused, auto &out) { 388 // Ignore FUSE_INTERRUPT; respond to the FUSE_MKDIR 389 std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out); 390 out0->header.unique = mkdir_unique; 391 SET_OUT_HEADER_LEN(*out0, entry); 392 out0->body.create.entry.attr.mode = S_IFDIR | MODE; 393 out0->body.create.entry.nodeid = ino; 394 out.push_back(std::move(out0)); 395 })); 396 397 setup_interruptor(self); 398 ASSERT_EQ(0, mkdir(FULLDIRPATH0, MODE)) << strerror(errno); 399 } 400 401 /* 402 * A restartable operation (basically, anything except write or setextattr) 403 * that hasn't yet been sent to userland can be interrupted without sending 404 * FUSE_INTERRUPT, and will be automatically restarted. 405 */ 406 TEST_F(Intr, in_kernel_restartable) 407 { 408 const char FULLPATH1[] = "mountpoint/other_file.txt"; 409 const char RELPATH1[] = "other_file.txt"; 410 uint64_t ino0 = 42, ino1 = 43; 411 int fd1; 412 pthread_t self, th0, th1; 413 sem_t sem0, sem1; 414 void *thr0_value, *thr1_value; 415 416 ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno); 417 ASSERT_EQ(0, sem_init(&sem1, 0, 0)) << strerror(errno); 418 self = pthread_self(); 419 420 EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0) 421 .WillOnce(Invoke(ReturnErrno(ENOENT))); 422 expect_lookup(RELPATH1, ino1); 423 expect_open(ino1, 0, 1); 424 EXPECT_CALL(*m_mock, process( 425 ResultOf([=](auto in) { 426 return (in.header.opcode == FUSE_MKDIR); 427 }, Eq(true)), 428 _) 429 ).WillOnce(Invoke(ReturnImmediate([&](auto in __unused, auto& out) { 430 /* Let the next write proceed */ 431 sem_post(&sem1); 432 /* Pause the daemon thread so it won't read the next op */ 433 sem_wait(&sem0); 434 435 SET_OUT_HEADER_LEN(out, entry); 436 out.body.create.entry.attr.mode = S_IFDIR | MODE; 437 out.body.create.entry.nodeid = ino0; 438 }))); 439 FuseTest::expect_read(ino1, 0, FILESIZE, 0, NULL); 440 441 fd1 = open(FULLPATH1, O_RDONLY); 442 ASSERT_LE(0, fd1) << strerror(errno); 443 444 /* Use a separate thread for each operation */ 445 ASSERT_EQ(0, pthread_create(&th0, NULL, mkdir0, NULL)) 446 << strerror(errno); 447 448 sem_wait(&sem1); /* Sequence the two operations */ 449 450 ASSERT_EQ(0, pthread_create(&th1, NULL, read1, (void*)(intptr_t)fd1)) 451 << strerror(errno); 452 453 setup_interruptor(self, true); 454 455 pause(); /* Wait for signal */ 456 457 /* Unstick the daemon */ 458 ASSERT_EQ(0, sem_post(&sem0)) << strerror(errno); 459 460 /* Wait awhile to make sure the signal generates no FUSE_INTERRUPT */ 461 nap(); 462 463 pthread_join(th1, &thr1_value); 464 pthread_join(th0, &thr0_value); 465 EXPECT_EQ(0, (intptr_t)thr1_value); 466 EXPECT_EQ(0, (intptr_t)thr0_value); 467 sem_destroy(&sem1); 468 sem_destroy(&sem0); 469 470 leak(fd1); 471 } 472 473 /* 474 * An operation that hasn't yet been sent to userland can be interrupted 475 * without sending FUSE_INTERRUPT. If it's a non-restartable operation (write 476 * or setextattr) it will return EINTR. 477 */ 478 TEST_F(Intr, in_kernel_nonrestartable) 479 { 480 const char FULLPATH1[] = "mountpoint/other_file.txt"; 481 const char RELPATH1[] = "other_file.txt"; 482 const char value[] = "whatever"; 483 ssize_t value_len = strlen(value) + 1; 484 uint64_t ino0 = 42, ino1 = 43; 485 int ns = EXTATTR_NAMESPACE_USER; 486 int fd1; 487 pthread_t self, th0; 488 sem_t sem0, sem1; 489 void *thr0_value; 490 ssize_t r; 491 492 ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno); 493 ASSERT_EQ(0, sem_init(&sem1, 0, 0)) << strerror(errno); 494 self = pthread_self(); 495 496 EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0) 497 .WillOnce(Invoke(ReturnErrno(ENOENT))); 498 expect_lookup(RELPATH1, ino1); 499 expect_open(ino1, 0, 1); 500 EXPECT_CALL(*m_mock, process( 501 ResultOf([=](auto in) { 502 return (in.header.opcode == FUSE_MKDIR); 503 }, Eq(true)), 504 _) 505 ).WillOnce(Invoke(ReturnImmediate([&](auto in __unused, auto& out) { 506 /* Let the next write proceed */ 507 sem_post(&sem1); 508 /* Pause the daemon thread so it won't read the next op */ 509 sem_wait(&sem0); 510 511 SET_OUT_HEADER_LEN(out, entry); 512 out.body.create.entry.attr.mode = S_IFDIR | MODE; 513 out.body.create.entry.nodeid = ino0; 514 }))); 515 516 fd1 = open(FULLPATH1, O_WRONLY); 517 ASSERT_LE(0, fd1) << strerror(errno); 518 519 /* Use a separate thread for the first write */ 520 ASSERT_EQ(0, pthread_create(&th0, NULL, mkdir0, NULL)) 521 << strerror(errno); 522 523 sem_wait(&sem1); /* Sequence the two operations */ 524 525 setup_interruptor(self, true); 526 527 r = extattr_set_fd(fd1, ns, "foo", (const void*)value, value_len); 528 EXPECT_NE(0, r); 529 EXPECT_EQ(EINTR, errno); 530 531 /* Unstick the daemon */ 532 ASSERT_EQ(0, sem_post(&sem0)) << strerror(errno); 533 534 /* Wait awhile to make sure the signal generates no FUSE_INTERRUPT */ 535 nap(); 536 537 pthread_join(th0, &thr0_value); 538 EXPECT_EQ(0, (intptr_t)thr0_value); 539 sem_destroy(&sem1); 540 sem_destroy(&sem0); 541 542 leak(fd1); 543 } 544 545 /* 546 * A syscall that gets interrupted while blocking on FUSE I/O should send a 547 * FUSE_INTERRUPT command to the fuse filesystem, which should then send EINTR 548 * in response to the _original_ operation. The kernel should ultimately 549 * return EINTR to userspace 550 */ 551 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */ 552 TEST_F(Intr, in_progress) 553 { 554 pthread_t self; 555 uint64_t mkdir_unique; 556 557 self = pthread_self(); 558 559 EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0) 560 .WillOnce(Invoke(ReturnErrno(ENOENT))); 561 expect_mkdir(&mkdir_unique); 562 EXPECT_CALL(*m_mock, process( 563 ResultOf([&](auto in) { 564 return (in.header.opcode == FUSE_INTERRUPT && 565 in.body.interrupt.unique == mkdir_unique); 566 }, Eq(true)), 567 _) 568 ).WillOnce(Invoke([&](auto in __unused, auto &out) { 569 std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out); 570 out0->header.error = -EINTR; 571 out0->header.unique = mkdir_unique; 572 out0->header.len = sizeof(out0->header); 573 out.push_back(std::move(out0)); 574 })); 575 576 setup_interruptor(self); 577 ASSERT_EQ(-1, mkdir(FULLDIRPATH0, MODE)); 578 EXPECT_EQ(EINTR, errno); 579 } 580 581 /* Reads should also be interruptible */ 582 TEST_F(Intr, in_progress_read) 583 { 584 const char FULLPATH[] = "mountpoint/some_file.txt"; 585 const char RELPATH[] = "some_file.txt"; 586 const size_t bufsize = 80; 587 char buf[bufsize]; 588 uint64_t ino = 42; 589 int fd; 590 pthread_t self; 591 uint64_t read_unique; 592 593 self = pthread_self(); 594 595 expect_lookup(RELPATH, ino); 596 expect_open(ino, 0, 1); 597 expect_read(ino, &read_unique); 598 EXPECT_CALL(*m_mock, process( 599 ResultOf([&](auto in) { 600 return (in.header.opcode == FUSE_INTERRUPT && 601 in.body.interrupt.unique == read_unique); 602 }, Eq(true)), 603 _) 604 ).WillOnce(Invoke([&](auto in __unused, auto &out) { 605 std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out); 606 out0->header.error = -EINTR; 607 out0->header.unique = read_unique; 608 out0->header.len = sizeof(out0->header); 609 out.push_back(std::move(out0)); 610 })); 611 612 fd = open(FULLPATH, O_RDONLY); 613 ASSERT_LE(0, fd) << strerror(errno); 614 615 setup_interruptor(self); 616 ASSERT_EQ(-1, read(fd, buf, bufsize)); 617 EXPECT_EQ(EINTR, errno); 618 619 leak(fd); 620 } 621 622 /* 623 * When mounted with -o nointr, fusefs will block signals while waiting for the 624 * server. 625 */ 626 TEST_F(Nointr, block) 627 { 628 uint64_t ino = 42; 629 pthread_t self; 630 sem_t sem0; 631 632 ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno); 633 signaled_semaphore = &sem0; 634 self = pthread_self(); 635 636 EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0) 637 .WillOnce(Invoke(ReturnErrno(ENOENT))); 638 EXPECT_CALL(*m_mock, process( 639 ResultOf([=](auto in) { 640 return (in.header.opcode == FUSE_MKDIR); 641 }, Eq(true)), 642 _) 643 ).WillOnce(Invoke(ReturnImmediate([&](auto in __unused, auto& out) { 644 /* Let the killer proceed */ 645 sem_post(blocked_semaphore); 646 647 /* Wait until after the signal has been sent */ 648 sem_wait(signaled_semaphore); 649 /* Allow time for the mkdir thread to receive the signal */ 650 nap(); 651 652 /* Finally, complete the original op */ 653 SET_OUT_HEADER_LEN(out, entry); 654 out.body.create.entry.attr.mode = S_IFDIR | MODE; 655 out.body.create.entry.nodeid = ino; 656 }))); 657 EXPECT_CALL(*m_mock, process( 658 ResultOf([&](auto in) { 659 return (in.header.opcode == FUSE_INTERRUPT); 660 }, Eq(true)), 661 _) 662 ).Times(0); 663 664 setup_interruptor(self); 665 ASSERT_EQ(0, mkdir(FULLDIRPATH0, MODE)) << strerror(errno); 666 667 sem_destroy(&sem0); 668 } 669 670 /* FUSE_INTERRUPT operations should take priority over other pending ops */ 671 TEST_F(Intr, priority) 672 { 673 Sequence seq; 674 uint64_t ino1 = 43; 675 uint64_t mkdir_unique; 676 pthread_t th0; 677 sem_t sem0, sem1; 678 679 ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno); 680 ASSERT_EQ(0, sem_init(&sem1, 0, 0)) << strerror(errno); 681 682 EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0) 683 .WillOnce(Invoke(ReturnErrno(ENOENT))); 684 EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH1) 685 .WillOnce(Invoke(ReturnErrno(ENOENT))); 686 EXPECT_CALL(*m_mock, process( 687 ResultOf([=](auto in) { 688 return (in.header.opcode == FUSE_MKDIR); 689 }, Eq(true)), 690 _) 691 ).InSequence(seq) 692 .WillOnce(Invoke(ReturnImmediate([&](auto in, auto& out) { 693 mkdir_unique = in.header.unique; 694 695 /* Let the next mkdir proceed */ 696 sem_post(&sem1); 697 698 /* Pause the daemon thread so it won't read the next op */ 699 sem_wait(&sem0); 700 701 /* Finally, interrupt the original op */ 702 out.header.error = -EINTR; 703 out.header.unique = mkdir_unique; 704 out.header.len = sizeof(out.header); 705 }))); 706 /* 707 * FUSE_INTERRUPT should be received before the second FUSE_MKDIR, 708 * even though it was generated later 709 */ 710 EXPECT_CALL(*m_mock, process( 711 ResultOf([&](auto in) { 712 return (in.header.opcode == FUSE_INTERRUPT && 713 in.body.interrupt.unique == mkdir_unique); 714 }, Eq(true)), 715 _) 716 ).InSequence(seq) 717 .WillOnce(Invoke(ReturnErrno(EAGAIN))); 718 EXPECT_CALL(*m_mock, process( 719 ResultOf([&](auto in) { 720 return (in.header.opcode == FUSE_MKDIR); 721 }, Eq(true)), 722 _) 723 ).InSequence(seq) 724 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 725 SET_OUT_HEADER_LEN(out, entry); 726 out.body.create.entry.attr.mode = S_IFDIR | MODE; 727 out.body.create.entry.nodeid = ino1; 728 }))); 729 730 /* Use a separate thread for the first mkdir */ 731 ASSERT_EQ(0, pthread_create(&th0, NULL, mkdir0, NULL)) 732 << strerror(errno); 733 734 signaled_semaphore = &sem0; 735 736 sem_wait(&sem1); /* Sequence the two mkdirs */ 737 setup_interruptor(th0, true); 738 ASSERT_EQ(0, mkdir(FULLDIRPATH1, MODE)) << strerror(errno); 739 740 pthread_join(th0, NULL); 741 sem_destroy(&sem1); 742 sem_destroy(&sem0); 743 } 744 745 /* 746 * If the FUSE filesystem receives the FUSE_INTERRUPT operation before 747 * processing the original, then it should wait for "some timeout" for the 748 * original operation to arrive. If not, it should send EAGAIN to the 749 * INTERRUPT operation, and the kernel should requeue the INTERRUPT. 750 * 751 * In this test, we'll pretend that the INTERRUPT arrives too soon, gets 752 * EAGAINed, then the kernel requeues it, and the second time around it 753 * successfully interrupts the original 754 */ 755 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */ 756 TEST_F(Intr, too_soon) 757 { 758 Sequence seq; 759 pthread_t self; 760 uint64_t mkdir_unique; 761 762 self = pthread_self(); 763 764 EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0) 765 .WillOnce(Invoke(ReturnErrno(ENOENT))); 766 expect_mkdir(&mkdir_unique); 767 768 EXPECT_CALL(*m_mock, process( 769 ResultOf([&](auto in) { 770 return (in.header.opcode == FUSE_INTERRUPT && 771 in.body.interrupt.unique == mkdir_unique); 772 }, Eq(true)), 773 _) 774 ).InSequence(seq) 775 .WillOnce(Invoke(ReturnErrno(EAGAIN))); 776 777 EXPECT_CALL(*m_mock, process( 778 ResultOf([&](auto in) { 779 return (in.header.opcode == FUSE_INTERRUPT && 780 in.body.interrupt.unique == mkdir_unique); 781 }, Eq(true)), 782 _) 783 ).InSequence(seq) 784 .WillOnce(Invoke([&](auto in __unused, auto &out __unused) { 785 std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out); 786 out0->header.error = -EINTR; 787 out0->header.unique = mkdir_unique; 788 out0->header.len = sizeof(out0->header); 789 out.push_back(std::move(out0)); 790 })); 791 792 setup_interruptor(self); 793 ASSERT_EQ(-1, mkdir(FULLDIRPATH0, MODE)); 794 EXPECT_EQ(EINTR, errno); 795 } 796 797 798 // TODO: add a test where write returns EWOULDBLOCK 799