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 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 541 /* 542 * A syscall that gets interrupted while blocking on FUSE I/O should send a 543 * FUSE_INTERRUPT command to the fuse filesystem, which should then send EINTR 544 * in response to the _original_ operation. The kernel should ultimately 545 * return EINTR to userspace 546 */ 547 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */ 548 TEST_F(Intr, in_progress) 549 { 550 pthread_t self; 551 uint64_t mkdir_unique; 552 553 self = pthread_self(); 554 555 EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0) 556 .WillOnce(Invoke(ReturnErrno(ENOENT))); 557 expect_mkdir(&mkdir_unique); 558 EXPECT_CALL(*m_mock, process( 559 ResultOf([&](auto in) { 560 return (in.header.opcode == FUSE_INTERRUPT && 561 in.body.interrupt.unique == mkdir_unique); 562 }, Eq(true)), 563 _) 564 ).WillOnce(Invoke([&](auto in __unused, auto &out) { 565 std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out); 566 out0->header.error = -EINTR; 567 out0->header.unique = mkdir_unique; 568 out0->header.len = sizeof(out0->header); 569 out.push_back(std::move(out0)); 570 })); 571 572 setup_interruptor(self); 573 ASSERT_EQ(-1, mkdir(FULLDIRPATH0, MODE)); 574 EXPECT_EQ(EINTR, errno); 575 } 576 577 /* Reads should also be interruptible */ 578 TEST_F(Intr, in_progress_read) 579 { 580 const char FULLPATH[] = "mountpoint/some_file.txt"; 581 const char RELPATH[] = "some_file.txt"; 582 const size_t bufsize = 80; 583 char buf[bufsize]; 584 uint64_t ino = 42; 585 int fd; 586 pthread_t self; 587 uint64_t read_unique; 588 589 self = pthread_self(); 590 591 expect_lookup(RELPATH, ino); 592 expect_open(ino, 0, 1); 593 expect_read(ino, &read_unique); 594 EXPECT_CALL(*m_mock, process( 595 ResultOf([&](auto in) { 596 return (in.header.opcode == FUSE_INTERRUPT && 597 in.body.interrupt.unique == read_unique); 598 }, Eq(true)), 599 _) 600 ).WillOnce(Invoke([&](auto in __unused, auto &out) { 601 std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out); 602 out0->header.error = -EINTR; 603 out0->header.unique = read_unique; 604 out0->header.len = sizeof(out0->header); 605 out.push_back(std::move(out0)); 606 })); 607 608 fd = open(FULLPATH, O_RDONLY); 609 ASSERT_LE(0, fd) << strerror(errno); 610 611 setup_interruptor(self); 612 ASSERT_EQ(-1, read(fd, buf, bufsize)); 613 EXPECT_EQ(EINTR, errno); 614 } 615 616 /* 617 * When mounted with -o nointr, fusefs will block signals while waiting for the 618 * server. 619 */ 620 TEST_F(Nointr, block) 621 { 622 uint64_t ino = 42; 623 pthread_t self; 624 sem_t sem0; 625 626 ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno); 627 signaled_semaphore = &sem0; 628 self = pthread_self(); 629 630 EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0) 631 .WillOnce(Invoke(ReturnErrno(ENOENT))); 632 EXPECT_CALL(*m_mock, process( 633 ResultOf([=](auto in) { 634 return (in.header.opcode == FUSE_MKDIR); 635 }, Eq(true)), 636 _) 637 ).WillOnce(Invoke(ReturnImmediate([&](auto in __unused, auto& out) { 638 /* Let the killer proceed */ 639 sem_post(blocked_semaphore); 640 641 /* Wait until after the signal has been sent */ 642 sem_wait(signaled_semaphore); 643 /* Allow time for the mkdir thread to receive the signal */ 644 nap(); 645 646 /* Finally, complete the original op */ 647 SET_OUT_HEADER_LEN(out, entry); 648 out.body.create.entry.attr.mode = S_IFDIR | MODE; 649 out.body.create.entry.nodeid = ino; 650 }))); 651 EXPECT_CALL(*m_mock, process( 652 ResultOf([&](auto in) { 653 return (in.header.opcode == FUSE_INTERRUPT); 654 }, Eq(true)), 655 _) 656 ).Times(0); 657 658 setup_interruptor(self); 659 ASSERT_EQ(0, mkdir(FULLDIRPATH0, MODE)) << strerror(errno); 660 661 sem_destroy(&sem0); 662 } 663 664 /* FUSE_INTERRUPT operations should take priority over other pending ops */ 665 TEST_F(Intr, priority) 666 { 667 Sequence seq; 668 uint64_t ino1 = 43; 669 uint64_t mkdir_unique; 670 pthread_t th0; 671 sem_t sem0, sem1; 672 673 ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno); 674 ASSERT_EQ(0, sem_init(&sem1, 0, 0)) << strerror(errno); 675 676 EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0) 677 .WillOnce(Invoke(ReturnErrno(ENOENT))); 678 EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH1) 679 .WillOnce(Invoke(ReturnErrno(ENOENT))); 680 EXPECT_CALL(*m_mock, process( 681 ResultOf([=](auto in) { 682 return (in.header.opcode == FUSE_MKDIR); 683 }, Eq(true)), 684 _) 685 ).InSequence(seq) 686 .WillOnce(Invoke(ReturnImmediate([&](auto in, auto& out) { 687 mkdir_unique = in.header.unique; 688 689 /* Let the next mkdir proceed */ 690 sem_post(&sem1); 691 692 /* Pause the daemon thread so it won't read the next op */ 693 sem_wait(&sem0); 694 695 /* Finally, interrupt the original op */ 696 out.header.error = -EINTR; 697 out.header.unique = mkdir_unique; 698 out.header.len = sizeof(out.header); 699 }))); 700 /* 701 * FUSE_INTERRUPT should be received before the second FUSE_MKDIR, 702 * even though it was generated later 703 */ 704 EXPECT_CALL(*m_mock, process( 705 ResultOf([&](auto in) { 706 return (in.header.opcode == FUSE_INTERRUPT && 707 in.body.interrupt.unique == mkdir_unique); 708 }, Eq(true)), 709 _) 710 ).InSequence(seq) 711 .WillOnce(Invoke(ReturnErrno(EAGAIN))); 712 EXPECT_CALL(*m_mock, process( 713 ResultOf([&](auto in) { 714 return (in.header.opcode == FUSE_MKDIR); 715 }, Eq(true)), 716 _) 717 ).InSequence(seq) 718 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 719 SET_OUT_HEADER_LEN(out, entry); 720 out.body.create.entry.attr.mode = S_IFDIR | MODE; 721 out.body.create.entry.nodeid = ino1; 722 }))); 723 724 /* Use a separate thread for the first mkdir */ 725 ASSERT_EQ(0, pthread_create(&th0, NULL, mkdir0, NULL)) 726 << strerror(errno); 727 728 signaled_semaphore = &sem0; 729 730 sem_wait(&sem1); /* Sequence the two mkdirs */ 731 setup_interruptor(th0, true); 732 ASSERT_EQ(0, mkdir(FULLDIRPATH1, MODE)) << strerror(errno); 733 734 pthread_join(th0, NULL); 735 sem_destroy(&sem1); 736 sem_destroy(&sem0); 737 } 738 739 /* 740 * If the FUSE filesystem receives the FUSE_INTERRUPT operation before 741 * processing the original, then it should wait for "some timeout" for the 742 * original operation to arrive. If not, it should send EAGAIN to the 743 * INTERRUPT operation, and the kernel should requeue the INTERRUPT. 744 * 745 * In this test, we'll pretend that the INTERRUPT arrives too soon, gets 746 * EAGAINed, then the kernel requeues it, and the second time around it 747 * successfully interrupts the original 748 */ 749 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */ 750 TEST_F(Intr, too_soon) 751 { 752 Sequence seq; 753 pthread_t self; 754 uint64_t mkdir_unique; 755 756 self = pthread_self(); 757 758 EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0) 759 .WillOnce(Invoke(ReturnErrno(ENOENT))); 760 expect_mkdir(&mkdir_unique); 761 762 EXPECT_CALL(*m_mock, process( 763 ResultOf([&](auto in) { 764 return (in.header.opcode == FUSE_INTERRUPT && 765 in.body.interrupt.unique == mkdir_unique); 766 }, Eq(true)), 767 _) 768 ).InSequence(seq) 769 .WillOnce(Invoke(ReturnErrno(EAGAIN))); 770 771 EXPECT_CALL(*m_mock, process( 772 ResultOf([&](auto in) { 773 return (in.header.opcode == FUSE_INTERRUPT && 774 in.body.interrupt.unique == mkdir_unique); 775 }, Eq(true)), 776 _) 777 ).InSequence(seq) 778 .WillOnce(Invoke([&](auto in __unused, auto &out __unused) { 779 std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out); 780 out0->header.error = -EINTR; 781 out0->header.unique = mkdir_unique; 782 out0->header.len = sizeof(out0->header); 783 out.push_back(std::move(out0)); 784 })); 785 786 setup_interruptor(self); 787 ASSERT_EQ(-1, mkdir(FULLDIRPATH0, MODE)); 788 EXPECT_EQ(EINTR, errno); 789 } 790 791 792 // TODO: add a test where write returns EWOULDBLOCK 793