aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorStefan Boberg <[email protected]>2023-11-21 14:52:56 +0100
committerGitHub <[email protected]>2023-11-21 14:52:56 +0100
commit8e1cb139d817880c557d407c363eb838c0893f5a (patch)
tree69da94b1df3f2fd1d19e623084c88543c0ce85f6 /src
parentbasic ZEN_ASSERT_FORMAT implementation (#556) (diff)
downloadzen-8e1cb139d817880c557d407c363eb838c0893f5a.tar.xz
zen-8e1cb139d817880c557d407c363eb838c0893f5a.zip
zen run command (#552)
initial version -- this is primarily intended to be used for running stress tests and/or benchmarks example usage: `zen run -n 10 -- zenserver-test` `zen run -n 10 -- zenserver-test --ts=core.assert` run zenserver-test 10 times (testing only the `core.assert` test suite) `zen run --time 600 --basepath=d:\test_dir\test1 -- zenserver-test` keeps spawning new instances for 10 minutes (600 seconds)
Diffstat (limited to 'src')
-rw-r--r--src/zen/cmds/admin_cmd.h10
-rw-r--r--src/zen/cmds/bench_cmd.cpp4
-rw-r--r--src/zen/cmds/bench_cmd.h5
-rw-r--r--src/zen/cmds/cache_cmd.cpp4
-rw-r--r--src/zen/cmds/cache_cmd.h12
-rw-r--r--src/zen/cmds/copy_cmd.h1
-rw-r--r--src/zen/cmds/dedup_cmd.h1
-rw-r--r--src/zen/cmds/hash_cmd.cpp172
-rw-r--r--src/zen/cmds/hash_cmd.h27
-rw-r--r--src/zen/cmds/print_cmd.h2
-rw-r--r--src/zen/cmds/projectstore_cmd.h29
-rw-r--r--src/zen/cmds/rpcreplay_cmd.h6
-rw-r--r--src/zen/cmds/run_cmd.cpp197
-rw-r--r--src/zen/cmds/run_cmd.h27
-rw-r--r--src/zen/cmds/vfs_cmd.h2
-rw-r--r--src/zen/internalfile.cpp306
-rw-r--r--src/zen/internalfile.h64
-rw-r--r--src/zen/zen.cpp184
-rw-r--r--src/zen/zen.h29
-rw-r--r--src/zencore/filesystem.cpp119
-rw-r--r--src/zencore/include/zencore/filesystem.h3
-rw-r--r--src/zencore/include/zencore/process.h4
-rw-r--r--src/zencore/process.cpp61
-rw-r--r--src/zenserver-test/zenserver-test.cpp11
24 files changed, 605 insertions, 675 deletions
diff --git a/src/zen/cmds/admin_cmd.h b/src/zen/cmds/admin_cmd.h
index 356f58363..1938ad1fa 100644
--- a/src/zen/cmds/admin_cmd.h
+++ b/src/zen/cmds/admin_cmd.h
@@ -10,7 +10,7 @@ namespace zen {
/** Scrub storage
*/
-class ScrubCommand : public ZenCmdBase
+class ScrubCommand : public StorageCommand
{
public:
ScrubCommand();
@@ -26,7 +26,7 @@ private:
/** Garbage collect storage
*/
-class GcCommand : public ZenCmdBase
+class GcCommand : public StorageCommand
{
public:
GcCommand();
@@ -47,7 +47,7 @@ private:
bool m_ForceUseGCV2{false};
};
-class GcStatusCommand : public ZenCmdBase
+class GcStatusCommand : public StorageCommand
{
public:
GcStatusCommand();
@@ -101,7 +101,7 @@ private:
/** Flush storage
*/
-class FlushCommand : public ZenCmdBase
+class FlushCommand : public StorageCommand
{
public:
FlushCommand();
@@ -117,7 +117,7 @@ private:
/** Copy state
*/
-class CopyStateCommand : public ZenCmdBase
+class CopyStateCommand : public StorageCommand
{
public:
CopyStateCommand();
diff --git a/src/zen/cmds/bench_cmd.cpp b/src/zen/cmds/bench_cmd.cpp
index 4b5c471de..d0a513a1d 100644
--- a/src/zen/cmds/bench_cmd.cpp
+++ b/src/zen/cmds/bench_cmd.cpp
@@ -131,6 +131,8 @@ EmptyStandByList()
#endif
+namespace zen {
+
BenchCommand::BenchCommand()
{
m_Options.add_options()("h,help", "Print help");
@@ -216,3 +218,5 @@ BenchCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
return 0;
}
+
+} // namespace zen
diff --git a/src/zen/cmds/bench_cmd.h b/src/zen/cmds/bench_cmd.h
index 8a8bd4a7c..29d7fcc08 100644
--- a/src/zen/cmds/bench_cmd.h
+++ b/src/zen/cmds/bench_cmd.h
@@ -4,6 +4,8 @@
#include "../zen.h"
+namespace zen {
+
class BenchCommand : public ZenCmdBase
{
public:
@@ -12,9 +14,12 @@ public:
virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
virtual cxxopts::Options& Options() override { return m_Options; }
+ virtual ZenCmdCategory& CommandCategory() const override { return g_UtilitiesCategory; }
private:
cxxopts::Options m_Options{"bench", "Benchmarking utility command"};
bool m_PurgeStandbyLists = false;
bool m_SingleProcess = false;
};
+
+} // namespace zen
diff --git a/src/zen/cmds/cache_cmd.cpp b/src/zen/cmds/cache_cmd.cpp
index 1bf6ee60e..823f10f1c 100644
--- a/src/zen/cmds/cache_cmd.cpp
+++ b/src/zen/cmds/cache_cmd.cpp
@@ -14,6 +14,8 @@ ZEN_THIRD_PARTY_INCLUDES_START
#include <cpr/cpr.h>
ZEN_THIRD_PARTY_INCLUDES_END
+namespace zen {
+
DropCommand::DropCommand()
{
m_Options.add_options()("h,help", "Print help");
@@ -302,3 +304,5 @@ CacheDetailsCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** ar
return 1;
}
+
+} // namespace zen
diff --git a/src/zen/cmds/cache_cmd.h b/src/zen/cmds/cache_cmd.h
index 1f368bdec..80079c452 100644
--- a/src/zen/cmds/cache_cmd.h
+++ b/src/zen/cmds/cache_cmd.h
@@ -4,7 +4,9 @@
#include "../zen.h"
-class DropCommand : public ZenCmdBase
+namespace zen {
+
+class DropCommand : public CacheStoreCommand
{
public:
DropCommand();
@@ -20,7 +22,7 @@ private:
std::string m_BucketName;
};
-class CacheInfoCommand : public ZenCmdBase
+class CacheInfoCommand : public CacheStoreCommand
{
public:
CacheInfoCommand();
@@ -35,7 +37,7 @@ private:
std::string m_BucketName;
};
-class CacheStatsCommand : public ZenCmdBase
+class CacheStatsCommand : public CacheStoreCommand
{
public:
CacheStatsCommand();
@@ -48,7 +50,7 @@ private:
std::string m_HostName;
};
-class CacheDetailsCommand : public ZenCmdBase
+class CacheDetailsCommand : public CacheStoreCommand
{
public:
CacheDetailsCommand();
@@ -66,3 +68,5 @@ private:
std::string m_Bucket;
std::string m_ValueKey;
};
+
+} // namespace zen
diff --git a/src/zen/cmds/copy_cmd.h b/src/zen/cmds/copy_cmd.h
index 549114160..0dbebddfa 100644
--- a/src/zen/cmds/copy_cmd.h
+++ b/src/zen/cmds/copy_cmd.h
@@ -16,6 +16,7 @@ public:
virtual cxxopts::Options& Options() override { return m_Options; }
virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
+ virtual ZenCmdCategory& CommandCategory() const override { return g_UtilitiesCategory; }
private:
cxxopts::Options m_Options{"copy", "Copy files"};
diff --git a/src/zen/cmds/dedup_cmd.h b/src/zen/cmds/dedup_cmd.h
index 6318704f5..c4f0068e4 100644
--- a/src/zen/cmds/dedup_cmd.h
+++ b/src/zen/cmds/dedup_cmd.h
@@ -16,6 +16,7 @@ public:
virtual cxxopts::Options& Options() override { return m_Options; }
virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
+ virtual ZenCmdCategory& CommandCategory() const override { return g_UtilitiesCategory; }
private:
cxxopts::Options m_Options{"dedup", "Deduplicate files"};
diff --git a/src/zen/cmds/hash_cmd.cpp b/src/zen/cmds/hash_cmd.cpp
deleted file mode 100644
index f5541906b..000000000
--- a/src/zen/cmds/hash_cmd.cpp
+++ /dev/null
@@ -1,172 +0,0 @@
-// Copyright Epic Games, Inc. All Rights Reserved.
-
-#include "hash_cmd.h"
-
-#include <zencore/blake3.h>
-#include <zencore/fmtutils.h>
-#include <zencore/logging.h>
-#include <zencore/string.h>
-#include <zencore/timer.h>
-
-#if ZEN_PLATFORM_WINDOWS
-# include <ppl.h>
-#endif
-
-namespace zen {
-
-////////////////////////////////////////////////////////////////////////////////
-
-#if ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
-
-namespace Concurrency {
-
- template<typename IterType, typename LambdaType>
- void parallel_for_each(IterType Cursor, IterType End, const LambdaType& Lambda)
- {
- for (; Cursor < End; ++Cursor)
- {
- Lambda(*Cursor);
- }
- }
-
- template<typename T>
- struct combinable
- {
- combinable<T>& local() { return *this; }
-
- void operator+=(T Rhs) { Value += Rhs; }
-
- template<typename LambdaType>
- void combine_each(const LambdaType& Lambda)
- {
- Lambda(Value);
- }
-
- T Value = 0;
- };
-
-} // namespace Concurrency
-
-#endif // ZEN_PLATFORM_LINUX|MAC
-
-////////////////////////////////////////////////////////////////////////////////
-
-HashCommand::HashCommand()
-{
- m_Options.add_options()("d,dir", "Directory to scan", cxxopts::value<std::string>(m_ScanDirectory))(
- "o,output",
- "Output file",
- cxxopts::value<std::string>(m_OutputFile));
-}
-
-HashCommand::~HashCommand() = default;
-
-int
-HashCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
-{
- ZEN_UNUSED(GlobalOptions);
-
- if (!ParseOptions(argc, argv))
- {
- return 0;
- }
-
- bool valid = m_ScanDirectory.length();
-
- if (!valid)
- throw zen::OptionParseException("Hash command requires a directory to scan");
-
- // Gather list of files to process
-
- ZEN_CONSOLE("Gathering files from {}", m_ScanDirectory);
-
- struct FileEntry
- {
- std::filesystem::path FilePath;
- zen::BLAKE3 FileHash;
- };
-
- std::vector<FileEntry> FileList;
- uint64_t FileBytes = 0;
-
- std::filesystem::path ScanDirectoryPath{m_ScanDirectory};
-
- for (const std::filesystem::directory_entry& Entry : std::filesystem::recursive_directory_iterator(ScanDirectoryPath))
- {
- if (Entry.is_regular_file())
- {
- FileList.push_back({Entry.path()});
- FileBytes += Entry.file_size();
- }
- }
-
- ZEN_CONSOLE("Gathered {} files, total size {}", FileList.size(), zen::NiceBytes(FileBytes));
-
- Concurrency::combinable<uint64_t> TotalBytes;
-
- auto hashFile = [&](FileEntry& File) {
- InternalFile InputFile;
- InputFile.OpenRead(File.FilePath);
- const uint8_t* DataPointer = (const uint8_t*)InputFile.MemoryMapFile();
- const size_t DataSize = InputFile.GetFileSize();
-
- File.FileHash = zen::BLAKE3::HashMemory(DataPointer, DataSize);
-
- TotalBytes.local() += DataSize;
- };
-
- // Process them as quickly as possible
-
- zen::Stopwatch Timer;
-
-#if 1
- Concurrency::parallel_for_each(begin(FileList), end(FileList), [&](auto& file) { hashFile(file); });
-#else
- for (const auto& file : FileList)
- {
- hashFile(file);
- }
-#endif
-
- size_t TotalByteCount = 0;
-
- TotalBytes.combine_each([&](size_t Total) { TotalByteCount += Total; });
-
- const uint64_t ElapsedMs = Timer.GetElapsedTimeMs();
- ZEN_CONSOLE("Scanned {} files in {}", FileList.size(), zen::NiceTimeSpanMs(ElapsedMs));
- ZEN_CONSOLE("Total bytes {} ({})", zen::NiceBytes(TotalByteCount), zen::NiceByteRate(TotalByteCount, ElapsedMs));
-
- InternalFile Output;
-
- if (m_OutputFile.empty())
- {
- // TEMPORARY -- should properly open stdout
- Output.OpenWrite("CONOUT$", false);
- }
- else
- {
- Output.OpenWrite(m_OutputFile, true);
- }
-
- zen::ExtendableStringBuilder<256> Line;
-
- uint64_t CurrentOffset = 0;
-
- for (const auto& File : FileList)
- {
- Line.Append(File.FilePath.generic_u8string().c_str());
- Line.Append(',');
- File.FileHash.ToHexString(Line);
- Line.Append('\n');
-
- Output.Write(Line.Data(), Line.Size(), CurrentOffset);
- CurrentOffset += Line.Size();
-
- Line.Reset();
- }
-
- // TODO: implement snapshot enumeration and display
- return 0;
-}
-
-} // namespace zen
diff --git a/src/zen/cmds/hash_cmd.h b/src/zen/cmds/hash_cmd.h
deleted file mode 100644
index e5ee071e9..000000000
--- a/src/zen/cmds/hash_cmd.h
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright Epic Games, Inc. All Rights Reserved.
-
-#pragma once
-
-#include "../internalfile.h"
-#include "../zen.h"
-
-namespace zen {
-
-/** Generate hash list file
- */
-class HashCommand : public ZenCmdBase
-{
-public:
- HashCommand();
- ~HashCommand();
-
- virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
- virtual cxxopts::Options& Options() override { return m_Options; }
-
-private:
- cxxopts::Options m_Options{"hash", "Hash files"};
- std::string m_ScanDirectory;
- std::string m_OutputFile;
-};
-
-} // namespace zen
diff --git a/src/zen/cmds/print_cmd.h b/src/zen/cmds/print_cmd.h
index 09d91830a..4d6a492b7 100644
--- a/src/zen/cmds/print_cmd.h
+++ b/src/zen/cmds/print_cmd.h
@@ -16,6 +16,7 @@ public:
virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
virtual cxxopts::Options& Options() override { return m_Options; }
+ virtual ZenCmdCategory& CommandCategory() const override { return g_UtilitiesCategory; }
private:
cxxopts::Options m_Options{"print", "Print compact binary object"};
@@ -32,6 +33,7 @@ public:
virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
virtual cxxopts::Options& Options() override { return m_Options; }
+ virtual ZenCmdCategory& CommandCategory() const override { return g_UtilitiesCategory; }
private:
cxxopts::Options m_Options{"printpkg", "Print compact binary package"};
diff --git a/src/zen/cmds/projectstore_cmd.h b/src/zen/cmds/projectstore_cmd.h
index fd1590423..8891fdaf4 100644
--- a/src/zen/cmds/projectstore_cmd.h
+++ b/src/zen/cmds/projectstore_cmd.h
@@ -6,7 +6,12 @@
namespace zen {
-class DropProjectCommand : public ZenCmdBase
+class ProjectStoreCommand : public ZenCmdBase
+{
+ virtual ZenCmdCategory& CommandCategory() const override { return g_ProjectStoreCategory; }
+};
+
+class DropProjectCommand : public ProjectStoreCommand
{
public:
DropProjectCommand();
@@ -22,7 +27,7 @@ private:
std::string m_OplogName;
};
-class ProjectInfoCommand : public ZenCmdBase
+class ProjectInfoCommand : public ProjectStoreCommand
{
public:
ProjectInfoCommand();
@@ -37,7 +42,7 @@ private:
std::string m_OplogName;
};
-class CreateProjectCommand : public ZenCmdBase
+class CreateProjectCommand : public ProjectStoreCommand
{
public:
CreateProjectCommand();
@@ -57,7 +62,7 @@ private:
bool m_ForceUpdate = false;
};
-class DeleteProjectCommand : public ZenCmdBase
+class DeleteProjectCommand : public ProjectStoreCommand
{
public:
DeleteProjectCommand();
@@ -72,7 +77,7 @@ private:
std::string m_ProjectId;
};
-class CreateOplogCommand : public ZenCmdBase
+class CreateOplogCommand : public ProjectStoreCommand
{
public:
CreateOplogCommand();
@@ -90,7 +95,7 @@ private:
bool m_ForceUpdate = false;
};
-class DeleteOplogCommand : public ZenCmdBase
+class DeleteOplogCommand : public ProjectStoreCommand
{
public:
DeleteOplogCommand();
@@ -106,7 +111,7 @@ private:
std::string m_OplogId;
};
-class ExportOplogCommand : public ZenCmdBase
+class ExportOplogCommand : public ProjectStoreCommand
{
public:
ExportOplogCommand();
@@ -150,7 +155,7 @@ private:
bool m_FileForceEnableTempBlocks = false;
};
-class ImportOplogCommand : public ZenCmdBase
+class ImportOplogCommand : public ProjectStoreCommand
{
public:
ImportOplogCommand();
@@ -188,7 +193,7 @@ private:
std::string m_FileName;
};
-class SnapshotOplogCommand : public ZenCmdBase
+class SnapshotOplogCommand : public ProjectStoreCommand
{
public:
SnapshotOplogCommand();
@@ -204,7 +209,7 @@ private:
std::string m_OplogName;
};
-class ProjectStatsCommand : public ZenCmdBase
+class ProjectStatsCommand : public ProjectStoreCommand
{
public:
ProjectStatsCommand();
@@ -217,7 +222,7 @@ private:
std::string m_HostName;
};
-class ProjectDetailsCommand : public ZenCmdBase
+class ProjectDetailsCommand : public ProjectStoreCommand
{
public:
ProjectDetailsCommand();
@@ -237,7 +242,7 @@ private:
std::string m_OpId;
};
-class OplogMirrorCommand : public ZenCmdBase
+class OplogMirrorCommand : public ProjectStoreCommand
{
public:
OplogMirrorCommand();
diff --git a/src/zen/cmds/rpcreplay_cmd.h b/src/zen/cmds/rpcreplay_cmd.h
index e1c2831a5..42cdd4ac1 100644
--- a/src/zen/cmds/rpcreplay_cmd.h
+++ b/src/zen/cmds/rpcreplay_cmd.h
@@ -6,7 +6,7 @@
namespace zen {
-class RpcStartRecordingCommand : public ZenCmdBase
+class RpcStartRecordingCommand : public CacheStoreCommand
{
public:
RpcStartRecordingCommand();
@@ -21,7 +21,7 @@ private:
std::string m_RecordingPath;
};
-class RpcStopRecordingCommand : public ZenCmdBase
+class RpcStopRecordingCommand : public CacheStoreCommand
{
public:
RpcStopRecordingCommand();
@@ -35,7 +35,7 @@ private:
std::string m_HostName;
};
-class RpcReplayCommand : public ZenCmdBase
+class RpcReplayCommand : public CacheStoreCommand
{
public:
RpcReplayCommand();
diff --git a/src/zen/cmds/run_cmd.cpp b/src/zen/cmds/run_cmd.cpp
new file mode 100644
index 000000000..a99ba9704
--- /dev/null
+++ b/src/zen/cmds/run_cmd.cpp
@@ -0,0 +1,197 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#include "run_cmd.h"
+
+#include <zencore/filesystem.h>
+#include <zencore/fmtutils.h>
+#include <zencore/logging.h>
+#include <zencore/process.h>
+#include <zencore/string.h>
+#include <zencore/timer.h>
+
+using namespace std::literals;
+
+#define ZEN_COLOR_BLACK "\033[0;30m"
+#define ZEN_COLOR_RED "\033[0;31m"
+#define ZEN_COLOR_GREEN "\033[0;32m"
+#define ZEN_COLOR_YELLOW "\033[0;33m"
+#define ZEN_COLOR_BLUE "\033[0;34m"
+#define ZEN_COLOR_MAGENTA "\033[0;35m"
+#define ZEN_COLOR_CYAN "\033[0;36m"
+#define ZEN_COLOR_WHITE "\033[0;37m"
+
+#define ZEN_BRIGHT_COLOR_BLACK "\033[1;30m"
+#define ZEN_BRIGHT_COLOR_RED "\033[1;31m"
+#define ZEN_BRIGHT_COLOR_GREEN "\033[1;32m"
+#define ZEN_BRIGHT_COLOR_YELLOW "\033[1;33m"
+#define ZEN_BRIGHT_COLOR_BLUE "\033[1;34m"
+#define ZEN_BRIGHT_COLOR_MAGENTA "\033[1;35m"
+#define ZEN_BRIGHT_COLOR_CYAN "\033[1;36m"
+#define ZEN_BRIGHT_COLOR_WHITE "\033[1;37m"
+
+#define ZEN_COLOR_RESET "\033[0m"
+
+namespace zen {
+
+RunCommand::RunCommand()
+{
+ m_Options.add_options()("h,help", "Print help");
+ m_Options.add_option("", "n", "count", "Number of times to run command", cxxopts::value(m_RunCount), "<count>");
+ m_Options.add_option("", "t", "time", "How long to run command(s) for", cxxopts::value(m_RunTime), "<seconds>");
+ m_Options.add_option("",
+ "",
+ "basepath",
+ "Where to run command. Each run will execute in its own numbered subdirectory below this directory. Additionally, "
+ "stdout will be redirected to a file in the provided directory",
+ cxxopts::value(m_BaseDirectory),
+ "<path>");
+ m_Options.add_option("",
+ "",
+ "max-dirs",
+ "Number of base directories to retain on rotation",
+ cxxopts::value(m_MaxBaseDirectoryCount),
+ "<count>");
+}
+
+RunCommand::~RunCommand()
+{
+}
+
+int
+RunCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv)
+{
+ ZEN_UNUSED(GlobalOptions);
+
+ if (!ParseOptions(argc, argv))
+ {
+ return 0;
+ }
+
+ // Validate arguments
+
+ if (GlobalOptions.PassthroughArgV.empty() || GlobalOptions.PassthroughArgV[0].empty())
+ throw OptionParseException("No command specified. The command to run is passed in after a double dash ('--') on the command line");
+
+ if (m_RunCount < 0)
+ throw OptionParseException("Invalid count specified");
+
+ if (m_RunTime < -1 || m_RunTime == 0)
+ throw OptionParseException("Invalid run time specified");
+
+ if (m_MaxBaseDirectoryCount < 0)
+ throw OptionParseException("Invalid directory count specified");
+
+ if (m_RunTime > 0 && m_RunCount > 0)
+ throw OptionParseException("Specify either time or count, not both");
+
+ if (m_RunCount == 0)
+ m_RunCount = 1;
+
+ std::filesystem::path BaseDirectory;
+
+ if (!m_BaseDirectory.empty())
+ {
+ BaseDirectory = m_BaseDirectory;
+
+ if (m_MaxBaseDirectoryCount)
+ {
+ RotateDirectories(BaseDirectory, m_MaxBaseDirectoryCount);
+ CreateDirectories(BaseDirectory);
+ }
+ else
+ {
+ CleanDirectory(BaseDirectory);
+ }
+ }
+
+ bool TimedRun = false;
+ auto CommandStartTime = std::chrono::system_clock::now();
+ auto CommandDeadlineTime = std::chrono::system_clock::now();
+
+ if (m_RunTime > 0)
+ {
+ m_RunCount = 1'000'000;
+ TimedRun = true;
+
+ CommandDeadlineTime += std::chrono::seconds(m_RunTime);
+ }
+
+ struct RunResults
+ {
+ int ExitCode = 0;
+ std::chrono::duration<long, std::milli> Duration{};
+ };
+
+ std::vector<RunResults> Results;
+ int ErrorCount = 0;
+
+ std::filesystem::path ExecutablePath = SearchPathForExecutable(GlobalOptions.PassthroughArgV[0]);
+ std::string CommandArguments = GlobalOptions.PassthroughArgs;
+
+ for (int i = 0; i < m_RunCount; ++i)
+ {
+ std::filesystem::path RunDir;
+ if (!BaseDirectory.empty())
+ {
+ RunDir = BaseDirectory / IntNum(i + 1).c_str();
+ CreateDirectories(RunDir);
+ }
+
+ Stopwatch Timer;
+
+ CreateProcOptions ProcOptions;
+
+ if (!RunDir.empty())
+ {
+ ProcOptions.WorkingDirectory = &RunDir;
+ ProcOptions.StdoutFile = RunDir / "stdout.txt";
+ }
+
+ fmt::print(ZEN_BRIGHT_COLOR_WHITE "run #{}" ZEN_COLOR_RESET ": {}\n", i + 1, GlobalOptions.PassthroughCommandLine);
+
+ ProcessHandle Proc;
+ Proc.Initialize(CreateProc(ExecutablePath, GlobalOptions.PassthroughCommandLine, ProcOptions));
+ if (!Proc.IsValid())
+ {
+ throw std::runtime_error(fmt::format("failed to launch '{}'", ExecutablePath));
+ }
+
+ int ExitCode = Proc.WaitExitCode();
+
+ auto EndTime = std::chrono::system_clock::now();
+
+ if (ExitCode)
+ ++ErrorCount;
+
+ Results.emplace_back(RunResults{.ExitCode = ExitCode, .Duration = std::chrono::milliseconds(Timer.GetElapsedTimeMs())});
+
+ if (TimedRun)
+ {
+ if (EndTime >= CommandDeadlineTime)
+ {
+ m_RunCount = i + 1;
+ break;
+ }
+ }
+ }
+
+ fmt::print("{:>5} {:>3} {:>6}\n", "run", "rc", "time");
+ int i = 0;
+ for (const auto& Entry : Results)
+ {
+ fmt::print("{:5} {:3} {:>6}\n", ++i, Entry.ExitCode, NiceTimeSpanMs(Entry.Duration.count()));
+ }
+
+ if (ErrorCount)
+ {
+ fmt::print("run complete ({}/{} failed)\n", ErrorCount, m_RunCount);
+ }
+ else
+ {
+ fmt::print("run complete, no error exit code\n", m_RunCount);
+ }
+
+ return 0;
+}
+
+} // namespace zen
diff --git a/src/zen/cmds/run_cmd.h b/src/zen/cmds/run_cmd.h
new file mode 100644
index 000000000..f6512a4e8
--- /dev/null
+++ b/src/zen/cmds/run_cmd.h
@@ -0,0 +1,27 @@
+// Copyright Epic Games, Inc. All Rights Reserved.
+
+#pragma once
+
+#include "../zen.h"
+
+namespace zen {
+
+class RunCommand : public ZenCmdBase
+{
+public:
+ RunCommand();
+ ~RunCommand();
+
+ virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override;
+ virtual cxxopts::Options& Options() override { return m_Options; }
+ virtual ZenCmdCategory& CommandCategory() const override { return g_UtilitiesCategory; }
+
+private:
+ cxxopts::Options m_Options{"run", "Run executable"};
+ int m_RunCount = 0;
+ int m_RunTime = -1;
+ std::string m_BaseDirectory;
+ int m_MaxBaseDirectoryCount = 10;
+};
+
+} // namespace zen
diff --git a/src/zen/cmds/vfs_cmd.h b/src/zen/cmds/vfs_cmd.h
index 35546c9b6..9b2497c0e 100644
--- a/src/zen/cmds/vfs_cmd.h
+++ b/src/zen/cmds/vfs_cmd.h
@@ -6,7 +6,7 @@
namespace zen {
-class VfsCommand : public ZenCmdBase
+class VfsCommand : public StorageCommand
{
public:
VfsCommand();
diff --git a/src/zen/internalfile.cpp b/src/zen/internalfile.cpp
deleted file mode 100644
index 671a2093e..000000000
--- a/src/zen/internalfile.cpp
+++ /dev/null
@@ -1,306 +0,0 @@
-// Copyright Epic Games, Inc. All Rights Reserved.
-
-#include "internalfile.h"
-
-#include <zencore/except.h>
-#include <zencore/filesystem.h>
-#include <zencore/fmtutils.h>
-#include <zencore/logging.h>
-#include <zencore/memory.h>
-
-#if ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
-# include <fcntl.h>
-# include <sys/file.h>
-# include <sys/mman.h>
-# include <sys/stat.h>
-# include <unistd.h>
-#endif
-
-#include <gsl/gsl-lite.hpp>
-
-namespace zen {
-
-#define ZEN_USE_SLIST ZEN_PLATFORM_WINDOWS
-
-#if ZEN_USE_SLIST == 0
-struct FileBufferManager::Impl
-{
- RwLock m_Lock;
- std::list<IoBuffer> m_FreeBuffers;
-
- uint64_t m_BufferSize;
- uint64_t m_MaxBufferCount;
-
- Impl(uint64_t BufferSize, uint64_t MaxBuffers) : m_BufferSize(BufferSize), m_MaxBufferCount(MaxBuffers) {}
-
- IoBuffer AllocBuffer()
- {
- RwLock::ExclusiveLockScope _(m_Lock);
-
- if (m_FreeBuffers.empty())
- {
- return IoBuffer{m_BufferSize, 64 * 1024};
- }
- else
- {
- IoBuffer Buffer = std::move(m_FreeBuffers.front());
- m_FreeBuffers.pop_front();
- return Buffer;
- }
- }
-
- void ReturnBuffer(IoBuffer Buffer)
- {
- RwLock::ExclusiveLockScope _(m_Lock);
-
- m_FreeBuffers.push_front(std::move(Buffer));
- }
-};
-#else
-struct FileBufferManager::Impl
-{
- struct BufferItem
- {
- SLIST_ENTRY ItemEntry;
- IoBuffer Buffer;
- };
-
- SLIST_HEADER m_FreeList;
- uint64_t m_BufferSize;
- uint64_t m_MaxBufferCount;
-
- Impl(uint64_t BufferSize, uint64_t MaxBuffers) : m_BufferSize(BufferSize), m_MaxBufferCount(MaxBuffers)
- {
- InitializeSListHead(&m_FreeList);
- }
-
- ~Impl()
- {
- while (SLIST_ENTRY* Entry = InterlockedPopEntrySList(&m_FreeList))
- {
- BufferItem* Item = reinterpret_cast<BufferItem*>(Entry);
- delete Item;
- }
- }
-
- IoBuffer AllocBuffer()
- {
- SLIST_ENTRY* Entry = InterlockedPopEntrySList(&m_FreeList);
-
- if (Entry == nullptr)
- {
- return IoBuffer{m_BufferSize, 64 * 1024};
- }
- else
- {
- BufferItem* Item = reinterpret_cast<BufferItem*>(Entry);
- IoBuffer Buffer = std::move(Item->Buffer);
- delete Item; // Todo: could keep this around in another list
-
- return Buffer;
- }
- }
-
- void ReturnBuffer(IoBuffer Buffer)
- {
- BufferItem* Item = new BufferItem{nullptr, std::move(Buffer)};
-
- InterlockedPushEntrySList(&m_FreeList, &Item->ItemEntry);
- }
-};
-#endif
-
-FileBufferManager::FileBufferManager(uint64_t BufferSize, uint64_t MaxBuffers)
-{
- m_Impl = new Impl{BufferSize, MaxBuffers};
-}
-
-FileBufferManager::~FileBufferManager()
-{
- delete m_Impl;
-}
-
-IoBuffer
-FileBufferManager::AllocBuffer()
-{
- return m_Impl->AllocBuffer();
-}
-
-void
-FileBufferManager::ReturnBuffer(IoBuffer Buffer)
-{
- return m_Impl->ReturnBuffer(Buffer);
-}
-
-//////////////////////////////////////////////////////////////////////////
-
-InternalFile::InternalFile()
-{
-}
-
-InternalFile::~InternalFile()
-{
- if (m_Memory)
- Memory::Free(m_Memory);
-
-#if ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
- if (m_Mmap)
- munmap(m_Mmap, GetFileSize());
- if (m_File)
- close(int(intptr_t(m_File)));
-#endif
-}
-
-size_t
-InternalFile::GetFileSize()
-{
-#if ZEN_PLATFORM_WINDOWS
- ULONGLONG sz;
- m_File.GetSize(sz);
- return size_t(sz);
-#else
- int Fd = int(intptr_t(m_File));
- static_assert(sizeof(decltype(stat::st_size)) == sizeof(uint64_t), "fstat() doesn't support large files");
- struct stat Stat;
- fstat(Fd, &Stat);
- return size_t(Stat.st_size);
-#endif
-}
-
-void
-InternalFile::OpenWrite(std::filesystem::path FileName, bool IsCreate)
-{
- bool Success = false;
-
-#if ZEN_PLATFORM_WINDOWS
- const DWORD dwCreationDisposition = IsCreate ? CREATE_ALWAYS : OPEN_EXISTING;
-
- HRESULT hRes = m_File.Create(FileName.c_str(), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, dwCreationDisposition);
- Success = SUCCEEDED(hRes);
-#else
- int OpenFlags = O_RDWR | O_CLOEXEC;
- OpenFlags |= IsCreate ? O_CREAT | O_TRUNC : 0;
-
- int Fd = open(FileName.c_str(), OpenFlags, 0666);
- if (Fd >= 0)
- {
- if (IsCreate)
- {
- fchmod(Fd, 0666);
- }
- Success = true;
- m_File = (void*)(intptr_t(Fd));
- }
-#endif // ZEN_PLATFORM_WINDOWS
-
- if (Success)
- {
- ThrowLastError(fmt::format("Failed to open file for writing: '{}'", FileName));
- }
-}
-
-void
-InternalFile::OpenRead(std::filesystem::path FileName)
-{
- bool Success = false;
-
-#if ZEN_PLATFORM_WINDOWS
- const DWORD dwCreationDisposition = OPEN_EXISTING;
-
- HRESULT hRes = m_File.Create(FileName.c_str(), GENERIC_READ, FILE_SHARE_READ, dwCreationDisposition);
- Success = SUCCEEDED(hRes);
-#else
- int Fd = open(FileName.c_str(), O_RDONLY);
- if (Fd >= 0)
- {
- Success = true;
- m_File = (void*)(intptr_t(Fd));
- }
-#endif
-
- if (Success)
- {
- ThrowLastError(fmt::format("Failed to open file for reading: '{}'", FileName));
- }
-}
-
-const void*
-InternalFile::MemoryMapFile()
-{
- auto FileSize = GetFileSize();
-
- if (FileSize <= 100 * 1024 * 1024)
- {
- m_Memory = Memory::Alloc(FileSize, 64);
- if (!m_Memory)
- {
- ThrowOutOfMemory(fmt::format("failed allocating {:#x} bytes aligned to {:#x}", FileSize, 64));
- }
- Read(m_Memory, FileSize, 0);
-
- return m_Memory;
- }
-
-#if ZEN_PLATFORM_WINDOWS
- m_Mmap.MapFile(m_File);
- return m_Mmap.GetData();
-#else
- int Fd = int(intptr_t(m_File));
- m_Mmap = mmap(nullptr, FileSize, PROT_READ, MAP_PRIVATE, Fd, 0);
- return m_Mmap;
-#endif
-}
-
-void
-InternalFile::Read(void* Data, uint64_t Size, uint64_t Offset)
-{
- bool Success;
-
-#if ZEN_PLATFORM_WINDOWS
- OVERLAPPED ovl{};
-
- ovl.Offset = DWORD(Offset & 0xffff'ffffu);
- ovl.OffsetHigh = DWORD(Offset >> 32);
-
- HRESULT hRes = m_File.Read(Data, gsl::narrow<DWORD>(Size), &ovl);
- Success = SUCCEEDED(hRes);
-#else
- int Fd = int(intptr_t(m_File));
- int BytesRead = pread(Fd, Data, Size, Offset);
- Success = (BytesRead > 0);
-#endif
-
- if (Success)
- {
- std::error_code DummyEc;
- ThrowLastError(fmt::format("Failed to read from file '{}'", PathFromHandle(m_File, DummyEc)));
- }
-}
-
-void
-InternalFile::Write(const void* Data, uint64_t Size, uint64_t Offset)
-{
- bool Success;
-
-#if ZEN_PLATFORM_WINDOWS
- OVERLAPPED Ovl{};
-
- Ovl.Offset = DWORD(Offset & 0xffff'ffffu);
- Ovl.OffsetHigh = DWORD(Offset >> 32);
-
- HRESULT hRes = m_File.Write(Data, gsl::narrow<DWORD>(Size), &Ovl);
- Success = SUCCEEDED(hRes);
-#else
- int Fd = int(intptr_t(m_File));
- int BytesWritten = pwrite(Fd, Data, Size, Offset);
- Success = (BytesWritten > 0);
-#endif
-
- if (Success)
- {
- std::error_code DummyEc;
- ThrowLastError(fmt::format("Failed to write to file '{}'", PathFromHandle(m_File, DummyEc)));
- }
-}
-
-} // namespace zen
diff --git a/src/zen/internalfile.h b/src/zen/internalfile.h
deleted file mode 100644
index 90d370e28..000000000
--- a/src/zen/internalfile.h
+++ /dev/null
@@ -1,64 +0,0 @@
-// Copyright Epic Games, Inc. All Rights Reserved.
-
-#pragma once
-
-#include <zencore/zencore.h>
-
-#include <zenbase/refcount.h>
-#include <zencore/iobuffer.h>
-#include <zencore/thread.h>
-
-#if ZEN_PLATFORM_WINDOWS
-# include <zencore/windows.h>
-#endif
-
-#include <filesystem>
-#include <list>
-
-namespace zen {
-
-//////////////////////////////////////////////////////////////////////////
-
-class FileBufferManager : public RefCounted
-{
-public:
- FileBufferManager(uint64_t BufferSize, uint64_t MaxBufferCount);
- ~FileBufferManager();
-
- IoBuffer AllocBuffer();
- void ReturnBuffer(IoBuffer Buffer);
-
-private:
- struct Impl;
-
- Impl* m_Impl;
-};
-
-class InternalFile : public RefCounted
-{
-public:
- InternalFile();
- ~InternalFile();
-
- void OpenRead(std::filesystem::path FileName);
- void Read(void* Data, uint64_t Size, uint64_t Offset);
-
- void OpenWrite(std::filesystem::path FileName, bool isCreate);
- void Write(const void* Data, uint64_t Size, uint64_t Offset);
-
- const void* MemoryMapFile();
- size_t GetFileSize();
-
-private:
-#if ZEN_PLATFORM_WINDOWS
- windows::FileHandle m_File;
- windows::FileMapping m_Mmap;
-#else
- void* m_File = nullptr;
- void* m_Mmap = nullptr;
-#endif
-
- void* m_Memory = nullptr;
-};
-
-} // namespace zen
diff --git a/src/zen/zen.cpp b/src/zen/zen.cpp
index 21dea44bc..2e9cf6d01 100644
--- a/src/zen/zen.cpp
+++ b/src/zen/zen.cpp
@@ -10,10 +10,10 @@
#include "cmds/cache_cmd.h"
#include "cmds/copy_cmd.h"
#include "cmds/dedup_cmd.h"
-#include "cmds/hash_cmd.h"
#include "cmds/print_cmd.h"
#include "cmds/projectstore_cmd.h"
#include "cmds/rpcreplay_cmd.h"
+#include "cmds/run_cmd.h"
#include "cmds/serve_cmd.h"
#include "cmds/status_cmd.h"
#include "cmds/top_cmd.h"
@@ -47,6 +47,20 @@ ZEN_THIRD_PARTY_INCLUDES_END
//////////////////////////////////////////////////////////////////////////
+namespace zen {
+
+ZenCmdCategory DefaultCategory{.Name = "general commands"};
+ZenCmdCategory g_UtilitiesCategory{.Name = "utility commands"};
+ZenCmdCategory g_ProjectStoreCategory{.Name = "project store commands"};
+ZenCmdCategory g_CacheStoreCategory{.Name = "cache store commands"};
+ZenCmdCategory g_StorageCategory{.Name = "storage management commands"};
+
+ZenCmdCategory&
+ZenCmdBase::CommandCategory() const
+{
+ return DefaultCategory;
+}
+
bool
ZenCmdBase::ParseOptions(int argc, char** argv)
{
@@ -149,6 +163,62 @@ ZenCmdBase::MapHttpToCommandReturnCode(const cpr::Response& Response)
return 1;
}
+std::string
+ZenCmdBase::ResolveTargetHostSpec(const std::string& InHostSpec, uint16_t& OutEffectivePort)
+{
+ if (InHostSpec.empty())
+ {
+ // If no host is specified then look to see if we have an instance
+ // running on this host and use that as the default to interact with
+
+ zen::ZenServerState Servers;
+
+ if (Servers.InitializeReadOnly())
+ {
+ std::string ResolvedSpec;
+
+ Servers.Snapshot([&](const zen::ZenServerState::ZenServerEntry& Entry) {
+ if (ResolvedSpec.empty())
+ {
+ ResolvedSpec = fmt::format("http://localhost:{}", Entry.EffectiveListenPort.load());
+ OutEffectivePort = Entry.EffectiveListenPort;
+ }
+ });
+
+ return ResolvedSpec;
+ }
+ }
+
+ // Parse out port from the specification provided, to be consistent with
+ // the auto-discovery logic above.
+
+ std::string_view PortSpec(InHostSpec);
+ if (size_t PrefixIndex = PortSpec.find_last_of(":"); PrefixIndex != std::string_view::npos)
+ {
+ PortSpec.remove_prefix(PrefixIndex + 1);
+
+ std::optional<uint16_t> EffectivePort = zen::ParseInt<uint16_t>(PortSpec);
+
+ if (EffectivePort)
+ {
+ OutEffectivePort = EffectivePort.value();
+ }
+ }
+
+ // note: We should consider adding validation/normalization of the provided spec here
+
+ return InHostSpec;
+}
+
+std::string
+ZenCmdBase::ResolveTargetHostSpec(const std::string& InHostSpec)
+{
+ uint16_t Dummy = 0;
+ return ResolveTargetHostSpec(InHostSpec, /* out */ Dummy);
+}
+
+} // namespace zen
+
//////////////////////////////////////////////////////////////////////////
// TODO: should make this Unicode-aware so we can pass anything in on the
// command line.
@@ -157,6 +227,7 @@ int
main(int argc, char** argv)
{
using namespace zen;
+ using namespace std::literals;
#if ZEN_USE_MIMALLOC
mi_version();
@@ -200,7 +271,6 @@ main(int argc, char** argv)
FlushCommand FlushCmd;
GcCommand GcCmd;
GcStatusCommand GcStatusCmd;
- HashCommand HashCmd;
ImportOplogCommand ImportOplogCmd;
JobCommand JobCmd;
OplogMirrorCommand OplogMirrorCmd;
@@ -213,6 +283,7 @@ main(int argc, char** argv)
RpcReplayCommand RpcReplayCmd;
RpcStartRecordingCommand RpcStartRecordingCmd;
RpcStopRecordingCommand RpcStopRecordingCmd;
+ RunCommand RunCmd;
ScrubCommand ScrubCmd;
ServeCommand ServeCmd;
SnapshotOplogCommand SnapshotOplogCmd;
@@ -233,7 +304,6 @@ main(int argc, char** argv)
// clang-format off
{"attach", &AttachCmd, "Add a sponsor process to a running zen service"},
{"bench", &BenchCmd, "Utility command for benchmarking"},
-// {"chunk", &ChunkCmd, "Perform chunking"},
{"cache-details", &CacheDetailsCmd, "Details on cache"},
{"cache-info", &CacheInfoCmd, "Info on cache, namespace or bucket"},
{"cache-stats", &CacheStatsCmd, "Stats on cache"},
@@ -244,7 +314,6 @@ main(int argc, char** argv)
{"drop", &DropCmd, "Drop cache namespace or bucket"},
{"gc-status", &GcStatusCmd, "Garbage collect zen storage status check"},
{"gc", &GcCmd, "Garbage collect zen storage"},
- {"hash", &HashCmd, "Compute file hashes"},
{"jobs", &JobCmd, "Show/cancel zen background jobs"},
{"logs", &LoggingCmd, "Show/control zen logging"},
{"oplog-create", &CreateOplogCmd, "Create a project oplog"},
@@ -265,6 +334,7 @@ main(int argc, char** argv)
{"rpc-record-replay", &RpcReplayCmd, "Stops recording of cache rpc requests on a host"},
{"rpc-record-start", &RpcStartRecordingCmd, "Replays a previously recorded session of rpc requests"},
{"rpc-record-stop", &RpcStopRecordingCmd, "Starts recording of cache rpc requests on a host"},
+ {"run", &RunCmd, "Run command with special options"},
{"scrub", &ScrubCmd, "Scrub zen storage (verify data integrity)"},
{"serve", &ServeCmd, "Serve files from a directory"},
{"status", &StatusCmd, "Show zen status"},
@@ -287,46 +357,57 @@ main(int argc, char** argv)
// Split command line into options, commands and any pass-through arguments
std::string Passthrough;
- std::vector<std::string> PassthroughV;
+ std::string PassthroughArgs;
+ std::vector<std::string> PassthroughArgV;
for (int i = 1; i < argc; ++i)
{
- if (strcmp(argv[i], "--") == 0)
+ if ("--"sv == argv[i])
{
bool IsFirst = true;
zen::ExtendableStringBuilder<256> Line;
+ zen::ExtendableStringBuilder<256> Arguments;
for (int j = i + 1; j < argc; ++j)
{
+ auto AppendAscii = [&](auto X) {
+ Line.Append(X);
+ if (!IsFirst)
+ {
+ Arguments.Append(X);
+ }
+ };
+
if (!IsFirst)
{
- Line.AppendAscii(" ");
+ AppendAscii(" ");
}
std::string_view ThisArg(argv[j]);
- PassthroughV.push_back(std::string(ThisArg));
+ PassthroughArgV.push_back(std::string(ThisArg));
const bool NeedsQuotes = (ThisArg.find(' ') != std::string_view::npos);
if (NeedsQuotes)
{
- Line.AppendAscii("\"");
+ AppendAscii("\"");
}
- Line.Append(ThisArg);
+ AppendAscii(ThisArg);
if (NeedsQuotes)
{
- Line.AppendAscii("\"");
+ AppendAscii("\"");
}
IsFirst = false;
}
- Passthrough = Line.c_str();
+ Passthrough = Line.c_str();
+ PassthroughArgs = Arguments.c_str();
// This will "truncate" the arg vector and terminate the loop
- argc = i - 1;
+ argc = i;
}
}
@@ -354,8 +435,9 @@ main(int argc, char** argv)
ZenCliOptions GlobalOptions;
- GlobalOptions.PassthroughArgs = Passthrough;
- GlobalOptions.PassthroughV = PassthroughV;
+ GlobalOptions.PassthroughCommandLine = Passthrough;
+ GlobalOptions.PassthroughArgs = PassthroughArgs;
+ GlobalOptions.PassthroughArgV = PassthroughArgV;
std::string SubCommand = "<None>";
@@ -382,16 +464,26 @@ main(int argc, char** argv)
printf("available commands:\n");
- std::map<std::string, std::string> SortedCmds;
+ std::map<std::string, ZenCmdCategory*> Categories;
for (const CommandInfo& CmdInfo : Commands)
{
- SortedCmds[CmdInfo.CmdName] = CmdInfo.CmdSummary;
+ ZenCmdCategory& Category = CmdInfo.Cmd->CommandCategory();
+
+ Categories[Category.Name] = &Category;
+ Category.SortedCmds[CmdInfo.CmdName] = CmdInfo.CmdSummary;
}
- for (const auto& Kv : SortedCmds)
+ for (const auto& CategoryKv : Categories)
{
- printf(" %-20s %s\n", Kv.first.c_str(), Kv.second.c_str());
+ fmt::print(" {}\n\n", CategoryKv.first);
+
+ for (const auto& Kv : CategoryKv.second->SortedCmds)
+ {
+ printf(" %-20s %s\n", Kv.first.c_str(), Kv.second.c_str());
+ }
+
+ printf("\n");
}
exit(0);
@@ -446,57 +538,3 @@ main(int argc, char** argv)
return 0;
}
-
-std::string
-ZenCmdBase::ResolveTargetHostSpec(const std::string& InHostSpec, uint16_t& OutEffectivePort)
-{
- if (InHostSpec.empty())
- {
- // If no host is specified then look to see if we have an instance
- // running on this host and use that as the default to interact with
-
- zen::ZenServerState Servers;
-
- if (Servers.InitializeReadOnly())
- {
- std::string ResolvedSpec;
-
- Servers.Snapshot([&](const zen::ZenServerState::ZenServerEntry& Entry) {
- if (ResolvedSpec.empty())
- {
- ResolvedSpec = fmt::format("http://localhost:{}", Entry.EffectiveListenPort.load());
- OutEffectivePort = Entry.EffectiveListenPort;
- }
- });
-
- return ResolvedSpec;
- }
- }
-
- // Parse out port from the specification provided, to be consistent with
- // the auto-discovery logic above.
-
- std::string_view PortSpec(InHostSpec);
- if (size_t PrefixIndex = PortSpec.find_last_of(":"); PrefixIndex != std::string_view::npos)
- {
- PortSpec.remove_prefix(PrefixIndex + 1);
-
- std::optional<uint16_t> EffectivePort = zen::ParseInt<uint16_t>(PortSpec);
-
- if (EffectivePort)
- {
- OutEffectivePort = EffectivePort.value();
- }
- }
-
- // note: We should consider adding validation/normalization of the provided spec here
-
- return InHostSpec;
-}
-
-std::string
-ZenCmdBase::ResolveTargetHostSpec(const std::string& InHostSpec)
-{
- uint16_t Dummy = 0;
- return ResolveTargetHostSpec(InHostSpec, /* out */ Dummy);
-}
diff --git a/src/zen/zen.h b/src/zen/zen.h
index 7258c10ce..78f22cad6 100644
--- a/src/zen/zen.h
+++ b/src/zen/zen.h
@@ -13,16 +13,30 @@ namespace cpr {
class Response;
}
+namespace zen {
+
struct ZenCliOptions
{
bool IsDebug = false;
bool IsVerbose = false;
// Arguments after " -- " on command line are passed through and not parsed
+ std::string PassthroughCommandLine;
std::string PassthroughArgs;
- std::vector<std::string> PassthroughV;
+ std::vector<std::string> PassthroughArgV;
+};
+
+struct ZenCmdCategory
+{
+ std::string Name;
+ std::map<std::string, std::string> SortedCmds;
};
+extern ZenCmdCategory g_UtilitiesCategory;
+extern ZenCmdCategory g_ProjectStoreCategory;
+extern ZenCmdCategory g_CacheStoreCategory;
+extern ZenCmdCategory g_StorageCategory;
+
/** Base class for command implementations
*/
@@ -31,6 +45,7 @@ class ZenCmdBase
public:
virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) = 0;
virtual cxxopts::Options& Options() = 0;
+ virtual ZenCmdCategory& CommandCategory() const;
bool ParseOptions(int argc, char** argv);
static std::string FormatHttpResponse(const cpr::Response& Response);
@@ -38,3 +53,15 @@ public:
static std::string ResolveTargetHostSpec(const std::string& InHostSpec);
static std::string ResolveTargetHostSpec(const std::string& InHostSpec, uint16_t& OutEffectivePort);
};
+
+class StorageCommand : public ZenCmdBase
+{
+ virtual ZenCmdCategory& CommandCategory() const override { return g_StorageCategory; }
+};
+
+class CacheStoreCommand : public ZenCmdBase
+{
+ virtual ZenCmdCategory& CommandCategory() const override { return g_CacheStoreCategory; }
+};
+
+} // namespace zen
diff --git a/src/zencore/filesystem.cpp b/src/zencore/filesystem.cpp
index b941613ec..f117001b5 100644
--- a/src/zencore/filesystem.cpp
+++ b/src/zencore/filesystem.cpp
@@ -1558,6 +1558,76 @@ RotateFiles(const std::filesystem::path& Filename, std::size_t MaxFiles)
return Result;
}
+std::error_code
+RotateDirectories(const std::filesystem::path& DirectoryName, std::size_t MaxDirectories)
+{
+ const std::filesystem::path BasePath(DirectoryName.parent_path());
+ const std::string Stem(DirectoryName.stem().string());
+
+ auto GetPathForIndex = [&](size_t Index) -> std::filesystem::path {
+ if (Index == 0)
+ {
+ return BasePath / Stem;
+ }
+ return BasePath / fmt::format("{}.{}", Stem, Index);
+ };
+
+ auto IsEmpty = [](const std::filesystem::path& Path, std::error_code& Ec) -> bool { return std::filesystem::is_empty(Path, Ec); };
+
+ std::error_code Result;
+ const bool BaseIsEmpty = IsEmpty(GetPathForIndex(0), Result);
+ if (Result)
+ {
+ return Result;
+ }
+
+ if (BaseIsEmpty)
+ return Result;
+
+ for (std::size_t i = MaxDirectories; i > 0; i--)
+ {
+ const std::filesystem::path SourcePath = GetPathForIndex(i - 1);
+
+ if (std::filesystem::exists(SourcePath))
+ {
+ std::filesystem::path TargetPath = GetPathForIndex(i);
+
+ std::error_code DummyEc;
+ if (std::filesystem::exists(TargetPath, DummyEc))
+ {
+ std::filesystem::remove_all(TargetPath, DummyEc);
+ }
+ std::filesystem::rename(SourcePath, TargetPath, DummyEc);
+ }
+ }
+
+ return Result;
+}
+
+std::filesystem::path
+SearchPathForExecutable(std::string_view ExecutableName)
+{
+#if ZEN_PLATFORM_WINDOWS
+ std::wstring Executable(Utf8ToWide(ExecutableName));
+
+ DWORD Result = SearchPathW(nullptr, Executable.c_str(), L".exe", 0, nullptr, nullptr);
+
+ if (!Result)
+ return ExecutableName;
+
+ auto PathBuffer = std::make_unique_for_overwrite<WCHAR[]>(Result);
+
+ Result = SearchPathW(nullptr, Executable.c_str(), L".exe", Result, PathBuffer.get(), nullptr);
+
+ if (!Result)
+ return ExecutableName;
+
+ return PathBuffer.get();
+#else
+ return ExecutableName;
+#endif
+}
+
//////////////////////////////////////////////////////////////////////////
//
// Testing related code follows...
@@ -1717,6 +1787,55 @@ TEST_CASE("PathBuilder")
# endif
}
+TEST_CASE("RotateDirectories")
+{
+ std::filesystem::path TestBaseDir = GetRunningExecutablePath().parent_path() / ".test";
+ CleanDirectory(TestBaseDir);
+ std::filesystem::path RotateDir = TestBaseDir / "rotate_dir" / "dir_to_rotate";
+ IoBuffer DummyFileData = IoBufferBuilder::MakeCloneFromMemory("blubb", 5);
+
+ auto NewDir = [&] {
+ CreateDirectories(RotateDir);
+ WriteFile(RotateDir / ".placeholder", DummyFileData);
+ };
+
+ auto DirWithSuffix = [&](int Index) -> std::filesystem::path { return RotateDir.generic_string().append(fmt::format(".{}", Index)); };
+
+ const int RotateMax = 10;
+
+ NewDir();
+ CHECK(std::filesystem::exists(RotateDir));
+ RotateDirectories(RotateDir, RotateMax);
+ CHECK(!std::filesystem::exists(RotateDir));
+ CHECK(std::filesystem::exists(DirWithSuffix(1)));
+ NewDir();
+ CHECK(std::filesystem::exists(RotateDir));
+ RotateDirectories(RotateDir, RotateMax);
+ CHECK(!std::filesystem::exists(RotateDir));
+ CHECK(std::filesystem::exists(DirWithSuffix(1)));
+ CHECK(std::filesystem::exists(DirWithSuffix(2)));
+
+ for (int i = 0; i < RotateMax; ++i)
+ {
+ NewDir();
+ std::error_code Ec = RotateDirectories(RotateDir, 10);
+ const bool IsError = !!Ec;
+ CHECK_EQ(IsError, false);
+ }
+
+ CHECK(!std::filesystem::exists(RotateDir));
+
+ for (int i = 0; i < RotateMax; ++i)
+ {
+ CHECK(std::filesystem::exists(DirWithSuffix(i + 1)));
+ }
+
+ for (int i = RotateMax; i < RotateMax + 5; ++i)
+ {
+ CHECK(!std::filesystem::exists(DirWithSuffix(RotateMax + i + 1)));
+ }
+}
+
#endif
} // namespace zen
diff --git a/src/zencore/include/zencore/filesystem.h b/src/zencore/include/zencore/filesystem.h
index 963890fb9..07eb72879 100644
--- a/src/zencore/include/zencore/filesystem.h
+++ b/src/zencore/include/zencore/filesystem.h
@@ -211,7 +211,10 @@ void GetDirectoryContent(const std::filesystem::path& RootDir, uint8_t Flags, Di
std::string GetEnvVariable(std::string_view VariableName);
+std::filesystem::path SearchPathForExecutable(std::string_view ExecutableName);
+
std::error_code RotateFiles(const std::filesystem::path& Filename, std::size_t MaxFiles);
+std::error_code RotateDirectories(const std::filesystem::path& DirectoryName, std::size_t MaxDirectories);
//////////////////////////////////////////////////////////////////////////
diff --git a/src/zencore/include/zencore/process.h b/src/zencore/include/zencore/process.h
index ec598b929..d90a32301 100644
--- a/src/zencore/include/zencore/process.h
+++ b/src/zencore/include/zencore/process.h
@@ -34,6 +34,9 @@ public:
private:
void* m_ProcessHandle = nullptr;
int m_Pid = 0;
+#if ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
+ int m_ExitCode = -1;
+#endif
};
/** Basic process creation
@@ -49,6 +52,7 @@ struct CreateProcOptions
const std::filesystem::path* WorkingDirectory = nullptr;
uint32_t Flags = 0;
+ std::filesystem::path StdoutFile;
};
#if ZEN_PLATFORM_WINDOWS
diff --git a/src/zencore/process.cpp b/src/zencore/process.cpp
index 1c208701c..2d0ec2de6 100644
--- a/src/zencore/process.cpp
+++ b/src/zencore/process.cpp
@@ -185,6 +185,11 @@ ProcessHandle::Wait(int TimeoutMs)
int WaitState = 0;
waitpid(m_Pid, &WaitState, WNOHANG | WCONTINUED | WUNTRACED);
+ if (WIFEXITED(WaitState))
+ {
+ m_ExitCode = WEXITSTATUS(WaitState);
+ }
+
if (kill(m_Pid, 0) < 0)
{
int32_t LastError = zen::GetLastError();
@@ -220,6 +225,8 @@ ProcessHandle::WaitExitCode()
ZEN_ASSERT(ExitCode != STILL_ACTIVE);
return ExitCode;
+#elif ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC
+ return m_ExitCode;
#else
ZEN_NOT_IMPLEMENTED();
@@ -278,7 +285,7 @@ CreateProcNormal(const std::filesystem::path& Executable, std::string_view Comma
PROCESS_INFORMATION ProcessInfo{};
STARTUPINFO StartupInfo{.cb = sizeof(STARTUPINFO)};
- const bool InheritHandles = false;
+ bool InheritHandles = false;
void* Environment = nullptr;
LPSECURITY_ATTRIBUTES ProcessAttributes = nullptr;
LPSECURITY_ATTRIBUTES ThreadAttributes = nullptr;
@@ -298,6 +305,42 @@ CreateProcNormal(const std::filesystem::path& Executable, std::string_view Comma
ExtendableWideStringBuilder<256> CommandLineZ;
CommandLineZ << CommandLine;
+ if (!Options.StdoutFile.empty())
+ {
+ SECURITY_ATTRIBUTES sa;
+ sa.nLength = sizeof sa;
+ sa.lpSecurityDescriptor = nullptr;
+ sa.bInheritHandle = TRUE;
+
+ StartupInfo.hStdInput = nullptr;
+ StartupInfo.hStdOutput = CreateFileW(Options.StdoutFile.c_str(),
+ GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ,
+ &sa,
+ CREATE_ALWAYS,
+ FILE_ATTRIBUTE_NORMAL,
+ nullptr);
+
+ const BOOL Success = DuplicateHandle(GetCurrentProcess(),
+ StartupInfo.hStdOutput,
+ GetCurrentProcess(),
+ &StartupInfo.hStdError,
+ 0,
+ TRUE,
+ DUPLICATE_SAME_ACCESS);
+
+ if (Success)
+ {
+ StartupInfo.dwFlags |= STARTF_USESTDHANDLES;
+ InheritHandles = true;
+ }
+ else
+ {
+ CloseHandle(StartupInfo.hStdOutput);
+ StartupInfo.hStdOutput = 0;
+ }
+ }
+
BOOL Success = CreateProcessW(Executable.c_str(),
CommandLineZ.Data(),
ProcessAttributes,
@@ -309,6 +352,12 @@ CreateProcNormal(const std::filesystem::path& Executable, std::string_view Comma
&StartupInfo,
&ProcessInfo);
+ if (StartupInfo.dwFlags & STARTF_USESTDHANDLES)
+ {
+ CloseHandle(StartupInfo.hStdError);
+ CloseHandle(StartupInfo.hStdOutput);
+ }
+
if (!Success)
{
return nullptr;
@@ -395,6 +444,14 @@ CreateProcUnelevated(const std::filesystem::path& Executable, std::string_view C
ExtendableWideStringBuilder<256> CommandLineZ;
CommandLineZ << CommandLine;
+ ExtendableWideStringBuilder<256> CurrentDirZ;
+ LPCWSTR WorkingDirectoryPtr = nullptr;
+ if (Options.WorkingDirectory)
+ {
+ CurrentDirZ << Options.WorkingDirectory->native();
+ WorkingDirectoryPtr = CurrentDirZ.c_str();
+ }
+
bOk = CreateProcessW(Executable.c_str(),
CommandLineZ.Data(),
nullptr,
@@ -402,7 +459,7 @@ CreateProcUnelevated(const std::filesystem::path& Executable, std::string_view C
FALSE,
CreateProcFlags,
nullptr,
- nullptr,
+ WorkingDirectoryPtr,
&StartupInfo.StartupInfo,
&ProcessInfo);
if (bOk == FALSE)
diff --git a/src/zenserver-test/zenserver-test.cpp b/src/zenserver-test/zenserver-test.cpp
index 3cf6e2976..3efa57fdb 100644
--- a/src/zenserver-test/zenserver-test.cpp
+++ b/src/zenserver-test/zenserver-test.cpp
@@ -101,22 +101,23 @@ int
main(int argc, char** argv)
{
using namespace std::literals;
+ using namespace zen;
# if ZEN_USE_MIMALLOC
mi_version();
# endif
- zen::zencore_forcelinktests();
- zen::zenhttp_forcelinktests();
- zen::cacherequests_forcelink();
+ zencore_forcelinktests();
+ zenhttp_forcelinktests();
+ cacherequests_forcelink();
zen::logging::InitializeLogging();
zen::logging::SetLogLevel(zen::logging::level::Debug);
spdlog::set_formatter(std::make_unique<zen::logging::full_test_formatter>("test", std::chrono::system_clock::now()));
- std::filesystem::path ProgramBaseDir = std::filesystem::path(argv[0]).parent_path();
- std::filesystem::path TestBaseDir = ProgramBaseDir.parent_path().parent_path() / ".test";
+ std::filesystem::path ProgramBaseDir = GetRunningExecutablePath().parent_path();
+ std::filesystem::path TestBaseDir = std::filesystem::current_path() / ".test";
// This is pretty janky because we're passing most of the options through to the test
// framework, so we can't just use cxxopts (I think). This should ideally be cleaned up