|
20 | 20 | #include <unistd.h> |
21 | 21 |
|
22 | 22 | #include <cstdlib> |
| 23 | +#include <ctime> |
23 | 24 | #include <memory> |
24 | 25 |
|
25 | 26 | #include "gmock/gmock.h" |
26 | 27 | #include "gtest/gtest.h" |
27 | 28 | #include "absl/memory/memory.h" |
28 | 29 | #include "absl/strings/string_view.h" |
| 30 | +#include "absl/time/clock.h" |
| 31 | +#include "absl/time/time.h" |
29 | 32 | #include "test/syscalls/linux/file_base.h" |
30 | 33 | #include "test/util/capability_util.h" |
31 | 34 | #include "test/util/cleanup.h" |
@@ -542,6 +545,122 @@ TEST_F(OpenTest, OPathWithODirectory) { |
542 | 545 | SyscallFailsWithErrno(ENOTDIR)); |
543 | 546 | } |
544 | 547 |
|
| 548 | +// Truncate operations that don't change file size should still update mtime |
| 549 | +// and ctime. Tests truncation via open(O_TRUNC), ftruncate(2), and truncate(2). |
| 550 | +TEST_F(OpenTest, TruncateNoSizeChangeUpdatesTimestamps) { |
| 551 | + // Create three zero-length files and record their initial timestamps. |
| 552 | + auto path1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); |
| 553 | + auto path2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); |
| 554 | + auto path3 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); |
| 555 | + |
| 556 | + // When running on gVisor, only test on tmpfs. We don't know how the gofer |
| 557 | + // server implements open(O_TRUNC), ftruncate(2), and truncate(2). If it |
| 558 | + // uses truncate(2) under the hood and the underlying filesystem is host |
| 559 | + // tmpfs, then this test will fail. |
| 560 | + SKIP_IF(IsRunningOnGvisor() && |
| 561 | + !ASSERT_NO_ERRNO_AND_VALUE(IsTmpfs(path1.path()))); |
| 562 | + |
| 563 | + struct stat before1 = {}, before2 = {}, before3 = {}; |
| 564 | + ASSERT_THAT(stat(path1.path().c_str(), &before1), SyscallSucceeds()); |
| 565 | + ASSERT_THAT(stat(path2.path().c_str(), &before2), SyscallSucceeds()); |
| 566 | + ASSERT_THAT(stat(path3.path().c_str(), &before3), SyscallSucceeds()); |
| 567 | + EXPECT_EQ(before1.st_size, 0); |
| 568 | + EXPECT_EQ(before2.st_size, 0); |
| 569 | + EXPECT_EQ(before3.st_size, 0); |
| 570 | + |
| 571 | + const auto ts_gt = [](const struct timespec& a, const struct timespec& b) { |
| 572 | + return a.tv_sec > b.tv_sec || |
| 573 | + (a.tv_sec == b.tv_sec && a.tv_nsec > b.tv_nsec); |
| 574 | + }; |
| 575 | + |
| 576 | + absl::SleepFor(absl::Milliseconds(10)); |
| 577 | + |
| 578 | + // Test 1: open(O_TRUNC) on zero-length file. |
| 579 | + const FileDescriptor fd1 = |
| 580 | + ASSERT_NO_ERRNO_AND_VALUE(Open(path1.path(), O_WRONLY | O_TRUNC)); |
| 581 | + struct stat after1 = {}; |
| 582 | + ASSERT_THAT(fstat(fd1.get(), &after1), SyscallSucceeds()); |
| 583 | + EXPECT_EQ(after1.st_size, 0); |
| 584 | + EXPECT_TRUE(ts_gt(after1.st_mtim, before1.st_mtim)); |
| 585 | + EXPECT_TRUE(ts_gt(after1.st_ctim, before1.st_ctim)); |
| 586 | + |
| 587 | + // Test 2: ftruncate(2) to same size (0). |
| 588 | + const FileDescriptor fd2 = |
| 589 | + ASSERT_NO_ERRNO_AND_VALUE(Open(path2.path(), O_WRONLY)); |
| 590 | + ASSERT_THAT(ftruncate(fd2.get(), 0), SyscallSucceeds()); |
| 591 | + struct stat after2 = {}; |
| 592 | + ASSERT_THAT(fstat(fd2.get(), &after2), SyscallSucceeds()); |
| 593 | + EXPECT_EQ(after2.st_size, 0); |
| 594 | + EXPECT_TRUE(ts_gt(after2.st_mtim, before2.st_mtim)); |
| 595 | + EXPECT_TRUE(ts_gt(after2.st_ctim, before2.st_ctim)); |
| 596 | + |
| 597 | + // Test 3: truncate(2) to same size (0). Note that Linux tmpfs does NOT |
| 598 | + // update mtime/ctime for truncate(2) same-size, but ext4 does. gVisor |
| 599 | + // unconditionally updates for simplicity. Do not test this on native tmpfs. |
| 600 | + if (IsRunningOnGvisor() || |
| 601 | + !ASSERT_NO_ERRNO_AND_VALUE(IsTmpfs(path3.path()))) { |
| 602 | + ASSERT_THAT(truncate(path3.path().c_str(), 0), SyscallSucceeds()); |
| 603 | + struct stat after3 = {}; |
| 604 | + ASSERT_THAT(stat(path3.path().c_str(), &after3), SyscallSucceeds()); |
| 605 | + EXPECT_EQ(after3.st_size, 0); |
| 606 | + EXPECT_TRUE(ts_gt(after3.st_mtim, before3.st_mtim)); |
| 607 | + EXPECT_TRUE(ts_gt(after3.st_ctim, before3.st_ctim)); |
| 608 | + } |
| 609 | +} |
| 610 | + |
| 611 | +// Truncate operations that change file size should always update mtime and |
| 612 | +// ctime. Tests truncation via open(O_TRUNC), ftruncate(2), and truncate(2). |
| 613 | +TEST_F(OpenTest, TruncateSizeChangeUpdatesTimestamps) { |
| 614 | + auto path1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( |
| 615 | + GetAbsoluteTestTmpdir(), "initial data 1", 0644)); |
| 616 | + auto path2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( |
| 617 | + GetAbsoluteTestTmpdir(), "initial data 2", 0644)); |
| 618 | + auto path3 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith( |
| 619 | + GetAbsoluteTestTmpdir(), "initial data 3", 0644)); |
| 620 | + |
| 621 | + struct stat before1 = {}, before2 = {}, before3 = {}; |
| 622 | + ASSERT_THAT(stat(path1.path().c_str(), &before1), SyscallSucceeds()); |
| 623 | + ASSERT_THAT(stat(path2.path().c_str(), &before2), SyscallSucceeds()); |
| 624 | + ASSERT_THAT(stat(path3.path().c_str(), &before3), SyscallSucceeds()); |
| 625 | + EXPECT_GT(before1.st_size, 0); |
| 626 | + EXPECT_GT(before2.st_size, 0); |
| 627 | + EXPECT_GT(before3.st_size, 0); |
| 628 | + |
| 629 | + const auto ts_gt = [](const struct timespec& a, const struct timespec& b) { |
| 630 | + return a.tv_sec > b.tv_sec || |
| 631 | + (a.tv_sec == b.tv_sec && a.tv_nsec > b.tv_nsec); |
| 632 | + }; |
| 633 | + |
| 634 | + absl::SleepFor(absl::Milliseconds(10)); |
| 635 | + |
| 636 | + // Test 1: open(O_TRUNC) on non-zero-length file. |
| 637 | + const FileDescriptor fd1 = |
| 638 | + ASSERT_NO_ERRNO_AND_VALUE(Open(path1.path(), O_WRONLY | O_TRUNC)); |
| 639 | + struct stat after1 = {}; |
| 640 | + ASSERT_THAT(fstat(fd1.get(), &after1), SyscallSucceeds()); |
| 641 | + EXPECT_EQ(after1.st_size, 0); |
| 642 | + EXPECT_TRUE(ts_gt(after1.st_mtim, before1.st_mtim)); |
| 643 | + EXPECT_TRUE(ts_gt(after1.st_ctim, before1.st_ctim)); |
| 644 | + |
| 645 | + // Test 2: ftruncate(2) to smaller size. |
| 646 | + const FileDescriptor fd2 = |
| 647 | + ASSERT_NO_ERRNO_AND_VALUE(Open(path2.path(), O_WRONLY)); |
| 648 | + ASSERT_THAT(ftruncate(fd2.get(), 5), SyscallSucceeds()); |
| 649 | + struct stat after2 = {}; |
| 650 | + ASSERT_THAT(fstat(fd2.get(), &after2), SyscallSucceeds()); |
| 651 | + EXPECT_EQ(after2.st_size, 5); |
| 652 | + EXPECT_TRUE(ts_gt(after2.st_mtim, before2.st_mtim)); |
| 653 | + EXPECT_TRUE(ts_gt(after2.st_ctim, before2.st_ctim)); |
| 654 | + |
| 655 | + // Test 3: truncate(2) to 0. |
| 656 | + ASSERT_THAT(truncate(path3.path().c_str(), 0), SyscallSucceeds()); |
| 657 | + struct stat after3 = {}; |
| 658 | + ASSERT_THAT(stat(path3.path().c_str(), &after3), SyscallSucceeds()); |
| 659 | + EXPECT_EQ(after3.st_size, 0); |
| 660 | + EXPECT_TRUE(ts_gt(after3.st_mtim, before3.st_mtim)); |
| 661 | + EXPECT_TRUE(ts_gt(after3.st_ctim, before3.st_ctim)); |
| 662 | +} |
| 663 | + |
545 | 664 | } // namespace |
546 | 665 |
|
547 | 666 | } // namespace testing |
|
0 commit comments