| // 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 <dirent.h> |
| #include <fcntl.h> |
| #include <lib/syslog/cpp/macros.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <zircon/errors.h> |
| |
| #include <filesystem> |
| #include <memory> |
| #include <string> |
| #include <variant> |
| #include <vector> |
| |
| #include <fbl/unique_fd.h> |
| |
| #include "src/lib/files/directory.h" |
| #include "src/lib/files/file.h" |
| |
| namespace fshost { |
| namespace { |
| |
| struct DirCloser { |
| void operator()(DIR* dir) { closedir(dir); } |
| }; |
| // RAII wrapper around a DIR* that close the DIR when it goes out of scope. |
| using UniqueDir = std::unique_ptr<DIR, DirCloser>; |
| |
| UniqueDir OpenDir(fbl::unique_fd fd) { |
| UniqueDir dir(fdopendir(fd.get())); |
| if (dir) { |
| // DIR only takes ownership of the file descriptor on success. |
| fd.release(); |
| } |
| return dir; |
| } |
| |
| bool IsPathExcluded(const std::vector<std::filesystem::path>& excluded_paths, |
| const std::filesystem::path& path) { |
| for (const auto& exclusion : excluded_paths) { |
| if (exclusion.empty()) { |
| // Skip the empty path. It would cause all files to be excluded which is probably not what the |
| // caller wanted. |
| continue; |
| } |
| auto exclusion_it = exclusion.begin(); |
| auto path_it = path.begin(); |
| while (exclusion_it != exclusion.end() && path_it != path.end()) { |
| if (*exclusion_it != *path_it) { |
| break; |
| } |
| ++exclusion_it; |
| ++path_it; |
| } |
| if (exclusion_it == exclusion.end()) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| Copier::DirectoryEntry* GetEntry(Copier::DirectoryEntries& entries, const std::string& name) { |
| for (auto& entry : entries) { |
| if (std::visit([&name](auto& entry) { return entry.name == name; }, entry)) { |
| return &entry; |
| } |
| } |
| return nullptr; |
| } |
| |
| } // namespace |
| |
| zx::status<Copier> Copier::Read(fbl::unique_fd root_fd, |
| const std::vector<std::filesystem::path>& excluded_paths) { |
| struct PendingRead { |
| UniqueDir dir; |
| DirectoryEntries* entries; |
| // The path relative to |root_fd|. |
| std::filesystem::path path; |
| }; |
| std::vector<PendingRead> pending; |
| |
| Copier copier; |
| { |
| UniqueDir dir = OpenDir(std::move(root_fd)); |
| if (!dir) |
| return zx::error(ZX_ERR_BAD_STATE); |
| pending.push_back({ |
| .dir = std::move(dir), |
| .entries = &copier.entries_, |
| .path = "", |
| }); |
| } |
| while (!pending.empty()) { |
| PendingRead current = std::move(pending.back()); |
| pending.pop_back(); |
| struct dirent* entry; |
| while ((entry = readdir(current.dir.get())) != nullptr) { |
| std::string name(entry->d_name); |
| std::filesystem::path path = current.path / name; |
| if (IsPathExcluded(excluded_paths, path)) |
| continue; |
| fbl::unique_fd fd(openat(dirfd(current.dir.get()), name.c_str(), O_RDONLY)); |
| if (!fd) |
| return zx::error(ZX_ERR_BAD_STATE); |
| switch (entry->d_type) { |
| case DT_REG: { |
| struct stat stat_buf; |
| if (fstat(fd.get(), &stat_buf) != 0) { |
| return zx::error(ZX_ERR_BAD_STATE); |
| } |
| std::string buf; |
| buf.reserve(stat_buf.st_size); |
| if (!files::ReadFileDescriptorToString(fd.get(), &buf)) { |
| return zx::error(ZX_ERR_BAD_STATE); |
| } |
| current.entries->push_back(File{std::move(name), std::move(buf)}); |
| break; |
| } |
| case DT_DIR: { |
| if (name == "." || name == "..") |
| continue; |
| UniqueDir child_dir = OpenDir(std::move(fd)); |
| if (!child_dir) |
| return zx::error(ZX_ERR_BAD_STATE); |
| current.entries->push_back(Directory{std::move(name), {}}); |
| pending.push_back({ |
| .dir = std::move(child_dir), |
| .entries = &std::get<Directory>(current.entries->back()).entries, |
| .path = std::move(path), |
| }); |
| break; |
| } |
| } |
| } |
| } |
| return zx::ok(std::move(copier)); |
| } |
| |
| zx_status_t Copier::Write(fbl::unique_fd root_fd) const { |
| std::vector<std::pair<fbl::unique_fd, const DirectoryEntries*>> pending; |
| pending.emplace_back(std::move(root_fd), &entries_); |
| while (!pending.empty()) { |
| fbl::unique_fd fd = std::move(pending.back().first); |
| const DirectoryEntries* entries = pending.back().second; |
| pending.pop_back(); |
| // Fail to compile if extra types are added. |
| static_assert(std::variant_size_v<DirectoryEntry> == 2); |
| for (const auto& entry : *entries) { |
| if (std::holds_alternative<File>(entry)) { |
| const File& file = std::get<File>(entry); |
| if (!files::WriteFileAt(fd.get(), file.name, file.contents.data(), |
| static_cast<ssize_t>(file.contents.size()))) { |
| FX_LOGS(ERROR) << "Unable to write to " << file.name; |
| return ZX_ERR_BAD_STATE; |
| } |
| } else if (std::holds_alternative<Directory>(entry)) { |
| const Directory& directory = std::get<Directory>(entry); |
| if (!files::CreateDirectoryAt(fd.get(), directory.name)) { |
| FX_LOGS(ERROR) << "Unable to make directory " << directory.name; |
| return ZX_ERR_BAD_STATE; |
| } |
| fbl::unique_fd child_fd(openat(fd.get(), directory.name.c_str(), O_RDONLY)); |
| if (!child_fd) { |
| FX_LOGS(ERROR) << "Unable to open directory " << directory.name; |
| return ZX_ERR_BAD_STATE; |
| } |
| pending.emplace_back(std::move(child_fd), &directory.entries); |
| } |
| } |
| } |
| return ZX_OK; |
| } |
| |
| zx::status<> Copier::InsertFile(const std::filesystem::path& path, std::string contents) { |
| if (path.filename().empty() || path.is_absolute()) { |
| // |path| was either empty, ended with '/', or started with '/'. |
| return zx::error(ZX_ERR_INVALID_ARGS); |
| } |
| DirectoryEntries* entries = &entries_; |
| for (const auto& parent : path.parent_path()) { |
| DirectoryEntry* entry = GetEntry(*entries, parent); |
| if (entry == nullptr) { |
| entries->push_back(Directory{parent, {}}); |
| entries = &std::get<Directory>(entries->back()).entries; |
| } else if (Directory* child_dir = std::get_if<Directory>(entry); child_dir != nullptr) { |
| entries = &child_dir->entries; |
| } else { |
| // A file exists where a directory needed to be created. |
| return zx::error(ZX_ERR_BAD_STATE); |
| } |
| } |
| if (GetEntry(*entries, path.filename()) != nullptr) { |
| // The file already exists. |
| return zx::error(ZX_ERR_ALREADY_EXISTS); |
| } |
| entries->push_back(File{path.filename(), std::move(contents)}); |
| return zx::ok(); |
| } |
| |
| } // namespace fshost |