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