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