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