gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/test/syscalls/linux/stat_times.cc (about)

     1  // Copyright 2018 The gVisor Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  #include <fcntl.h>
    16  #include <sys/stat.h>
    17  
    18  #include <tuple>
    19  
    20  #include "gmock/gmock.h"
    21  #include "gtest/gtest.h"
    22  #include "absl/time/clock.h"
    23  #include "absl/time/time.h"
    24  #include "test/util/file_descriptor.h"
    25  #include "test/util/temp_path.h"
    26  #include "test/util/test_util.h"
    27  
    28  namespace gvisor {
    29  namespace testing {
    30  
    31  namespace {
    32  
    33  using ::testing::IsEmpty;
    34  using ::testing::Not;
    35  
    36  std::tuple<absl::Time, absl::Time, absl::Time> GetTime(const TempPath& file) {
    37    struct stat statbuf = {};
    38    EXPECT_THAT(stat(file.path().c_str(), &statbuf), SyscallSucceeds());
    39  
    40    const auto atime = absl::TimeFromTimespec(statbuf.st_atim);
    41    const auto mtime = absl::TimeFromTimespec(statbuf.st_mtim);
    42    const auto ctime = absl::TimeFromTimespec(statbuf.st_ctim);
    43    return std::make_tuple(atime, mtime, ctime);
    44  }
    45  
    46  enum class AtimeEffect {
    47    Unchanged,
    48    Changed,
    49  };
    50  
    51  enum class MtimeEffect {
    52    Unchanged,
    53    Changed,
    54  };
    55  
    56  enum class CtimeEffect {
    57    Unchanged,
    58    Changed,
    59  };
    60  
    61  // Tests that fn modifies the atime/mtime/ctime of path as specified.
    62  void CheckTimes(const TempPath& path, std::function<void()> fn,
    63                  AtimeEffect atime_effect, MtimeEffect mtime_effect,
    64                  CtimeEffect ctime_effect) {
    65    absl::Time atime, mtime, ctime;
    66    std::tie(atime, mtime, ctime) = GetTime(path);
    67  
    68    // FIXME(b/132819225): gVisor filesystem timestamps inconsistently use the
    69    // internal or host clock, which may diverge slightly. Allow some slack on
    70    // times to account for the difference.
    71    //
    72    // Here we sleep for 1s so that initial creation of path doesn't fall within
    73    // the before slack window.
    74    absl::SleepFor(absl::Seconds(1));
    75  
    76    const absl::Time before = absl::Now() - absl::Seconds(1);
    77  
    78    // Perform the op.
    79    fn();
    80  
    81    const absl::Time after = absl::Now() + absl::Seconds(1);
    82  
    83    absl::Time atime2, mtime2, ctime2;
    84    std::tie(atime2, mtime2, ctime2) = GetTime(path);
    85  
    86    if (atime_effect == AtimeEffect::Changed) {
    87      EXPECT_LE(before, atime2);
    88      EXPECT_GE(after, atime2);
    89      EXPECT_GT(atime2, atime);
    90    } else {
    91      EXPECT_EQ(atime2, atime);
    92    }
    93  
    94    if (mtime_effect == MtimeEffect::Changed) {
    95      EXPECT_LE(before, mtime2);
    96      EXPECT_GE(after, mtime2);
    97      EXPECT_GT(mtime2, mtime);
    98    } else {
    99      EXPECT_EQ(mtime2, mtime);
   100    }
   101  
   102    if (ctime_effect == CtimeEffect::Changed) {
   103      EXPECT_LE(before, ctime2);
   104      EXPECT_GE(after, ctime2);
   105      EXPECT_GT(ctime2, ctime);
   106    } else {
   107      EXPECT_EQ(ctime2, ctime);
   108    }
   109  }
   110  
   111  // File creation time is reflected in atime, mtime, and ctime.
   112  TEST(StatTimesTest, FileCreation) {
   113    const DisableSave ds;  // Timing-related test.
   114  
   115    // Get a time for when the file is created.
   116    //
   117    // FIXME(b/132819225): See above.
   118    const absl::Time before = absl::Now() - absl::Seconds(1);
   119    const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
   120    const absl::Time after = absl::Now() + absl::Seconds(1);
   121  
   122    absl::Time atime, mtime, ctime;
   123    std::tie(atime, mtime, ctime) = GetTime(file);
   124  
   125    EXPECT_LE(before, atime);
   126    EXPECT_LE(before, mtime);
   127    EXPECT_LE(before, ctime);
   128    EXPECT_GE(after, atime);
   129    EXPECT_GE(after, mtime);
   130    EXPECT_GE(after, ctime);
   131  }
   132  
   133  // Calling chmod on a file changes ctime.
   134  TEST(StatTimesTest, FileChmod) {
   135    TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
   136  
   137    auto fn = [&] {
   138      EXPECT_THAT(chmod(file.path().c_str(), 0666), SyscallSucceeds());
   139    };
   140    CheckTimes(file, fn, AtimeEffect::Unchanged, MtimeEffect::Unchanged,
   141               CtimeEffect::Changed);
   142  }
   143  
   144  // Renaming a file changes ctime.
   145  TEST(StatTimesTest, FileRename) {
   146    TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
   147  
   148    const std::string newpath = NewTempAbsPath();
   149  
   150    auto fn = [&] {
   151      ASSERT_THAT(rename(file.release().c_str(), newpath.c_str()),
   152                  SyscallSucceeds());
   153      file.reset(newpath);
   154    };
   155    CheckTimes(file, fn, AtimeEffect::Unchanged, MtimeEffect::Unchanged,
   156               CtimeEffect::Changed);
   157  }
   158  
   159  // Renaming a file changes ctime, even with an open FD.
   160  //
   161  // NOTE(b/132732387): This is a regression test for fs/gofer failing to update
   162  // cached ctime.
   163  TEST(StatTimesTest, FileRenameOpenFD) {
   164    TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
   165  
   166    // Holding an FD shouldn't affect behavior.
   167    const FileDescriptor fd =
   168        ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY));
   169  
   170    const std::string newpath = NewTempAbsPath();
   171  
   172    // FIXME(b/132814682): Restore fails with an uncached gofer and an open FD
   173    // across rename.
   174    //
   175    // N.B. The logic here looks backwards because it isn't possible to
   176    // conditionally disable save, only conditionally re-enable it.
   177    DisableSave ds;
   178    if (!getenv("GVISOR_GOFER_UNCACHED")) {
   179      ds.reset();
   180    }
   181  
   182    auto fn = [&] {
   183      ASSERT_THAT(rename(file.release().c_str(), newpath.c_str()),
   184                  SyscallSucceeds());
   185      file.reset(newpath);
   186    };
   187    CheckTimes(file, fn, AtimeEffect::Unchanged, MtimeEffect::Unchanged,
   188               CtimeEffect::Changed);
   189  }
   190  
   191  // Calling utimes on a file changes ctime and the time that we ask to change
   192  // (atime to now in this case).
   193  TEST(StatTimesTest, FileUtimes) {
   194    TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
   195  
   196    auto fn = [&] {
   197      const struct timespec ts[2] = {{0, UTIME_NOW}, {0, UTIME_OMIT}};
   198      ASSERT_THAT(utimensat(AT_FDCWD, file.path().c_str(), ts, 0),
   199                  SyscallSucceeds());
   200    };
   201    CheckTimes(file, fn, AtimeEffect::Changed, MtimeEffect::Unchanged,
   202               CtimeEffect::Changed);
   203  }
   204  
   205  // Truncating a file changes mtime and ctime.
   206  TEST(StatTimesTest, FileTruncate) {
   207    const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(
   208        TempPath::CreateFileWith(GetAbsoluteTestTmpdir(), "yaaass", 0666));
   209  
   210    auto fn = [&] {
   211      EXPECT_THAT(truncate(file.path().c_str(), 0), SyscallSucceeds());
   212    };
   213    CheckTimes(file, fn, AtimeEffect::Unchanged, MtimeEffect::Changed,
   214               CtimeEffect::Changed);
   215  }
   216  
   217  // Writing a file changes mtime and ctime.
   218  TEST(StatTimesTest, FileWrite) {
   219    const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(
   220        TempPath::CreateFileWith(GetAbsoluteTestTmpdir(), "yaaass", 0666));
   221  
   222    const FileDescriptor fd =
   223        ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR, 0));
   224  
   225    auto fn = [&] {
   226      const std::string contents = "all the single dollars";
   227      EXPECT_THAT(WriteFd(fd.get(), contents.data(), contents.size()),
   228                  SyscallSucceeds());
   229    };
   230    CheckTimes(file, fn, AtimeEffect::Unchanged, MtimeEffect::Changed,
   231               CtimeEffect::Changed);
   232  }
   233  
   234  // Reading a file changes atime.
   235  TEST(StatTimesTest, FileRead) {
   236    const std::string contents = "bills bills bills";
   237    const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(
   238        TempPath::CreateFileWith(GetAbsoluteTestTmpdir(), contents, 0666));
   239  
   240    const FileDescriptor fd =
   241        ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY, 0));
   242  
   243    auto fn = [&] {
   244      char buf[20];
   245      ASSERT_THAT(ReadFd(fd.get(), buf, sizeof(buf)),
   246                  SyscallSucceedsWithValue(contents.size()));
   247    };
   248    CheckTimes(file, fn, AtimeEffect::Changed, MtimeEffect::Unchanged,
   249               CtimeEffect::Unchanged);
   250  }
   251  
   252  // Listing files in a directory changes atime.
   253  TEST(StatTimesTest, DirList) {
   254    const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
   255    const TempPath file =
   256        ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir.path()));
   257  
   258    auto fn = [&] {
   259      const auto contents = ASSERT_NO_ERRNO_AND_VALUE(ListDir(dir.path(), false));
   260      EXPECT_THAT(contents, Not(IsEmpty()));
   261    };
   262    CheckTimes(dir, fn, AtimeEffect::Changed, MtimeEffect::Unchanged,
   263               CtimeEffect::Unchanged);
   264  }
   265  
   266  // Creating a file in a directory changes mtime and ctime.
   267  TEST(StatTimesTest, DirCreateFile) {
   268    const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
   269  
   270    TempPath file;
   271    auto fn = [&] {
   272      file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir.path()));
   273    };
   274    CheckTimes(dir, fn, AtimeEffect::Unchanged, MtimeEffect::Changed,
   275               CtimeEffect::Changed);
   276  }
   277  
   278  // Creating a directory in a directory changes mtime and ctime.
   279  TEST(StatTimesTest, DirCreateDir) {
   280    const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
   281  
   282    TempPath dir2;
   283    auto fn = [&] {
   284      dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(dir.path()));
   285    };
   286    CheckTimes(dir, fn, AtimeEffect::Unchanged, MtimeEffect::Changed,
   287               CtimeEffect::Changed);
   288  }
   289  
   290  // Removing a file from a directory changes mtime and ctime.
   291  TEST(StatTimesTest, DirRemoveFile) {
   292    const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
   293  
   294    TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir.path()));
   295    auto fn = [&] { file.reset(); };
   296    CheckTimes(dir, fn, AtimeEffect::Unchanged, MtimeEffect::Changed,
   297               CtimeEffect::Changed);
   298  }
   299  
   300  }  // namespace
   301  
   302  }  // namespace testing
   303  }  // namespace gvisor