blob: 14a3f8fac91065faf313faf76d5b6ba3b11f363e [file] [log] [blame]
// 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