2019-04-29 21:25:05 +00:00
|
|
|
// Copyright 2018 The gVisor Authors.
|
2018-12-10 22:41:40 +00:00
|
|
|
//
|
|
|
|
// 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 <fcntl.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <sys/syscall.h>
|
|
|
|
#include <sys/time.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <time.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <utime.h>
|
2019-11-21 19:29:49 +00:00
|
|
|
|
2018-12-10 22:41:40 +00:00
|
|
|
#include <string>
|
|
|
|
|
|
|
|
#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 {
|
|
|
|
|
2019-04-29 21:03:04 +00:00
|
|
|
// TODO(b/36516566): utimes(nullptr) does not pick the "now" time in the
|
2018-12-10 22:41:40 +00:00
|
|
|
// application's time domain, so when asserting that times are within a window,
|
|
|
|
// we expand the window to allow for differences between the time domains.
|
|
|
|
constexpr absl::Duration kClockSlack = absl::Milliseconds(100);
|
|
|
|
|
|
|
|
// 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.
|
|
|
|
//
|
|
|
|
// * See the comment on kClockSlack. gVisor breaks this guarantee.
|
|
|
|
void TimeBoxed(absl::Time* before, absl::Time* after,
|
|
|
|
std::function<void()> 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;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (IsRunningOnGvisor()) {
|
|
|
|
// See comment on kClockSlack.
|
|
|
|
*before -= kClockSlack;
|
|
|
|
*after += kClockSlack;
|
|
|
|
}
|
|
|
|
} 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] = {{
|
2020-01-22 00:16:51 +00:00
|
|
|
{0, 1}, // Valid
|
|
|
|
{1, static_cast<int64_t>(1e7)} // Invalid
|
2018-12-10 22:41:40 +00:00
|
|
|
},
|
|
|
|
{
|
2020-01-22 00:16:51 +00:00
|
|
|
{1, static_cast<int64_t>(1e7)}, // Invalid
|
|
|
|
{0, 1} // Valid
|
2018-12-10 22:41:40 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
{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);
|
|
|
|
|
|
|
|
if (!IsRunningOnGvisor()) {
|
2019-04-29 21:03:04 +00:00
|
|
|
// FIXME(b/36516566): Gofers set atime and mtime to different "now" times.
|
2018-12-10 22:41:40 +00:00
|
|
|
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());
|
2020-01-22 00:16:51 +00:00
|
|
|
struct timespec times[2][2] = {
|
|
|
|
{
|
|
|
|
{0, UTIME_OMIT}, // Valid
|
|
|
|
{2, static_cast<int64_t>(1e10)} // Invalid
|
|
|
|
},
|
|
|
|
{
|
|
|
|
{2, static_cast<int64_t>(1e10)}, // Invalid
|
|
|
|
{0, UTIME_OMIT} // Valid
|
|
|
|
}};
|
2018-12-10 22:41:40 +00:00
|
|
|
|
|
|
|
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
|