// Copyright Epic Games, Inc. All Rights Reserved. #include "copy_cmd.h" #include #include #include #include #include namespace zen { CopyCommand::CopyCommand() { m_Options.add_options()("h,help", "Print help"); m_Options.add_options()("no-clone", "Do not perform block clone", cxxopts::value(m_NoClone)->default_value("false")); m_Options.add_options()("must-clone", "Always perform block clone (fails if clone is not possible)", cxxopts::value(m_MustClone)->default_value("false")); m_Options.add_option("", "s", "source", "Copy source", cxxopts::value(m_CopySource), ""); m_Options.add_option("", "t", "target", "Copy target", cxxopts::value(m_CopyTarget), ""); m_Options.parse_positional({"source", "target"}); } CopyCommand::~CopyCommand() = default; int CopyCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) { ZEN_UNUSED(GlobalOptions); if (!ZenCmdBase::ParseOptions(argc, argv)) { return 0; } // Validate arguments if (m_CopySource.empty()) throw std::runtime_error("No source specified"); if (m_CopyTarget.empty()) throw std::runtime_error("No target specified"); std::filesystem::path FromPath; std::filesystem::path ToPath; FromPath = m_CopySource; ToPath = m_CopyTarget; std::error_code Ec; std::filesystem::path FromCanonical = std::filesystem::canonical(FromPath, Ec); if (!Ec) { std::filesystem::path ToCanonical = std::filesystem::canonical(ToPath, Ec); if (!Ec) { if (FromCanonical == ToCanonical) { throw std::runtime_error("Target and source must be distinct files or directories"); } } } const bool IsFileCopy = IsFile(m_CopySource); const bool IsDirCopy = IsDir(m_CopySource); if (!IsFileCopy && !IsDirCopy) { throw std::runtime_error("Invalid source specification (neither directory nor file)"); } if (IsFileCopy && IsDirCopy) { throw std::runtime_error("Invalid source specification (both directory AND file!?)"); } if (IsDirCopy) { if (IsFile(ToPath)) { throw std::runtime_error("Attempted copy of directory into file"); } if (!IsDir(ToPath)) { CreateDirectories(ToPath); } std::filesystem::path ToCanonical = std::filesystem::canonical(ToPath, Ec); if (!Ec) { if (ToCanonical.generic_string().starts_with(FromCanonical.generic_string()) || FromCanonical.generic_string().starts_with(ToCanonical.generic_string())) { throw std::runtime_error("Invalid parent/child relationship for source/target directories"); } } // Multi file copy ZEN_CONSOLE("copying {} -> {}", FromPath, ToPath); zen::Stopwatch Timer; struct CopyVisitor : public FileSystemTraversal::TreeVisitor { CopyVisitor(std::filesystem::path InBasePath, zen::CopyFileOptions InCopyOptions) : BasePath(InBasePath) , CopyOptions(InCopyOptions) { } virtual void VisitFile(const std::filesystem::path& Parent, const path_view& File, uint64_t FileSize, uint32_t, uint64_t) override { ZEN_UNUSED(FileSize); std::error_code Ec; const std::filesystem::path Relative = std::filesystem::relative(Parent, BasePath, Ec); if (Ec) { FailedFileCount++; } else { const std::filesystem::path FromPath = Parent / File; const std::filesystem::path ToPath = TargetPath / Relative / File; try { zen::CreateDirectories(TargetPath / Relative); if (zen::CopyFile(FromPath, ToPath, CopyOptions)) { ++FileCount; ByteCount += FileSize; } else { throw std::logic_error("CopyFile failed in an unexpected way"); } } catch (const std::exception& Ex) { ++FailedFileCount; ZEN_CONSOLE_ERROR("failed to copy '{}' to '{}': '{}'", FromPath, ToPath, Ex.what()); } } } virtual bool VisitDirectory(const std::filesystem::path&, const path_view&, uint32_t) override { return true; } std::filesystem::path BasePath; std::filesystem::path TargetPath; zen::CopyFileOptions CopyOptions; int FileCount = 0; uint64_t ByteCount = 0; int FailedFileCount = 0; }; zen::CopyFileOptions CopyOptions; CopyOptions.EnableClone = !m_NoClone; CopyOptions.MustClone = m_MustClone; CopyVisitor Visitor{FromPath, CopyOptions}; Visitor.TargetPath = ToPath; FileSystemTraversal Traversal; Traversal.TraverseFileSystem(FromPath, Visitor); ZEN_CONSOLE("Copy of {} files ({}) completed in {} ({})", Visitor.FileCount, NiceBytes(Visitor.ByteCount), zen::NiceTimeSpanMs(Timer.GetElapsedTimeMs()), zen::NiceRate(Visitor.ByteCount, (uint32_t)Timer.GetElapsedTimeMs())); if (Visitor.FailedFileCount) { ZEN_CONSOLE_ERROR("{} file copy operations FAILED", Visitor.FailedFileCount); return 1; } } else { // Single file copy zen::Stopwatch Timer; zen::CopyFileOptions CopyOptions; CopyOptions.EnableClone = !m_NoClone; try { zen::CreateDirectories(ToPath.parent_path()); if (zen::CopyFile(FromPath, ToPath, CopyOptions)) { ZEN_CONSOLE("Copy completed in {}", zen::NiceTimeSpanMs(Timer.GetElapsedTimeMs())); } else { throw std::logic_error("CopyFile failed in an unexpected way"); } } catch (const std::exception& Ex) { ZEN_CONSOLE_ERROR("Error: failed to copy '{}' to '{}': '{}'", FromPath, ToPath, Ex.what()); return 1; } } return 0; } } // namespace zen