// Copyright Epic Games, Inc. All Rights Reserved. // Zen command line client utility // #include "zen.h" #include "chunk/chunk.h" #include "cmds/cache.h" #include "cmds/copy.h" #include "cmds/dedup.h" #include "cmds/exportproject.h" #include "cmds/hash.h" #include "cmds/importproject.h" #include "cmds/print.h" #include "cmds/run.h" #include "cmds/status.h" #include "cmds/top.h" #include "cmds/up.h" #include "cmds/version.h" #include #include #include #include #if ZEN_WITH_TESTS # define ZEN_TEST_WITH_RUNNER # include #endif #include #if ZEN_USE_MIMALLOC # include #endif ////////////////////////////////////////////////////////////////////////// class TemplateCommand : public ZenCmdBase { public: TemplateCommand() { m_Options.add_options()("r,root", "Root directory for CAS pool", cxxopts::value(m_RootDirectory)); } virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override { ZEN_UNUSED(GlobalOptions, argc, argv); return 0; } virtual cxxopts::Options* Options() override { return &m_Options; } private: cxxopts::Options m_Options{"template", "EDIT THIS COMMAND DESCRIPTION"}; std::string m_RootDirectory; }; ////////////////////////////////////////////////////////////////////////// #if ZEN_WITH_TESTS class RunTestsCommand : public ZenCmdBase { public: virtual int Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) override { ZEN_UNUSED(GlobalOptions); // Set output mode to handle virtual terminal sequences # if ZEN_PLATFORM_WINDOWS HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); if (hOut == INVALID_HANDLE_VALUE) return GetLastError(); DWORD dwMode = 0; if (!GetConsoleMode(hOut, &dwMode)) return GetLastError(); dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; if (!SetConsoleMode(hOut, dwMode)) return GetLastError(); # endif // ZEN_PLATFORM_WINDOWS return ZEN_RUN_TESTS(argc, argv); } virtual cxxopts::Options* Options() override { return &m_Options; } private: cxxopts::Options m_Options{"runtests", "Run tests"}; }; #endif ////////////////////////////////////////////////////////////////////////// // TODO: should make this Unicode-aware so we can pass anything in on the // command line. int main(int argc, char** argv) { using namespace zen; #if ZEN_USE_MIMALLOC mi_version(); #endif zen::logging::InitializeLogging(); MaximizeOpenFileCount(); ////////////////////////////////////////////////////////////////////////// auto _ = zen::MakeGuard([] { spdlog::shutdown(); }); HashCommand HashCmd; CopyCommand CopyCmd; DedupCommand DedupCmd; DropCommand DropCmd; #if ZEN_WITH_EXEC_SERVICES RunCommand RunCmd; #endif StatusCommand StatusCmd; TopCommand TopCmd; PrintCommand PrintCmd; PrintPackageCommand PrintPkgCmd; PsCommand PsCmd; UpCommand UpCmd; DownCommand DownCmd; ExportProjectCommand ExportProjectCmd; ImportProjectCommand ImportProjectCmd; VersionCommand VersionCmd; #if ZEN_WITH_TESTS RunTestsCommand RunTestsCmd; #endif const struct CommandInfo { const char* CmdName; ZenCmdBase* Cmd; const char* CmdSummary; } Commands[] = { // clang-format off // {"chunk", &ChunkCmd, "Perform chunking"}, {"copy", &CopyCmd, "Copy file(s)"}, {"dedup", &DedupCmd, "Dedup files"}, {"drop", &DropCmd, "Drop cache bucket(s)"}, {"export-project", &ExportProjectCmd, "Export project store oplog"}, {"hash", &HashCmd, "Compute file hashes"}, {"import-project", &ImportProjectCmd, "Import project store oplog"}, {"print", &PrintCmd, "Print compact binary object"}, {"printpackage", &PrintPkgCmd, "Print compact binary package"}, #if ZEN_WITH_EXEC_SERVICES {"run", &RunCmd, "Remote execution"}, #endif // ZEN_WITH_EXEC_SERVICES {"status", &StatusCmd, "Show zen status"}, {"ps", &PsCmd, "Enumerate running zen server instances"}, {"top", &TopCmd, "Monitor zen server activity"}, {"up", &UpCmd, "Bring zen server up"}, {"down", &DownCmd, "Bring zen server down"}, {"version", &VersionCmd, "Get zen server version"}, // clang-format on #if ZEN_WITH_TESTS {"runtests", &RunTestsCmd, "Run zen tests"}, #endif }; // Build set containing available commands std::unordered_set CommandSet; for (const auto& Cmd : Commands) CommandSet.insert(Cmd.CmdName); // Split command line into options, commands and any pass-through arguments std::string Passthrough; std::vector PassthroughV; for (int i = 1; i < argc; ++i) { if (strcmp(argv[i], "--") == 0) { bool IsFirst = true; zen::ExtendableStringBuilder<256> Line; for (int j = i + 1; j < argc; ++j) { if (!IsFirst) { Line.AppendAscii(" "); } std::string_view ThisArg(argv[j]); PassthroughV.push_back(std::string(ThisArg)); const bool NeedsQuotes = (ThisArg.find(' ') != std::string_view::npos); if (NeedsQuotes) { Line.AppendAscii("\""); } Line.Append(ThisArg); if (NeedsQuotes) { Line.AppendAscii("\""); } IsFirst = false; } Passthrough = Line.c_str(); // This will "truncate" the arg vector and terminate the loop argc = i - 1; } } // Split command line into global vs command options. We do this by simply // scanning argv for a string we recognise as a command and split it there std::vector CommandArgVec; CommandArgVec.push_back(argv[0]); for (int i = 1; i < argc; ++i) { if (CommandSet.find(argv[i]) != CommandSet.end()) { int commandArgCount = /* exec name */ 1 + argc - (i + 1); CommandArgVec.resize(commandArgCount); std::copy(argv + i + 1, argv + argc, CommandArgVec.begin() + 1); argc = i + 1; break; } } // Parse global CLI arguments ZenCliOptions GlobalOptions; GlobalOptions.PassthroughArgs = Passthrough; GlobalOptions.PassthroughV = PassthroughV; std::string SubCommand = ""; cxxopts::Options Options("zen", "Zen management tool"); Options.add_options()("d, debug", "Enable debugging", cxxopts::value(GlobalOptions.IsDebug)); Options.add_options()("v, verbose", "Enable verbose logging", cxxopts::value(GlobalOptions.IsVerbose)); Options.add_options()("help", "Show command line help"); Options.add_options()("c, command", "Sub command", cxxopts::value(SubCommand)); Options.parse_positional({"command"}); const bool IsNullInvoke = (argc == 1); // If no arguments are passed we want to print usage information try { auto ParseResult = Options.parse(argc, argv); if (ParseResult.count("help") || IsNullInvoke == 1) { std::string Help = Options.help(); printf("%s\n", Help.c_str()); printf("available commands:\n"); for (const auto& CmdInfo : Commands) { printf(" %-10s %s\n", CmdInfo.CmdName, CmdInfo.CmdSummary); } exit(0); } if (GlobalOptions.IsDebug) { spdlog::set_level(spdlog::level::debug); } for (const CommandInfo& CmdInfo : Commands) { if (StrCaseCompare(SubCommand.c_str(), CmdInfo.CmdName) == 0) { cxxopts::Options* VerbOptions = CmdInfo.Cmd->Options(); try { return CmdInfo.Cmd->Run(GlobalOptions, (int)CommandArgVec.size(), CommandArgVec.data()); } catch (cxxopts::OptionParseException& Ex) { if (VerbOptions) { std::string help = VerbOptions->help(); printf("Error parsing arguments for command '%s': %s\n\n%s", SubCommand.c_str(), Ex.what(), help.c_str()); exit(11); } else { printf("Error parsing arguments for command '%s': %s\n\n", SubCommand.c_str(), Ex.what()); exit(11); } } } } printf("Unknown command specified: '%s', exiting\n", SubCommand.c_str()); } catch (cxxopts::OptionParseException& Ex) { std::string HelpMessage = Options.help(); printf("Error parsing snapshot program arguments: %s\n\n%s", Ex.what(), HelpMessage.c_str()); return 9; } catch (std::exception& Ex) { printf("Exception caught from 'main': %s\n", Ex.what()); return 10; } return 0; }