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/wait.h> 33 #include <fcntl.h> 34 #include <pthread.h> 35 #include <signal.h> 36 } 37 38 #include "mockfs.hh" 39 #include "utils.hh" 40 41 using namespace testing; 42 43 /* Don't do anything; all we care about is that the syscall gets interrupted */ 44 void sigusr2_handler(int __unused sig) { 45 if (verbosity > 1) { 46 printf("Signaled! thread %p\n", pthread_self()); 47 } 48 49 } 50 51 void* killer(void* target) { 52 /* 53 * Sleep for awhile so we can be mostly confident that the main thread 54 * is already blocked in write(2) 55 */ 56 usleep(250'000); 57 if (verbosity > 1) 58 printf("Signalling! thread %p\n", target); 59 pthread_kill((pthread_t)target, SIGUSR2); 60 61 return(NULL); 62 } 63 64 class Interrupt: public FuseTest { 65 public: 66 pthread_t m_child; 67 68 Interrupt(): m_child(NULL) {}; 69 70 void expect_lookup(const char *relpath, uint64_t ino) 71 { 72 FuseTest::expect_lookup(relpath, ino, S_IFREG | 0644, 1000, 1); 73 } 74 75 /* 76 * Expect a FUSE_READ but don't reply. Instead, just record the unique value 77 * to the provided pointer 78 */ 79 void expect_read(uint64_t ino, uint64_t *read_unique) 80 { 81 EXPECT_CALL(*m_mock, process( 82 ResultOf([=](auto in) { 83 return (in->header.opcode == FUSE_READ && 84 in->header.nodeid == ino); 85 }, Eq(true)), 86 _) 87 ).WillOnce(Invoke([=](auto in, auto &out __unused) { 88 *read_unique = in->header.unique; 89 })); 90 } 91 92 /* 93 * Expect a FUSE_WRITE but don't reply. Instead, just record the unique value 94 * to the provided pointer 95 */ 96 void expect_write(uint64_t ino, uint64_t *write_unique) 97 { 98 EXPECT_CALL(*m_mock, process( 99 ResultOf([=](auto in) { 100 return (in->header.opcode == FUSE_WRITE && 101 in->header.nodeid == ino); 102 }, Eq(true)), 103 _) 104 ).WillOnce(Invoke([=](auto in, auto &out __unused) { 105 *write_unique = in->header.unique; 106 })); 107 } 108 109 void setup_interruptor(pthread_t self) 110 { 111 ASSERT_NE(SIG_ERR, signal(SIGUSR2, sigusr2_handler)) << strerror(errno); 112 ASSERT_EQ(0, pthread_create(&m_child, NULL, killer, (void*)self)) 113 << strerror(errno); 114 } 115 116 void TearDown() { 117 struct sigaction sa; 118 119 if (m_child != NULL) { 120 pthread_join(m_child, NULL); 121 } 122 bzero(&sa, sizeof(sa)); 123 sa.sa_handler = SIG_DFL; 124 sigaction(SIGUSR2, &sa, NULL); 125 126 FuseTest::TearDown(); 127 } 128 }; 129 130 /* 131 * An interrupt operation that gets received after the original command is 132 * complete should generate an EAGAIN response. 133 */ 134 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */ 135 TEST_F(Interrupt, already_complete) 136 { 137 const char FULLPATH[] = "mountpoint/some_file.txt"; 138 const char RELPATH[] = "some_file.txt"; 139 const char *CONTENTS = "abcdefgh"; 140 uint64_t ino = 42; 141 int fd; 142 ssize_t bufsize = strlen(CONTENTS); 143 pthread_t self; 144 uint64_t write_unique = 0; 145 146 self = pthread_self(); 147 148 expect_lookup(RELPATH, ino); 149 expect_open(ino, 0, 1); 150 expect_write(ino, &write_unique); 151 EXPECT_CALL(*m_mock, process( 152 ResultOf([&](auto in) { 153 return (in->header.opcode == FUSE_INTERRUPT && 154 in->body.interrupt.unique == write_unique); 155 }, Eq(true)), 156 _) 157 ).WillOnce(Invoke([&](auto in, auto &out) { 158 // First complete the write request 159 auto out0 = new mockfs_buf_out; 160 out0->header.unique = write_unique; 161 SET_OUT_HEADER_LEN(out0, write); 162 out0->body.write.size = bufsize; 163 out.push_back(out0); 164 165 // Then, respond EAGAIN to the interrupt request 166 auto out1 = new mockfs_buf_out; 167 out1->header.unique = in->header.unique; 168 out1->header.error = -EAGAIN; 169 out1->header.len = sizeof(out1->header); 170 out.push_back(out1); 171 })); 172 173 fd = open(FULLPATH, O_WRONLY); 174 ASSERT_LE(0, fd) << strerror(errno); 175 176 setup_interruptor(self); 177 EXPECT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno); 178 179 /* Deliberately leak fd. close(2) will be tested in release.cc */ 180 } 181 182 /* 183 * Upon receipt of a fatal signal, fusefs should return ASAP after sending 184 * FUSE_INTERRUPT. 185 */ 186 TEST_F(Interrupt, fatal_signal) 187 { 188 const char FULLPATH[] = "mountpoint/some_file.txt"; 189 const char *CONTENTS = "abcdefgh"; 190 const char RELPATH[] = "some_file.txt"; 191 ssize_t bufsize = strlen(CONTENTS); 192 uint64_t ino = 42; 193 int status; 194 pthread_t self; 195 uint64_t write_unique; 196 197 self = pthread_self(); 198 199 expect_lookup(RELPATH, ino); 200 expect_open(ino, 0, 1); 201 expect_write(ino, &write_unique); 202 EXPECT_CALL(*m_mock, process( 203 ResultOf([&](auto in) { 204 return (in->header.opcode == FUSE_INTERRUPT && 205 in->body.interrupt.unique == write_unique); 206 }, Eq(true)), 207 _) 208 ).WillOnce(Invoke([&](auto in __unused, auto &out __unused) { 209 /* Don't respond. The process should exit anyway */ 210 })); 211 expect_flush(ino, 1, ReturnErrno(0)); 212 expect_release(ino, FH); 213 214 fork(false, &status, [&] { 215 }, [&]() { 216 struct sigaction sa; 217 int fd, r; 218 pthread_t killer_th; 219 pthread_t self; 220 221 fd = open(FULLPATH, O_WRONLY); 222 if (fd < 0) { 223 perror("open"); 224 return 1; 225 } 226 227 /* SIGUSR2 terminates the process by default */ 228 bzero(&sa, sizeof(sa)); 229 sa.sa_handler = SIG_DFL; 230 r = sigaction(SIGUSR2, &sa, NULL); 231 if (r != 0) { 232 perror("sigaction"); 233 return 1; 234 } 235 self = pthread_self(); 236 r = pthread_create(&killer_th, NULL, killer, (void*)self); 237 if (r != 0) { 238 perror("pthread_create"); 239 return 1; 240 } 241 242 write(fd, CONTENTS, bufsize); 243 return 1; 244 }); 245 ASSERT_EQ(SIGUSR2, WTERMSIG(status)); 246 247 /* Deliberately leak fd. close(2) will be tested in release.cc */ 248 } 249 250 /* 251 * A FUSE filesystem is legally allowed to ignore INTERRUPT operations, and 252 * complete the original operation whenever it damn well pleases. 253 */ 254 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */ 255 TEST_F(Interrupt, ignore) 256 { 257 const char FULLPATH[] = "mountpoint/some_file.txt"; 258 const char RELPATH[] = "some_file.txt"; 259 const char *CONTENTS = "abcdefgh"; 260 uint64_t ino = 42; 261 int fd; 262 ssize_t bufsize = strlen(CONTENTS); 263 pthread_t self; 264 uint64_t write_unique; 265 266 self = pthread_self(); 267 268 expect_lookup(RELPATH, ino); 269 expect_open(ino, 0, 1); 270 expect_write(ino, &write_unique); 271 EXPECT_CALL(*m_mock, process( 272 ResultOf([&](auto in) { 273 return (in->header.opcode == FUSE_INTERRUPT && 274 in->body.interrupt.unique == write_unique); 275 }, Eq(true)), 276 _) 277 ).WillOnce(Invoke([&](auto in __unused, auto &out) { 278 // Ignore FUSE_INTERRUPT; respond to the FUSE_WRITE 279 auto out0 = new mockfs_buf_out; 280 out0->header.unique = write_unique; 281 SET_OUT_HEADER_LEN(out0, write); 282 out0->body.write.size = bufsize; 283 out.push_back(out0); 284 })); 285 286 fd = open(FULLPATH, O_WRONLY); 287 ASSERT_LE(0, fd) << strerror(errno); 288 289 setup_interruptor(self); 290 ASSERT_EQ(bufsize, write(fd, CONTENTS, bufsize)) << strerror(errno); 291 292 /* Deliberately leak fd. close(2) will be tested in release.cc */ 293 } 294 295 /* 296 * A syscall that gets interrupted while blocking on FUSE I/O should send a 297 * FUSE_INTERRUPT command to the fuse filesystem, which should then send EINTR 298 * in response to the _original_ operation. The kernel should ultimately 299 * return EINTR to userspace 300 */ 301 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */ 302 TEST_F(Interrupt, in_progress) 303 { 304 const char FULLPATH[] = "mountpoint/some_file.txt"; 305 const char RELPATH[] = "some_file.txt"; 306 const char *CONTENTS = "abcdefgh"; 307 uint64_t ino = 42; 308 int fd; 309 ssize_t bufsize = strlen(CONTENTS); 310 pthread_t self; 311 uint64_t write_unique; 312 313 self = pthread_self(); 314 315 expect_lookup(RELPATH, ino); 316 expect_open(ino, 0, 1); 317 expect_write(ino, &write_unique); 318 EXPECT_CALL(*m_mock, process( 319 ResultOf([&](auto in) { 320 return (in->header.opcode == FUSE_INTERRUPT && 321 in->body.interrupt.unique == write_unique); 322 }, Eq(true)), 323 _) 324 ).WillOnce(Invoke([&](auto in __unused, auto &out) { 325 auto out0 = new mockfs_buf_out; 326 out0->header.error = -EINTR; 327 out0->header.unique = write_unique; 328 out0->header.len = sizeof(out0->header); 329 out.push_back(out0); 330 })); 331 332 fd = open(FULLPATH, O_WRONLY); 333 ASSERT_LE(0, fd) << strerror(errno); 334 335 setup_interruptor(self); 336 ASSERT_EQ(-1, write(fd, CONTENTS, bufsize)); 337 EXPECT_EQ(EINTR, errno); 338 339 /* Deliberately leak fd. close(2) will be tested in release.cc */ 340 } 341 342 /* Reads should also be interruptible */ 343 TEST_F(Interrupt, in_progress_read) 344 { 345 const char FULLPATH[] = "mountpoint/some_file.txt"; 346 const char RELPATH[] = "some_file.txt"; 347 const size_t bufsize = 80; 348 char buf[bufsize]; 349 uint64_t ino = 42; 350 int fd; 351 pthread_t self; 352 uint64_t read_unique; 353 354 self = pthread_self(); 355 356 expect_lookup(RELPATH, ino); 357 expect_open(ino, 0, 1); 358 expect_read(ino, &read_unique); 359 EXPECT_CALL(*m_mock, process( 360 ResultOf([&](auto in) { 361 return (in->header.opcode == FUSE_INTERRUPT && 362 in->body.interrupt.unique == read_unique); 363 }, Eq(true)), 364 _) 365 ).WillOnce(Invoke([&](auto in __unused, auto &out) { 366 auto out0 = new mockfs_buf_out; 367 out0->header.error = -EINTR; 368 out0->header.unique = read_unique; 369 out0->header.len = sizeof(out0->header); 370 out.push_back(out0); 371 })); 372 373 fd = open(FULLPATH, O_RDONLY); 374 ASSERT_LE(0, fd) << strerror(errno); 375 376 setup_interruptor(self); 377 ASSERT_EQ(-1, read(fd, buf, bufsize)); 378 EXPECT_EQ(EINTR, errno); 379 380 /* Deliberately leak fd. close(2) will be tested in release.cc */ 381 } 382 383 /* 384 * If the FUSE filesystem receives the FUSE_INTERRUPT operation before 385 * processing the original, then it should wait for "some timeout" for the 386 * original operation to arrive. If not, it should send EAGAIN to the 387 * INTERRUPT operation, and the kernel should requeue the INTERRUPT. 388 * 389 * In this test, we'll pretend that the INTERRUPT arrives too soon, gets 390 * EAGAINed, then the kernel requeues it, and the second time around it 391 * successfully interrupts the original 392 */ 393 /* https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=236530 */ 394 TEST_F(Interrupt, too_soon) 395 { 396 const char FULLPATH[] = "mountpoint/some_file.txt"; 397 const char RELPATH[] = "some_file.txt"; 398 const char *CONTENTS = "abcdefgh"; 399 Sequence seq; 400 uint64_t ino = 42; 401 int fd; 402 ssize_t bufsize = strlen(CONTENTS); 403 pthread_t self; 404 uint64_t write_unique; 405 406 self = pthread_self(); 407 408 expect_lookup(RELPATH, ino); 409 expect_open(ino, 0, 1); 410 expect_write(ino, &write_unique); 411 412 EXPECT_CALL(*m_mock, process( 413 ResultOf([&](auto in) { 414 return (in->header.opcode == FUSE_INTERRUPT && 415 in->body.interrupt.unique == write_unique); 416 }, Eq(true)), 417 _) 418 ).InSequence(seq) 419 .WillOnce(Invoke(ReturnErrno(EAGAIN))); 420 421 EXPECT_CALL(*m_mock, process( 422 ResultOf([&](auto in) { 423 return (in->header.opcode == FUSE_INTERRUPT && 424 in->body.interrupt.unique == write_unique); 425 }, Eq(true)), 426 _) 427 ).InSequence(seq) 428 .WillOnce(Invoke([&](auto in __unused, auto &out __unused) { 429 auto out0 = new mockfs_buf_out; 430 out0->header.error = -EINTR; 431 out0->header.unique = write_unique; 432 out0->header.len = sizeof(out0->header); 433 out.push_back(out0); 434 })); 435 436 fd = open(FULLPATH, O_WRONLY); 437 ASSERT_LE(0, fd) << strerror(errno); 438 439 setup_interruptor(self); 440 ASSERT_EQ(-1, write(fd, CONTENTS, bufsize)); 441 EXPECT_EQ(EINTR, errno); 442 443 /* Deliberately leak fd. close(2) will be tested in release.cc */ 444 } 445 446 447 // TODO: add a test that uses siginterrupt and an interruptible signal 448 // TODO: add a test that verifies a process can be cleanly killed even if a 449 // FUSE_WRITE command never returns. 450 // TODO: write in-progress tests for other operations 451 // TODO: add a test where write returns EWOULDBLOCK 452 // TODO: test that operations that haven't been received by the server can be 453 // interrupted without generating a FUSE_INTERRUPT. 454