| // Copyright 2021 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/storage/fshost/copier.h" |
| |
| #include <fcntl.h> |
| #include <unistd.h> |
| #include <zircon/errors.h> |
| |
| #include <cerrno> |
| #include <filesystem> |
| |
| #include <fbl/unique_fd.h> |
| #include <gtest/gtest.h> |
| |
| #include "src/lib/files/directory.h" |
| #include "src/lib/files/file.h" |
| |
| namespace fshost { |
| namespace { |
| |
| class CopierTest : public testing::Test { |
| public: |
| CopierTest() |
| : src_dir_(std::filesystem::temp_directory_path() / "copier_test_src"), |
| dst_dir_(std::filesystem::temp_directory_path() / "copier_test_dst") {} |
| |
| void SetUp() override { |
| ASSERT_TRUE(files::CreateDirectory(src_dir_)); |
| ASSERT_TRUE(files::CreateDirectory(dst_dir_)); |
| } |
| |
| ~CopierTest() override { |
| std::filesystem::remove_all(src_dir_); |
| std::filesystem::remove_all(dst_dir_); |
| } |
| |
| std::filesystem::path src_path(const std::filesystem::path& p) { return src_dir() / p; } |
| const std::filesystem::path& src_dir() { return src_dir_; } |
| |
| std::filesystem::path dst_path(const std::filesystem::path& p) { return dst_dir() / p; } |
| const std::filesystem::path& dst_dir() { return dst_dir_; } |
| |
| static std::string GetFileContents(const std::filesystem::path& path) { |
| std::string contents; |
| EXPECT_TRUE(files::ReadFileToString(path, &contents)); |
| return contents; |
| } |
| |
| static bool WriteFileContents(const std::filesystem::path& path, const std::string& contents) { |
| return files::WriteFile(path, contents.data(), static_cast<ssize_t>(contents.size())); |
| } |
| |
| static bool DoesFileExist(const std::filesystem::path& path) { |
| if (access(path.c_str(), F_OK) != 0) { |
| if (errno != ENOENT) { |
| ADD_FAILURE() << "IO error"; |
| } |
| return false; |
| } |
| return true; |
| } |
| |
| private: |
| std::filesystem::path src_dir_; |
| std::filesystem::path dst_dir_; |
| }; |
| |
| TEST_F(CopierTest, Copy) { |
| ASSERT_TRUE(WriteFileContents(src_path("file1"), "hello1")); |
| ASSERT_TRUE(files::CreateDirectory(src_path("dir"))); |
| ASSERT_TRUE(WriteFileContents(src_path("dir/file2"), "hello2")); |
| |
| fbl::unique_fd fd(open(src_dir().c_str(), O_RDONLY)); |
| ASSERT_TRUE(fd); |
| |
| auto data_or = Copier::Read(std::move(fd)); |
| ASSERT_TRUE(data_or.is_ok()) << data_or.status_string(); |
| |
| fd = fbl::unique_fd(open(dst_dir().c_str(), O_RDONLY)); |
| ASSERT_TRUE(fd); |
| |
| EXPECT_EQ(data_or->Write(std::move(fd)), ZX_OK); |
| |
| EXPECT_EQ(GetFileContents(dst_path("file1")), "hello1"); |
| EXPECT_EQ(GetFileContents(dst_path("dir/file2")), "hello2"); |
| } |
| |
| TEST_F(CopierTest, ReadWithEmptyPathExcludedIsIgnored) { |
| ASSERT_TRUE(WriteFileContents(src_path("file1"), "hello1")); |
| ASSERT_TRUE(files::CreateDirectory(src_path("dir"))); |
| ASSERT_TRUE(WriteFileContents(src_path("dir/file2"), "hello2")); |
| |
| fbl::unique_fd fd(open(src_dir().c_str(), O_RDONLY)); |
| ASSERT_TRUE(fd); |
| |
| // The empty path would cause everything to be excluded which defeats the purpose of using the |
| // copier so it's explicitly ignored. |
| std::vector<std::filesystem::path> excluded_prefix_list = {""}; |
| auto data_or = Copier::Read(std::move(fd), excluded_prefix_list); |
| ASSERT_TRUE(data_or.is_ok()) << data_or.status_string(); |
| |
| fd = fbl::unique_fd(open(dst_dir().c_str(), O_RDONLY)); |
| ASSERT_TRUE(fd); |
| |
| EXPECT_EQ(data_or->Write(std::move(fd)), ZX_OK); |
| |
| EXPECT_TRUE(DoesFileExist(dst_path("file1"))); |
| EXPECT_TRUE(DoesFileExist(dst_path("dir"))); |
| EXPECT_TRUE(DoesFileExist(dst_path("dir/file2"))); |
| } |
| |
| TEST_F(CopierTest, ReadWithFileExcludedDoesNotCopyFile) { |
| ASSERT_TRUE(files::CreateDirectory(src_path("dir"))); |
| ASSERT_TRUE(WriteFileContents(src_path("dir/file"), "hello")); |
| |
| fbl::unique_fd fd(open(src_dir().c_str(), O_RDONLY)); |
| ASSERT_TRUE(fd); |
| |
| // Exact exclusion match. |
| std::vector<std::filesystem::path> excluded_prefix_list = {"dir/file"}; |
| auto data_or = Copier::Read(std::move(fd), excluded_prefix_list); |
| ASSERT_TRUE(data_or.is_ok()) << data_or.status_string(); |
| |
| fd = fbl::unique_fd(open(dst_dir().c_str(), O_RDONLY)); |
| ASSERT_TRUE(fd); |
| |
| EXPECT_EQ(data_or->Write(std::move(fd)), ZX_OK); |
| |
| // The parent directory wasn't matched, only the file was. |
| EXPECT_TRUE(DoesFileExist(dst_path("dir"))); |
| EXPECT_FALSE(DoesFileExist(dst_path("dir/file"))); |
| } |
| |
| TEST_F(CopierTest, ReadWithParentExcludedDoesNotCopyChild) { |
| ASSERT_TRUE(files::CreateDirectory(src_path("dir"))); |
| ASSERT_TRUE(WriteFileContents(src_path("dir/file"), "hello")); |
| |
| fbl::unique_fd fd(open(src_dir().c_str(), O_RDONLY)); |
| ASSERT_TRUE(fd); |
| |
| std::vector<std::filesystem::path> excluded_prefix_list = {"dir"}; |
| auto data_or = Copier::Read(std::move(fd), excluded_prefix_list); |
| ASSERT_TRUE(data_or.is_ok()) << data_or.status_string(); |
| |
| fd = fbl::unique_fd(open(dst_dir().c_str(), O_RDONLY)); |
| ASSERT_TRUE(fd); |
| |
| EXPECT_EQ(data_or->Write(std::move(fd)), ZX_OK); |
| |
| EXPECT_FALSE(DoesFileExist(dst_path("dir"))); |
| EXPECT_FALSE(DoesFileExist(dst_path("dir/file"))); |
| } |
| |
| TEST_F(CopierTest, ReadWithGrandparentExcludedDoesNotCopyGrandchild) { |
| ASSERT_TRUE(files::CreateDirectory(src_path("dir1"))); |
| ASSERT_TRUE(files::CreateDirectory(src_path("dir1/dir2"))); |
| ASSERT_TRUE(WriteFileContents(src_path("dir1/dir2/file"), "hello")); |
| |
| fbl::unique_fd fd(open(src_dir().c_str(), O_RDONLY)); |
| ASSERT_TRUE(fd); |
| |
| std::vector<std::filesystem::path> excluded_prefix_list = {"dir1"}; |
| auto data_or = Copier::Read(std::move(fd), excluded_prefix_list); |
| ASSERT_TRUE(data_or.is_ok()) << data_or.status_string(); |
| |
| fd = fbl::unique_fd(open(dst_dir().c_str(), O_RDONLY)); |
| ASSERT_TRUE(fd); |
| |
| EXPECT_EQ(data_or->Write(std::move(fd)), ZX_OK); |
| |
| EXPECT_FALSE(DoesFileExist(dst_path("dir1"))); |
| } |
| |
| TEST_F(CopierTest, ReadWithExclusionDoesNotMatchPartialNames) { |
| ASSERT_TRUE(WriteFileContents(src_path("filename"), "hello")); |
| |
| fbl::unique_fd fd(open(src_dir().c_str(), O_RDONLY)); |
| ASSERT_TRUE(fd); |
| |
| // "file" does not match "filename". |
| std::vector<std::filesystem::path> excluded_prefix_list = {"file"}; |
| auto data_or = Copier::Read(std::move(fd), excluded_prefix_list); |
| ASSERT_TRUE(data_or.is_ok()) << data_or.status_string(); |
| |
| fd = fbl::unique_fd(open(dst_dir().c_str(), O_RDONLY)); |
| ASSERT_TRUE(fd); |
| |
| EXPECT_EQ(data_or->Write(std::move(fd)), ZX_OK); |
| |
| EXPECT_TRUE(DoesFileExist(dst_path("filename"))); |
| } |
| |
| TEST_F(CopierTest, ReadWithExclusionDoesNotMatchSuffixes) { |
| ASSERT_TRUE(files::CreateDirectory(src_path("dir"))); |
| ASSERT_TRUE(WriteFileContents(src_path("dir/file"), "hello")); |
| |
| fbl::unique_fd fd(open(src_dir().c_str(), O_RDONLY)); |
| ASSERT_TRUE(fd); |
| |
| // "file" does not match "dir/file". |
| std::vector<std::filesystem::path> excluded_prefix_list = {"file"}; |
| auto data_or = Copier::Read(std::move(fd), excluded_prefix_list); |
| ASSERT_TRUE(data_or.is_ok()) << data_or.status_string(); |
| |
| fd = fbl::unique_fd(open(dst_dir().c_str(), O_RDONLY)); |
| ASSERT_TRUE(fd); |
| |
| EXPECT_EQ(data_or->Write(std::move(fd)), ZX_OK); |
| |
| EXPECT_TRUE(DoesFileExist(dst_path("dir/file"))); |
| } |
| |
| TEST_F(CopierTest, InsertFileWorks) { |
| Copier copier; |
| // File at root. |
| EXPECT_EQ(copier.InsertFile("file1.txt", "hello1").status_value(), ZX_OK); |
| // Create a parent directory. |
| EXPECT_EQ(copier.InsertFile("dir1/file2.txt", "hello2").status_value(), ZX_OK); |
| // Create a file in an existing directory. |
| EXPECT_EQ(copier.InsertFile("dir1/file3.txt", "hello3").status_value(), ZX_OK); |
| // Create a parent directory in another directory. |
| EXPECT_EQ(copier.InsertFile("dir1/dir2/file4.txt", "hello4").status_value(), ZX_OK); |
| |
| fbl::unique_fd fd = fbl::unique_fd(open(dst_dir().c_str(), O_RDONLY)); |
| ASSERT_TRUE(fd); |
| EXPECT_EQ(copier.Write(std::move(fd)), ZX_OK); |
| |
| EXPECT_EQ(GetFileContents(dst_path("file1.txt")), "hello1"); |
| EXPECT_EQ(GetFileContents(dst_path("dir1/file2.txt")), "hello2"); |
| EXPECT_EQ(GetFileContents(dst_path("dir1/file3.txt")), "hello3"); |
| EXPECT_EQ(GetFileContents(dst_path("dir1/dir2/file4.txt")), "hello4"); |
| } |
| |
| TEST_F(CopierTest, InsertFileWithExistingFileIsAnError) { |
| Copier copier; |
| EXPECT_EQ(copier.InsertFile("file1.txt", "hello1").status_value(), ZX_OK); |
| EXPECT_EQ(copier.InsertFile("file1.txt", "hello2").status_value(), ZX_ERR_ALREADY_EXISTS); |
| |
| EXPECT_EQ(copier.InsertFile("dir1/file2.txt", "hello3").status_value(), ZX_OK); |
| EXPECT_EQ(copier.InsertFile("dir1/file2.txt", "hello4").status_value(), ZX_ERR_ALREADY_EXISTS); |
| |
| fbl::unique_fd fd = fbl::unique_fd(open(dst_dir().c_str(), O_RDONLY)); |
| ASSERT_TRUE(fd); |
| EXPECT_EQ(copier.Write(std::move(fd)), ZX_OK); |
| |
| EXPECT_EQ(GetFileContents(dst_path("file1.txt")), "hello1"); |
| EXPECT_EQ(GetFileContents(dst_path("dir1/file2.txt")), "hello3"); |
| } |
| |
| TEST_F(CopierTest, InsertFileWithFileAtParentDirectoryIsAnError) { |
| Copier copier; |
| EXPECT_EQ(copier.InsertFile("foo", "hello1").status_value(), ZX_OK); |
| EXPECT_EQ(copier.InsertFile("foo/file1.txt", "hello1").status_value(), ZX_ERR_BAD_STATE); |
| } |
| |
| TEST_F(CopierTest, InsertFileWithEndingSlashIsAnError) { |
| Copier copier; |
| EXPECT_EQ(copier.InsertFile("file1/", "hello1").status_value(), ZX_ERR_INVALID_ARGS); |
| } |
| |
| TEST_F(CopierTest, InsertFileWithAbsolutePathIsAnError) { |
| Copier copier; |
| EXPECT_EQ(copier.InsertFile("/file1.txt", "hello1").status_value(), ZX_ERR_INVALID_ARGS); |
| } |
| |
| } // namespace |
| } // namespace fshost |