Add FileModificationTime

FileModificationTime gets the last write time for files, directories,
or symbolic links. Symbolic links may point to files, directories, or
be dangling.

Bug: crashpad:206
Change-Id: Ic83b5a7d318502ad5db5c01731d06c8624925e15
Reviewed-on: https://chromium-review.googlesource.com/744298
Commit-Queue: Joshua Peraza <jperaza@chromium.org>
Reviewed-by: Mark Mentovai <mark@chromium.org>
This commit is contained in:
Joshua Peraza 2017-11-08 21:39:08 -08:00 committed by Commit Bot
parent 2d077a2c57
commit 6f6f8a144d
6 changed files with 238 additions and 2 deletions

View File

@ -15,7 +15,9 @@
#include "test/filesystem.h" #include "test/filesystem.h"
#include <errno.h> #include <errno.h>
#include <fcntl.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h> #include <sys/types.h>
#include "base/logging.h" #include "base/logging.h"
@ -25,6 +27,7 @@
#include "test/scoped_temp_dir.h" #include "test/scoped_temp_dir.h"
#include "util/file/file_io.h" #include "util/file/file_io.h"
#include "util/file/filesystem.h" #include "util/file/filesystem.h"
#include "util/misc/time.h"
#if defined(OS_POSIX) #if defined(OS_POSIX)
#include <unistd.h> #include <unistd.h>
@ -116,6 +119,63 @@ bool PathExists(const base::FilePath& path) {
#endif #endif
} }
bool SetFileModificationTime(const base::FilePath& path,
const timespec& mtime) {
#if defined(OS_MACOSX)
// utimensat() isn't available on macOS until 10.13, so lutimes() is used
// instead.
struct stat st;
if (lstat(path.value().c_str(), &st) != 0) {
PLOG(ERROR) << "lstat " << path.value();
return false;
}
timeval times[2];
EXPECT_TRUE(TimespecToTimeval(st.st_atimespec, &times[0]));
EXPECT_TRUE(TimespecToTimeval(mtime, &times[1]));
if (lutimes(path.value().c_str(), times) != 0) {
PLOG(ERROR) << "lutimes " << path.value();
return false;
}
return true;
#elif defined(OS_POSIX)
timespec times[2];
times[0].tv_sec = 0;
times[0].tv_nsec = UTIME_OMIT;
times[1] = mtime;
if (utimensat(AT_FDCWD, path.value().c_str(), times, AT_SYMLINK_NOFOLLOW) !=
0) {
PLOG(ERROR) << "utimensat " << path.value();
return false;
}
return true;
#elif defined(OS_WIN)
DWORD flags = FILE_FLAG_OPEN_REPARSE_POINT;
if (IsDirectory(path, true)) {
// required for directory handles
flags |= FILE_FLAG_BACKUP_SEMANTICS;
}
ScopedFileHandle handle(::CreateFile(path.value().c_str(),
GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
nullptr,
OPEN_EXISTING,
flags,
nullptr));
if (!handle.is_valid()) {
PLOG(ERROR) << "CreateFile " << base::UTF16ToUTF8(path.value());
return false;
}
FILETIME filetime = TimespecToFiletimeEpoch(mtime);
if (!SetFileTime(handle.get(), nullptr, nullptr, &filetime)) {
PLOG(ERROR) << "SetFileTime " << base::UTF16ToUTF8(path.value());
return false;
}
return true;
#endif // OS_MACOSX
}
#if !defined(OS_FUCHSIA) #if !defined(OS_FUCHSIA)
bool CanCreateSymbolicLinks() { bool CanCreateSymbolicLinks() {

View File

@ -17,15 +17,26 @@
#include "base/files/file_path.h" #include "base/files/file_path.h"
#include <time.h>
#include "build/build_config.h" #include "build/build_config.h"
namespace crashpad { namespace crashpad {
namespace test { namespace test {
bool CreateFile(const base::FilePath& file); //! \brief Creates an empty file at path \a filepath.
bool CreateFile(const base::FilePath& filepath);
//! \brief Returns `true` if a filesystem node exists at path \a path.
bool PathExists(const base::FilePath& path); bool PathExists(const base::FilePath& path);
//! \brief Sets the modification time for a file, directory, or symbolic link.
//!
//! \param[in] path The path to the file to set the modification time for.
//! \param[in] mtime The new modification time for the file.
//! \return `true` on success. Otherwise `false` with a message logged.
bool SetFileModificationTime(const base::FilePath& path, const timespec& mtime);
#if !defined(OS_FUCHSIA) || DOXYGEN #if !defined(OS_FUCHSIA) || DOXYGEN
// There are no symbolic links on Fuchsia. Dont bother declaring or defining // There are no symbolic links on Fuchsia. Dont bother declaring or defining
// symbolic link-related functions at all, because its an error to even pretend // symbolic link-related functions at all, because its an error to even pretend
@ -48,6 +59,11 @@ bool PathExists(const base::FilePath& path);
//! in Windows 10!</a> //! in Windows 10!</a>
bool CanCreateSymbolicLinks(); bool CanCreateSymbolicLinks();
//! \brief Creates a new symbolic link.
//!
//! \param[in] target_path The target for the link.
//! \param[in] symlink_path The name for the new link.
//! \return `true` on success. Otherwise `false` with a message logged.
bool CreateSymbolicLink(const base::FilePath& target_path, bool CreateSymbolicLink(const base::FilePath& target_path,
const base::FilePath& symlink_path); const base::FilePath& symlink_path);

View File

@ -15,12 +15,21 @@
#ifndef CRASHPAD_UTIL_FILE_FILESYSTEM_H_ #ifndef CRASHPAD_UTIL_FILE_FILESYSTEM_H_
#define CRASHPAD_UTIL_FILE_FILESYSTEM_H_ #define CRASHPAD_UTIL_FILE_FILESYSTEM_H_
#include "base/files/file_path.h" #include <time.h>
#include "base/files/file_path.h"
#include "util/file/file_io.h" #include "util/file/file_io.h"
namespace crashpad { namespace crashpad {
//! \brief Determines the modification time for a file, directory, or symbolic
//! link, logging a message on failure.
//!
//! \param[in] path The file to get the modification time for.
//! \param[out] mtime The modification time as seconds since the POSIX Epoch.
//! \return `true` on success. `false` on failure with a message logged.
bool FileModificationTime(const base::FilePath& path, timespec* mtime);
//! \brief Creates a directory, logging a message on failure. //! \brief Creates a directory, logging a message on failure.
//! //!
//! \param[in] path The path to the directory to create. //! \param[in] path The path to the directory to create.

View File

@ -21,9 +21,25 @@
#include <unistd.h> #include <unistd.h>
#include "base/logging.h" #include "base/logging.h"
#include "build/build_config.h"
namespace crashpad { namespace crashpad {
bool FileModificationTime(const base::FilePath& path, timespec* mtime) {
struct stat st;
if (lstat(path.value().c_str(), &st) != 0) {
PLOG(ERROR) << "lstat " << path.value();
return false;
}
#if defined(OS_MACOSX)
*mtime = st.st_mtimespec;
#else
*mtime = st.st_mtim;
#endif
return true;
}
bool LoggingCreateDirectory(const base::FilePath& path, bool LoggingCreateDirectory(const base::FilePath& path,
FilePermissions permissions, FilePermissions permissions,
bool may_reuse) { bool may_reuse) {

View File

@ -17,6 +17,7 @@
#include "base/logging.h" #include "base/logging.h"
#include "build/build_config.h" #include "build/build_config.h"
#include "gtest/gtest.h" #include "gtest/gtest.h"
#include "test/errors.h"
#include "test/filesystem.h" #include "test/filesystem.h"
#include "test/gtest_disabled.h" #include "test/gtest_disabled.h"
#include "test/scoped_temp_dir.h" #include "test/scoped_temp_dir.h"
@ -25,6 +26,109 @@ namespace crashpad {
namespace test { namespace test {
namespace { namespace {
bool CurrentTime(timespec* now) {
#if defined(OS_POSIX)
int res = clock_gettime(CLOCK_REALTIME, now);
if (res != 0) {
EXPECT_EQ(res, 0) << ErrnoMessage("clock_gettime");
return false;
}
return true;
#else
int res = timespec_get(now, TIME_UTC);
if (res != TIME_UTC) {
EXPECT_EQ(res, TIME_UTC);
return false;
}
return true;
#endif
}
TEST(Filesystem, FileModificationTime) {
timespec expected_time_start, expected_time_end;
ASSERT_TRUE(CurrentTime(&expected_time_start));
ScopedTempDir temp_dir;
ASSERT_TRUE(CurrentTime(&expected_time_end));
timespec dir_mtime;
ASSERT_TRUE(FileModificationTime(temp_dir.path(), &dir_mtime));
EXPECT_GE(dir_mtime.tv_sec, expected_time_start.tv_sec - 2);
EXPECT_LE(dir_mtime.tv_sec, expected_time_end.tv_sec + 2);
base::FilePath file(temp_dir.path().Append(FILE_PATH_LITERAL("file")));
ASSERT_TRUE(CurrentTime(&expected_time_start));
ASSERT_TRUE(CreateFile(file));
ASSERT_TRUE(CurrentTime(&expected_time_end));
timespec file_mtime;
ASSERT_TRUE(FileModificationTime(file, &file_mtime));
EXPECT_GE(file_mtime.tv_sec, expected_time_start.tv_sec - 2);
EXPECT_LE(file_mtime.tv_sec, expected_time_end.tv_sec + 2);
timespec file_mtime_again;
ASSERT_TRUE(FileModificationTime(file, &file_mtime_again));
EXPECT_EQ(file_mtime.tv_sec, file_mtime_again.tv_sec);
EXPECT_EQ(file_mtime.tv_nsec, file_mtime_again.tv_nsec);
timespec mtime;
EXPECT_FALSE(FileModificationTime(base::FilePath(), &mtime));
EXPECT_FALSE(FileModificationTime(
temp_dir.path().Append(FILE_PATH_LITERAL("notafile")), &mtime));
}
#if !defined(OS_FUCHSIA)
TEST(Filesystem, FileModificationTime_SymbolicLinks) {
if (!CanCreateSymbolicLinks()) {
DISABLED_TEST();
}
ScopedTempDir temp_dir;
const base::FilePath dir_link(
temp_dir.path().Append(FILE_PATH_LITERAL("dir_link")));
timespec expected_time_start, expected_time_end;
ASSERT_TRUE(CurrentTime(&expected_time_start));
ASSERT_TRUE(CreateSymbolicLink(temp_dir.path(), dir_link));
ASSERT_TRUE(CurrentTime(&expected_time_end));
timespec mtime, mtime2;
ASSERT_TRUE(FileModificationTime(temp_dir.path(), &mtime));
mtime.tv_sec -= 100;
ASSERT_TRUE(SetFileModificationTime(temp_dir.path(), mtime));
ASSERT_TRUE(FileModificationTime(temp_dir.path(), &mtime2));
EXPECT_GE(mtime2.tv_sec, mtime.tv_sec - 2);
EXPECT_LE(mtime2.tv_sec, mtime.tv_sec + 2);
ASSERT_TRUE(FileModificationTime(dir_link, &mtime));
EXPECT_GE(mtime.tv_sec, expected_time_start.tv_sec - 2);
EXPECT_LE(mtime.tv_sec, expected_time_end.tv_sec + 2);
base::FilePath file(temp_dir.path().Append(FILE_PATH_LITERAL("file")));
ASSERT_TRUE(CreateFile(file));
base::FilePath link(temp_dir.path().Append(FILE_PATH_LITERAL("link")));
ASSERT_TRUE(CurrentTime(&expected_time_start));
ASSERT_TRUE(CreateSymbolicLink(file, link));
ASSERT_TRUE(CurrentTime(&expected_time_end));
ASSERT_TRUE(FileModificationTime(file, &mtime));
mtime.tv_sec -= 100;
ASSERT_TRUE(SetFileModificationTime(file, mtime));
ASSERT_TRUE(FileModificationTime(file, &mtime2));
EXPECT_GE(mtime2.tv_sec, mtime.tv_sec - 2);
EXPECT_LE(mtime2.tv_sec, mtime.tv_sec + 2);
ASSERT_TRUE(FileModificationTime(link, &mtime));
EXPECT_GE(mtime.tv_sec, expected_time_start.tv_sec - 2);
EXPECT_LE(mtime.tv_sec, expected_time_end.tv_sec + 2);
}
#endif // !OS_FUCHSIA
TEST(Filesystem, CreateDirectory) { TEST(Filesystem, CreateDirectory) {
ScopedTempDir temp_dir; ScopedTempDir temp_dir;

View File

@ -14,10 +14,12 @@
#include "util/file/filesystem.h" #include "util/file/filesystem.h"
#include <sys/time.h>
#include <windows.h> #include <windows.h>
#include "base/logging.h" #include "base/logging.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "util/misc/time.h"
namespace crashpad { namespace crashpad {
@ -50,6 +52,35 @@ bool LoggingRemoveDirectoryImpl(const base::FilePath& path) {
} // namespace } // namespace
bool FileModificationTime(const base::FilePath& path, timespec* mtime) {
DWORD flags = FILE_FLAG_OPEN_REPARSE_POINT;
if (IsDirectory(path, true)) {
// required for directory handles
flags |= FILE_FLAG_BACKUP_SEMANTICS;
}
ScopedFileHandle handle(
::CreateFile(path.value().c_str(),
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
nullptr,
OPEN_EXISTING,
flags,
nullptr));
if (!handle.is_valid()) {
PLOG(ERROR) << "CreateFile " << base::UTF16ToUTF8(path.value());
return false;
}
FILETIME file_mtime;
if (!GetFileTime(handle.get(), nullptr, nullptr, &file_mtime)) {
PLOG(ERROR) << "GetFileTime " << base::UTF16ToUTF8(path.value());
return false;
}
*mtime = FiletimeToTimespecEpoch(file_mtime);
return true;
}
bool LoggingCreateDirectory(const base::FilePath& path, bool LoggingCreateDirectory(const base::FilePath& path,
FilePermissions permissions, FilePermissions permissions,
bool may_reuse) { bool may_reuse) {