// Copyright 2018 The gVisor Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include #include #include #include #include #include #include #include #include "absl/time/time.h" #include "test/util/file_descriptor.h" #include "test/util/fs_util.h" #include "test/util/temp_path.h" #include "test/util/test_util.h" namespace gvisor { namespace testing { namespace { // TimeBoxed runs fn, setting before and after to (coarse realtime) times // guaranteed* to come before and after fn started and completed, respectively. // // fn may be called more than once if the clock is adjusted. void TimeBoxed(absl::Time* before, absl::Time* after, std::function const& fn) { do { // N.B. utimes and friends use CLOCK_REALTIME_COARSE for setting time (i.e., // current_kernel_time()). See fs/attr.c:notify_change. // // notify_change truncates the time to a multiple of s_time_gran, but most // filesystems set it to 1, so we don't do any truncation. struct timespec ts; EXPECT_THAT(clock_gettime(CLOCK_REALTIME_COARSE, &ts), SyscallSucceeds()); *before = absl::TimeFromTimespec(ts); fn(); EXPECT_THAT(clock_gettime(CLOCK_REALTIME_COARSE, &ts), SyscallSucceeds()); *after = absl::TimeFromTimespec(ts); if (*after < *before) { // Clock jumped backwards; retry. // // Technically this misses jumps small enough to keep after > before, // which could lead to test failures, but that is very unlikely to happen. continue; } } while (*after < *before); } void TestUtimesOnPath(std::string const& path) { struct stat statbuf; struct timeval times[2] = {{1, 0}, {2, 0}}; EXPECT_THAT(utimes(path.c_str(), times), SyscallSucceeds()); EXPECT_THAT(stat(path.c_str(), &statbuf), SyscallSucceeds()); EXPECT_EQ(1, statbuf.st_atime); EXPECT_EQ(2, statbuf.st_mtime); absl::Time before; absl::Time after; TimeBoxed(&before, &after, [&] { EXPECT_THAT(utimes(path.c_str(), nullptr), SyscallSucceeds()); }); EXPECT_THAT(stat(path.c_str(), &statbuf), SyscallSucceeds()); absl::Time atime = absl::TimeFromTimespec(statbuf.st_atim); EXPECT_GE(atime, before); EXPECT_LE(atime, after); absl::Time mtime = absl::TimeFromTimespec(statbuf.st_mtim); EXPECT_GE(mtime, before); EXPECT_LE(mtime, after); } TEST(UtimesTest, OnFile) { auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); TestUtimesOnPath(f.path()); } TEST(UtimesTest, OnDir) { auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); TestUtimesOnPath(dir.path()); } TEST(UtimesTest, MissingPath) { auto path = NewTempAbsPath(); struct timeval times[2] = {{1, 0}, {2, 0}}; EXPECT_THAT(utimes(path.c_str(), times), SyscallFailsWithErrno(ENOENT)); } void TestFutimesat(int dirFd, std::string const& path) { struct stat statbuf; struct timeval times[2] = {{1, 0}, {2, 0}}; EXPECT_THAT(futimesat(dirFd, path.c_str(), times), SyscallSucceeds()); EXPECT_THAT(fstatat(dirFd, path.c_str(), &statbuf, 0), SyscallSucceeds()); EXPECT_EQ(1, statbuf.st_atime); EXPECT_EQ(2, statbuf.st_mtime); absl::Time before; absl::Time after; TimeBoxed(&before, &after, [&] { EXPECT_THAT(futimesat(dirFd, path.c_str(), nullptr), SyscallSucceeds()); }); EXPECT_THAT(fstatat(dirFd, path.c_str(), &statbuf, 0), SyscallSucceeds()); absl::Time atime = absl::TimeFromTimespec(statbuf.st_atim); EXPECT_GE(atime, before); EXPECT_LE(atime, after); absl::Time mtime = absl::TimeFromTimespec(statbuf.st_mtim); EXPECT_GE(mtime, before); EXPECT_LE(mtime, after); } TEST(FutimesatTest, OnAbsPath) { auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); TestFutimesat(0, f.path()); } TEST(FutimesatTest, OnRelPath) { auto d = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(d.path())); auto basename = std::string(Basename(f.path())); const FileDescriptor dirFd = ASSERT_NO_ERRNO_AND_VALUE(Open(d.path(), O_RDONLY | O_DIRECTORY)); TestFutimesat(dirFd.get(), basename); } TEST(FutimesatTest, InvalidNsec) { auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); struct timeval times[4][2] = {{ {0, 1}, // Valid {1, static_cast(1e7)} // Invalid }, { {1, static_cast(1e7)}, // Invalid {0, 1} // Valid }, { {0, 1}, // Valid {1, -1} // Invalid }, { {1, -1}, // Invalid {0, 1} // Valid }}; for (unsigned int i = 0; i < sizeof(times) / sizeof(times[0]); i++) { std::cout << "test:" << i << "\n"; EXPECT_THAT(futimesat(0, f.path().c_str(), times[i]), SyscallFailsWithErrno(EINVAL)); } } void TestUtimensat(int dirFd, std::string const& path) { struct stat statbuf; const struct timespec times[2] = {{1, 0}, {2, 0}}; EXPECT_THAT(utimensat(dirFd, path.c_str(), times, 0), SyscallSucceeds()); EXPECT_THAT(fstatat(dirFd, path.c_str(), &statbuf, 0), SyscallSucceeds()); EXPECT_EQ(1, statbuf.st_atime); EXPECT_EQ(2, statbuf.st_mtime); // Test setting with UTIME_NOW and UTIME_OMIT. struct stat statbuf2; const struct timespec times2[2] = { {0, UTIME_NOW}, // Should set atime to now. {0, UTIME_OMIT} // Should not change mtime. }; absl::Time before; absl::Time after; TimeBoxed(&before, &after, [&] { EXPECT_THAT(utimensat(dirFd, path.c_str(), times2, 0), SyscallSucceeds()); }); EXPECT_THAT(fstatat(dirFd, path.c_str(), &statbuf2, 0), SyscallSucceeds()); absl::Time atime2 = absl::TimeFromTimespec(statbuf2.st_atim); EXPECT_GE(atime2, before); EXPECT_LE(atime2, after); absl::Time mtime = absl::TimeFromTimespec(statbuf.st_mtim); absl::Time mtime2 = absl::TimeFromTimespec(statbuf2.st_mtim); // mtime should not be changed. EXPECT_EQ(mtime, mtime2); // Test setting with times = NULL. Should set both atime and mtime to the // current system time. struct stat statbuf3; TimeBoxed(&before, &after, [&] { EXPECT_THAT(utimensat(dirFd, path.c_str(), nullptr, 0), SyscallSucceeds()); }); EXPECT_THAT(fstatat(dirFd, path.c_str(), &statbuf3, 0), SyscallSucceeds()); absl::Time atime3 = absl::TimeFromTimespec(statbuf3.st_atim); EXPECT_GE(atime3, before); EXPECT_LE(atime3, after); absl::Time mtime3 = absl::TimeFromTimespec(statbuf3.st_mtim); EXPECT_GE(mtime3, before); EXPECT_LE(mtime3, after); EXPECT_EQ(atime3, mtime3); } TEST(UtimensatTest, OnAbsPath) { auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); TestUtimensat(0, f.path()); } TEST(UtimensatTest, OnRelPath) { auto d = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(d.path())); auto basename = std::string(Basename(f.path())); const FileDescriptor dirFd = ASSERT_NO_ERRNO_AND_VALUE(Open(d.path(), O_RDONLY | O_DIRECTORY)); TestUtimensat(dirFd.get(), basename); } TEST(UtimensatTest, OmitNoop) { // Setting both timespecs to UTIME_OMIT on a nonexistant path should succeed. auto path = NewTempAbsPath(); const struct timespec times[2] = {{0, UTIME_OMIT}, {0, UTIME_OMIT}}; EXPECT_THAT(utimensat(0, path.c_str(), times, 0), SyscallSucceeds()); } // Verify that we can actually set atime and mtime to 0. TEST(UtimeTest, ZeroAtimeandMtime) { const auto tmp_dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); const auto tmp_file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(tmp_dir.path())); // Stat the file before and after updating atime and mtime. struct stat stat_before = {}; EXPECT_THAT(stat(tmp_file.path().c_str(), &stat_before), SyscallSucceeds()); ASSERT_NE(stat_before.st_atime, 0); ASSERT_NE(stat_before.st_mtime, 0); const struct utimbuf times = {}; // Zero for both atime and mtime. EXPECT_THAT(utime(tmp_file.path().c_str(), ×), SyscallSucceeds()); struct stat stat_after = {}; EXPECT_THAT(stat(tmp_file.path().c_str(), &stat_after), SyscallSucceeds()); // We should see the atime and mtime changed when we set them to 0. ASSERT_EQ(stat_after.st_atime, 0); ASSERT_EQ(stat_after.st_mtime, 0); } TEST(UtimensatTest, InvalidNsec) { auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); struct timespec times[2][2] = { { {0, UTIME_OMIT}, // Valid {2, static_cast(1e10)} // Invalid }, { {2, static_cast(1e10)}, // Invalid {0, UTIME_OMIT} // Valid }}; for (unsigned int i = 0; i < sizeof(times) / sizeof(times[0]); i++) { std::cout << "test:" << i << "\n"; EXPECT_THAT(utimensat(0, f.path().c_str(), times[i], 0), SyscallFailsWithErrno(EINVAL)); } } TEST(Utimensat, NullPath) { // From man utimensat(2): // "the Linux utimensat() system call implements a nonstandard feature: if // pathname is NULL, then the call modifies the timestamps of the file // referred to by the file descriptor dirfd (which may refer to any type of // file). // Note, however, that the glibc wrapper for utimensat() disallows // passing NULL as the value for file: the wrapper function returns the error // EINVAL in this case." auto f = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile()); const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(f.path(), O_RDWR)); struct stat statbuf; const struct timespec times[2] = {{1, 0}, {2, 0}}; // Call syscall directly. EXPECT_THAT(syscall(SYS_utimensat, fd.get(), NULL, times, 0), SyscallSucceeds()); EXPECT_THAT(fstatat(0, f.path().c_str(), &statbuf, 0), SyscallSucceeds()); EXPECT_EQ(1, statbuf.st_atime); EXPECT_EQ(2, statbuf.st_mtime); } } // namespace } // namespace testing } // namespace gvisor