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