1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD 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 * $FreeBSD$ 28 */ 29 30 extern "C" { 31 #include <sys/param.h> 32 #include <sys/mount.h> 33 #include <sys/resource.h> 34 #include <sys/time.h> 35 36 #include <fcntl.h> 37 #include <signal.h> 38 #include <unistd.h> 39 40 #include "mntopts.h" // for build_iovec 41 } 42 43 #include "mockfs.hh" 44 #include "utils.hh" 45 46 using namespace testing; 47 48 /* Is buf all zero? */ 49 static bool 50 is_zero(const char *buf, uint64_t size) 51 { 52 return buf[0] == 0 && !memcmp(buf, buf + 1, size - 1); 53 } 54 55 class Fallocate: public FuseTest { 56 public: 57 /* 58 * expect VOP_DEALLOCATE to be implemented by vop_stddeallocate. 59 */ 60 void expect_vop_stddeallocate(uint64_t ino, uint64_t off, uint64_t length) 61 { 62 /* XXX read offset and size may depend on cache mode */ 63 EXPECT_CALL(*m_mock, process( 64 ResultOf([=](auto in) { 65 return (in.header.opcode == FUSE_READ && 66 in.header.nodeid == ino && 67 in.body.read.offset <= off && 68 in.body.read.offset + in.body.read.size >= 69 off + length); 70 }, Eq(true)), 71 _) 72 ).WillOnce(Invoke(ReturnImmediate([=](auto in, auto& out) { 73 out.header.len = sizeof(struct fuse_out_header) + 74 in.body.read.size; 75 memset(out.body.bytes, 'X', in.body.read.size); 76 }))).RetiresOnSaturation(); 77 EXPECT_CALL(*m_mock, process( 78 ResultOf([=](auto in) { 79 const char *buf = (const char*)in.body.bytes + 80 sizeof(struct fuse_write_in); 81 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 305 EXPECT_EQ(-1, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, NULL)); 306 EXPECT_EQ(EROFS, errno); 307 308 leak(fd); 309 } 310 311 TEST_F(Fspacectl, ok) 312 { 313 const char FULLPATH[] = "mountpoint/some_file.txt"; 314 const char RELPATH[] = "some_file.txt"; 315 struct spacectl_range rqsr, rmsr; 316 struct stat sb0, sb1; 317 uint64_t ino = 42; 318 uint64_t fsize = 2000; 319 uint64_t offset = 500; 320 uint64_t length = 1000; 321 int fd; 322 323 expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1); 324 expect_open(ino, 0, 1); 325 expect_fallocate(ino, offset, length, 326 FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, 0); 327 328 fd = open(FULLPATH, O_RDWR); 329 ASSERT_LE(0, fd) << strerror(errno); 330 ASSERT_EQ(0, fstat(fd, &sb0)) << strerror(errno); 331 rqsr.r_offset = offset; 332 rqsr.r_len = length; 333 EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, &rmsr)); 334 EXPECT_EQ(0, rmsr.r_len); 335 EXPECT_EQ((off_t)(offset + length), rmsr.r_offset); 336 337 /* 338 * The file's attributes should not have been invalidated, so this fstat 339 * will not requery the daemon. 340 */ 341 EXPECT_EQ(0, fstat(fd, &sb1)); 342 EXPECT_EQ(fsize, (uint64_t)sb1.st_size); 343 344 /* mtime and ctime should be updated */ 345 EXPECT_EQ(sb0.st_atime, sb1.st_atime); 346 EXPECT_NE(sb0.st_mtime, sb1.st_mtime); 347 EXPECT_NE(sb0.st_ctime, sb1.st_ctime); 348 349 leak(fd); 350 } 351 352 /* The returned rqsr.r_off should be clipped at EoF */ 353 TEST_F(Fspacectl, past_eof) 354 { 355 const char FULLPATH[] = "mountpoint/some_file.txt"; 356 const char RELPATH[] = "some_file.txt"; 357 struct spacectl_range rqsr, rmsr; 358 uint64_t ino = 42; 359 uint64_t fsize = 1000; 360 uint64_t offset = 1500; 361 uint64_t length = 1000; 362 int fd; 363 364 expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1); 365 expect_open(ino, 0, 1); 366 expect_fallocate(ino, offset, length, 367 FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, 0); 368 369 fd = open(FULLPATH, O_RDWR); 370 ASSERT_LE(0, fd) << strerror(errno); 371 rqsr.r_offset = offset; 372 rqsr.r_len = length; 373 EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, &rmsr)); 374 EXPECT_EQ(0, rmsr.r_len); 375 EXPECT_EQ((off_t)fsize, rmsr.r_offset); 376 377 leak(fd); 378 } 379 380 /* The returned rqsr.r_off should be clipped at EoF */ 381 TEST_F(Fspacectl, spans_eof) 382 { 383 const char FULLPATH[] = "mountpoint/some_file.txt"; 384 const char RELPATH[] = "some_file.txt"; 385 struct spacectl_range rqsr, rmsr; 386 uint64_t ino = 42; 387 uint64_t fsize = 1000; 388 uint64_t offset = 500; 389 uint64_t length = 1000; 390 int fd; 391 392 expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1); 393 expect_open(ino, 0, 1); 394 expect_fallocate(ino, offset, length, 395 FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, 0); 396 397 fd = open(FULLPATH, O_RDWR); 398 ASSERT_LE(0, fd) << strerror(errno); 399 rqsr.r_offset = offset; 400 rqsr.r_len = length; 401 EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, &rmsr)); 402 EXPECT_EQ(0, rmsr.r_len); 403 EXPECT_EQ((off_t)fsize, rmsr.r_offset); 404 405 leak(fd); 406 } 407 408 /* 409 * With older servers, no FUSE_FALLOCATE should be attempted. The kernel 410 * should fall back to vop_stddeallocate. 411 */ 412 TEST_F(Fspacectl_7_18, ok) 413 { 414 const char FULLPATH[] = "mountpoint/some_file.txt"; 415 const char RELPATH[] = "some_file.txt"; 416 struct spacectl_range rqsr, rmsr; 417 void *buf; 418 uint64_t ino = 42; 419 uint64_t fsize = 2000; 420 uint64_t offset = 500; 421 uint64_t length = 1000; 422 int fd; 423 424 buf = malloc(length); 425 426 expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1); 427 expect_open(ino, 0, 1); 428 expect_vop_stddeallocate(ino, offset, length); 429 430 fd = open(FULLPATH, O_RDWR); 431 ASSERT_LE(0, fd) << strerror(errno); 432 rqsr.r_offset = offset; 433 rqsr.r_len = length; 434 EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, &rmsr)); 435 EXPECT_EQ(0, rmsr.r_len); 436 EXPECT_EQ((off_t)(offset + length), rmsr.r_offset); 437 438 leak(fd); 439 free(buf); 440 } 441 442 /* 443 * A successful fspacectl should clear the zeroed data from the kernel cache. 444 */ 445 TEST_P(FspacectlCache, clears_cache) 446 { 447 const char FULLPATH[] = "mountpoint/some_file.txt"; 448 const char RELPATH[] = "some_file.txt"; 449 const char *CONTENTS = "abcdefghijklmnopqrstuvwxyz"; 450 struct spacectl_range rqsr, rmsr; 451 uint64_t ino = 42; 452 ssize_t bufsize = strlen(CONTENTS); 453 uint64_t fsize = bufsize; 454 uint8_t buf[bufsize]; 455 char zbuf[bufsize]; 456 uint64_t offset = 0; 457 uint64_t length = bufsize; 458 int fd; 459 460 bzero(zbuf, bufsize); 461 462 expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1); 463 expect_open(ino, 0, 1); 464 /* NB: expectations are applied in LIFO order */ 465 expect_read(ino, 0, fsize, fsize, zbuf); 466 expect_read(ino, 0, fsize, fsize, CONTENTS); 467 expect_fallocate(ino, offset, length, 468 FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, 0); 469 470 fd = open(FULLPATH, O_RDWR); 471 ASSERT_LE(0, fd) << strerror(errno); 472 473 /* Populate the cache */ 474 ASSERT_EQ(fsize, (uint64_t)pread(fd, buf, bufsize, 0)) 475 << strerror(errno); 476 ASSERT_EQ(0, memcmp(buf, CONTENTS, fsize)); 477 478 /* Zero the file */ 479 rqsr.r_offset = offset; 480 rqsr.r_len = length; 481 EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, &rmsr)); 482 EXPECT_EQ(0, rmsr.r_len); 483 EXPECT_EQ((off_t)(offset + length), rmsr.r_offset); 484 485 /* Read again. This should query the daemon */ 486 ASSERT_EQ(fsize, (uint64_t)pread(fd, buf, bufsize, 0)) 487 << strerror(errno); 488 ASSERT_EQ(0, memcmp(buf, zbuf, fsize)); 489 490 leak(fd); 491 } 492 493 INSTANTIATE_TEST_CASE_P(FspacectlCache, FspacectlCache, 494 Values(Uncached, Writethrough, Writeback), 495 ); 496 497 /* 498 * If the server returns ENOSYS, it indicates that the server does not support 499 * FUSE_FALLOCATE. This and future calls should return EINVAL. 500 */ 501 TEST_F(PosixFallocate, enosys) 502 { 503 const char FULLPATH[] = "mountpoint/some_file.txt"; 504 const char RELPATH[] = "some_file.txt"; 505 uint64_t ino = 42; 506 uint64_t off0 = 0; 507 uint64_t len0 = 1000; 508 off_t off1 = 100; 509 off_t len1 = 200; 510 uint64_t fsize = 500; 511 struct spacectl_range rqsr = { .r_offset = off1, .r_len = len1 }; 512 int fd; 513 514 expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1); 515 expect_open(ino, 0, 1); 516 expect_fallocate(ino, off0, len0, 0, ENOSYS); 517 expect_vop_stddeallocate(ino, off1, len1); 518 519 fd = open(FULLPATH, O_RDWR); 520 ASSERT_LE(0, fd) << strerror(errno); 521 EXPECT_EQ(EINVAL, posix_fallocate(fd, off0, len0)); 522 523 /* Subsequent calls shouldn't query the daemon*/ 524 EXPECT_EQ(EINVAL, posix_fallocate(fd, off0, len0)); 525 526 /* Neither should VOP_DEALLOCATE query the daemon */ 527 EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, NULL)); 528 529 leak(fd); 530 } 531 532 /* 533 * EOPNOTSUPP means "the file system does not support fallocate with the 534 * supplied mode on this particular file". So we should fallback, but not 535 * assume anything about whether the operation will fail on a different file or 536 * with a different mode. 537 */ 538 TEST_F(PosixFallocate, eopnotsupp) 539 { 540 const char FULLPATH[] = "mountpoint/some_file.txt"; 541 const char RELPATH[] = "some_file.txt"; 542 struct spacectl_range rqsr; 543 uint64_t ino = 42; 544 uint64_t fsize = 2000; 545 uint64_t offset = 0; 546 uint64_t length = 1000; 547 int fd; 548 549 expect_lookup(RELPATH, ino, S_IFREG | 0644, fsize, 1); 550 expect_open(ino, 0, 1); 551 expect_fallocate(ino, fsize, length, 0, EOPNOTSUPP); 552 expect_fallocate(ino, offset, length, 0, EOPNOTSUPP); 553 expect_fallocate(ino, offset, length, 554 FUSE_FALLOC_FL_KEEP_SIZE | FUSE_FALLOC_FL_PUNCH_HOLE, 0); 555 556 fd = open(FULLPATH, O_RDWR); 557 ASSERT_LE(0, fd) << strerror(errno); 558 EXPECT_EQ(EINVAL, posix_fallocate(fd, fsize, length)); 559 560 /* Subsequent calls should still query the daemon*/ 561 EXPECT_EQ(EINVAL, posix_fallocate(fd, offset, length)); 562 563 /* And subsequent VOP_DEALLOCATE calls should also query the daemon */ 564 rqsr.r_len = length; 565 rqsr.r_offset = offset; 566 EXPECT_EQ(0, fspacectl(fd, SPACECTL_DEALLOC, &rqsr, 0, NULL)); 567 568 leak(fd); 569 } 570 571 /* EIO is not a permanent error, and may be retried */ 572 TEST_F(PosixFallocate, eio) 573 { 574 const char FULLPATH[] = "mountpoint/some_file.txt"; 575 const char RELPATH[] = "some_file.txt"; 576 uint64_t ino = 42; 577 uint64_t offset = 0; 578 uint64_t length = 1000; 579 int fd; 580 581 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); 582 expect_open(ino, 0, 1); 583 expect_fallocate(ino, offset, length, 0, EIO); 584 585 fd = open(FULLPATH, O_RDWR); 586 ASSERT_LE(0, fd) << strerror(errno); 587 EXPECT_EQ(EIO, posix_fallocate(fd, offset, length)); 588 589 expect_fallocate(ino, offset, length, 0, 0); 590 591 EXPECT_EQ(0, posix_fallocate(fd, offset, length)); 592 593 leak(fd); 594 } 595 596 TEST_F(PosixFallocate, erofs) 597 { 598 const char FULLPATH[] = "mountpoint/some_file.txt"; 599 const char RELPATH[] = "some_file.txt"; 600 struct statfs statbuf; 601 struct iovec *iov = NULL; 602 int iovlen = 0; 603 uint64_t ino = 42; 604 uint64_t offset = 0; 605 uint64_t length = 1000; 606 int fd; 607 int newflags; 608 609 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); 610 expect_open(ino, 0, 1); 611 EXPECT_CALL(*m_mock, process( 612 ResultOf([](auto in) { 613 return (in.header.opcode == FUSE_STATFS); 614 }, Eq(true)), 615 _) 616 ).WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out) 617 { 618 /* 619 * All of the fields except f_flags are don't care, and f_flags 620 * is set by the VFS 621 */ 622 SET_OUT_HEADER_LEN(out, statfs); 623 }))); 624 625 fd = open(FULLPATH, O_RDWR); 626 ASSERT_LE(0, fd) << strerror(errno); 627 628 /* Remount read-only */ 629 ASSERT_EQ(0, statfs("mountpoint", &statbuf)) << strerror(errno); 630 newflags = statbuf.f_flags | MNT_UPDATE | MNT_RDONLY; 631 build_iovec(&iov, &iovlen, "fstype", (void*)statbuf.f_fstypename, -1); 632 build_iovec(&iov, &iovlen, "fspath", (void*)statbuf.f_mntonname, -1); 633 build_iovec(&iov, &iovlen, "from", __DECONST(void *, "/dev/fuse"), -1); 634 ASSERT_EQ(0, nmount(iov, iovlen, newflags)) << strerror(errno); 635 636 EXPECT_EQ(EROFS, posix_fallocate(fd, offset, length)); 637 638 leak(fd); 639 } 640 641 TEST_F(PosixFallocate, ok) 642 { 643 const char FULLPATH[] = "mountpoint/some_file.txt"; 644 const char RELPATH[] = "some_file.txt"; 645 struct stat sb0, sb1; 646 uint64_t ino = 42; 647 uint64_t offset = 0; 648 uint64_t length = 1000; 649 int fd; 650 651 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 652 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 653 SET_OUT_HEADER_LEN(out, entry); 654 out.body.entry.attr.mode = S_IFREG | 0644; 655 out.body.entry.nodeid = ino; 656 out.body.entry.entry_valid = UINT64_MAX; 657 out.body.entry.attr_valid = UINT64_MAX; 658 }))); 659 expect_open(ino, 0, 1); 660 expect_fallocate(ino, offset, length, 0, 0); 661 662 fd = open(FULLPATH, O_RDWR); 663 ASSERT_LE(0, fd) << strerror(errno); 664 ASSERT_EQ(0, fstat(fd, &sb0)) << strerror(errno); 665 EXPECT_EQ(0, posix_fallocate(fd, offset, length)); 666 /* 667 * Despite the originally cached file size of zero, stat should now 668 * return either the new size or requery the daemon. 669 */ 670 EXPECT_EQ(0, stat(FULLPATH, &sb1)); 671 EXPECT_EQ(length, (uint64_t)sb1.st_size); 672 673 /* mtime and ctime should be updated */ 674 EXPECT_EQ(sb0.st_atime, sb1.st_atime); 675 EXPECT_NE(sb0.st_mtime, sb1.st_mtime); 676 EXPECT_NE(sb0.st_ctime, sb1.st_ctime); 677 678 leak(fd); 679 } 680 681 /* fusefs should respect RLIMIT_FSIZE */ 682 TEST_F(PosixFallocate, rlimit_fsize) 683 { 684 const char FULLPATH[] = "mountpoint/some_file.txt"; 685 const char RELPATH[] = "some_file.txt"; 686 struct rlimit rl; 687 uint64_t ino = 42; 688 uint64_t offset = 0; 689 uint64_t length = 1'000'000; 690 int fd; 691 692 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); 693 expect_open(ino, 0, 1); 694 695 rl.rlim_cur = length / 2; 696 rl.rlim_max = 10 * length; 697 ASSERT_EQ(0, setrlimit(RLIMIT_FSIZE, &rl)) << strerror(errno); 698 ASSERT_NE(SIG_ERR, signal(SIGXFSZ, sigxfsz_handler)) << strerror(errno); 699 700 fd = open(FULLPATH, O_RDWR); 701 ASSERT_LE(0, fd) << strerror(errno); 702 EXPECT_EQ(EFBIG, posix_fallocate(fd, offset, length)); 703 EXPECT_EQ(1, s_sigxfsz); 704 705 leak(fd); 706 } 707 708 /* With older servers, no FUSE_FALLOCATE should be attempted */ 709 TEST_F(PosixFallocate_7_18, einval) 710 { 711 const char FULLPATH[] = "mountpoint/some_file.txt"; 712 const char RELPATH[] = "some_file.txt"; 713 uint64_t ino = 42; 714 uint64_t offset = 0; 715 uint64_t length = 1000; 716 int fd; 717 718 expect_lookup(RELPATH, ino, S_IFREG | 0644, 0, 1); 719 expect_open(ino, 0, 1); 720 721 fd = open(FULLPATH, O_RDWR); 722 ASSERT_LE(0, fd) << strerror(errno); 723 EXPECT_EQ(EINVAL, posix_fallocate(fd, offset, length)); 724 725 leak(fd); 726 } 727