aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/zen/cmds/copy_cmd.cpp27
-rw-r--r--src/zencore/filesystem.cpp97
-rw-r--r--src/zencore/include/zencore/filesystem.h2
-rw-r--r--src/zenserver/config.cpp14
-rw-r--r--src/zenserver/config.h1
-rw-r--r--src/zenserver/main.cpp7
6 files changed, 146 insertions, 2 deletions
diff --git a/src/zen/cmds/copy_cmd.cpp b/src/zen/cmds/copy_cmd.cpp
index e5ddbfa85..d68a99616 100644
--- a/src/zen/cmds/copy_cmd.cpp
+++ b/src/zen/cmds/copy_cmd.cpp
@@ -45,6 +45,22 @@ CopyCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
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 = std::filesystem::is_regular_file(m_CopySource);
const bool IsDirCopy = std::filesystem::is_directory(m_CopySource);
@@ -76,6 +92,17 @@ CopyCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
std::filesystem::create_directories(ToPath);
}
+ std::filesystem::path ToCanonical = std::filesystem::canonical(ToPath, Ec);
+
+ if (!Ec)
+ {
+ if (!std::filesystem::relative(ToCanonical, FromCanonical).empty() ||
+ !std::filesystem::relative(FromCanonical, ToCanonical).empty())
+ {
+ throw std::runtime_error("Invalid parent/child relationship for source/target directories");
+ }
+ }
+
// Multi file copy
ZEN_CONSOLE("copying {} -> {}", FromPath, ToPath);
diff --git a/src/zencore/filesystem.cpp b/src/zencore/filesystem.cpp
index 06cda7382..4a26c64e7 100644
--- a/src/zencore/filesystem.cpp
+++ b/src/zencore/filesystem.cpp
@@ -613,6 +613,103 @@ CopyFile(std::filesystem::path FromPath, std::filesystem::path ToPath, const Cop
}
void
+CopyTree(std::filesystem::path FromPath, std::filesystem::path ToPath, const CopyFileOptions& Options)
+{
+ // Validate arguments
+
+ if (FromPath.empty() || !std::filesystem::is_directory(FromPath))
+ throw std::runtime_error("invalid CopyTree source directory specified");
+
+ if (ToPath.empty())
+ throw std::runtime_error("no CopyTree target specified");
+
+ if (std::filesystem::exists(ToPath))
+ {
+ if (!std::filesystem::is_directory(ToPath))
+ {
+ throw std::runtime_error(fmt::format("specified CopyTree target '{}' is not a directory", ToPath));
+ }
+ }
+ else
+ {
+ std::filesystem::create_directories(ToPath);
+ }
+
+ 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) override
+ {
+ 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;
+ std::filesystem::path ToPath;
+
+ if (Relative.compare("."))
+ {
+ zen::CreateDirectories(TargetPath / Relative);
+
+ ToPath = TargetPath / Relative / File;
+ }
+ else
+ {
+ ToPath = TargetPath / File;
+ }
+
+ try
+ {
+ if (zen::CopyFile(FromPath, ToPath, CopyOptions))
+ {
+ ++FileCount;
+ ByteCount += FileSize;
+ }
+ else
+ {
+ throw std::runtime_error("CopyFile failed in an unexpected way");
+ }
+ }
+ catch (std::exception& Ex)
+ {
+ ++FailedFileCount;
+
+ throw std::runtime_error(fmt::format("failed to copy '{}' to '{}': '{}'", FromPath, ToPath, Ex.what()));
+ }
+ }
+ }
+
+ virtual bool VisitDirectory(const std::filesystem::path&, const path_view&) override { return true; }
+
+ std::filesystem::path BasePath;
+ std::filesystem::path TargetPath;
+ zen::CopyFileOptions CopyOptions;
+ int FileCount = 0;
+ uint64_t ByteCount = 0;
+ int FailedFileCount = 0;
+ };
+
+ CopyVisitor Visitor{FromPath, Options};
+ Visitor.TargetPath = ToPath;
+
+ FileSystemTraversal Traversal;
+ Traversal.TraverseFileSystem(FromPath, Visitor);
+
+ if (Visitor.FailedFileCount)
+ {
+ throw std::runtime_error(fmt::format("{} file copy operations FAILED", Visitor.FailedFileCount));
+ }
+}
+
+void
WriteFile(std::filesystem::path Path, const IoBuffer* const* Data, size_t BufferCount)
{
#if ZEN_PLATFORM_WINDOWS
diff --git a/src/zencore/include/zencore/filesystem.h b/src/zencore/include/zencore/filesystem.h
index 22eb40e45..963890fb9 100644
--- a/src/zencore/include/zencore/filesystem.h
+++ b/src/zencore/include/zencore/filesystem.h
@@ -1,5 +1,4 @@
// Copyright Epic Games, Inc. All Rights Reserved.
-// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
@@ -87,6 +86,7 @@ struct CopyFileOptions
};
ZENCORE_API bool CopyFile(std::filesystem::path FromPath, std::filesystem::path ToPath, const CopyFileOptions& Options);
+ZENCORE_API void CopyTree(std::filesystem::path FromPath, std::filesystem::path ToPath, const CopyFileOptions& Options);
ZENCORE_API bool SupportsBlockRefCounting(std::filesystem::path Path);
ZENCORE_API void PathToUtf8(const std::filesystem::path& Path, StringBuilderBase& Out);
diff --git a/src/zenserver/config.cpp b/src/zenserver/config.cpp
index e7edd5745..e33c6ff70 100644
--- a/src/zenserver/config.cpp
+++ b/src/zenserver/config.cpp
@@ -938,6 +938,7 @@ ParseCliOptions(int argc, char* argv[], ZenServerOptions& ServerOptions)
std::string AbsLogFile;
std::string ConfigFile;
std::string OutputConfigFile;
+ std::string BaseSnapshotDir;
cxxopts::Options options("zenserver", "Zen Server");
options.add_options()("dedicated",
@@ -951,6 +952,9 @@ ParseCliOptions(int argc, char* argv[], ZenServerOptions& ServerOptions)
options.add_options()("t, test", "Enable test mode", cxxopts::value<bool>(ServerOptions.IsTest)->default_value("false"));
options.add_options()("log-id", "Specify id for adding context to log output", cxxopts::value<std::string>(ServerOptions.LogId));
options.add_options()("data-dir", "Specify persistence root", cxxopts::value<std::string>(DataDir));
+ options.add_options()("snapshot-dir",
+ "Specify a snapshot of server state to mirror into the persistence root at startup",
+ cxxopts::value<std::string>(BaseSnapshotDir));
options.add_options()("content-dir", "Frontend content directory", cxxopts::value<std::string>(ContentDir));
options.add_options()("abslog", "Path to log file", cxxopts::value<std::string>(AbsLogFile));
options.add_options()("config", "Path to Lua config file", cxxopts::value<std::string>(ConfigFile));
@@ -1362,11 +1366,21 @@ ParseCliOptions(int argc, char* argv[], ZenServerOptions& ServerOptions)
}
ServerOptions.DataDir = MakeSafePath(DataDir);
+ ServerOptions.BaseSnapshotDir = MakeSafePath(BaseSnapshotDir);
ServerOptions.ContentDir = MakeSafePath(ContentDir);
ServerOptions.AbsLogFile = MakeSafePath(AbsLogFile);
ServerOptions.ConfigFile = MakeSafePath(ConfigFile);
ServerOptions.UpstreamCacheConfig.CachePolicy = ParseUpstreamCachePolicy(UpstreamCachePolicyOptions);
+ if (!BaseSnapshotDir.empty())
+ {
+ if (DataDir.empty())
+ throw zen::OptionParseException("You must explicitly specify a data directory when specifying a base snapshot");
+
+ if (!std::filesystem::is_directory(ServerOptions.BaseSnapshotDir))
+ throw OptionParseException(fmt::format("Snapshot directory must be a directory: '{}", BaseSnapshotDir));
+ }
+
if (OpenIdProviderUrl.empty() == false)
{
if (OpenIdClientId.empty())
diff --git a/src/zenserver/config.h b/src/zenserver/config.h
index d55f0d5a1..406fb0b70 100644
--- a/src/zenserver/config.h
+++ b/src/zenserver/config.h
@@ -129,6 +129,7 @@ struct ZenServerOptions
std::filesystem::path ContentDir; // Root directory for serving frontend content (experimental)
std::filesystem::path AbsLogFile; // Absolute path to main log file
std::filesystem::path ConfigFile; // Path to Lua config file
+ std::filesystem::path BaseSnapshotDir; // Path to server state snapshot (will be copied into data dir on start)
std::string ChildId; // Id assigned by parent process (used for lifetime management)
std::string LogId; // Id for tagging log output
std::string EncryptionKey; // 256 bit AES encryption key
diff --git a/src/zenserver/main.cpp b/src/zenserver/main.cpp
index 44330d4c8..1aa0aa4df 100644
--- a/src/zenserver/main.cpp
+++ b/src/zenserver/main.cpp
@@ -336,7 +336,7 @@ main(int argc, char* argv[])
ZenServerOptions ServerOptions;
ParseCliOptions(argc, argv, ServerOptions);
- if (ServerOptions.IsCleanStart)
+ if (ServerOptions.IsCleanStart || !ServerOptions.BaseSnapshotDir.empty())
{
DeleteDirectories(ServerOptions.DataDir);
}
@@ -347,6 +347,11 @@ main(int argc, char* argv[])
std::filesystem::create_directories(ServerOptions.DataDir);
}
+ if (!ServerOptions.BaseSnapshotDir.empty())
+ {
+ CopyTree(ServerOptions.BaseSnapshotDir, ServerOptions.DataDir, {.EnableClone = true});
+ }
+
#if ZEN_WITH_TRACE
if (ServerOptions.TraceHost.size())
{