diff --git a/util/file/filesystem.h b/util/file/filesystem.h index 12823372..acdd8d81 100644 --- a/util/file/filesystem.h +++ b/util/file/filesystem.h @@ -35,6 +35,31 @@ bool LoggingCreateDirectory(const base::FilePath& path, FilePermissions permissions, bool may_reuse); +//! \brief Moves a file, symbolic link, or directory, logging a message on +//! failure. +//! +//! \a source must exist and refer to a file, symbolic link, or directory. +//! +//! \a source and \a dest must be on the same filesystem. +//! +//! If \a dest exists, it may be overwritten: +//! +//! If \a dest exists and refers to a file or to a live or dangling symbolic +//! link to a file, it will be overwritten if \a source also refers to a file or +//! to a live or dangling symbolic link to a file or directory. +//! +//! On POSIX, if \a dest refers to a directory, it will be overwritten only if +//! it is empty and \a source also refers to a directory. +//! +//! On Windows, if \a dest refers to a directory or to a live or dangling +//! symbolic link to a directory, it will not be overwritten. +//! +//! \param[in] source The path to the file to be moved. +//! \param[in] dest The new path for the file. +//! \return `true` on success. `false` on failure with a message logged. +bool MoveFileOrDirectory(const base::FilePath& source, + const base::FilePath& dest); + //! \brief Determines if a path refers to a regular file, logging a message on //! failure. //! diff --git a/util/file/filesystem_posix.cc b/util/file/filesystem_posix.cc index 843a1b63..802c3fc4 100644 --- a/util/file/filesystem_posix.cc +++ b/util/file/filesystem_posix.cc @@ -15,6 +15,7 @@ #include "util/file/filesystem.h" #include +#include #include #include #include @@ -42,6 +43,16 @@ bool LoggingCreateDirectory(const base::FilePath& path, return false; } +bool MoveFileOrDirectory(const base::FilePath& source, + const base::FilePath& dest) { + if (rename(source.value().c_str(), dest.value().c_str()) != 0) { + PLOG(ERROR) << "rename " << source.value().c_str() << ", " + << dest.value().c_str(); + return false; + } + return true; +} + bool IsRegularFile(const base::FilePath& path) { struct stat st; if (lstat(path.value().c_str(), &st) != 0) { diff --git a/util/file/filesystem_test.cc b/util/file/filesystem_test.cc index dee1560d..4fc856a1 100644 --- a/util/file/filesystem_test.cc +++ b/util/file/filesystem_test.cc @@ -16,7 +16,6 @@ #include "base/logging.h" #include "gtest/gtest.h" -#include "test/file.h" #include "test/scoped_temp_dir.h" #include "util/file/filesystem_test_util.h" @@ -45,6 +44,110 @@ TEST(Filesystem, CreateDirectory) { EXPECT_TRUE(IsRegularFile(file)); } +TEST(Filesystem, MoveFileOrDirectory) { + ScopedTempDir temp_dir; + + base::FilePath file(temp_dir.path().Append(FILE_PATH_LITERAL("file"))); + ASSERT_TRUE(CreateFile(file)); + + // empty paths + EXPECT_FALSE(MoveFileOrDirectory(base::FilePath(), base::FilePath())); + EXPECT_FALSE(MoveFileOrDirectory(base::FilePath(), file)); + EXPECT_FALSE(MoveFileOrDirectory(file, base::FilePath())); + EXPECT_TRUE(IsRegularFile(file)); + + // files + base::FilePath file2(temp_dir.path().Append(FILE_PATH_LITERAL("file2"))); + EXPECT_TRUE(MoveFileOrDirectory(file, file2)); + EXPECT_TRUE(IsRegularFile(file2)); + EXPECT_FALSE(IsRegularFile(file)); + + EXPECT_FALSE(MoveFileOrDirectory(file, file2)); + EXPECT_TRUE(IsRegularFile(file2)); + EXPECT_FALSE(IsRegularFile(file)); + + ASSERT_TRUE(CreateFile(file)); + EXPECT_TRUE(MoveFileOrDirectory(file2, file)); + EXPECT_TRUE(IsRegularFile(file)); + EXPECT_FALSE(IsRegularFile(file2)); + + // directories + base::FilePath dir(temp_dir.path().Append(FILE_PATH_LITERAL("dir"))); + ASSERT_TRUE( + LoggingCreateDirectory(dir, FilePermissions::kWorldReadable, false)); + + base::FilePath nested(dir.Append(FILE_PATH_LITERAL("nested"))); + ASSERT_TRUE(CreateFile(nested)); + + base::FilePath dir2(temp_dir.path().Append(FILE_PATH_LITERAL("dir2"))); + EXPECT_TRUE(MoveFileOrDirectory(dir, dir2)); + EXPECT_FALSE(IsDirectory(dir, true)); + EXPECT_TRUE(IsDirectory(dir2, false)); + EXPECT_TRUE(IsRegularFile(dir2.Append(nested.BaseName()))); + + ASSERT_TRUE( + LoggingCreateDirectory(dir, FilePermissions::kWorldReadable, false)); + EXPECT_FALSE(MoveFileOrDirectory(dir, dir2)); + EXPECT_TRUE(IsDirectory(dir, false)); + EXPECT_TRUE(IsDirectory(dir2, false)); + EXPECT_TRUE(IsRegularFile(dir2.Append(nested.BaseName()))); + + // files <-> directories + EXPECT_FALSE(MoveFileOrDirectory(file, dir2)); + EXPECT_TRUE(IsDirectory(dir2, false)); + EXPECT_TRUE(IsRegularFile(file)); + + EXPECT_FALSE(MoveFileOrDirectory(dir2, file)); + EXPECT_TRUE(IsDirectory(dir2, false)); + EXPECT_TRUE(IsRegularFile(file)); + + // file links + base::FilePath link(temp_dir.path().Append(FILE_PATH_LITERAL("link"))); + ASSERT_TRUE(CreateSymbolicLink(file, link)); + + base::FilePath link2(temp_dir.path().Append(FILE_PATH_LITERAL("link2"))); + EXPECT_TRUE(MoveFileOrDirectory(link, link2)); + EXPECT_TRUE(PathExists(link2)); + EXPECT_FALSE(PathExists(link)); + + ASSERT_TRUE(CreateSymbolicLink(file, link)); + EXPECT_TRUE(MoveFileOrDirectory(link, link2)); + EXPECT_TRUE(PathExists(link2)); + EXPECT_FALSE(PathExists(link)); + + // file links <-> files + EXPECT_TRUE(MoveFileOrDirectory(file, link2)); + EXPECT_TRUE(IsRegularFile(link2)); + EXPECT_FALSE(PathExists(file)); + + ASSERT_TRUE(MoveFileOrDirectory(link2, file)); + ASSERT_TRUE(CreateSymbolicLink(file, link)); + EXPECT_TRUE(MoveFileOrDirectory(link, file)); + EXPECT_TRUE(PathExists(file)); + EXPECT_FALSE(IsRegularFile(file)); + EXPECT_FALSE(PathExists(link)); + EXPECT_TRUE(LoggingRemoveFile(file)); + + // dangling file links + ASSERT_TRUE(CreateSymbolicLink(file, link)); + EXPECT_TRUE(MoveFileOrDirectory(link, link2)); + EXPECT_TRUE(PathExists(link2)); + EXPECT_FALSE(PathExists(link)); + + // directory links + ASSERT_TRUE(CreateSymbolicLink(dir, link)); + + EXPECT_TRUE(MoveFileOrDirectory(link, link2)); + EXPECT_TRUE(PathExists(link2)); + EXPECT_FALSE(PathExists(link)); + + // dangling directory links + ASSERT_TRUE(LoggingRemoveDirectory(dir)); + EXPECT_TRUE(MoveFileOrDirectory(link2, link)); + EXPECT_TRUE(PathExists(link)); + EXPECT_FALSE(PathExists(link2)); +} + TEST(Filesystem, IsRegularFile) { EXPECT_FALSE(IsRegularFile(base::FilePath())); @@ -116,8 +219,8 @@ TEST(Filesystem, RemoveFile) { base::FilePath link(temp_dir.path().Append(FILE_PATH_LITERAL("link"))); ASSERT_TRUE(CreateSymbolicLink(file, link)); EXPECT_TRUE(LoggingRemoveFile(link)); - EXPECT_FALSE(FileExists(link)); - EXPECT_TRUE(FileExists(file)); + EXPECT_FALSE(PathExists(link)); + EXPECT_TRUE(PathExists(file)); base::FilePath dir(temp_dir.path().Append(FILE_PATH_LITERAL("dir"))); ASSERT_TRUE( @@ -126,8 +229,8 @@ TEST(Filesystem, RemoveFile) { ASSERT_TRUE(CreateSymbolicLink(dir, link)); EXPECT_TRUE(LoggingRemoveFile(link)); - EXPECT_FALSE(FileExists(link)); - EXPECT_TRUE(FileExists(dir)); + EXPECT_FALSE(PathExists(link)); + EXPECT_TRUE(PathExists(dir)); EXPECT_TRUE(LoggingRemoveFile(file)); EXPECT_FALSE(IsRegularFile(file)); diff --git a/util/file/filesystem_test_util.cc b/util/file/filesystem_test_util.cc index 9113b87f..49e13ecb 100644 --- a/util/file/filesystem_test_util.cc +++ b/util/file/filesystem_test_util.cc @@ -14,9 +14,15 @@ #include "util/file/filesystem_test_util.h" +#include +#include +#include + #include "base/logging.h" +#include "base/strings/utf_string_conversions.h" #include "build/build_config.h" #include "gtest/gtest.h" +#include "test/errors.h" #include "util/file/file_io.h" #include "util/file/filesystem.h" @@ -59,5 +65,23 @@ bool CreateSymbolicLink(const base::FilePath& target_path, return true; } +bool PathExists(const base::FilePath& path) { +#if defined(OS_POSIX) + struct stat st; + if (lstat(path.value().c_str(), &st) != 0) { + EXPECT_EQ(errno, ENOENT) << ErrnoMessage("lstat ") << path.value(); + return false; + } +#elif defined(OS_WIN) + if (GetFileAttributes(path.value().c_str()) == INVALID_FILE_ATTRIBUTES) { + EXPECT_EQ(GetLastError(), ERROR_FILE_NOT_FOUND) + << ErrorMessage("GetFileAttributes ") + << base::UTF16ToUTF8(path.value()); + return false; + } +#endif + return true; +} + } // namespace test } // namespace crashpad diff --git a/util/file/filesystem_test_util.h b/util/file/filesystem_test_util.h index 19812f47..393bc8b5 100644 --- a/util/file/filesystem_test_util.h +++ b/util/file/filesystem_test_util.h @@ -25,6 +25,8 @@ bool CreateFile(const base::FilePath& file); bool CreateSymbolicLink(const base::FilePath& target_path, const base::FilePath& symlink_path); +bool PathExists(const base::FilePath& path); + } // namespace test } // namespace crashpad diff --git a/util/file/filesystem_win.cc b/util/file/filesystem_win.cc index 64531b32..5a1242ec 100644 --- a/util/file/filesystem_win.cc +++ b/util/file/filesystem_win.cc @@ -67,6 +67,18 @@ bool LoggingCreateDirectory(const base::FilePath& path, return false; } +bool MoveFileOrDirectory(const base::FilePath& source, + const base::FilePath& dest) { + if (!MoveFileEx(source.value().c_str(), + dest.value().c_str(), + IsDirectory(source, false) ? 0 : MOVEFILE_REPLACE_EXISTING)) { + PLOG(ERROR) << "MoveFileEx" << base::UTF16ToUTF8(source.value()) << ", " + << base::UTF16ToUTF8(dest.value()); + return false; + } + return true; +} + bool IsRegularFile(const base::FilePath& path) { DWORD fileattr = GetFileAttributes(path.value().c_str()); if (fileattr == INVALID_FILE_ATTRIBUTES) {