1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 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/types.h> 33 #include <sys/resource.h> 34 #include <sys/stat.h> 35 #include <sys/time.h> 36 37 #include <fcntl.h> 38 #include <semaphore.h> 39 #include <signal.h> 40 } 41 42 #include "mockfs.hh" 43 #include "utils.hh" 44 45 using namespace testing; 46 47 class Setattr : public FuseTest { 48 public: 49 static sig_atomic_t s_sigxfsz; 50 }; 51 52 class RofsSetattr: public Setattr { 53 public: 54 virtual void SetUp() { 55 s_sigxfsz = 0; 56 m_ro = true; 57 Setattr::SetUp(); 58 } 59 }; 60 61 class Setattr_7_8: public Setattr { 62 public: 63 virtual void SetUp() { 64 m_kernel_minor_version = 8; 65 Setattr::SetUp(); 66 } 67 }; 68 69 70 sig_atomic_t Setattr::s_sigxfsz = 0; 71 72 void sigxfsz_handler(int __unused sig) { 73 Setattr::s_sigxfsz = 1; 74 } 75 76 /* 77 * If setattr returns a non-zero cache timeout, then subsequent VOP_GETATTRs 78 * should use the cached attributes, rather than query the daemon 79 */ 80 TEST_F(Setattr, attr_cache) 81 { 82 const char FULLPATH[] = "mountpoint/some_file.txt"; 83 const char RELPATH[] = "some_file.txt"; 84 const uint64_t ino = 42; 85 struct stat sb; 86 const mode_t newmode = 0644; 87 88 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 89 .WillRepeatedly(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 90 SET_OUT_HEADER_LEN(out, entry); 91 out.body.entry.attr.mode = S_IFREG | 0644; 92 out.body.entry.nodeid = ino; 93 out.body.entry.entry_valid = UINT64_MAX; 94 }))); 95 96 EXPECT_CALL(*m_mock, process( 97 ResultOf([](auto in) { 98 return (in.header.opcode == FUSE_SETATTR && 99 in.header.nodeid == ino); 100 }, Eq(true)), 101 _) 102 ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) { 103 SET_OUT_HEADER_LEN(out, attr); 104 out.body.attr.attr.ino = ino; // Must match nodeid 105 out.body.attr.attr.mode = S_IFREG | newmode; 106 out.body.attr.attr_valid = UINT64_MAX; 107 }))); 108 EXPECT_CALL(*m_mock, process( 109 ResultOf([](auto in) { 110 return (in.header.opcode == FUSE_GETATTR); 111 }, Eq(true)), 112 _) 113 ).Times(0); 114 115 /* Set an attribute with SETATTR */ 116 ASSERT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno); 117 118 /* The stat(2) should use cached attributes */ 119 ASSERT_EQ(0, stat(FULLPATH, &sb)); 120 EXPECT_EQ(S_IFREG | newmode, sb.st_mode); 121 } 122 123 /* Change the mode of a file */ 124 TEST_F(Setattr, chmod) 125 { 126 const char FULLPATH[] = "mountpoint/some_file.txt"; 127 const char RELPATH[] = "some_file.txt"; 128 const uint64_t ino = 42; 129 const mode_t oldmode = 0755; 130 const mode_t newmode = 0644; 131 132 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 133 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 134 SET_OUT_HEADER_LEN(out, entry); 135 out.body.entry.attr.mode = S_IFREG | oldmode; 136 out.body.entry.nodeid = ino; 137 }))); 138 139 EXPECT_CALL(*m_mock, process( 140 ResultOf([](auto in) { 141 uint32_t valid = FATTR_MODE; 142 return (in.header.opcode == FUSE_SETATTR && 143 in.header.nodeid == ino && 144 in.body.setattr.valid == valid && 145 in.body.setattr.mode == newmode); 146 }, Eq(true)), 147 _) 148 ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) { 149 SET_OUT_HEADER_LEN(out, attr); 150 out.body.attr.attr.ino = ino; // Must match nodeid 151 out.body.attr.attr.mode = S_IFREG | newmode; 152 }))); 153 EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno); 154 } 155 156 /* 157 * Chmod a multiply-linked file with cached attributes. Check that both files' 158 * attributes have changed. 159 */ 160 TEST_F(Setattr, chmod_multiply_linked) 161 { 162 const char FULLPATH0[] = "mountpoint/some_file.txt"; 163 const char RELPATH0[] = "some_file.txt"; 164 const char FULLPATH1[] = "mountpoint/other_file.txt"; 165 const char RELPATH1[] = "other_file.txt"; 166 struct stat sb; 167 const uint64_t ino = 42; 168 const mode_t oldmode = 0777; 169 const mode_t newmode = 0666; 170 171 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH0) 172 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 173 SET_OUT_HEADER_LEN(out, entry); 174 out.body.entry.attr.mode = S_IFREG | oldmode; 175 out.body.entry.nodeid = ino; 176 out.body.entry.attr.nlink = 2; 177 out.body.entry.attr_valid = UINT64_MAX; 178 out.body.entry.entry_valid = UINT64_MAX; 179 }))); 180 181 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH1) 182 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 183 SET_OUT_HEADER_LEN(out, entry); 184 out.body.entry.attr.mode = S_IFREG | oldmode; 185 out.body.entry.nodeid = ino; 186 out.body.entry.attr.nlink = 2; 187 out.body.entry.attr_valid = UINT64_MAX; 188 out.body.entry.entry_valid = UINT64_MAX; 189 }))); 190 191 EXPECT_CALL(*m_mock, process( 192 ResultOf([](auto in) { 193 uint32_t valid = FATTR_MODE; 194 return (in.header.opcode == FUSE_SETATTR && 195 in.header.nodeid == ino && 196 in.body.setattr.valid == valid && 197 in.body.setattr.mode == newmode); 198 }, Eq(true)), 199 _) 200 ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) { 201 SET_OUT_HEADER_LEN(out, attr); 202 out.body.attr.attr.ino = ino; 203 out.body.attr.attr.mode = S_IFREG | newmode; 204 out.body.attr.attr.nlink = 2; 205 out.body.attr.attr_valid = UINT64_MAX; 206 }))); 207 208 /* For a lookup of the 2nd file to get it into the cache*/ 209 ASSERT_EQ(0, stat(FULLPATH1, &sb)) << strerror(errno); 210 EXPECT_EQ(S_IFREG | oldmode, sb.st_mode); 211 212 ASSERT_EQ(0, chmod(FULLPATH0, newmode)) << strerror(errno); 213 ASSERT_EQ(0, stat(FULLPATH0, &sb)) << strerror(errno); 214 EXPECT_EQ(S_IFREG | newmode, sb.st_mode); 215 ASSERT_EQ(0, stat(FULLPATH1, &sb)) << strerror(errno); 216 EXPECT_EQ(S_IFREG | newmode, sb.st_mode); 217 } 218 219 220 /* Change the owner and group of a file */ 221 TEST_F(Setattr, chown) 222 { 223 const char FULLPATH[] = "mountpoint/some_file.txt"; 224 const char RELPATH[] = "some_file.txt"; 225 const uint64_t ino = 42; 226 const gid_t oldgroup = 66; 227 const gid_t newgroup = 99; 228 const uid_t olduser = 33; 229 const uid_t newuser = 44; 230 231 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 232 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 233 SET_OUT_HEADER_LEN(out, entry); 234 out.body.entry.attr.mode = S_IFREG | 0644; 235 out.body.entry.nodeid = ino; 236 out.body.entry.attr.gid = oldgroup; 237 out.body.entry.attr.uid = olduser; 238 }))); 239 240 EXPECT_CALL(*m_mock, process( 241 ResultOf([](auto in) { 242 uint32_t valid = FATTR_GID | FATTR_UID; 243 return (in.header.opcode == FUSE_SETATTR && 244 in.header.nodeid == ino && 245 in.body.setattr.valid == valid && 246 in.body.setattr.uid == newuser && 247 in.body.setattr.gid == newgroup); 248 }, Eq(true)), 249 _) 250 ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) { 251 SET_OUT_HEADER_LEN(out, attr); 252 out.body.attr.attr.ino = ino; // Must match nodeid 253 out.body.attr.attr.mode = S_IFREG | 0644; 254 out.body.attr.attr.uid = newuser; 255 out.body.attr.attr.gid = newgroup; 256 }))); 257 EXPECT_EQ(0, chown(FULLPATH, newuser, newgroup)) << strerror(errno); 258 } 259 260 261 262 /* 263 * FUSE daemons are allowed to check permissions however they like. If the 264 * daemon returns EPERM, even if the file permissions "should" grant access, 265 * then fuse(4) should return EPERM too. 266 */ 267 TEST_F(Setattr, eperm) 268 { 269 const char FULLPATH[] = "mountpoint/some_file.txt"; 270 const char RELPATH[] = "some_file.txt"; 271 const uint64_t ino = 42; 272 273 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 274 .WillOnce(Invoke(ReturnImmediate([=](auto in, auto& out) { 275 SET_OUT_HEADER_LEN(out, entry); 276 out.body.entry.attr.mode = S_IFREG | 0777; 277 out.body.entry.nodeid = ino; 278 out.body.entry.attr.uid = in.header.uid; 279 out.body.entry.attr.gid = in.header.gid; 280 }))); 281 282 EXPECT_CALL(*m_mock, process( 283 ResultOf([](auto in) { 284 return (in.header.opcode == FUSE_SETATTR && 285 in.header.nodeid == ino); 286 }, Eq(true)), 287 _) 288 ).WillOnce(Invoke(ReturnErrno(EPERM))); 289 EXPECT_NE(0, truncate(FULLPATH, 10)); 290 EXPECT_EQ(EPERM, errno); 291 } 292 293 /* Change the mode of an open file, by its file descriptor */ 294 TEST_F(Setattr, fchmod) 295 { 296 const char FULLPATH[] = "mountpoint/some_file.txt"; 297 const char RELPATH[] = "some_file.txt"; 298 uint64_t ino = 42; 299 int fd; 300 const mode_t oldmode = 0755; 301 const mode_t newmode = 0644; 302 303 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 304 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 305 SET_OUT_HEADER_LEN(out, entry); 306 out.body.entry.attr.mode = S_IFREG | oldmode; 307 out.body.entry.nodeid = ino; 308 out.body.entry.attr_valid = UINT64_MAX; 309 }))); 310 311 EXPECT_CALL(*m_mock, process( 312 ResultOf([=](auto in) { 313 return (in.header.opcode == FUSE_OPEN && 314 in.header.nodeid == ino); 315 }, Eq(true)), 316 _) 317 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 318 out.header.len = sizeof(out.header); 319 SET_OUT_HEADER_LEN(out, open); 320 }))); 321 322 EXPECT_CALL(*m_mock, process( 323 ResultOf([=](auto in) { 324 uint32_t valid = FATTR_MODE; 325 return (in.header.opcode == FUSE_SETATTR && 326 in.header.nodeid == ino && 327 in.body.setattr.valid == valid && 328 in.body.setattr.mode == newmode); 329 }, Eq(true)), 330 _) 331 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 332 SET_OUT_HEADER_LEN(out, attr); 333 out.body.attr.attr.ino = ino; // Must match nodeid 334 out.body.attr.attr.mode = S_IFREG | newmode; 335 }))); 336 337 fd = open(FULLPATH, O_RDONLY); 338 ASSERT_LE(0, fd) << strerror(errno); 339 ASSERT_EQ(0, fchmod(fd, newmode)) << strerror(errno); 340 leak(fd); 341 } 342 343 /* Change the size of an open file, by its file descriptor */ 344 TEST_F(Setattr, ftruncate) 345 { 346 const char FULLPATH[] = "mountpoint/some_file.txt"; 347 const char RELPATH[] = "some_file.txt"; 348 uint64_t ino = 42; 349 int fd; 350 uint64_t fh = 0xdeadbeef1a7ebabe; 351 const off_t oldsize = 99; 352 const off_t newsize = 12345; 353 354 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 355 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 356 SET_OUT_HEADER_LEN(out, entry); 357 out.body.entry.attr.mode = S_IFREG | 0755; 358 out.body.entry.nodeid = ino; 359 out.body.entry.attr_valid = UINT64_MAX; 360 out.body.entry.attr.size = oldsize; 361 }))); 362 363 EXPECT_CALL(*m_mock, process( 364 ResultOf([=](auto in) { 365 return (in.header.opcode == FUSE_OPEN && 366 in.header.nodeid == ino); 367 }, Eq(true)), 368 _) 369 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 370 out.header.len = sizeof(out.header); 371 SET_OUT_HEADER_LEN(out, open); 372 out.body.open.fh = fh; 373 }))); 374 375 EXPECT_CALL(*m_mock, process( 376 ResultOf([=](auto in) { 377 uint32_t valid = FATTR_SIZE | FATTR_FH; 378 return (in.header.opcode == FUSE_SETATTR && 379 in.header.nodeid == ino && 380 in.body.setattr.valid == valid && 381 in.body.setattr.fh == fh); 382 }, Eq(true)), 383 _) 384 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 385 SET_OUT_HEADER_LEN(out, attr); 386 out.body.attr.attr.ino = ino; // Must match nodeid 387 out.body.attr.attr.mode = S_IFREG | 0755; 388 out.body.attr.attr.size = newsize; 389 }))); 390 391 fd = open(FULLPATH, O_RDWR); 392 ASSERT_LE(0, fd) << strerror(errno); 393 ASSERT_EQ(0, ftruncate(fd, newsize)) << strerror(errno); 394 leak(fd); 395 } 396 397 /* Change the size of the file */ 398 TEST_F(Setattr, truncate) { 399 const char FULLPATH[] = "mountpoint/some_file.txt"; 400 const char RELPATH[] = "some_file.txt"; 401 const uint64_t ino = 42; 402 const uint64_t oldsize = 100'000'000; 403 const uint64_t newsize = 20'000'000; 404 405 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 406 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 407 SET_OUT_HEADER_LEN(out, entry); 408 out.body.entry.attr.mode = S_IFREG | 0644; 409 out.body.entry.nodeid = ino; 410 out.body.entry.attr.size = oldsize; 411 }))); 412 413 EXPECT_CALL(*m_mock, process( 414 ResultOf([](auto in) { 415 uint32_t valid = FATTR_SIZE; 416 return (in.header.opcode == FUSE_SETATTR && 417 in.header.nodeid == ino && 418 in.body.setattr.valid == valid && 419 in.body.setattr.size == newsize); 420 }, Eq(true)), 421 _) 422 ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) { 423 SET_OUT_HEADER_LEN(out, attr); 424 out.body.attr.attr.ino = ino; // Must match nodeid 425 out.body.attr.attr.mode = S_IFREG | 0644; 426 out.body.attr.attr.size = newsize; 427 }))); 428 EXPECT_EQ(0, truncate(FULLPATH, newsize)) << strerror(errno); 429 } 430 431 /* 432 * Truncating a file should discard cached data past the truncation point. 433 * This is a regression test for bug 233783. 434 * 435 * There are two distinct failure modes. The first one is a failure to zero 436 * the portion of the file's final buffer past EOF. It can be reproduced by 437 * fsx -WR -P /tmp -S10 fsx.bin 438 * 439 * The second is a failure to drop buffers beyond that. It can be reproduced by 440 * fsx -WR -P /tmp -S18 -n fsx.bin 441 * Also reproducible in sh with: 442 * $> /path/to/libfuse/build/example/passthrough -d /tmp/mnt 443 * $> cd /tmp/mnt/tmp 444 * $> dd if=/dev/random of=randfile bs=1k count=192 445 * $> truncate -s 1k randfile && truncate -s 192k randfile 446 * $> xxd randfile | less # xxd will wrongly show random data at offset 0x8000 447 */ 448 TEST_F(Setattr, truncate_discards_cached_data) { 449 const char FULLPATH[] = "mountpoint/some_file.txt"; 450 const char RELPATH[] = "some_file.txt"; 451 char *w0buf, *r0buf, *r1buf, *expected; 452 off_t w0_offset = 0; 453 size_t w0_size = 0x30000; 454 off_t r0_offset = 0; 455 off_t r0_size = w0_size; 456 size_t trunc0_size = 0x400; 457 size_t trunc1_size = w0_size; 458 off_t r1_offset = trunc0_size; 459 off_t r1_size = w0_size - trunc0_size; 460 size_t cur_size = 0; 461 const uint64_t ino = 42; 462 mode_t mode = S_IFREG | 0644; 463 int fd, r; 464 bool should_have_data = false; 465 466 w0buf = new char[w0_size]; 467 memset(w0buf, 'X', w0_size); 468 469 r0buf = new char[r0_size]; 470 r1buf = new char[r1_size]; 471 472 expected = new char[r1_size](); 473 474 expect_lookup(RELPATH, ino, mode, 0, 1); 475 expect_open(ino, O_RDWR, 1); 476 EXPECT_CALL(*m_mock, process( 477 ResultOf([=](auto in) { 478 return (in.header.opcode == FUSE_GETATTR && 479 in.header.nodeid == ino); 480 }, Eq(true)), 481 _) 482 ).WillRepeatedly(Invoke(ReturnImmediate([&](auto i __unused, auto& out) { 483 SET_OUT_HEADER_LEN(out, attr); 484 out.body.attr.attr.ino = ino; 485 out.body.attr.attr.mode = mode; 486 out.body.attr.attr.size = cur_size; 487 }))); 488 EXPECT_CALL(*m_mock, process( 489 ResultOf([=](auto in) { 490 return (in.header.opcode == FUSE_WRITE); 491 }, Eq(true)), 492 _) 493 ).WillRepeatedly(Invoke(ReturnImmediate([&](auto in, auto& out) { 494 SET_OUT_HEADER_LEN(out, write); 495 out.body.attr.attr.ino = ino; 496 out.body.write.size = in.body.write.size; 497 cur_size = std::max(static_cast<uint64_t>(cur_size), 498 in.body.write.size + in.body.write.offset); 499 }))); 500 501 EXPECT_CALL(*m_mock, process( 502 ResultOf([=](auto in) { 503 return (in.header.opcode == FUSE_SETATTR && 504 in.header.nodeid == ino && 505 (in.body.setattr.valid & FATTR_SIZE)); 506 }, Eq(true)), 507 _) 508 ).WillRepeatedly(Invoke(ReturnImmediate([&](auto in, auto& out) { 509 auto trunc_size = in.body.setattr.size; 510 SET_OUT_HEADER_LEN(out, attr); 511 out.body.attr.attr.ino = ino; 512 out.body.attr.attr.mode = mode; 513 out.body.attr.attr.size = trunc_size; 514 cur_size = trunc_size; 515 }))); 516 517 EXPECT_CALL(*m_mock, process( 518 ResultOf([=](auto in) { 519 return (in.header.opcode == FUSE_READ); 520 }, Eq(true)), 521 _) 522 ).WillRepeatedly(Invoke(ReturnImmediate([&](auto in, auto& out) { 523 auto osize = std::min( 524 static_cast<uint64_t>(cur_size) - in.body.read.offset, 525 static_cast<uint64_t>(in.body.read.size)); 526 assert(osize <= sizeof(out.body.bytes)); 527 out.header.len = sizeof(struct fuse_out_header) + osize; 528 if (should_have_data) 529 memset(out.body.bytes, 'X', osize); 530 else 531 bzero(out.body.bytes, osize); 532 }))); 533 534 fd = open(FULLPATH, O_RDWR, 0644); 535 ASSERT_LE(0, fd) << strerror(errno); 536 537 /* Fill the file with Xs */ 538 ASSERT_EQ(static_cast<ssize_t>(w0_size), 539 pwrite(fd, w0buf, w0_size, w0_offset)); 540 should_have_data = true; 541 /* Fill the cache */ 542 ASSERT_EQ(static_cast<ssize_t>(r0_size), 543 pread(fd, r0buf, r0_size, r0_offset)); 544 /* 1st truncate should discard cached data */ 545 EXPECT_EQ(0, ftruncate(fd, trunc0_size)) << strerror(errno); 546 should_have_data = false; 547 /* 2nd truncate extends file into previously cached data */ 548 EXPECT_EQ(0, ftruncate(fd, trunc1_size)) << strerror(errno); 549 /* Read should return all zeros */ 550 ASSERT_EQ(static_cast<ssize_t>(r1_size), 551 pread(fd, r1buf, r1_size, r1_offset)); 552 553 r = memcmp(expected, r1buf, r1_size); 554 ASSERT_EQ(0, r); 555 556 delete[] expected; 557 delete[] r1buf; 558 delete[] r0buf; 559 delete[] w0buf; 560 561 leak(fd); 562 } 563 564 /* truncate should fail if it would cause the file to exceed RLIMIT_FSIZE */ 565 TEST_F(Setattr, truncate_rlimit_rsize) 566 { 567 const char FULLPATH[] = "mountpoint/some_file.txt"; 568 const char RELPATH[] = "some_file.txt"; 569 struct rlimit rl; 570 const uint64_t ino = 42; 571 const uint64_t oldsize = 0; 572 const uint64_t newsize = 100'000'000; 573 574 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 575 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 576 SET_OUT_HEADER_LEN(out, entry); 577 out.body.entry.attr.mode = S_IFREG | 0644; 578 out.body.entry.nodeid = ino; 579 out.body.entry.attr.size = oldsize; 580 }))); 581 582 rl.rlim_cur = newsize / 2; 583 rl.rlim_max = 10 * newsize; 584 ASSERT_EQ(0, setrlimit(RLIMIT_FSIZE, &rl)) << strerror(errno); 585 ASSERT_NE(SIG_ERR, signal(SIGXFSZ, sigxfsz_handler)) << strerror(errno); 586 587 EXPECT_EQ(-1, truncate(FULLPATH, newsize)); 588 EXPECT_EQ(EFBIG, errno); 589 EXPECT_EQ(1, s_sigxfsz); 590 } 591 592 /* Change a file's timestamps */ 593 TEST_F(Setattr, utimensat) { 594 const char FULLPATH[] = "mountpoint/some_file.txt"; 595 const char RELPATH[] = "some_file.txt"; 596 const uint64_t ino = 42; 597 const timespec oldtimes[2] = { 598 {.tv_sec = 1, .tv_nsec = 2}, 599 {.tv_sec = 3, .tv_nsec = 4}, 600 }; 601 const timespec newtimes[2] = { 602 {.tv_sec = 5, .tv_nsec = 6}, 603 {.tv_sec = 7, .tv_nsec = 8}, 604 }; 605 606 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 607 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 608 SET_OUT_HEADER_LEN(out, entry); 609 out.body.entry.attr.mode = S_IFREG | 0644; 610 out.body.entry.nodeid = ino; 611 out.body.entry.attr_valid = UINT64_MAX; 612 out.body.entry.attr.atime = oldtimes[0].tv_sec; 613 out.body.entry.attr.atimensec = oldtimes[0].tv_nsec; 614 out.body.entry.attr.mtime = oldtimes[1].tv_sec; 615 out.body.entry.attr.mtimensec = oldtimes[1].tv_nsec; 616 }))); 617 618 EXPECT_CALL(*m_mock, process( 619 ResultOf([=](auto in) { 620 uint32_t valid = FATTR_ATIME | FATTR_MTIME; 621 return (in.header.opcode == FUSE_SETATTR && 622 in.header.nodeid == ino && 623 in.body.setattr.valid == valid && 624 (time_t)in.body.setattr.atime == 625 newtimes[0].tv_sec && 626 (long)in.body.setattr.atimensec == 627 newtimes[0].tv_nsec && 628 (time_t)in.body.setattr.mtime == 629 newtimes[1].tv_sec && 630 (long)in.body.setattr.mtimensec == 631 newtimes[1].tv_nsec); 632 }, Eq(true)), 633 _) 634 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 635 SET_OUT_HEADER_LEN(out, attr); 636 out.body.attr.attr.ino = ino; // Must match nodeid 637 out.body.attr.attr.mode = S_IFREG | 0644; 638 out.body.attr.attr.atime = newtimes[0].tv_sec; 639 out.body.attr.attr.atimensec = newtimes[0].tv_nsec; 640 out.body.attr.attr.mtime = newtimes[1].tv_sec; 641 out.body.attr.attr.mtimensec = newtimes[1].tv_nsec; 642 }))); 643 EXPECT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &newtimes[0], 0)) 644 << strerror(errno); 645 } 646 647 /* Change a file mtime but not its atime */ 648 TEST_F(Setattr, utimensat_mtime_only) { 649 const char FULLPATH[] = "mountpoint/some_file.txt"; 650 const char RELPATH[] = "some_file.txt"; 651 const uint64_t ino = 42; 652 const timespec oldtimes[2] = { 653 {.tv_sec = 1, .tv_nsec = 2}, 654 {.tv_sec = 3, .tv_nsec = 4}, 655 }; 656 const timespec newtimes[2] = { 657 {.tv_sec = 5, .tv_nsec = UTIME_OMIT}, 658 {.tv_sec = 7, .tv_nsec = 8}, 659 }; 660 661 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 662 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 663 SET_OUT_HEADER_LEN(out, entry); 664 out.body.entry.attr.mode = S_IFREG | 0644; 665 out.body.entry.nodeid = ino; 666 out.body.entry.attr_valid = UINT64_MAX; 667 out.body.entry.attr.atime = oldtimes[0].tv_sec; 668 out.body.entry.attr.atimensec = oldtimes[0].tv_nsec; 669 out.body.entry.attr.mtime = oldtimes[1].tv_sec; 670 out.body.entry.attr.mtimensec = oldtimes[1].tv_nsec; 671 }))); 672 673 EXPECT_CALL(*m_mock, process( 674 ResultOf([=](auto in) { 675 uint32_t valid = FATTR_MTIME; 676 return (in.header.opcode == FUSE_SETATTR && 677 in.header.nodeid == ino && 678 in.body.setattr.valid == valid && 679 (time_t)in.body.setattr.mtime == 680 newtimes[1].tv_sec && 681 (long)in.body.setattr.mtimensec == 682 newtimes[1].tv_nsec); 683 }, Eq(true)), 684 _) 685 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 686 SET_OUT_HEADER_LEN(out, attr); 687 out.body.attr.attr.ino = ino; // Must match nodeid 688 out.body.attr.attr.mode = S_IFREG | 0644; 689 out.body.attr.attr.atime = oldtimes[0].tv_sec; 690 out.body.attr.attr.atimensec = oldtimes[0].tv_nsec; 691 out.body.attr.attr.mtime = newtimes[1].tv_sec; 692 out.body.attr.attr.mtimensec = newtimes[1].tv_nsec; 693 }))); 694 EXPECT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &newtimes[0], 0)) 695 << strerror(errno); 696 } 697 698 /* 699 * Set a file's mtime and atime to now 700 * 701 * The design of FreeBSD's VFS does not allow fusefs to set just one of atime 702 * or mtime to UTIME_NOW; it's both or neither. 703 */ 704 TEST_F(Setattr, utimensat_utime_now) { 705 const char FULLPATH[] = "mountpoint/some_file.txt"; 706 const char RELPATH[] = "some_file.txt"; 707 const uint64_t ino = 42; 708 const timespec oldtimes[2] = { 709 {.tv_sec = 1, .tv_nsec = 2}, 710 {.tv_sec = 3, .tv_nsec = 4}, 711 }; 712 const timespec newtimes[2] = { 713 {.tv_sec = 0, .tv_nsec = UTIME_NOW}, 714 {.tv_sec = 0, .tv_nsec = UTIME_NOW}, 715 }; 716 /* "now" is whatever the server says it is */ 717 const timespec now[2] = { 718 {.tv_sec = 5, .tv_nsec = 7}, 719 {.tv_sec = 6, .tv_nsec = 8}, 720 }; 721 struct stat sb; 722 723 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 724 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 725 SET_OUT_HEADER_LEN(out, entry); 726 out.body.entry.attr.mode = S_IFREG | 0644; 727 out.body.entry.nodeid = ino; 728 out.body.entry.attr_valid = UINT64_MAX; 729 out.body.entry.entry_valid = UINT64_MAX; 730 out.body.entry.attr.atime = oldtimes[0].tv_sec; 731 out.body.entry.attr.atimensec = oldtimes[0].tv_nsec; 732 out.body.entry.attr.mtime = oldtimes[1].tv_sec; 733 out.body.entry.attr.mtimensec = oldtimes[1].tv_nsec; 734 }))); 735 736 EXPECT_CALL(*m_mock, process( 737 ResultOf([=](auto in) { 738 uint32_t valid = FATTR_ATIME | FATTR_ATIME_NOW | 739 FATTR_MTIME | FATTR_MTIME_NOW; 740 return (in.header.opcode == FUSE_SETATTR && 741 in.header.nodeid == ino && 742 in.body.setattr.valid == valid); 743 }, Eq(true)), 744 _) 745 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 746 SET_OUT_HEADER_LEN(out, attr); 747 out.body.attr.attr.ino = ino; // Must match nodeid 748 out.body.attr.attr.mode = S_IFREG | 0644; 749 out.body.attr.attr.atime = now[0].tv_sec; 750 out.body.attr.attr.atimensec = now[0].tv_nsec; 751 out.body.attr.attr.mtime = now[1].tv_sec; 752 out.body.attr.attr.mtimensec = now[1].tv_nsec; 753 out.body.attr.attr_valid = UINT64_MAX; 754 }))); 755 ASSERT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &newtimes[0], 0)) 756 << strerror(errno); 757 ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno); 758 EXPECT_EQ(now[0].tv_sec, sb.st_atim.tv_sec); 759 EXPECT_EQ(now[0].tv_nsec, sb.st_atim.tv_nsec); 760 EXPECT_EQ(now[1].tv_sec, sb.st_mtim.tv_sec); 761 EXPECT_EQ(now[1].tv_nsec, sb.st_mtim.tv_nsec); 762 } 763 764 /* 765 * FUSE_SETATTR returns a different file type, even though the entry cache 766 * hasn't expired. This is a server bug! It probably means that the server 767 * removed the file and recreated it with the same inode but a different vtyp. 768 * The best thing fusefs can do is return ENOENT to the caller. After all, the 769 * entry must not have existed recently. 770 */ 771 TEST_F(Setattr, vtyp_conflict) 772 { 773 const char FULLPATH[] = "mountpoint/some_file.txt"; 774 const char RELPATH[] = "some_file.txt"; 775 const uint64_t ino = 42; 776 uid_t newuser = 12345; 777 sem_t sem; 778 779 ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno); 780 781 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 782 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 783 SET_OUT_HEADER_LEN(out, entry); 784 out.body.entry.attr.mode = S_IFREG | 0777; 785 out.body.entry.nodeid = ino; 786 out.body.entry.entry_valid = UINT64_MAX; 787 }))); 788 789 EXPECT_CALL(*m_mock, process( 790 ResultOf([](auto in) { 791 return (in.header.opcode == FUSE_SETATTR && 792 in.header.nodeid == ino); 793 }, Eq(true)), 794 _) 795 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 796 SET_OUT_HEADER_LEN(out, attr); 797 out.body.attr.attr.ino = ino; 798 out.body.attr.attr.mode = S_IFDIR | 0777; // Changed! 799 out.body.attr.attr.uid = newuser; 800 }))); 801 // We should reclaim stale vnodes 802 expect_forget(ino, 1, &sem); 803 804 EXPECT_NE(0, chown(FULLPATH, newuser, -1)); 805 EXPECT_EQ(ENOENT, errno); 806 807 sem_wait(&sem); 808 sem_destroy(&sem); 809 } 810 811 /* On a read-only mount, no attributes may be changed */ 812 TEST_F(RofsSetattr, erofs) 813 { 814 const char FULLPATH[] = "mountpoint/some_file.txt"; 815 const char RELPATH[] = "some_file.txt"; 816 const uint64_t ino = 42; 817 const mode_t oldmode = 0755; 818 const mode_t newmode = 0644; 819 820 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 821 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 822 SET_OUT_HEADER_LEN(out, entry); 823 out.body.entry.attr.mode = S_IFREG | oldmode; 824 out.body.entry.nodeid = ino; 825 }))); 826 827 ASSERT_EQ(-1, chmod(FULLPATH, newmode)); 828 ASSERT_EQ(EROFS, errno); 829 } 830 831 /* Change the mode of a file */ 832 TEST_F(Setattr_7_8, chmod) 833 { 834 const char FULLPATH[] = "mountpoint/some_file.txt"; 835 const char RELPATH[] = "some_file.txt"; 836 const uint64_t ino = 42; 837 const mode_t oldmode = 0755; 838 const mode_t newmode = 0644; 839 840 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 841 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 842 SET_OUT_HEADER_LEN(out, entry_7_8); 843 out.body.entry.attr.mode = S_IFREG | oldmode; 844 out.body.entry.nodeid = ino; 845 }))); 846 847 EXPECT_CALL(*m_mock, process( 848 ResultOf([](auto in) { 849 uint32_t valid = FATTR_MODE; 850 return (in.header.opcode == FUSE_SETATTR && 851 in.header.nodeid == ino && 852 in.body.setattr.valid == valid && 853 in.body.setattr.mode == newmode); 854 }, Eq(true)), 855 _) 856 ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) { 857 SET_OUT_HEADER_LEN(out, attr_7_8); 858 out.body.attr.attr.ino = ino; // Must match nodeid 859 out.body.attr.attr.mode = S_IFREG | newmode; 860 }))); 861 EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno); 862 } 863