1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2021 Alan Somers 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions 8 * are met: 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 16 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 18 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 19 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 20 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 21 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 22 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 23 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 24 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 25 * SUCH DAMAGE. 26 */ 27 28 extern "C" { 29 #include <sys/param.h> 30 #include <sys/mount.h> 31 #include <sys/resource.h> 32 #include <sys/time.h> 33 34 #include <fcntl.h> 35 #include <signal.h> 36 #include <unistd.h> 37 38 #include "mntopts.h" // for build_iovec 39 } 40 41 #include "mockfs.hh" 42 #include "utils.hh" 43 44 using namespace testing; 45 46 /* Is buf all zero? */ 47 static bool 48 is_zero(const char *buf, uint64_t size) 49 { 50 return buf[0] == 0 && !memcmp(buf, buf + 1, size - 1); 51 } 52 53 class Fallocate: public FuseTest { 54 public: 55 /* 56 * expect VOP_DEALLOCATE to be implemented by vop_stddeallocate. 57 */ 58 void expect_vop_stddeallocate(uint64_t ino, uint64_t off, uint64_t length) 59 { 60 /* XXX read offset and size may depend on cache mode */ 61 EXPECT_CALL(*m_mock, process( 62 ResultOf([=](auto in) { 63 return (in.header.opcode == FUSE_READ && 64 in.header.nodeid == ino && 65 in.body.read.offset <= off && 66 in.body.read.offset + in.body.read.size >= 67 off + length); 68 }, Eq(true)), 69 _) 70 ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto& out) { 71 assert(in.body.read.size <= sizeof(out.body.bytes)); 72 out.header.len = sizeof(struct fuse_out_header) + 73 in.body.read.size; 74 memset(out.body.bytes, 'X', in.body.read.size); 75 }))).RetiresOnSaturation(); 76 EXPECT_CALL(*m_mock, process( 77 ResultOf([=](auto in) { 78 const char *buf = (const char*)in.body.bytes + 79 sizeof(struct fuse_write_in); 80 81 assert(length <= sizeof(in.body.bytes) - 82 sizeof(struct fuse_write_in)); 83 return (in.header.opcode == FUSE_WRITE && 84 in.header.nodeid == ino && 85 in.body.write.offset == off && 86 in.body.write.size == length && 87 is_zero(buf, length)); 88 }, Eq(true)), 89 _) 90 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 91 SET_OUT_HEADER_LEN(out, write); 92 out.body.write.size = length; 93 }))); 94 } 95 }; 96 97 class Fspacectl: public Fallocate {}; 98 99 class Fspacectl_7_18: public Fspacectl { 100 public: 101 virtual void SetUp() { 102 m_kernel_minor_version = 18; 103 Fspacectl::SetUp(); 104 } 105 }; 106 107 class FspacectlCache: public Fspacectl, public WithParamInterface<cache_mode> { 108 public: 109 bool m_direct_io; 110 111 FspacectlCache(): m_direct_io(false) {}; 112 113 virtual void SetUp() { 114 int cache_mode = GetParam(); 115 switch (cache_mode) { 116 case Uncached: 117 m_direct_io = true; 118 break; 119 case WritebackAsync: 120 m_async = true; 121 /* FALLTHROUGH */ 122 case Writeback: 123 m_init_flags |= FUSE_WRITEBACK_CACHE; 124 /* FALLTHROUGH */ 125 case Writethrough: 126 break; 127 default: 128 FAIL() << "Unknown cache mode"; 129 } 130 131 FuseTest::SetUp(); 132 if (IsSkipped()) 133 return; 134 } 135 }; 136 137 class PosixFallocate: public Fallocate { 138 public: 139 static sig_atomic_t s_sigxfsz; 140 141 void SetUp() { 142 s_sigxfsz = 0; 143 FuseTest::SetUp(); 144 } 145 146 void TearDown() { 147 struct sigaction sa; 148 149 bzero(&sa, sizeof(sa)); 150 sa.sa_handler = SIG_DFL; 151 sigaction(SIGXFSZ, &sa, NULL); 152 153 Fallocate::TearDown(); 154 } 155 156 }; 157 158 sig_atomic_t PosixFallocate::s_sigxfsz = 0; 159 160 void sigxfsz_handler(int __unused sig) { 161 PosixFallocate::s_sigxfsz = 1; 162 } 163 164 class PosixFallocate_7_18: public PosixFallocate { 165 public: 166 virtual void SetUp() { 167 m_kernel_minor_version = 18; 168 PosixFallocate::SetUp(); 169 } 170 }; 171 172 173 /* 174 * If the server returns ENOSYS, it indicates that the server does not support 175 * FUSE_FALLOCATE. This and future calls should fall back to vop_stddeallocate. 176 */ 177 TEST_F(Fspacectl, enosys) 178 { 179 const char FULLPATH[] = "mountpoint/some_file.txt"; 180 const char RELPATH[] = "some_file.txt"; 181 off_t fsize = 1 << 20; 182 off_t off0 = 100; 183 off_t len0 = 500; 184 struct spacectl_range rqsr = { .r_offset = off0, .r_len = len0 }; 185 uint64_t ino = 42; 186 uint64_t off1 = fsize; 187 uint64_t len1 = 1000; 188 off_t off2 = fsize / 2; 189 off_t len2 = 500; 190 int fd; 191 192 expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1); 193 expect_open(ino, 0, 1); 194 expect_fallocate(ino, off0, len0, 195 FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, ENOSYS); 196 expect_vop_stddeallocate(ino, off0, len0); 197 expect_vop_stddeallocate(ino, off2, len2); 198 199 fd = open(FULLPATH, O_RDWR); 200 ASSERT_LE(0, fd) << strerror(errno); 201 EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, NULL)); 202 203 /* Subsequent calls shouldn't query the daemon either */ 204 rqsr.r_offset = off2; 205 rqsr.r_len = len2; 206 EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, NULL)); 207 208 /* Neither should posix_fallocate query the daemon */ 209 EXPECT_EQ(EINVAL, posix_fallocate(fd, off1, len1)); 210 211 leak(fd); 212 } 213 214 /* 215 * EOPNOTSUPP means "the file system does not support fallocate with the 216 * supplied mode on this particular file". So we should fallback, but not 217 * assume anything about whether the operation will fail on a different file or 218 * with a different mode. 219 */ 220 TEST_F(Fspacectl, eopnotsupp) 221 { 222 const char FULLPATH[] = "mountpoint/some_file.txt"; 223 const char RELPATH[] = "some_file.txt"; 224 struct spacectl_range rqsr; 225 uint64_t ino = 42; 226 uint64_t fsize = 1 << 20; 227 uint64_t off0 = 500; 228 uint64_t len = 1000; 229 uint64_t off1 = fsize / 2; 230 int fd; 231 232 expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1); 233 expect_open(ino, 0, 1); 234 expect_fallocate(ino, off0, len, 235 FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, 236 EOPNOTSUPP); 237 expect_vop_stddeallocate(ino, off0, len); 238 expect_fallocate(ino, off1, len, 239 FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, 240 EOPNOTSUPP); 241 expect_vop_stddeallocate(ino, off1, len); 242 expect_fallocate(ino, fsize, len, 0, 0); 243 244 fd = open(FULLPATH, O_RDWR); 245 ASSERT_LE(0, fd) << strerror(errno); 246 247 /* 248 * Though the FUSE daemon will reject the call, the kernel should fall 249 * back to a read-modify-write approach. 250 */ 251 rqsr.r_offset = off0; 252 rqsr.r_len = len; 253 EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, NULL)); 254 255 /* Subsequent calls should still query the daemon */ 256 rqsr.r_offset = off1; 257 rqsr.r_len = len; 258 EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, NULL)); 259 260 /* But subsequent posix_fallocate calls _should_ query the daemon */ 261 EXPECT_EQ(0, posix_fallocate(fd, fsize, len)); 262 263 leak(fd); 264 } 265 266 TEST_F(Fspacectl, erofs) 267 { 268 const char FULLPATH[] = "mountpoint/some_file.txt"; 269 const char RELPATH[] = "some_file.txt"; 270 struct statfs statbuf; 271 uint64_t fsize = 2000; 272 struct spacectl_range rqsr = { .r_offset = 0, .r_len = 1 }; 273 struct iovec *iov = NULL; 274 int iovlen = 0; 275 uint64_t ino = 42; 276 int fd; 277 int newflags; 278 279 expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1); 280 expect_open(ino, 0, 1); 281 EXPECT_CALL(*m_mock, process( 282 ResultOf([](auto in) { 283 return (in.header.opcode == FUSE_STATFS); 284 }, Eq(true)), 285 _) 286 ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out) 287 { 288 /* 289 * All of the fields except f_flags are don't care, and f_flags 290 * is set by the VFS 291 */ 292 SET_OUT_HEADER_LEN(out, statfs); 293 }))); 294 295 fd = open(FULLPATH, O_RDWR); 296 ASSERT_LE(0, fd) << strerror(errno); 297 298 /* Remount read-only */ 299 ASSERT_EQ(0, statfs("mountpoint", &statbuf)) << strerror(errno); 300 newflags = statbuf.f_flags | MNT_UPDATE | MNT_RDONLY; 301 build_iovec(&iov, &iovlen, "fstype", (void*)statbuf.f_fstypename, -1); 302 build_iovec(&iov, &iovlen, "fspath", (void*)statbuf.f_mntonname, -1); 303 build_iovec(&iov, &iovlen, "from", __DECONST(void *, "/dev/fuse"), -1); 304 ASSERT_EQ(0, nmount(iov, iovlen, newflags)) << strerror(errno); 305 free_iovec(&iov, &iovlen); 306 307 EXPECT_EQ(-1, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, NULL)); 308 EXPECT_EQ(EROFS, errno); 309 310 leak(fd); 311 } 312 313 /* 314 * If FUSE_GETATTR fails when determining the size of the file, fspacectl 315 * should fail gracefully. This failure mode is easiest to trigger when 316 * attribute caching is disabled. 317 */ 318 TEST_F(Fspacectl, getattr_fails) 319 { 320 const char FULLPATH[] = "mountpoint/some_file.txt"; 321 const char RELPATH[] = "some_file.txt"; 322 Sequence seq; 323 struct spacectl_range rqsr; 324 const uint64_t ino = 42; 325 const uint64_t fsize = 2000; 326 int fd; 327 328 expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1, 0); 329 expect_open(ino, 0, 1); 330 EXPECT_CALL(*m_mock, process( 331 ResultOf([](auto in) { 332 return (in.header.opcode == FUSE_GETATTR && 333 in.header.nodeid == ino); 334 }, Eq(true)), 335 _) 336 ).Times(1) 337 .InSequence(seq) 338 .WillOnce(Invoke(ReturnImmediate([](auto i __unused, auto& out) { 339 SET_OUT_HEADER_LEN(out, attr); 340 out.body.attr.attr.ino = ino; 341 out.body.attr.attr.mode = S_IFREG | 0644; 342 out.body.attr.attr.size = fsize; 343 out.body.attr.attr_valid = 0; 344 }))); 345 EXPECT_CALL(*m_mock, process( 346 ResultOf([](auto in) { 347 return (in.header.opcode == FUSE_GETATTR && 348 in.header.nodeid == ino); 349 }, Eq(true)), 350 _) 351 ).InSequence(seq) 352 .WillOnce(ReturnErrno(EIO)); 353 354 fd = open(FULLPATH, O_RDWR); 355 ASSERT_LE(0, fd) << strerror(errno); 356 rqsr.r_offset = 500; 357 rqsr.r_len = 1000; 358 EXPECT_EQ(-1, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, NULL)); 359 EXPECT_EQ(EIO, errno); 360 361 leak(fd); 362 } 363 364 TEST_F(Fspacectl, ok) 365 { 366 const char FULLPATH[] = "mountpoint/some_file.txt"; 367 const char RELPATH[] = "some_file.txt"; 368 struct spacectl_range rqsr, rmsr; 369 struct stat sb0, sb1; 370 uint64_t ino = 42; 371 uint64_t fsize = 2000; 372 uint64_t offset = 500; 373 uint64_t length = 1000; 374 int fd; 375 376 expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1); 377 expect_open(ino, 0, 1); 378 expect_fallocate(ino, offset, length, 379 FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, 0); 380 381 fd = open(FULLPATH, O_RDWR); 382 ASSERT_LE(0, fd) << strerror(errno); 383 ASSERT_EQ(0, fstat(fd, &sb0)) << strerror(errno); 384 rqsr.r_offset = offset; 385 rqsr.r_len = length; 386 EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, &rmsr)); 387 EXPECT_EQ(0, rmsr.r_len); 388 EXPECT_EQ((off_t)(offset + length), rmsr.r_offset); 389 390 /* 391 * The file's attributes should not have been invalidated, so this fstat 392 * will not requery the daemon. 393 */ 394 EXPECT_EQ(0, fstat(fd, &sb1)); 395 EXPECT_EQ(fsize, (uint64_t)sb1.st_size); 396 397 /* mtime and ctime should be updated */ 398 EXPECT_EQ(sb0.st_atime, sb1.st_atime); 399 EXPECT_NE(sb0.st_mtime, sb1.st_mtime); 400 EXPECT_NE(sb0.st_ctime, sb1.st_ctime); 401 402 leak(fd); 403 } 404 405 /* The returned rqsr.r_off should be clipped at EoF */ 406 TEST_F(Fspacectl, past_eof) 407 { 408 const char FULLPATH[] = "mountpoint/some_file.txt"; 409 const char RELPATH[] = "some_file.txt"; 410 struct spacectl_range rqsr, rmsr; 411 uint64_t ino = 42; 412 uint64_t fsize = 1000; 413 uint64_t offset = 1500; 414 uint64_t length = 1000; 415 int fd; 416 417 expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1); 418 expect_open(ino, 0, 1); 419 expect_fallocate(ino, offset, length, 420 FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, 0); 421 422 fd = open(FULLPATH, O_RDWR); 423 ASSERT_LE(0, fd) << strerror(errno); 424 rqsr.r_offset = offset; 425 rqsr.r_len = length; 426 EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, &rmsr)); 427 EXPECT_EQ(0, rmsr.r_len); 428 EXPECT_EQ((off_t)fsize, rmsr.r_offset); 429 430 leak(fd); 431 } 432 433 /* The returned rqsr.r_off should be clipped at EoF */ 434 TEST_F(Fspacectl, spans_eof) 435 { 436 const char FULLPATH[] = "mountpoint/some_file.txt"; 437 const char RELPATH[] = "some_file.txt"; 438 struct spacectl_range rqsr, rmsr; 439 uint64_t ino = 42; 440 uint64_t fsize = 1000; 441 uint64_t offset = 500; 442 uint64_t length = 1000; 443 int fd; 444 445 expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1); 446 expect_open(ino, 0, 1); 447 expect_fallocate(ino, offset, length, 448 FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, 0); 449 450 fd = open(FULLPATH, O_RDWR); 451 ASSERT_LE(0, fd) << strerror(errno); 452 rqsr.r_offset = offset; 453 rqsr.r_len = length; 454 EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, &rmsr)); 455 EXPECT_EQ(0, rmsr.r_len); 456 EXPECT_EQ((off_t)fsize, rmsr.r_offset); 457 458 leak(fd); 459 } 460 461 /* 462 * With older servers, no FUSE_FALLOCATE should be attempted. The kernel 463 * should fall back to vop_stddeallocate. 464 */ 465 TEST_F(Fspacectl_7_18, ok) 466 { 467 const char FULLPATH[] = "mountpoint/some_file.txt"; 468 const char RELPATH[] = "some_file.txt"; 469 struct spacectl_range rqsr, rmsr; 470 char *buf; 471 uint64_t ino = 42; 472 uint64_t fsize = 2000; 473 uint64_t offset = 500; 474 uint64_t length = 1000; 475 int fd; 476 477 buf = new char[length]; 478 479 expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1); 480 expect_open(ino, 0, 1); 481 expect_vop_stddeallocate(ino, offset, length); 482 483 fd = open(FULLPATH, O_RDWR); 484 ASSERT_LE(0, fd) << strerror(errno); 485 rqsr.r_offset = offset; 486 rqsr.r_len = length; 487 EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, &rmsr)); 488 EXPECT_EQ(0, rmsr.r_len); 489 EXPECT_EQ((off_t)(offset + length), rmsr.r_offset); 490 491 leak(fd); 492 delete[] buf; 493 } 494 495 /* 496 * A successful fspacectl should clear the zeroed data from the kernel cache. 497 */ 498 TEST_P(FspacectlCache, clears_cache) 499 { 500 const char FULLPATH[] = "mountpoint/some_file.txt"; 501 const char RELPATH[] = "some_file.txt"; 502 const char *CONTENTS = "abcdefghijklmnopqrstuvwxyz"; 503 struct spacectl_range rqsr, rmsr; 504 uint64_t ino = 42; 505 ssize_t bufsize = strlen(CONTENTS); 506 uint64_t fsize = bufsize; 507 uint8_t buf[bufsize]; 508 char zbuf[bufsize]; 509 uint64_t offset = 0; 510 uint64_t length = bufsize; 511 int fd; 512 513 bzero(zbuf, bufsize); 514 515 expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1); 516 expect_open(ino, 0, 1); 517 /* NB: expectations are applied in LIFO order */ 518 expect_read(ino, 0, fsize, fsize, zbuf); 519 expect_read(ino, 0, fsize, fsize, CONTENTS); 520 expect_fallocate(ino, offset, length, 521 FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, 0); 522 523 fd = open(FULLPATH, O_RDWR); 524 ASSERT_LE(0, fd) << strerror(errno); 525 526 /* Populate the cache */ 527 ASSERT_EQ(fsize, (uint64_t)pread(fd, buf, bufsize, 0)) 528 << strerror(errno); 529 ASSERT_EQ(0, memcmp(buf, CONTENTS, fsize)); 530 531 /* Zero the file */ 532 rqsr.r_offset = offset; 533 rqsr.r_len = length; 534 EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, &rmsr)); 535 EXPECT_EQ(0, rmsr.r_len); 536 EXPECT_EQ((off_t)(offset + length), rmsr.r_offset); 537 538 /* Read again. This should query the daemon */ 539 ASSERT_EQ(fsize, (uint64_t)pread(fd, buf, bufsize, 0)) 540 << strerror(errno); 541 ASSERT_EQ(0, memcmp(buf, zbuf, fsize)); 542 543 leak(fd); 544 } 545 546 INSTANTIATE_TEST_SUITE_P(FspacectlCache, FspacectlCache, 547 Values(Uncached, Writethrough, Writeback) 548 ); 549 550 /* 551 * If the server returns ENOSYS, it indicates that the server does not support 552 * FUSE_FALLOCATE. This and future calls should return EINVAL. 553 */ 554 TEST_F(PosixFallocate, enosys) 555 { 556 const char FULLPATH[] = "mountpoint/some_file.txt"; 557 const char RELPATH[] = "some_file.txt"; 558 uint64_t ino = 42; 559 uint64_t off0 = 0; 560 uint64_t len0 = 1000; 561 off_t off1 = 100; 562 off_t len1 = 200; 563 uint64_t fsize = 500; 564 struct spacectl_range rqsr = { .r_offset = off1, .r_len = len1 }; 565 int fd; 566 567 expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1); 568 expect_open(ino, 0, 1); 569 expect_fallocate(ino, off0, len0, 0, ENOSYS); 570 expect_vop_stddeallocate(ino, off1, len1); 571 572 fd = open(FULLPATH, O_RDWR); 573 ASSERT_LE(0, fd) << strerror(errno); 574 EXPECT_EQ(EINVAL, posix_fallocate(fd, off0, len0)); 575 576 /* Subsequent calls shouldn't query the daemon*/ 577 EXPECT_EQ(EINVAL, posix_fallocate(fd, off0, len0)); 578 579 /* Neither should VOP_DEALLOCATE query the daemon */ 580 EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, NULL)); 581 582 leak(fd); 583 } 584 585 /* 586 * EOPNOTSUPP means "the file system does not support fallocate with the 587 * supplied mode on this particular file". So we should fallback, but not 588 * assume anything about whether the operation will fail on a different file or 589 * with a different mode. 590 */ 591 TEST_F(PosixFallocate, eopnotsupp) 592 { 593 const char FULLPATH[] = "mountpoint/some_file.txt"; 594 const char RELPATH[] = "some_file.txt"; 595 struct spacectl_range rqsr; 596 uint64_t ino = 42; 597 uint64_t fsize = 2000; 598 uint64_t offset = 0; 599 uint64_t length = 1000; 600 int fd; 601 602 expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1); 603 expect_open(ino, 0, 1); 604 expect_fallocate(ino, fsize, length, 0, EOPNOTSUPP); 605 expect_fallocate(ino, offset, length, 0, EOPNOTSUPP); 606 expect_fallocate(ino, offset, length, 607 FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, 0); 608 609 fd = open(FULLPATH, O_RDWR); 610 ASSERT_LE(0, fd) << strerror(errno); 611 EXPECT_EQ(EINVAL, posix_fallocate(fd, fsize, length)); 612 613 /* Subsequent calls should still query the daemon*/ 614 EXPECT_EQ(EINVAL, posix_fallocate(fd, offset, length)); 615 616 /* And subsequent VOP_DEALLOCATE calls should also query the daemon */ 617 rqsr.r_len = length; 618 rqsr.r_offset = offset; 619 EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, NULL)); 620 621 leak(fd); 622 } 623 624 /* EIO is not a permanent error, and may be retried */ 625 TEST_F(PosixFallocate, eio) 626 { 627 const char FULLPATH[] = "mountpoint/some_file.txt"; 628 const char RELPATH[] = "some_file.txt"; 629 uint64_t ino = 42; 630 uint64_t offset = 0; 631 uint64_t length = 1000; 632 int fd; 633 634 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); 635 expect_open(ino, 0, 1); 636 expect_fallocate(ino, offset, length, 0, EIO); 637 638 fd = open(FULLPATH, O_RDWR); 639 ASSERT_LE(0, fd) << strerror(errno); 640 EXPECT_EQ(EIO, posix_fallocate(fd, offset, length)); 641 642 expect_fallocate(ino, offset, length, 0, 0); 643 644 EXPECT_EQ(0, posix_fallocate(fd, offset, length)); 645 646 leak(fd); 647 } 648 649 TEST_F(PosixFallocate, erofs) 650 { 651 const char FULLPATH[] = "mountpoint/some_file.txt"; 652 const char RELPATH[] = "some_file.txt"; 653 struct statfs statbuf; 654 struct iovec *iov = NULL; 655 int iovlen = 0; 656 uint64_t ino = 42; 657 uint64_t offset = 0; 658 uint64_t length = 1000; 659 int fd; 660 int newflags; 661 662 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); 663 expect_open(ino, 0, 1); 664 EXPECT_CALL(*m_mock, process( 665 ResultOf([](auto in) { 666 return (in.header.opcode == FUSE_STATFS); 667 }, Eq(true)), 668 _) 669 ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out) 670 { 671 /* 672 * All of the fields except f_flags are don't care, and f_flags 673 * is set by the VFS 674 */ 675 SET_OUT_HEADER_LEN(out, statfs); 676 }))); 677 678 fd = open(FULLPATH, O_RDWR); 679 ASSERT_LE(0, fd) << strerror(errno); 680 681 /* Remount read-only */ 682 ASSERT_EQ(0, statfs("mountpoint", &statbuf)) << strerror(errno); 683 newflags = statbuf.f_flags | MNT_UPDATE | MNT_RDONLY; 684 build_iovec(&iov, &iovlen, "fstype", (void*)statbuf.f_fstypename, -1); 685 build_iovec(&iov, &iovlen, "fspath", (void*)statbuf.f_mntonname, -1); 686 build_iovec(&iov, &iovlen, "from", __DECONST(void *, "/dev/fuse"), -1); 687 ASSERT_EQ(0, nmount(iov, iovlen, newflags)) << strerror(errno); 688 free_iovec(&iov, &iovlen); 689 690 EXPECT_EQ(EROFS, posix_fallocate(fd, offset, length)); 691 692 leak(fd); 693 } 694 695 TEST_F(PosixFallocate, ok) 696 { 697 const char FULLPATH[] = "mountpoint/some_file.txt"; 698 const char RELPATH[] = "some_file.txt"; 699 struct stat sb0, sb1; 700 uint64_t ino = 42; 701 uint64_t offset = 0; 702 uint64_t length = 1000; 703 int fd; 704 705 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 706 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 707 SET_OUT_HEADER_LEN(out, entry); 708 out.body.entry.attr.mode = S_IFREG | 0644; 709 out.body.entry.nodeid = ino; 710 out.body.entry.entry_valid = UINT64_MAX; 711 out.body.entry.attr_valid = UINT64_MAX; 712 }))); 713 expect_open(ino, 0, 1); 714 expect_fallocate(ino, offset, length, 0, 0); 715 716 fd = open(FULLPATH, O_RDWR); 717 ASSERT_LE(0, fd) << strerror(errno); 718 ASSERT_EQ(0, fstat(fd, &sb0)) << strerror(errno); 719 EXPECT_EQ(0, posix_fallocate(fd, offset, length)); 720 /* 721 * Despite the originally cached file size of zero, stat should now 722 * return either the new size or requery the daemon. 723 */ 724 EXPECT_EQ(0, stat(FULLPATH, &sb1)); 725 EXPECT_EQ(length, (uint64_t)sb1.st_size); 726 727 /* mtime and ctime should be updated */ 728 EXPECT_EQ(sb0.st_atime, sb1.st_atime); 729 EXPECT_NE(sb0.st_mtime, sb1.st_mtime); 730 EXPECT_NE(sb0.st_ctime, sb1.st_ctime); 731 732 leak(fd); 733 } 734 735 /* fusefs should respect RLIMIT_FSIZE */ 736 TEST_F(PosixFallocate, rlimit_fsize) 737 { 738 const char FULLPATH[] = "mountpoint/some_file.txt"; 739 const char RELPATH[] = "some_file.txt"; 740 struct rlimit rl; 741 uint64_t ino = 42; 742 uint64_t offset = 0; 743 uint64_t length = 1'000'000; 744 int fd; 745 746 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); 747 expect_open(ino, 0, 1); 748 749 rl.rlim_cur = length / 2; 750 rl.rlim_max = 10 * length; 751 ASSERT_EQ(0, setrlimit(RLIMIT_FSIZE, &rl)) << strerror(errno); 752 ASSERT_NE(SIG_ERR, signal(SIGXFSZ, sigxfsz_handler)) << strerror(errno); 753 754 fd = open(FULLPATH, O_RDWR); 755 ASSERT_LE(0, fd) << strerror(errno); 756 EXPECT_EQ(EFBIG, posix_fallocate(fd, offset, length)); 757 EXPECT_EQ(1, s_sigxfsz); 758 759 leak(fd); 760 } 761 762 /* With older servers, no FUSE_FALLOCATE should be attempted */ 763 TEST_F(PosixFallocate_7_18, einval) 764 { 765 const char FULLPATH[] = "mountpoint/some_file.txt"; 766 const char RELPATH[] = "some_file.txt"; 767 uint64_t ino = 42; 768 uint64_t offset = 0; 769 uint64_t length = 1000; 770 int fd; 771 772 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); 773 expect_open(ino, 0, 1); 774 775 fd = open(FULLPATH, O_RDWR); 776 ASSERT_LE(0, fd) << strerror(errno); 777 EXPECT_EQ(EINVAL, posix_fallocate(fd, offset, length)); 778 779 leak(fd); 780 } 781