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 static void* mkdir0(void* arg __unused) { 189 ssize_t r; 190 191 r = mkdir(FULLDIRPATH0, MODE); 192 if (r >= 0) 193 return 0; 194 else 195 return (void*)(intptr_t)errno; 196 } 197 198 static void* read1(void* arg) { 199 const size_t bufsize = FILESIZE; 200 char buf[bufsize]; 201 int fd = (int)(intptr_t)arg; 202 ssize_t r; 203 204 r = read(fd, buf, bufsize); 205 if (r >= 0) 206 return 0; 207 else 208 return (void*)(intptr_t)errno; 209 } 210 211 /* 212 * An interrupt operation that gets received after the original command is 213 * complete should generate an EAGAIN response. 214 */ 215 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */ 216 TEST_F(Interrupt, already_complete) 217 { 218 uint64_t ino = 42; 219 pthread_t self; 220 uint64_t mkdir_unique = 0; 221 Sequence seq; 222 223 self = pthread_self(); 224 225 EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0) 226 .InSequence(seq) 227 .WillOnce(Invoke(ReturnErrno(ENOENT))); 228 expect_mkdir(&mkdir_unique); 229 EXPECT_CALL(*m_mock, process( 230 ResultOf([&](auto in) { 231 return (in.header.opcode == FUSE_INTERRUPT && 232 in.body.interrupt.unique == mkdir_unique); 233 }, Eq(true)), 234 _) 235 ).WillOnce(Invoke([&](auto in, auto &out) { 236 // First complete the mkdir request 237 std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out); 238 out0->header.unique = mkdir_unique; 239 SET_OUT_HEADER_LEN(*out0, entry); 240 out0->body.create.entry.attr.mode = S_IFDIR | MODE; 241 out0->body.create.entry.nodeid = ino; 242 out.push_back(std::move(out0)); 243 244 // Then, respond EAGAIN to the interrupt request 245 std::unique_ptr<mockfs_buf_out> out1(new mockfs_buf_out); 246 out1->header.unique = in.header.unique; 247 out1->header.error = -EAGAIN; 248 out1->header.len = sizeof(out1->header); 249 out.push_back(std::move(out1)); 250 })); 251 EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0) 252 .InSequence(seq) 253 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 254 SET_OUT_HEADER_LEN(out, entry); 255 out.body.entry.attr.mode = S_IFDIR | MODE; 256 out.body.entry.nodeid = ino; 257 out.body.entry.attr.nlink = 2; 258 }))); 259 260 setup_interruptor(self); 261 EXPECT_EQ(0, mkdir(FULLDIRPATH0, MODE)) << strerror(errno); 262 /* 263 * The final syscall simply ensures that the test's main thread doesn't 264 * end before the daemon finishes responding to the FUSE_INTERRUPT. 265 */ 266 EXPECT_EQ(0, access(FULLDIRPATH0, F_OK)) << strerror(errno); 267 } 268 269 /* 270 * If a FUSE file system returns ENOSYS for a FUSE_INTERRUPT operation, the 271 * kernel should not attempt to interrupt any other operations on that mount 272 * point. 273 */ 274 TEST_F(Interrupt, enosys) 275 { 276 uint64_t ino0 = 42, ino1 = 43;; 277 uint64_t mkdir_unique; 278 pthread_t self, th0; 279 sem_t sem0, sem1; 280 void *thr0_value; 281 Sequence seq; 282 283 self = pthread_self(); 284 ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno); 285 ASSERT_EQ(0, sem_init(&sem1, 0, 0)) << strerror(errno); 286 287 EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH1) 288 .WillOnce(Invoke(ReturnErrno(ENOENT))); 289 EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0) 290 .WillOnce(Invoke(ReturnErrno(ENOENT))); 291 expect_mkdir(&mkdir_unique); 292 EXPECT_CALL(*m_mock, process( 293 ResultOf([&](auto in) { 294 return (in.header.opcode == FUSE_INTERRUPT && 295 in.body.interrupt.unique == mkdir_unique); 296 }, Eq(true)), 297 _) 298 ).InSequence(seq) 299 .WillOnce(Invoke([&](auto in, auto &out) { 300 // reject FUSE_INTERRUPT and respond to the FUSE_MKDIR 301 std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out); 302 std::unique_ptr<mockfs_buf_out> out1(new mockfs_buf_out); 303 304 out0->header.unique = in.header.unique; 305 out0->header.error = -ENOSYS; 306 out0->header.len = sizeof(out0->header); 307 out.push_back(std::move(out0)); 308 309 SET_OUT_HEADER_LEN(*out1, entry); 310 out1->body.create.entry.attr.mode = S_IFDIR | MODE; 311 out1->body.create.entry.nodeid = ino1; 312 out1->header.unique = mkdir_unique; 313 out.push_back(std::move(out1)); 314 })); 315 EXPECT_CALL(*m_mock, process( 316 ResultOf([&](auto in) { 317 return (in.header.opcode == FUSE_MKDIR); 318 }, Eq(true)), 319 _) 320 ).InSequence(seq) 321 .WillOnce(Invoke([&](auto in, auto &out) { 322 std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out); 323 324 sem_post(&sem0); 325 sem_wait(&sem1); 326 327 SET_OUT_HEADER_LEN(*out0, entry); 328 out0->body.create.entry.attr.mode = S_IFDIR | MODE; 329 out0->body.create.entry.nodeid = ino0; 330 out0->header.unique = in.header.unique; 331 out.push_back(std::move(out0)); 332 })); 333 334 setup_interruptor(self); 335 /* First mkdir operation should finish synchronously */ 336 ASSERT_EQ(0, mkdir(FULLDIRPATH1, MODE)) << strerror(errno); 337 338 ASSERT_EQ(0, pthread_create(&th0, NULL, mkdir0, NULL)) 339 << strerror(errno); 340 341 sem_wait(&sem0); 342 /* 343 * th0 should be blocked waiting for the fuse daemon thread. 344 * Signal it. No FUSE_INTERRUPT should result 345 */ 346 pthread_kill(th0, SIGUSR1); 347 /* Allow the daemon thread to proceed */ 348 sem_post(&sem1); 349 pthread_join(th0, &thr0_value); 350 /* Second mkdir should've finished without error */ 351 EXPECT_EQ(0, (intptr_t)thr0_value); 352 } 353 354 /* 355 * A FUSE filesystem is legally allowed to ignore INTERRUPT operations, and 356 * complete the original operation whenever it damn well pleases. 357 */ 358 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */ 359 TEST_F(Interrupt, ignore) 360 { 361 uint64_t ino = 42; 362 pthread_t self; 363 uint64_t mkdir_unique; 364 365 self = pthread_self(); 366 367 EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0) 368 .WillOnce(Invoke(ReturnErrno(ENOENT))); 369 expect_mkdir(&mkdir_unique); 370 EXPECT_CALL(*m_mock, process( 371 ResultOf([&](auto in) { 372 return (in.header.opcode == FUSE_INTERRUPT && 373 in.body.interrupt.unique == mkdir_unique); 374 }, Eq(true)), 375 _) 376 ).WillOnce(Invoke([&](auto in __unused, auto &out) { 377 // Ignore FUSE_INTERRUPT; respond to the FUSE_MKDIR 378 std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out); 379 out0->header.unique = mkdir_unique; 380 SET_OUT_HEADER_LEN(*out0, entry); 381 out0->body.create.entry.attr.mode = S_IFDIR | MODE; 382 out0->body.create.entry.nodeid = ino; 383 out.push_back(std::move(out0)); 384 })); 385 386 setup_interruptor(self); 387 ASSERT_EQ(0, mkdir(FULLDIRPATH0, MODE)) << strerror(errno); 388 } 389 390 /* 391 * A restartable operation (basically, anything except write or setextattr) 392 * that hasn't yet been sent to userland can be interrupted without sending 393 * FUSE_INTERRUPT, and will be automatically restarted. 394 */ 395 TEST_F(Interrupt, in_kernel_restartable) 396 { 397 const char FULLPATH1[] = "mountpoint/other_file.txt"; 398 const char RELPATH1[] = "other_file.txt"; 399 uint64_t ino0 = 42, ino1 = 43; 400 int fd1; 401 pthread_t self, th0, th1; 402 sem_t sem0, sem1; 403 void *thr0_value, *thr1_value; 404 405 ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno); 406 ASSERT_EQ(0, sem_init(&sem1, 0, 0)) << strerror(errno); 407 self = pthread_self(); 408 409 EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0) 410 .WillOnce(Invoke(ReturnErrno(ENOENT))); 411 expect_lookup(RELPATH1, ino1); 412 expect_open(ino1, 0, 1); 413 EXPECT_CALL(*m_mock, process( 414 ResultOf([=](auto in) { 415 return (in.header.opcode == FUSE_MKDIR); 416 }, Eq(true)), 417 _) 418 ).WillOnce(Invoke(ReturnImmediate([&](auto in __unused, auto& out) { 419 /* Let the next write proceed */ 420 sem_post(&sem1); 421 /* Pause the daemon thread so it won't read the next op */ 422 sem_wait(&sem0); 423 424 SET_OUT_HEADER_LEN(out, entry); 425 out.body.create.entry.attr.mode = S_IFDIR | MODE; 426 out.body.create.entry.nodeid = ino0; 427 }))); 428 FuseTest::expect_read(ino1, 0, FILESIZE, 0, NULL); 429 430 fd1 = open(FULLPATH1, O_RDONLY); 431 ASSERT_LE(0, fd1) << strerror(errno); 432 433 /* Use a separate thread for each operation */ 434 ASSERT_EQ(0, pthread_create(&th0, NULL, mkdir0, NULL)) 435 << strerror(errno); 436 437 sem_wait(&sem1); /* Sequence the two operations */ 438 439 ASSERT_EQ(0, pthread_create(&th1, NULL, read1, (void*)(intptr_t)fd1)) 440 << strerror(errno); 441 442 setup_interruptor(self, true); 443 444 pause(); /* Wait for signal */ 445 446 /* Unstick the daemon */ 447 ASSERT_EQ(0, sem_post(&sem0)) << strerror(errno); 448 449 /* Wait awhile to make sure the signal generates no FUSE_INTERRUPT */ 450 nap(); 451 452 pthread_join(th1, &thr1_value); 453 pthread_join(th0, &thr0_value); 454 EXPECT_EQ(0, (intptr_t)thr1_value); 455 EXPECT_EQ(0, (intptr_t)thr0_value); 456 sem_destroy(&sem1); 457 sem_destroy(&sem0); 458 } 459 460 /* 461 * An operation that hasn't yet been sent to userland can be interrupted 462 * without sending FUSE_INTERRUPT. If it's a non-restartable operation (write 463 * or setextattr) it will return EINTR. 464 */ 465 TEST_F(Interrupt, in_kernel_nonrestartable) 466 { 467 const char FULLPATH1[] = "mountpoint/other_file.txt"; 468 const char RELPATH1[] = "other_file.txt"; 469 const char value[] = "whatever"; 470 ssize_t value_len = strlen(value) + 1; 471 uint64_t ino0 = 42, ino1 = 43; 472 int ns = EXTATTR_NAMESPACE_USER; 473 int fd1; 474 pthread_t self, th0; 475 sem_t sem0, sem1; 476 void *thr0_value; 477 ssize_t r; 478 479 ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno); 480 ASSERT_EQ(0, sem_init(&sem1, 0, 0)) << strerror(errno); 481 self = pthread_self(); 482 483 EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0) 484 .WillOnce(Invoke(ReturnErrno(ENOENT))); 485 expect_lookup(RELPATH1, ino1); 486 expect_open(ino1, 0, 1); 487 EXPECT_CALL(*m_mock, process( 488 ResultOf([=](auto in) { 489 return (in.header.opcode == FUSE_MKDIR); 490 }, Eq(true)), 491 _) 492 ).WillOnce(Invoke(ReturnImmediate([&](auto in __unused, auto& out) { 493 /* Let the next write proceed */ 494 sem_post(&sem1); 495 /* Pause the daemon thread so it won't read the next op */ 496 sem_wait(&sem0); 497 498 SET_OUT_HEADER_LEN(out, entry); 499 out.body.create.entry.attr.mode = S_IFDIR | MODE; 500 out.body.create.entry.nodeid = ino0; 501 }))); 502 503 fd1 = open(FULLPATH1, O_WRONLY); 504 ASSERT_LE(0, fd1) << strerror(errno); 505 506 /* Use a separate thread for the first write */ 507 ASSERT_EQ(0, pthread_create(&th0, NULL, mkdir0, NULL)) 508 << strerror(errno); 509 510 sem_wait(&sem1); /* Sequence the two operations */ 511 512 setup_interruptor(self, true); 513 514 r = extattr_set_fd(fd1, ns, "foo", (void*)value, value_len); 515 EXPECT_EQ(EINTR, errno); 516 517 /* Unstick the daemon */ 518 ASSERT_EQ(0, sem_post(&sem0)) << strerror(errno); 519 520 /* Wait awhile to make sure the signal generates no FUSE_INTERRUPT */ 521 nap(); 522 523 pthread_join(th0, &thr0_value); 524 EXPECT_EQ(0, (intptr_t)thr0_value); 525 sem_destroy(&sem1); 526 sem_destroy(&sem0); 527 } 528 529 /* 530 * A syscall that gets interrupted while blocking on FUSE I/O should send a 531 * FUSE_INTERRUPT command to the fuse filesystem, which should then send EINTR 532 * in response to the _original_ operation. The kernel should ultimately 533 * return EINTR to userspace 534 */ 535 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */ 536 TEST_F(Interrupt, in_progress) 537 { 538 pthread_t self; 539 uint64_t mkdir_unique; 540 541 self = pthread_self(); 542 543 EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0) 544 .WillOnce(Invoke(ReturnErrno(ENOENT))); 545 expect_mkdir(&mkdir_unique); 546 EXPECT_CALL(*m_mock, process( 547 ResultOf([&](auto in) { 548 return (in.header.opcode == FUSE_INTERRUPT && 549 in.body.interrupt.unique == mkdir_unique); 550 }, Eq(true)), 551 _) 552 ).WillOnce(Invoke([&](auto in __unused, auto &out) { 553 std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out); 554 out0->header.error = -EINTR; 555 out0->header.unique = mkdir_unique; 556 out0->header.len = sizeof(out0->header); 557 out.push_back(std::move(out0)); 558 })); 559 560 setup_interruptor(self); 561 ASSERT_EQ(-1, mkdir(FULLDIRPATH0, MODE)); 562 EXPECT_EQ(EINTR, errno); 563 } 564 565 /* Reads should also be interruptible */ 566 TEST_F(Interrupt, in_progress_read) 567 { 568 const char FULLPATH[] = "mountpoint/some_file.txt"; 569 const char RELPATH[] = "some_file.txt"; 570 const size_t bufsize = 80; 571 char buf[bufsize]; 572 uint64_t ino = 42; 573 int fd; 574 pthread_t self; 575 uint64_t read_unique; 576 577 self = pthread_self(); 578 579 expect_lookup(RELPATH, ino); 580 expect_open(ino, 0, 1); 581 expect_read(ino, &read_unique); 582 EXPECT_CALL(*m_mock, process( 583 ResultOf([&](auto in) { 584 return (in.header.opcode == FUSE_INTERRUPT && 585 in.body.interrupt.unique == read_unique); 586 }, Eq(true)), 587 _) 588 ).WillOnce(Invoke([&](auto in __unused, auto &out) { 589 std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out); 590 out0->header.error = -EINTR; 591 out0->header.unique = read_unique; 592 out0->header.len = sizeof(out0->header); 593 out.push_back(std::move(out0)); 594 })); 595 596 fd = open(FULLPATH, O_RDONLY); 597 ASSERT_LE(0, fd) << strerror(errno); 598 599 setup_interruptor(self); 600 ASSERT_EQ(-1, read(fd, buf, bufsize)); 601 EXPECT_EQ(EINTR, errno); 602 } 603 604 /* FUSE_INTERRUPT operations should take priority over other pending ops */ 605 TEST_F(Interrupt, priority) 606 { 607 Sequence seq; 608 uint64_t ino1 = 43; 609 uint64_t mkdir_unique; 610 pthread_t self, th0; 611 sem_t sem0, sem1; 612 613 ASSERT_EQ(0, sem_init(&sem0, 0, 0)) << strerror(errno); 614 ASSERT_EQ(0, sem_init(&sem1, 0, 0)) << strerror(errno); 615 self = pthread_self(); 616 617 EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0) 618 .WillOnce(Invoke(ReturnErrno(ENOENT))); 619 EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH1) 620 .WillOnce(Invoke(ReturnErrno(ENOENT))); 621 EXPECT_CALL(*m_mock, process( 622 ResultOf([=](auto in) { 623 return (in.header.opcode == FUSE_MKDIR); 624 }, Eq(true)), 625 _) 626 ).InSequence(seq) 627 .WillOnce(Invoke(ReturnImmediate([&](auto in, auto& out) { 628 mkdir_unique = in.header.unique; 629 630 /* Let the next mkdir proceed */ 631 sem_post(&sem1); 632 633 /* Pause the daemon thread so it won't read the next op */ 634 sem_wait(&sem0); 635 636 /* Finally, interrupt the original op */ 637 out.header.error = -EINTR; 638 out.header.unique = mkdir_unique; 639 out.header.len = sizeof(out.header); 640 }))); 641 /* 642 * FUSE_INTERRUPT should be received before the second FUSE_MKDIR, 643 * even though it was generated later 644 */ 645 EXPECT_CALL(*m_mock, process( 646 ResultOf([&](auto in) { 647 return (in.header.opcode == FUSE_INTERRUPT && 648 in.body.interrupt.unique == mkdir_unique); 649 }, Eq(true)), 650 _) 651 ).InSequence(seq) 652 .WillOnce(Invoke(ReturnErrno(EAGAIN))); 653 EXPECT_CALL(*m_mock, process( 654 ResultOf([&](auto in) { 655 return (in.header.opcode == FUSE_MKDIR); 656 }, Eq(true)), 657 _) 658 ).InSequence(seq) 659 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 660 SET_OUT_HEADER_LEN(out, entry); 661 out.body.create.entry.attr.mode = S_IFDIR | MODE; 662 out.body.create.entry.nodeid = ino1; 663 }))); 664 665 /* Use a separate thread for the first mkdir */ 666 ASSERT_EQ(0, pthread_create(&th0, NULL, mkdir0, NULL)) 667 << strerror(errno); 668 669 signaled_semaphore = &sem0; 670 671 sem_wait(&sem1); /* Sequence the two mkdirs */ 672 setup_interruptor(th0, true); 673 ASSERT_EQ(0, mkdir(FULLDIRPATH1, MODE)) << strerror(errno); 674 675 pthread_join(th0, NULL); 676 sem_destroy(&sem1); 677 sem_destroy(&sem0); 678 } 679 680 /* 681 * If the FUSE filesystem receives the FUSE_INTERRUPT operation before 682 * processing the original, then it should wait for "some timeout" for the 683 * original operation to arrive. If not, it should send EAGAIN to the 684 * INTERRUPT operation, and the kernel should requeue the INTERRUPT. 685 * 686 * In this test, we'll pretend that the INTERRUPT arrives too soon, gets 687 * EAGAINed, then the kernel requeues it, and the second time around it 688 * successfully interrupts the original 689 */ 690 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */ 691 TEST_F(Interrupt, too_soon) 692 { 693 Sequence seq; 694 pthread_t self; 695 uint64_t mkdir_unique; 696 697 self = pthread_self(); 698 699 EXPECT_LOOKUP(FUSE_ROOT_ID, RELDIRPATH0) 700 .WillOnce(Invoke(ReturnErrno(ENOENT))); 701 expect_mkdir(&mkdir_unique); 702 703 EXPECT_CALL(*m_mock, process( 704 ResultOf([&](auto in) { 705 return (in.header.opcode == FUSE_INTERRUPT && 706 in.body.interrupt.unique == mkdir_unique); 707 }, Eq(true)), 708 _) 709 ).InSequence(seq) 710 .WillOnce(Invoke(ReturnErrno(EAGAIN))); 711 712 EXPECT_CALL(*m_mock, process( 713 ResultOf([&](auto in) { 714 return (in.header.opcode == FUSE_INTERRUPT && 715 in.body.interrupt.unique == mkdir_unique); 716 }, Eq(true)), 717 _) 718 ).InSequence(seq) 719 .WillOnce(Invoke([&](auto in __unused, auto &out __unused) { 720 std::unique_ptr<mockfs_buf_out> out0(new mockfs_buf_out); 721 out0->header.error = -EINTR; 722 out0->header.unique = mkdir_unique; 723 out0->header.len = sizeof(out0->header); 724 out.push_back(std::move(out0)); 725 })); 726 727 setup_interruptor(self); 728 ASSERT_EQ(-1, mkdir(FULLDIRPATH0, MODE)); 729 EXPECT_EQ(EINTR, errno); 730 } 731 732 733 // TODO: add a test where write returns EWOULDBLOCK 734