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 void *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 = malloc(w0_size); 467 ASSERT_NE(nullptr, w0buf) << strerror(errno); 468 memset(w0buf, 'X', w0_size); 469 470 r0buf = malloc(r0_size); 471 ASSERT_NE(nullptr, r0buf) << strerror(errno); 472 r1buf = malloc(r1_size); 473 ASSERT_NE(nullptr, r1buf) << strerror(errno); 474 475 expected = malloc(r1_size); 476 ASSERT_NE(nullptr, expected) << strerror(errno); 477 memset(expected, 0, r1_size); 478 479 expect_lookup(RELPATH, ino, mode, 0, 1); 480 expect_open(ino, O_RDWR, 1); 481 EXPECT_CALL(*m_mock, process( 482 ResultOf([=](auto in) { 483 return (in.header.opcode == FUSE_GETATTR && 484 in.header.nodeid == ino); 485 }, Eq(true)), 486 _) 487 ).WillRepeatedly(Invoke(ReturnImmediate([&](auto i __unused, auto& out) { 488 SET_OUT_HEADER_LEN(out, attr); 489 out.body.attr.attr.ino = ino; 490 out.body.attr.attr.mode = mode; 491 out.body.attr.attr.size = cur_size; 492 }))); 493 EXPECT_CALL(*m_mock, process( 494 ResultOf([=](auto in) { 495 return (in.header.opcode == FUSE_WRITE); 496 }, Eq(true)), 497 _) 498 ).WillRepeatedly(Invoke(ReturnImmediate([&](auto in, auto& out) { 499 SET_OUT_HEADER_LEN(out, write); 500 out.body.attr.attr.ino = ino; 501 out.body.write.size = in.body.write.size; 502 cur_size = std::max(static_cast<uint64_t>(cur_size), 503 in.body.write.size + in.body.write.offset); 504 }))); 505 506 EXPECT_CALL(*m_mock, process( 507 ResultOf([=](auto in) { 508 return (in.header.opcode == FUSE_SETATTR && 509 in.header.nodeid == ino && 510 (in.body.setattr.valid & FATTR_SIZE)); 511 }, Eq(true)), 512 _) 513 ).WillRepeatedly(Invoke(ReturnImmediate([&](auto in, auto& out) { 514 auto trunc_size = in.body.setattr.size; 515 SET_OUT_HEADER_LEN(out, attr); 516 out.body.attr.attr.ino = ino; 517 out.body.attr.attr.mode = mode; 518 out.body.attr.attr.size = trunc_size; 519 cur_size = trunc_size; 520 }))); 521 522 EXPECT_CALL(*m_mock, process( 523 ResultOf([=](auto in) { 524 return (in.header.opcode == FUSE_READ); 525 }, Eq(true)), 526 _) 527 ).WillRepeatedly(Invoke(ReturnImmediate([&](auto in, auto& out) { 528 auto osize = std::min( 529 static_cast<uint64_t>(cur_size) - in.body.read.offset, 530 static_cast<uint64_t>(in.body.read.size)); 531 assert(osize <= sizeof(out.body.bytes)); 532 out.header.len = sizeof(struct fuse_out_header) + osize; 533 if (should_have_data) 534 memset(out.body.bytes, 'X', osize); 535 else 536 bzero(out.body.bytes, osize); 537 }))); 538 539 fd = open(FULLPATH, O_RDWR, 0644); 540 ASSERT_LE(0, fd) << strerror(errno); 541 542 /* Fill the file with Xs */ 543 ASSERT_EQ(static_cast<ssize_t>(w0_size), 544 pwrite(fd, w0buf, w0_size, w0_offset)); 545 should_have_data = true; 546 /* Fill the cache */ 547 ASSERT_EQ(static_cast<ssize_t>(r0_size), 548 pread(fd, r0buf, r0_size, r0_offset)); 549 /* 1st truncate should discard cached data */ 550 EXPECT_EQ(0, ftruncate(fd, trunc0_size)) << strerror(errno); 551 should_have_data = false; 552 /* 2nd truncate extends file into previously cached data */ 553 EXPECT_EQ(0, ftruncate(fd, trunc1_size)) << strerror(errno); 554 /* Read should return all zeros */ 555 ASSERT_EQ(static_cast<ssize_t>(r1_size), 556 pread(fd, r1buf, r1_size, r1_offset)); 557 558 r = memcmp(expected, r1buf, r1_size); 559 ASSERT_EQ(0, r); 560 561 free(expected); 562 free(r1buf); 563 free(r0buf); 564 free(w0buf); 565 566 leak(fd); 567 } 568 569 /* truncate should fail if it would cause the file to exceed RLIMIT_FSIZE */ 570 TEST_F(Setattr, truncate_rlimit_rsize) 571 { 572 const char FULLPATH[] = "mountpoint/some_file.txt"; 573 const char RELPATH[] = "some_file.txt"; 574 struct rlimit rl; 575 const uint64_t ino = 42; 576 const uint64_t oldsize = 0; 577 const uint64_t newsize = 100'000'000; 578 579 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 580 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 581 SET_OUT_HEADER_LEN(out, entry); 582 out.body.entry.attr.mode = S_IFREG | 0644; 583 out.body.entry.nodeid = ino; 584 out.body.entry.attr.size = oldsize; 585 }))); 586 587 rl.rlim_cur = newsize / 2; 588 rl.rlim_max = 10 * newsize; 589 ASSERT_EQ(0, setrlimit(RLIMIT_FSIZE, &rl)) << strerror(errno); 590 ASSERT_NE(SIG_ERR, signal(SIGXFSZ, sigxfsz_handler)) << strerror(errno); 591 592 EXPECT_EQ(-1, truncate(FULLPATH, newsize)); 593 EXPECT_EQ(EFBIG, errno); 594 EXPECT_EQ(1, s_sigxfsz); 595 } 596 597 /* Change a file's timestamps */ 598 TEST_F(Setattr, utimensat) { 599 const char FULLPATH[] = "mountpoint/some_file.txt"; 600 const char RELPATH[] = "some_file.txt"; 601 const uint64_t ino = 42; 602 const timespec oldtimes[2] = { 603 {.tv_sec = 1, .tv_nsec = 2}, 604 {.tv_sec = 3, .tv_nsec = 4}, 605 }; 606 const timespec newtimes[2] = { 607 {.tv_sec = 5, .tv_nsec = 6}, 608 {.tv_sec = 7, .tv_nsec = 8}, 609 }; 610 611 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 612 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 613 SET_OUT_HEADER_LEN(out, entry); 614 out.body.entry.attr.mode = S_IFREG | 0644; 615 out.body.entry.nodeid = ino; 616 out.body.entry.attr_valid = UINT64_MAX; 617 out.body.entry.attr.atime = oldtimes[0].tv_sec; 618 out.body.entry.attr.atimensec = oldtimes[0].tv_nsec; 619 out.body.entry.attr.mtime = oldtimes[1].tv_sec; 620 out.body.entry.attr.mtimensec = oldtimes[1].tv_nsec; 621 }))); 622 623 EXPECT_CALL(*m_mock, process( 624 ResultOf([=](auto in) { 625 uint32_t valid = FATTR_ATIME | FATTR_MTIME; 626 return (in.header.opcode == FUSE_SETATTR && 627 in.header.nodeid == ino && 628 in.body.setattr.valid == valid && 629 (time_t)in.body.setattr.atime == 630 newtimes[0].tv_sec && 631 (long)in.body.setattr.atimensec == 632 newtimes[0].tv_nsec && 633 (time_t)in.body.setattr.mtime == 634 newtimes[1].tv_sec && 635 (long)in.body.setattr.mtimensec == 636 newtimes[1].tv_nsec); 637 }, Eq(true)), 638 _) 639 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 640 SET_OUT_HEADER_LEN(out, attr); 641 out.body.attr.attr.ino = ino; // Must match nodeid 642 out.body.attr.attr.mode = S_IFREG | 0644; 643 out.body.attr.attr.atime = newtimes[0].tv_sec; 644 out.body.attr.attr.atimensec = newtimes[0].tv_nsec; 645 out.body.attr.attr.mtime = newtimes[1].tv_sec; 646 out.body.attr.attr.mtimensec = newtimes[1].tv_nsec; 647 }))); 648 EXPECT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &newtimes[0], 0)) 649 << strerror(errno); 650 } 651 652 /* Change a file mtime but not its atime */ 653 TEST_F(Setattr, utimensat_mtime_only) { 654 const char FULLPATH[] = "mountpoint/some_file.txt"; 655 const char RELPATH[] = "some_file.txt"; 656 const uint64_t ino = 42; 657 const timespec oldtimes[2] = { 658 {.tv_sec = 1, .tv_nsec = 2}, 659 {.tv_sec = 3, .tv_nsec = 4}, 660 }; 661 const timespec newtimes[2] = { 662 {.tv_sec = 5, .tv_nsec = UTIME_OMIT}, 663 {.tv_sec = 7, .tv_nsec = 8}, 664 }; 665 666 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 667 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 668 SET_OUT_HEADER_LEN(out, entry); 669 out.body.entry.attr.mode = S_IFREG | 0644; 670 out.body.entry.nodeid = ino; 671 out.body.entry.attr_valid = UINT64_MAX; 672 out.body.entry.attr.atime = oldtimes[0].tv_sec; 673 out.body.entry.attr.atimensec = oldtimes[0].tv_nsec; 674 out.body.entry.attr.mtime = oldtimes[1].tv_sec; 675 out.body.entry.attr.mtimensec = oldtimes[1].tv_nsec; 676 }))); 677 678 EXPECT_CALL(*m_mock, process( 679 ResultOf([=](auto in) { 680 uint32_t valid = FATTR_MTIME; 681 return (in.header.opcode == FUSE_SETATTR && 682 in.header.nodeid == ino && 683 in.body.setattr.valid == valid && 684 (time_t)in.body.setattr.mtime == 685 newtimes[1].tv_sec && 686 (long)in.body.setattr.mtimensec == 687 newtimes[1].tv_nsec); 688 }, Eq(true)), 689 _) 690 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 691 SET_OUT_HEADER_LEN(out, attr); 692 out.body.attr.attr.ino = ino; // Must match nodeid 693 out.body.attr.attr.mode = S_IFREG | 0644; 694 out.body.attr.attr.atime = oldtimes[0].tv_sec; 695 out.body.attr.attr.atimensec = oldtimes[0].tv_nsec; 696 out.body.attr.attr.mtime = newtimes[1].tv_sec; 697 out.body.attr.attr.mtimensec = newtimes[1].tv_nsec; 698 }))); 699 EXPECT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &newtimes[0], 0)) 700 << strerror(errno); 701 } 702 703 /* 704 * Set a file's mtime and atime to now 705 * 706 * The design of FreeBSD's VFS does not allow fusefs to set just one of atime 707 * or mtime to UTIME_NOW; it's both or neither. 708 */ 709 TEST_F(Setattr, utimensat_utime_now) { 710 const char FULLPATH[] = "mountpoint/some_file.txt"; 711 const char RELPATH[] = "some_file.txt"; 712 const uint64_t ino = 42; 713 const timespec oldtimes[2] = { 714 {.tv_sec = 1, .tv_nsec = 2}, 715 {.tv_sec = 3, .tv_nsec = 4}, 716 }; 717 const timespec newtimes[2] = { 718 {.tv_sec = 0, .tv_nsec = UTIME_NOW}, 719 {.tv_sec = 0, .tv_nsec = UTIME_NOW}, 720 }; 721 /* "now" is whatever the server says it is */ 722 const timespec now[2] = { 723 {.tv_sec = 5, .tv_nsec = 7}, 724 {.tv_sec = 6, .tv_nsec = 8}, 725 }; 726 struct stat sb; 727 728 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 729 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 730 SET_OUT_HEADER_LEN(out, entry); 731 out.body.entry.attr.mode = S_IFREG | 0644; 732 out.body.entry.nodeid = ino; 733 out.body.entry.attr_valid = UINT64_MAX; 734 out.body.entry.entry_valid = UINT64_MAX; 735 out.body.entry.attr.atime = oldtimes[0].tv_sec; 736 out.body.entry.attr.atimensec = oldtimes[0].tv_nsec; 737 out.body.entry.attr.mtime = oldtimes[1].tv_sec; 738 out.body.entry.attr.mtimensec = oldtimes[1].tv_nsec; 739 }))); 740 741 EXPECT_CALL(*m_mock, process( 742 ResultOf([=](auto in) { 743 uint32_t valid = FATTR_ATIME | FATTR_ATIME_NOW | 744 FATTR_MTIME | FATTR_MTIME_NOW; 745 return (in.header.opcode == FUSE_SETATTR && 746 in.header.nodeid == ino && 747 in.body.setattr.valid == valid); 748 }, Eq(true)), 749 _) 750 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 751 SET_OUT_HEADER_LEN(out, attr); 752 out.body.attr.attr.ino = ino; // Must match nodeid 753 out.body.attr.attr.mode = S_IFREG | 0644; 754 out.body.attr.attr.atime = now[0].tv_sec; 755 out.body.attr.attr.atimensec = now[0].tv_nsec; 756 out.body.attr.attr.mtime = now[1].tv_sec; 757 out.body.attr.attr.mtimensec = now[1].tv_nsec; 758 out.body.attr.attr_valid = UINT64_MAX; 759 }))); 760 ASSERT_EQ(0, utimensat(AT_FDCWD, FULLPATH, &newtimes[0], 0)) 761 << strerror(errno); 762 ASSERT_EQ(0, stat(FULLPATH, &sb)) << strerror(errno); 763 EXPECT_EQ(now[0].tv_sec, sb.st_atim.tv_sec); 764 EXPECT_EQ(now[0].tv_nsec, sb.st_atim.tv_nsec); 765 EXPECT_EQ(now[1].tv_sec, sb.st_mtim.tv_sec); 766 EXPECT_EQ(now[1].tv_nsec, sb.st_mtim.tv_nsec); 767 } 768 769 /* 770 * FUSE_SETATTR returns a different file type, even though the entry cache 771 * hasn't expired. This is a server bug! It probably means that the server 772 * removed the file and recreated it with the same inode but a different vtyp. 773 * The best thing fusefs can do is return ENOENT to the caller. After all, the 774 * entry must not have existed recently. 775 */ 776 TEST_F(Setattr, vtyp_conflict) 777 { 778 const char FULLPATH[] = "mountpoint/some_file.txt"; 779 const char RELPATH[] = "some_file.txt"; 780 const uint64_t ino = 42; 781 uid_t newuser = 12345; 782 sem_t sem; 783 784 ASSERT_EQ(0, sem_init(&sem, 0, 0)) << strerror(errno); 785 786 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 787 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 788 SET_OUT_HEADER_LEN(out, entry); 789 out.body.entry.attr.mode = S_IFREG | 0777; 790 out.body.entry.nodeid = ino; 791 out.body.entry.entry_valid = UINT64_MAX; 792 }))); 793 794 EXPECT_CALL(*m_mock, process( 795 ResultOf([](auto in) { 796 return (in.header.opcode == FUSE_SETATTR && 797 in.header.nodeid == ino); 798 }, Eq(true)), 799 _) 800 ).WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 801 SET_OUT_HEADER_LEN(out, attr); 802 out.body.attr.attr.ino = ino; 803 out.body.attr.attr.mode = S_IFDIR | 0777; // Changed! 804 out.body.attr.attr.uid = newuser; 805 }))); 806 // We should reclaim stale vnodes 807 expect_forget(ino, 1, &sem); 808 809 EXPECT_NE(0, chown(FULLPATH, newuser, -1)); 810 EXPECT_EQ(ENOENT, errno); 811 812 sem_wait(&sem); 813 sem_destroy(&sem); 814 } 815 816 /* On a read-only mount, no attributes may be changed */ 817 TEST_F(RofsSetattr, erofs) 818 { 819 const char FULLPATH[] = "mountpoint/some_file.txt"; 820 const char RELPATH[] = "some_file.txt"; 821 const uint64_t ino = 42; 822 const mode_t oldmode = 0755; 823 const mode_t newmode = 0644; 824 825 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 826 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 827 SET_OUT_HEADER_LEN(out, entry); 828 out.body.entry.attr.mode = S_IFREG | oldmode; 829 out.body.entry.nodeid = ino; 830 }))); 831 832 ASSERT_EQ(-1, chmod(FULLPATH, newmode)); 833 ASSERT_EQ(EROFS, errno); 834 } 835 836 /* Change the mode of a file */ 837 TEST_F(Setattr_7_8, chmod) 838 { 839 const char FULLPATH[] = "mountpoint/some_file.txt"; 840 const char RELPATH[] = "some_file.txt"; 841 const uint64_t ino = 42; 842 const mode_t oldmode = 0755; 843 const mode_t newmode = 0644; 844 845 EXPECT_LOOKUP(FUSE_ROOT_ID, RELPATH) 846 .WillOnce(Invoke(ReturnImmediate([=](auto in __unused, auto& out) { 847 SET_OUT_HEADER_LEN(out, entry_7_8); 848 out.body.entry.attr.mode = S_IFREG | oldmode; 849 out.body.entry.nodeid = ino; 850 }))); 851 852 EXPECT_CALL(*m_mock, process( 853 ResultOf([](auto in) { 854 uint32_t valid = FATTR_MODE; 855 return (in.header.opcode == FUSE_SETATTR && 856 in.header.nodeid == ino && 857 in.body.setattr.valid == valid && 858 in.body.setattr.mode == newmode); 859 }, Eq(true)), 860 _) 861 ).WillOnce(Invoke(ReturnImmediate([](auto in __unused, auto& out) { 862 SET_OUT_HEADER_LEN(out, attr_7_8); 863 out.body.attr.attr.ino = ino; // Must match nodeid 864 out.body.attr.attr.mode = S_IFREG | newmode; 865 }))); 866 EXPECT_EQ(0, chmod(FULLPATH, newmode)) << strerror(errno); 867 } 868