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