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