// Copyright Epic Games, Inc. All Rights Reserved. // Zen command line client utility // #include "zen.h" #include "cmds/admin_cmd.h" #include "cmds/bench_cmd.h" #include "cmds/builds_cmd.h" #include "cmds/cache_cmd.h" #include "cmds/compute_cmd.h" #include "cmds/dedup_cmd.h" #include "cmds/exec_cmd.h" #include "cmds/history_cmd.h" #include "cmds/hub_cmd.h" #include "cmds/info_cmd.h" #include "cmds/print_cmd.h" #include "cmds/projectstore_cmd.h" #include "cmds/serve_cmd.h" #include "cmds/service_cmd.h" #include "cmds/status_cmd.h" #include "cmds/top_cmd.h" #include "cmds/trace_cmd.h" #include "cmds/ui_cmd.h" #include "cmds/up_cmd.h" #include "cmds/version_cmd.h" #include "cmds/vfs_cmd.h" #include "cmds/wipe_cmd.h" #include "cmds/workspaces_cmd.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "consoleprogress.h" #if ZEN_WITH_TESTS # include #endif ZEN_THIRD_PARTY_INCLUDES_START #include ZEN_THIRD_PARTY_INCLUDES_END #include #if ZEN_PLATFORM_LINUX || ZEN_PLATFORM_MAC # include # include #endif ////////////////////////////////////////////////////////////////////////// namespace zen { enum class ReturnCode : std::int8_t { kSuccess = 0, kOtherError = 1, kBadInput = 2, kOutOfMemory = 16, kOutOfDisk = 17, kAssertError = 70, kHttpOtherClientError = 80, kHttpCantConnectError = 81, // CONNECTION_FAILURE kHttpNotFound = 66, // NotFound(404) kHttpUnauthorized = 77, // Unauthorized(401), kHttpSLLError = 82, // SSL_CONNECT_ERROR, SSL_LOCAL_CERTIFICATE_ERROR, SSL_REMOTE_CERTIFICATE_ERROR, SSL_CACERT_ERROR, GENERIC_SSL_ERROR kHttpForbidden = 83, // Forbidden(403) kHttpTimeout = 84, // NETWORK_RECEIVE_ERROR, NETWORK_SEND_FAILURE, OPERATION_TIMEDOUT, RequestTimeout(408) kHttpConflict = 85, // Conflict(409) kHttpNoHost = 86, // HOST_RESOLUTION_FAILURE, PROXY_RESOLUTION_FAILURE kHttpOtherServerError = 90, kHttpInternalServerError = 91, // InternalServerError(500) kHttpServiceUnavailable = 69, // ServiceUnavailable(503) kHttpBadGateway = 92, // BadGateway(502) kHttpGatewayTimeout = 93, // GatewayTimeout(504) }; 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; } std::string ZenCmdBase::HelpText() { std::vector Groups = Options().groups(); Groups.erase(std::remove(Groups.begin(), Groups.end(), std::string("__hidden__")), Groups.end()); Options().set_width(TuiConsoleColumns(80)); return Options().help(Groups); } void ZenCmdBase::ThrowOptionError(std::string_view Message) { throw OptionParseException(std::string(Message), HelpText()); } bool ZenCmdBase::ParseOptions(int argc, char** argv) { return ParseOptions(Options(), argc, argv); } bool ZenCmdBase::ParseOptions(cxxopts::Options& CmdOptions, int argc, char** argv) { CmdOptions.set_width(TuiConsoleColumns(80)); cxxopts::ParseResult Result; try { Result = CmdOptions.parse(argc, argv); } catch (const std::exception& Ex) { throw zen::OptionParseException(Ex.what(), CmdOptions.help()); } CmdOptions.show_positional_help(); if (Result.count("help")) { printf("%s\n", CmdOptions.help().c_str()); return false; } if (!Result.unmatched().empty()) { zen::ExtendableStringBuilder<64> StringBuilder; for (bool First = true; const auto& Param : Result.unmatched()) { if (!First) { StringBuilder.Append(", "); } StringBuilder.Append('"'); StringBuilder.Append(Param); StringBuilder.Append('"'); First = false; } throw zen::OptionParseException(fmt::format("Invalid arguments: {}", StringBuilder.ToView()), {}); } return true; } bool ZenCmdBase::ParseOptionsPermissive(cxxopts::Options& CmdOptions, int argc, char** argv, std::vector& OutUnmatched) { CmdOptions.set_width(TuiConsoleColumns(80)); CmdOptions.allow_unrecognised_options(); cxxopts::ParseResult Result; try { Result = CmdOptions.parse(argc, argv); } catch (const std::exception& Ex) { throw zen::OptionParseException(Ex.what(), CmdOptions.help()); } CmdOptions.show_positional_help(); if (Result.count("help")) { printf("%s\n", CmdOptions.help().c_str()); return false; } OutUnmatched = Result.unmatched(); return true; } // Get the number of args including the sub command // Build an array for sub command to parse int ZenCmdBase::GetSubCommand(cxxopts::Options&, int argc, char** argv, std::span SubOptions, cxxopts::Options*& OutSubOption, std::vector& OutSubCommandArguments) { for (int i = 1; i < argc; ++i) { if (auto It = std::find_if(SubOptions.begin(), SubOptions.end(), [&](cxxopts::Options* SubOption) { return SubOption->program() == argv[i]; }); It != SubOptions.end()) { OutSubOption = (*It); OutSubCommandArguments.push_back(argv[0]); std::copy(&argv[i + 1], &argv[argc], std::back_inserter(OutSubCommandArguments)); return i + 1; } } // No Sub command found OutSubOption = nullptr; return argc; } ZenSubCmdBase::ZenSubCmdBase(std::string_view Name, std::string_view Description) : m_SubOptions(std::string(Name), std::string(Description)) , m_Description(Description) { m_SubOptions.add_options()("h,help", "Print help"); } void ZenCmdWithSubCommands::AddSubCommand(ZenSubCmdBase& SubCmd) { m_SubCommands.push_back(&SubCmd); } bool ZenCmdWithSubCommands::OnParentOptionsParsed(const ZenCliOptions& /*GlobalOptions*/) { return true; } void ZenCmdWithSubCommands::PrintHelp() { // Show all option groups except the internal "__hidden__" group used to // silently capture positional arguments. std::vector Groups = Options().groups(); Groups.erase(std::remove(Groups.begin(), Groups.end(), std::string("__hidden__")), Groups.end()); Options().set_width(TuiConsoleColumns(80)); printf("%s\n", Options().help(Groups).c_str()); // Append subcommand listing. size_t MaxNameLen = 0; for (ZenSubCmdBase* SubCmd : m_SubCommands) { MaxNameLen = std::max(MaxNameLen, SubCmd->SubOptions().program().size()); } printf("subcommands:\n"); for (ZenSubCmdBase* SubCmd : m_SubCommands) { printf(" %-*s %s\n", static_cast(MaxNameLen), SubCmd->SubOptions().program().c_str(), std::string(SubCmd->Description()).c_str()); } printf("\nFor global options run: zen --help\n"); } void ZenCmdWithSubCommands::PrintSubCommandHelp(cxxopts::Options& SubCmdOptions) { // Show the subcommand's own options. SubCmdOptions.set_width(TuiConsoleColumns(80)); printf("%s\n", SubCmdOptions.help().c_str()); // Show the parent command's options (excluding the hidden positional group). std::vector ParentGroups = Options().groups(); ParentGroups.erase(std::remove(ParentGroups.begin(), ParentGroups.end(), std::string("__hidden__")), ParentGroups.end()); Options().set_width(TuiConsoleColumns(80)); printf("%s options:\n%s\n", Options().program().c_str(), Options().help(ParentGroups).c_str()); printf("For global options run: zen --help\n"); } void ZenCmdWithSubCommands::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) { std::vector SubOptionPtrs; SubOptionPtrs.reserve(m_SubCommands.size()); for (ZenSubCmdBase* SubCmd : m_SubCommands) { SubOptionPtrs.push_back(&SubCmd->SubOptions()); } cxxopts::Options* MatchedSubOption = nullptr; std::vector SubCommandArguments; int ParentArgc = GetSubCommand(Options(), argc, argv, SubOptionPtrs, MatchedSubOption, SubCommandArguments); // Intercept --help/-h in the parent arg range before calling ParseOptions so // we can append subcommand information to the output. When a subcommand was // found argv[ParentArgc-1] is the subcommand name itself, which we exclude. int ParentArgEnd = (MatchedSubOption != nullptr) ? ParentArgc - 1 : ParentArgc; for (int i = 1; i < ParentArgEnd; ++i) { std::string_view Arg(argv[i]); if (Arg == "--help" || Arg == "-h") { PrintHelp(); return; } } if (MatchedSubOption == nullptr) { if (!ParseOptions(Options(), ParentArgc, argv)) { return; } PrintHelp(); fflush(stdout); throw OptionParseException("No subcommand specified", {}); } ZenSubCmdBase* MatchedSubCmd = nullptr; for (ZenSubCmdBase* SubCmd : m_SubCommands) { if (&SubCmd->SubOptions() == MatchedSubOption) { MatchedSubCmd = SubCmd; break; } } ZEN_ASSERT(MatchedSubCmd != nullptr); // Intercept --help/-h in the subcommand args so we can show combined help // (subcommand options + parent options) without requiring parent options to // be populated. for (size_t i = 1; i < SubCommandArguments.size(); ++i) { std::string_view Arg(SubCommandArguments[i]); if (Arg == "--help" || Arg == "-h") { PrintSubCommandHelp(*MatchedSubOption); return; } } // Parse subcommand args permissively - unrecognised options are collected // and forwarded to the parent parser so that parent options (e.g. --path) // can appear after the subcommand name on the command line. std::vector SubUnmatched; if (!ParseOptionsPermissive(*MatchedSubOption, gsl::narrow(SubCommandArguments.size()), SubCommandArguments.data(), SubUnmatched)) { return; } // Build parent arg list: original parent args (without subcommand name) + forwarded unmatched. std::vector UnmatchedStorage = std::move(SubUnmatched); std::vector ParentArgs; ParentArgs.reserve(static_cast(ParentArgc - 1) + UnmatchedStorage.size()); ParentArgs.push_back(argv[0]); std::copy(argv + 1, argv + ParentArgc - 1, std::back_inserter(ParentArgs)); for (std::string& Arg : UnmatchedStorage) { ParentArgs.push_back(Arg.data()); } if (!ParseOptions(Options(), static_cast(ParentArgs.size()), ParentArgs.data())) { return; } if (!OnParentOptionsParsed(GlobalOptions)) { return; } MatchedSubCmd->Run(GlobalOptions); } static ReturnCode GetReturnCodeFromHttpResult(const HttpClientError& Ex) { HttpClientError::ResponseClass ResponseClass = Ex.GetResponseClass(); if (ResponseClass == HttpClientError::ResponseClass::kSuccess) return ReturnCode::kSuccess; switch (ResponseClass) { #define HANDLE_CASE(ErrorClass) \ case HttpClientError::ResponseClass::ErrorClass: \ return ReturnCode::ErrorClass HANDLE_CASE(kHttpOtherClientError); HANDLE_CASE(kHttpCantConnectError); HANDLE_CASE(kHttpNotFound); HANDLE_CASE(kHttpUnauthorized); HANDLE_CASE(kHttpSLLError); HANDLE_CASE(kHttpForbidden); HANDLE_CASE(kHttpTimeout); HANDLE_CASE(kHttpConflict); HANDLE_CASE(kHttpNoHost); HANDLE_CASE(kHttpOtherServerError); HANDLE_CASE(kHttpInternalServerError); HANDLE_CASE(kHttpServiceUnavailable); HANDLE_CASE(kHttpBadGateway); HANDLE_CASE(kHttpGatewayTimeout); #undef HANDLE_CASE default: return ReturnCode::kOtherError; } } bool ZenCmdBase::IsUnixSocketSpec(std::string_view Spec) { return Spec.starts_with("unix://"); } HttpClient ZenCmdBase::CreateHttpClient(const std::string& HostSpec, HttpClientSettings Settings) { if (IsUnixSocketSpec(HostSpec)) { Settings.UnixSocketPath = HostSpec.substr(7); // strip "unix://" return HttpClient("http://localhost", Settings); } return HttpClient(HostSpec, Settings); } std::string ZenCmdBase::ResolveTargetHostSpec(const std::string& InHostSpec, uint16_t& OutEffectivePort) { if (IsUnixSocketSpec(InHostSpec)) { return InHostSpec; // pass through as-is; parsed later in CreateHttpClient } 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()) { OutEffectivePort = Entry.EffectiveListenPort; // Check for per-instance info (e.g. UDS path) if (Entry.HasInstanceInfo()) { ZenServerInstanceInfo Info; if (Info.OpenReadOnly(Entry.GetSessionId())) { InstanceInfoData Data = Info.Read(); if (!Data.UnixSocketPath.empty()) { ResolvedSpec = "unix://" + PathToUtf8(Data.UnixSocketPath); return; } } } // Skip servers with --no-network since TCP is not reachable if (Entry.IsNoNetwork()) { return; } ResolvedSpec = fmt::format("http://localhost:{}", Entry.EffectiveListenPort.load()); } }); 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 EffectivePort = zen::ParseInt(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); } void ZenCmdBase::LogExecutableVersionAndPid() { ZEN_CONSOLE("Running {}: {} (pid {})", GetRunningExecutablePath(), ZEN_CFG_VERSION_BUILD_STRING_FULL, GetCurrentProcessId()); } } // namespace zen ////////////////////////////////////////////////////////////////////////// // TODO: should make this Unicode-aware so we can pass anything in on the // command line. int main(int argc, char** argv) { zen::InstallCrashHandler(); zen::LogInvocation("zen", /*Mode*/ "", argc, argv, {zen::HistoryCommand::Name}); #if ZEN_PLATFORM_WINDOWS setlocale(LC_ALL, "en_us.UTF8"); #endif // ZEN_PLATFORM_WINDOWS zen::SetCurrentThreadName("main"); zen::CommandLineConverter ArgConverter(argc, argv); using namespace zen; using namespace std::literals; #if ZEN_WITH_TRACE TraceInit("zen"); TraceOptions TraceCommandlineOptions; if (GetTraceOptionsFromCommandline(TraceCommandlineOptions)) { TraceConfigure(TraceCommandlineOptions); } #endif // ZEN_WITH_TRACE AttachCommand AttachCmd; BenchCommand BenchCmd; BuildsCommand BuildsCmd; CacheCommand CacheCmd; CacheDetailsCommand CacheDetailsCmd; CacheGetCommand CacheGetCmd; CacheGenerateCommand CacheGenerateCmd; CacheInfoCommand CacheInfoCmd; CacheStatsCommand CacheStatsCmd; CopyStateCommand CopyStateCmd; CreateOplogCommand CreateOplogCmd; CreateProjectCommand CreateProjectCmd; DedupCommand DedupCmd; DownCommand DownCmd; DropCommand DropCmd; DropProjectCommand ProjectDropCmd; #if ZEN_WITH_COMPUTE_SERVICES ComputeCommand ComputeCmd; ExecCommand ExecCmd; #endif // ZEN_WITH_COMPUTE_SERVICES ExportOplogCommand ExportOplogCmd; FlushCommand FlushCmd; GcCommand GcCmd; GcStatusCommand GcStatusCmd; GcStopCommand GcStopCmd; HistoryCommand HistoryCmd; HubCommand HubCmd; ImportOplogCommand ImportOplogCmd; InfoCommand InfoCmd; JobCommand JobCmd; OplogMirrorCommand OplogMirrorCmd; SnapshotOplogCommand SnapshotOplogCmd; OplogDownloadCommand OplogDownload; OplogValidateCommand OplogValidateCmd; PrintCommand PrintCmd; PrintPackageCommand PrintPkgCmd; ProjectOpDetailsCommand ProjectOpDetailsCmd; ProjectInfoCommand ProjectInfoCmd; ProjectStatsCommand ProjectStatsCmd; PsCommand PsCmd; RpcReplayCommand RpcReplayCmd; RpcStartRecordingCommand RpcStartRecordingCmd; RpcStopRecordingCommand RpcStopRecordingCmd; ScrubCommand ScrubCmd; ServeCommand ServeCmd; StatusCommand StatusCmd; LoggingCommand LoggingCmd; TopCommand TopCmd; TraceCommand TraceCmd; UiCommand UiCmd; UpCommand UpCmd; VersionCommand VersionCmd; VfsCommand VfsCmd; WipeCommand WipeCmd; WorkspaceCommand WorkspaceCmd; WorkspaceShareCommand WorkspaceShareCmd; ServiceCommand ServiceCmd; const struct CommandInfo { const char* CmdName; ZenCmdBase* Cmd; const char* CmdSummary; } Commands[] = { // clang-format off {AttachCommand::Name, &AttachCmd, AttachCommand::Description}, {BenchCommand::Name, &BenchCmd, BenchCommand::Description}, {BuildsCommand::Name, &BuildsCmd, BuildsCommand::Description}, {CacheCommand::Name, &CacheCmd, CacheCommand::Description}, {CacheDetailsCommand::Name, &CacheDetailsCmd, CacheDetailsCommand::Description}, {CacheInfoCommand::Name, &CacheInfoCmd, CacheInfoCommand::Description}, {CacheGetCommand::Name, &CacheGetCmd, CacheGetCommand::Description}, {CacheGenerateCommand::Name, &CacheGenerateCmd, CacheGenerateCommand::Description}, {CacheStatsCommand::Name, &CacheStatsCmd, CacheStatsCommand::Description}, {CopyStateCommand::Name, &CopyStateCmd, CopyStateCommand::Description}, {DedupCommand::Name, &DedupCmd, DedupCommand::Description}, {DownCommand::Name, &DownCmd, DownCommand::Description}, {DropCommand::Name, &DropCmd, DropCommand::Description}, #if ZEN_WITH_COMPUTE_SERVICES {ComputeCommand::Name, &ComputeCmd, ComputeCommand::Description}, {ExecCommand::Name, &ExecCmd, ExecCommand::Description}, #endif {GcStatusCommand::Name, &GcStatusCmd, GcStatusCommand::Description}, {GcStopCommand::Name, &GcStopCmd, GcStopCommand::Description}, {GcCommand::Name, &GcCmd, GcCommand::Description}, {HistoryCommand::Name, &HistoryCmd, HistoryCommand::Description}, {HubCommand::Name, &HubCmd, HubCommand::Description}, {InfoCommand::Name, &InfoCmd, InfoCommand::Description}, {JobCommand::Name, &JobCmd, JobCommand::Description}, {LoggingCommand::Name, &LoggingCmd, LoggingCommand::Description}, {CreateOplogCommand::Name, &CreateOplogCmd, CreateOplogCommand::Description}, {ExportOplogCommand::Name, &ExportOplogCmd, ExportOplogCommand::Description}, {ImportOplogCommand::Name, &ImportOplogCmd, ImportOplogCommand::Description}, {OplogMirrorCommand::Name, &OplogMirrorCmd, OplogMirrorCommand::Description}, {SnapshotOplogCommand::Name, &SnapshotOplogCmd, SnapshotOplogCommand::Description}, {OplogDownloadCommand::Name, &OplogDownload, OplogDownloadCommand::Description}, {OplogValidateCommand::Name, &OplogValidateCmd, OplogValidateCommand::Description}, {PrintCommand::Name, &PrintCmd, PrintCommand::Description}, {PrintPackageCommand::Name, &PrintPkgCmd, PrintPackageCommand::Description}, {CreateProjectCommand::Name, &CreateProjectCmd, CreateProjectCommand::Description}, {ProjectOpDetailsCommand::Name, &ProjectOpDetailsCmd, ProjectOpDetailsCommand::Description}, {DropProjectCommand::Name, &ProjectDropCmd, DropProjectCommand::Description}, {ProjectInfoCommand::Name, &ProjectInfoCmd, ProjectInfoCommand::Description}, {ProjectStatsCommand::Name, &ProjectStatsCmd, ProjectStatsCommand::Description}, {PsCommand::Name, &PsCmd, PsCommand::Description}, {RpcReplayCommand::Name, &RpcReplayCmd, RpcReplayCommand::Description}, {RpcStartRecordingCommand::Name, &RpcStartRecordingCmd, RpcStartRecordingCommand::Description}, {RpcStopRecordingCommand::Name, &RpcStopRecordingCmd, RpcStopRecordingCommand::Description}, {ScrubCommand::Name, &ScrubCmd, ScrubCommand::Description}, {ServeCommand::Name, &ServeCmd, ServeCommand::Description}, {StatusCommand::Name, &StatusCmd, StatusCommand::Description}, {TopCommand::Name, &TopCmd, TopCommand::Description}, {TraceCommand::Name, &TraceCmd, TraceCommand::Description}, {UiCommand::Name, &UiCmd, UiCommand::Description}, {UpCommand::Name, &UpCmd, UpCommand::Description}, {VersionCommand::Name, &VersionCmd, VersionCommand::Description}, {VfsCommand::Name, &VfsCmd, VfsCommand::Description}, {FlushCommand::Name, &FlushCmd, FlushCommand::Description}, {WipeCommand::Name, &WipeCmd, WipeCommand::Description}, {WorkspaceCommand::Name, &WorkspaceCmd, WorkspaceCommand::Description}, {WorkspaceShareCommand::Name, &WorkspaceShareCmd, WorkspaceShareCommand::Description}, {ServiceCommand::Name, &ServiceCmd, ServiceCommand::Description}, // clang-format on }; // 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::string PassthroughArgs; std::vector PassthroughArgV; for (int i = 1; i < argc; ++i) { 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) { AppendAscii(" "); } std::string_view ThisArg(argv[j]); PassthroughArgV.push_back(std::string(ThisArg)); const bool NeedsQuotes = (ThisArg.find(' ') != std::string_view::npos) && !(ThisArg.starts_with("\"") && ThisArg.ends_with("\"")); if (NeedsQuotes) { AppendAscii("\""); } AppendAscii(ThisArg); if (NeedsQuotes) { AppendAscii("\""); } IsFirst = false; } Passthrough = Line.c_str(); PassthroughArgs = Arguments.c_str(); // This will "truncate" the arg vector and terminate the loop argc = i; } } // 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.PassthroughCommandLine = Passthrough; GlobalOptions.PassthroughArgs = PassthroughArgs; GlobalOptions.PassthroughArgV = PassthroughArgV; std::string MemoryOptions; 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()("enable-execution-history", "Record this invocation in the per-user execution history (use --enable-execution-history=false to suppress)", cxxopts::value(GlobalOptions.EnableExecutionHistory)->default_value("true")->implicit_value("true")); Options.add_options()("malloc", "Configure memory allocator subsystem", cxxopts::value(MemoryOptions)->default_value("mimalloc")); Options.add_options()("help", "Show command line help"); Options.add_options()("c, command", "Sub command", cxxopts::value(SubCommand)); Options.add_options()("httpclient", "Select HTTP client implementation", cxxopts::value(GlobalOptions.HttpClientBackend)->default_value("curl")); int CoreLimit = 0; Options.add_options()("corelimit", "Limit concurrency", cxxopts::value(CoreLimit)); ZenLoggingCmdLineOptions LoggingCmdLineOptions; LoggingCmdLineOptions.AddCliOptions(Options, GlobalOptions.LoggingConfig); #if ZEN_WITH_TRACE // We only have this in options for command line help purposes - we parse these argument separately earlier using // GetTraceOptionsFromCommandline() Options.add_option("ue-trace", "", "trace", "Specify which trace channels should be enabled", cxxopts::value(TraceCommandlineOptions.Channels)->default_value(""), ""); Options.add_option("ue-trace", "", "tracehost", "Hostname to send the trace to", cxxopts::value(TraceCommandlineOptions.Host)->default_value(""), ""); Options.add_option("ue-trace", "", "tracefile", "Path to write a trace to", cxxopts::value(TraceCommandlineOptions.File)->default_value(""), ""); #endif // ZEN_WITH_TRACE #if ZEN_USE_SENTRY SentryIntegration::Config SentryConfig; bool NoSentry = false; Options .add_option("sentry", "", "no-sentry", "Disable Sentry crash handler", cxxopts::value(NoSentry)->default_value("false"), ""); Options.add_option("sentry", "", "sentry-allow-personal-info", "Allow personally identifiable information in sentry crash reports", cxxopts::value(SentryConfig.AllowPII)->default_value("false"), ""); Options.add_option("sentry", "", "sentry-dsn", "Sentry DSN to send events to", cxxopts::value(SentryConfig.Dsn), ""); Options.add_option("sentry", "", "sentry-environment", "Sentry environment", cxxopts::value(SentryConfig.Environment), ""); Options.add_options()("sentry-debug", "Enable debug mode for Sentry", cxxopts::value(SentryConfig.Debug)->default_value("false")); #endif Options.parse_positional({"command"}); const bool IsNullInvoke = (argc == 1); // If no arguments are passed we want to print usage information try { cxxopts::ParseResult 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"); std::map Categories; for (const CommandInfo& CmdInfo : Commands) { if (CmdInfo.Cmd->IsHidden()) { continue; } ZenCmdCategory& Category = CmdInfo.Cmd->CommandCategory(); Categories[Category.Name] = &Category; Category.SortedCmds[CmdInfo.CmdName] = CmdInfo.CmdSummary; } for (const auto& CategoryKv : Categories) { 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"); } return (int)ReturnCode::kSuccess; } LimitHardwareConcurrency(CoreLimit); #if ZEN_USE_SENTRY { EnvironmentOptions EnvOptions; EnvOptions.AddOption("UE_ZEN_SENTRY_DSN"sv, SentryConfig.Dsn, "sentry-dsn"sv); EnvOptions.AddOption("UE_ZEN_SENTRY_ALLOWPERSONALINFO"sv, SentryConfig.AllowPII, "sentry-allow-personal-info"sv); EnvOptions.AddOption("UE_ZEN_SENTRY_ENVIRONMENT"sv, SentryConfig.Environment, "sentry-environment"sv); bool EnvEnableSentry = !NoSentry; EnvOptions.AddOption("UE_ZEN_SENTRY_ENABLED"sv, EnvEnableSentry, "no-sentry"sv); EnvOptions.AddOption("UE_ZEN_SENTRY_DEBUG"sv, SentryConfig.Debug, "sentry-debug"sv); EnvOptions.Parse(ParseResult); if (EnvEnableSentry != !NoSentry) { NoSentry = !EnvEnableSentry; } } SentryIntegration Sentry; if (NoSentry == false) { std::string SentryDatabasePath = (std::filesystem::temp_directory_path() / ".zen-sentry-native").string(); ExtendableStringBuilder<512> SB; for (int i = 0; i < argc; ++i) { if (i) { SB.Append(' '); } SB.Append(argv[i]); } SentryConfig.DatabasePath = SentryDatabasePath; if (std::filesystem::path HistoryPath = GetInvocationHistoryPath(); !HistoryPath.empty()) { SentryConfig.AttachmentPaths.push_back(std::move(HistoryPath)); } Sentry.Initialize(SentryConfig, SB.ToString()); SentryIntegration::ClearCaches(); } #endif LoggingCmdLineOptions.ApplyOptions(GlobalOptions.LoggingConfig); const LoggingOptions LogOptions = {.IsDebug = GlobalOptions.IsDebug, .IsVerbose = GlobalOptions.IsVerbose, .IsTest = false, .NoConsoleOutput = GlobalOptions.LoggingConfig.NoConsoleOutput, .QuietConsole = GlobalOptions.LoggingConfig.QuietConsole, .ForceColor = GlobalOptions.LoggingConfig.ForceColor, .AbsLogFile = GlobalOptions.LoggingConfig.AbsLogFile, .LogId = GlobalOptions.LoggingConfig.LogId}; zen::InitializeLogging(LogOptions); ApplyLoggingOptions(Options, GlobalOptions.LoggingConfig); std::set_terminate([]() { void* Frames[8]; uint32_t FrameCount = GetCallstack(2, 8, Frames); CallstackFrames* Callstack = CreateCallstack(FrameCount, Frames); ZEN_CRITICAL("Program exited abnormally via std::terminate()\n{}", CallstackToString(Callstack, " ")); FreeCallstack(Callstack); }); zen::SetDefaultHttpClientBackend(GlobalOptions.HttpClientBackend); zen::MaximizeOpenFileCount(); ////////////////////////////////////////////////////////////////////////// auto _ = zen::MakeGuard([] { ShutdownWorkerPools(); ShutdownLogging(); }); for (const CommandInfo& CmdInfo : Commands) { if (StrCaseCompare(SubCommand.c_str(), CmdInfo.CmdName) == 0) { try { CmdInfo.Cmd->Run(GlobalOptions, (int)CommandArgVec.size(), CommandArgVec.data()); return (int)ReturnCode::kSuccess; } catch (const OptionParseException& Ex) { ZEN_CONSOLE("{}\n", Ex.m_Help); ZEN_CONSOLE_ERROR("Invalid arguments for command '{}': {}", SubCommand, Ex.what()); return (int)ReturnCode::kBadInput; } catch (const std::system_error& Ex) { if (IsOOD(Ex)) { ZEN_CONSOLE_ERROR("Operation failed due to out of disk space: {}", Ex.what()); return (int)ReturnCode::kOutOfDisk; } else if (IsOOM(Ex)) { ZEN_CONSOLE_ERROR("Operation failed due to out of memory: {}", Ex.what()); return (int)ReturnCode::kOutOfMemory; } else { ZEN_CONSOLE_ERROR("Operation failed due to system exception: {} ({})\n", Ex.what(), Ex.code() ? Ex.code().value() : 0); return (int)ReturnCode::kOtherError; } } catch (const HttpClientError& Ex) { ZEN_CONSOLE_ERROR("Operation failed due to a http exception: {}", Ex.what()); ReturnCode Result = GetReturnCodeFromHttpResult(Ex); return (int)Result; } catch (const AssertException& Ex) { ZEN_CONSOLE_ERROR("Operation failed due to an assert exception: {}", Ex.FullDescription()); return (int)ReturnCode::kAssertError; } catch (const ErrorWithReturnCode& Ex) { ZEN_CONSOLE_ERROR("{}", Ex.what()); return Ex.m_ReturnCode; } catch (const std::exception& Ex) { ZEN_CONSOLE_ERROR("{}\n", Ex.what()); return (int)ReturnCode::kOtherError; } } } printf("Unknown command specified: '%s', exiting\n", SubCommand.c_str()); return (int)ReturnCode::kBadInput; } catch (const OptionParseException& Ex) { printf("%s\n\n", Ex.m_Help.c_str()); printf("Invalid arguments arguments: %s", Ex.what()); return (int)ReturnCode::kBadInput; } catch (const std::system_error& Ex) { if (IsOOD(Ex)) { printf("Operation failed due to out of disk space: %s", Ex.what()); return (int)ReturnCode::kOutOfDisk; } else if (IsOOM(Ex)) { printf("Operation failed due to out of memory: %s", Ex.what()); return (int)ReturnCode::kOutOfMemory; } else { printf("Operation failed due to system exception: %s (%d)\n", Ex.what(), Ex.code() ? Ex.code().value() : 0); return (int)ReturnCode::kOtherError; } } catch (const HttpClientError& Ex) { printf("Operation failed due to a http exception: %s", Ex.what()); ReturnCode Result = GetReturnCodeFromHttpResult(Ex); return (int)Result; } catch (const AssertException& Ex) { printf("Error: Operation failed due to an assert exception: %s", Ex.FullDescription().c_str()); return (int)ReturnCode::kAssertError; } catch (const ErrorWithReturnCode& Ex) { printf("Error: %s", Ex.what()); return Ex.m_ReturnCode; } catch (const std::exception& Ex) { printf("Error: %s\n", Ex.what()); return (int)ReturnCode::kOtherError; } return (int)ReturnCode::kSuccess; }