// Copyright Epic Games, Inc. All Rights Reserved. #include "config.h" #include "storage/storageconfig.h" #include "config/luaconfig.h" #include "diag/logging.h" #include #include #include #include #include #include #include #include #include #include #include ZEN_THIRD_PARTY_INCLUDES_START #include #include ZEN_THIRD_PARTY_INCLUDES_END #if ZEN_PLATFORM_WINDOWS # include #else # include #endif namespace zen { std::filesystem::path PickDefaultStateDirectory(std::filesystem::path SystemRoot) { if (SystemRoot.empty()) return SystemRoot; return SystemRoot / "Data"; } void EmitCentralManifest(const std::filesystem::path& SystemRoot, Oid Identifier, CbObject Manifest, std::filesystem::path ManifestPath) { CbObjectWriter Cbo; Cbo << "path" << ManifestPath.generic_wstring(); Cbo << "manifest" << Manifest; const std::filesystem::path StatesPath = SystemRoot / "States"; CreateDirectories(StatesPath); WriteFile(StatesPath / fmt::format("{}", Identifier), Cbo.Save().GetBuffer().AsIoBuffer()); } std::vector ReadAllCentralManifests(const std::filesystem::path& SystemRoot) { std::vector Manifests; DirectoryContent Content; GetDirectoryContent(SystemRoot / "States", DirectoryContentFlags::IncludeFiles, Content); for (std::filesystem::path& File : Content.Files) { try { FileContents FileData = ReadFile(File); CbValidateError ValidateError; if (CbObject Manifest = ValidateAndReadCompactBinaryObject(FileData.Flatten(), ValidateError); ValidateError == CbValidateError::None) { Manifests.emplace_back(std::move(Manifest)); } else { ZEN_WARN("failed to load manifest '{}': {}", File, ToString(ValidateError)); } } catch (const std::exception& Ex) { ZEN_WARN("failed to load manifest '{}': {}", File, Ex.what()); } } return Manifests; } void ZenServerConfiguratorBase::ParseEnvVariables(const cxxopts::ParseResult& CmdLineResult) { using namespace std::literals; EnvironmentOptions Options; ZenServerConfig& ServerOptions = m_ServerOptions; bool EnvEnableSentry = !ServerOptions.SentryConfig.Disable; // clang-format off Options.AddOption("UE_ZEN_SENTRY_ALLOWPERSONALINFO"sv, ServerOptions.SentryConfig.AllowPII, "sentry-allow-personal-info"sv); Options.AddOption("UE_ZEN_SENTRY_DSN"sv, ServerOptions.SentryConfig.Dsn, "sentry-dsn"sv); Options.AddOption("UE_ZEN_SENTRY_ENVIRONMENT"sv, ServerOptions.SentryConfig.Environment, "sentry-environment"sv); Options.AddOption("UE_ZEN_SENTRY_ENABLED"sv, EnvEnableSentry, "no-sentry"sv); Options.AddOption("UE_ZEN_SENTRY_DEBUG"sv, ServerOptions.SentryConfig.Debug, "sentry-debug"sv); // clang-format on Options.Parse(CmdLineResult); if (EnvEnableSentry != !ServerOptions.SentryConfig.Disable) { ServerOptions.SentryConfig.Disable = !EnvEnableSentry; } } void ZenServerConfiguratorBase::AddCommonConfigOptions(LuaConfig::Options& LuaOptions) { using namespace std::literals; ZenServerConfig& ServerOptions = m_ServerOptions; // logging LuaOptions.AddOption("server.logid"sv, ServerOptions.LoggingConfig.LogId, "log-id"sv); LuaOptions.AddOption("server.abslog"sv, ServerOptions.LoggingConfig.AbsLogFile, "abslog"sv); LuaOptions.AddOption("server.otlpendpoint"sv, ServerOptions.LoggingConfig.OtelEndpointUri, "otlp-endpoint"sv); LuaOptions.AddOption("server.quiet"sv, ServerOptions.LoggingConfig.QuietConsole, "quiet"sv); LuaOptions.AddOption("server.noconsole"sv, ServerOptions.LoggingConfig.NoConsoleOutput, "noconsole"sv); // server LuaOptions.AddOption("server.dedicated"sv, ServerOptions.IsDedicated, "dedicated"sv); LuaOptions.AddOption("server.sentry.disable"sv, ServerOptions.SentryConfig.Disable, "no-sentry"sv); LuaOptions.AddOption("server.sentry.allowpersonalinfo"sv, ServerOptions.SentryConfig.AllowPII, "sentry-allow-personal-info"sv); LuaOptions.AddOption("server.sentry.dsn"sv, ServerOptions.SentryConfig.Dsn, "sentry-dsn"sv); LuaOptions.AddOption("server.sentry.environment"sv, ServerOptions.SentryConfig.Environment, "sentry-environment"sv); LuaOptions.AddOption("server.sentry.debug"sv, ServerOptions.SentryConfig.Debug, "sentry-debug"sv); LuaOptions.AddOption("server.systemrootdir"sv, ServerOptions.SystemRootDir, "system-dir"sv); LuaOptions.AddOption("server.datadir"sv, ServerOptions.DataDir, "data-dir"sv); LuaOptions.AddOption("server.contentdir"sv, ServerOptions.ContentDir, "content-dir"sv); LuaOptions.AddOption("server.debug"sv, ServerOptions.IsDebug, "debug"sv); LuaOptions.AddOption("server.clean"sv, ServerOptions.IsCleanStart, "clean"sv); LuaOptions.AddOption("server.security.configpath"sv, ServerOptions.SecurityConfigPath, "security-config-path"sv); ////// network LuaOptions.AddOption("network.httpserverclass"sv, ServerOptions.HttpConfig.ServerClass, "http"sv); LuaOptions.AddOption("network.httpserverthreads"sv, ServerOptions.HttpConfig.ThreadCount, "http-threads"sv); LuaOptions.AddOption("network.port"sv, ServerOptions.BasePort, "port"sv); LuaOptions.AddOption("network.forceloopback"sv, ServerOptions.HttpConfig.ForceLoopback, "http-forceloopback"sv); #if ZEN_WITH_HTTPSYS LuaOptions.AddOption("network.httpsys.async.workthreads"sv, ServerOptions.HttpConfig.HttpSys.AsyncWorkThreadCount, "httpsys-async-work-threads"sv); LuaOptions.AddOption("network.httpsys.async.response"sv, ServerOptions.HttpConfig.HttpSys.IsAsyncResponseEnabled, "httpsys-enable-async-response"sv); LuaOptions.AddOption("network.httpsys.requestlogging"sv, ServerOptions.HttpConfig.HttpSys.IsRequestLoggingEnabled, "httpsys-enable-request-logging"sv); #endif #if ZEN_WITH_TRACE ////// trace LuaOptions.AddOption("trace.channels"sv, ServerOptions.TraceCmdLineOptions.Channels, "trace"sv); LuaOptions.AddOption("trace.host"sv, ServerOptions.TraceCmdLineOptions.Host, "tracehost"sv); LuaOptions.AddOption("trace.file"sv, ServerOptions.TraceCmdLineOptions.File, "tracefile"sv); #endif ////// stats LuaOptions.AddOption("stats.enable"sv, ServerOptions.StatsConfig.Enabled, "statsd"sv); LuaOptions.AddOption("stats.host"sv, ServerOptions.StatsConfig.StatsdHost); LuaOptions.AddOption("stats.port"sv, ServerOptions.StatsConfig.StatsdPort); } struct ZenServerCmdLineOptions { // Note to those adding future options; std::filesystem::path-type options // must be read into a std::string first. As of cxxopts-3.0.0 it uses a >> // stream operator to convert argv value into the options type. std::fs::path // expects paths in streams to be quoted but argv paths are unquoted. By // going into a std::string first, paths with whitespace parse correctly. std::string ConfigFile; std::string OutputConfigFile; std::string SystemRootDir; std::string ContentDir; std::string DataDir; std::string BaseSnapshotDir; std::string SecurityConfigPath; ZenLoggingCmdLineOptions LoggingOptions; void AddCliOptions(cxxopts::Options& options, ZenServerConfig& ServerOptions); void ApplyOptions(cxxopts::Options& options, ZenServerConfig& ServerOptions); }; void ZenServerCmdLineOptions::AddCliOptions(cxxopts::Options& options, ZenServerConfig& ServerOptions) { const char* DefaultHttp = "asio"; #if ZEN_WITH_HTTPSYS if (!windows::IsRunningOnWine()) { DefaultHttp = "httpsys"; } #endif options.add_options()("dedicated", "Enable dedicated server mode", cxxopts::value(ServerOptions.IsDedicated)->default_value("false")); options.add_options()("d, debug", "Enable debugging", cxxopts::value(ServerOptions.IsDebug)->default_value("false")); options.add_options()("clean", "Clean out all state at startup", cxxopts::value(ServerOptions.IsCleanStart)->default_value("false")); options.add_options()("help", "Show command line help"); options.add_options()("t, test", "Enable test mode", cxxopts::value(ServerOptions.IsTest)->default_value("false")); options.add_options()("data-dir", "Specify persistence root", cxxopts::value(DataDir)); options.add_options()("system-dir", "Specify system root", cxxopts::value(SystemRootDir)); options.add_options()("content-dir", "Frontend content directory", cxxopts::value(ContentDir)); options.add_options()("config", "Path to Lua config file", cxxopts::value(ConfigFile)); options.add_options()("write-config", "Path to output Lua config file", cxxopts::value(OutputConfigFile)); options.add_options()("snapshot-dir", "Specify a snapshot of server state to mirror into the persistence root at startup", cxxopts::value(BaseSnapshotDir)); options.add_options()("no-sentry", "Disable Sentry crash handler", cxxopts::value(ServerOptions.SentryConfig.Disable)->default_value("false")); options.add_options()("sentry-allow-personal-info", "Allow personally identifiable information in sentry crash reports", cxxopts::value(ServerOptions.SentryConfig.AllowPII)->default_value("false")); options.add_options()("sentry-dsn", "Sentry DSN to send events to", cxxopts::value(ServerOptions.SentryConfig.Dsn)); options.add_options()("sentry-environment", "Sentry environment", cxxopts::value(ServerOptions.SentryConfig.Environment)); options.add_options()("sentry-debug", "Enable debug mode for Sentry", cxxopts::value(ServerOptions.SentryConfig.Debug)->default_value("false")); options.add_options()("detach", "Indicate whether zenserver should detach from parent process group", cxxopts::value(ServerOptions.Detach)->default_value("true")); options.add_options()("malloc", "Configure memory allocator subsystem", cxxopts::value(ServerOptions.MemoryOptions)->default_value("mimalloc")); options.add_options()("corelimit", "Limit concurrency", cxxopts::value(ServerOptions.CoreLimit)); options.add_options()("powercycle", "Exit immediately after initialization is complete", cxxopts::value(ServerOptions.IsPowerCycle)); options.add_option("diagnostics", "", "crash", "Simulate a crash", cxxopts::value(ServerOptions.ShouldCrash)->default_value("false"), ""); LoggingOptions.AddCliOptions(options, ServerOptions.LoggingConfig); options .add_option("lifetime", "", "owner-pid", "Specify owning process id", cxxopts::value(ServerOptions.OwnerPid), ""); options.add_option("lifetime", "", "child-id", "Specify id which can be used to signal parent", cxxopts::value(ServerOptions.ChildId), ""); #if ZEN_PLATFORM_WINDOWS options.add_option("lifetime", "", "install", "Install zenserver as a Windows service", cxxopts::value(ServerOptions.InstallService), ""); options.add_option("lifetime", "", "uninstall", "Uninstall zenserver as a Windows service", cxxopts::value(ServerOptions.UninstallService), ""); #endif options.add_option("network", "", "http-threads", "Number of http server connection threads", cxxopts::value(ServerOptions.HttpConfig.ThreadCount), ""); options.add_option("network", "p", "port", "Select HTTP port", cxxopts::value(ServerOptions.BasePort)->default_value("8558"), ""); options.add_option("network", "", "http-forceloopback", "Force using local loopback interface", cxxopts::value(ServerOptions.HttpConfig.ForceLoopback)->default_value("false"), ""); options.add_option("network", "", "security-config-path", "Path to http security configuration file", cxxopts::value(SecurityConfigPath), ""); #if ZEN_WITH_HTTPSYS options.add_option("httpsys", "", "httpsys-async-work-threads", "Number of HttpSys async worker threads", cxxopts::value(ServerOptions.HttpConfig.HttpSys.AsyncWorkThreadCount), ""); options.add_option("httpsys", "", "httpsys-enable-async-response", "Enables Httpsys async response", cxxopts::value(ServerOptions.HttpConfig.HttpSys.IsAsyncResponseEnabled)->default_value("true"), ""); options.add_option("httpsys", "", "httpsys-enable-request-logging", "Enables Httpsys request logging", cxxopts::value(ServerOptions.HttpConfig.HttpSys.IsRequestLoggingEnabled), ""); #endif options.add_option("network", "", "http", "Select HTTP server implementation (asio|" #if ZEN_WITH_HTTPSYS "httpsys|" #endif "null)", cxxopts::value(ServerOptions.HttpConfig.ServerClass)->default_value(DefaultHttp), ""); #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(ServerOptions.TraceCmdLineOptions.Channels)->default_value(""), ""); options.add_option("ue-trace", "", "tracehost", "Hostname to send the trace to", cxxopts::value(ServerOptions.TraceCmdLineOptions.Host)->default_value(""), ""); options.add_option("ue-trace", "", "tracefile", "Path to write a trace to", cxxopts::value(ServerOptions.TraceCmdLineOptions.File)->default_value(""), ""); #endif // ZEN_WITH_TRACE options.add_option("stats", "", "statsd", "", cxxopts::value(ServerOptions.StatsConfig.Enabled)->default_value("false"), "Enable statsd reporter (localhost:8125)"); } void ZenServerCmdLineOptions::ApplyOptions(cxxopts::Options& options, ZenServerConfig& ServerOptions) { if (!BaseSnapshotDir.empty()) { if (ServerOptions.DataDir.empty()) throw OptionParseException("'--snapshot-dir' requires '--data-dir'", options.help()); if (!IsDir(ServerOptions.BaseSnapshotDir)) throw std::runtime_error(fmt::format("'--snapshot-dir' ('{}') must be a directory", ServerOptions.BaseSnapshotDir)); } ServerOptions.SystemRootDir = MakeSafeAbsolutePath(SystemRootDir); ServerOptions.DataDir = MakeSafeAbsolutePath(DataDir); ServerOptions.ContentDir = MakeSafeAbsolutePath(ContentDir); ServerOptions.ConfigFile = MakeSafeAbsolutePath(ConfigFile); ServerOptions.BaseSnapshotDir = MakeSafeAbsolutePath(BaseSnapshotDir); ServerOptions.SecurityConfigPath = MakeSafeAbsolutePath(SecurityConfigPath); LoggingOptions.ApplyOptions(ServerOptions.LoggingConfig); } ////////////////////////////////////////////////////////////////////////// ZenServerConfiguratorBase::ZenServerConfiguratorBase(ZenServerConfig& BaseServerOptions) : m_ServerOptions(BaseServerOptions) { } ZenServerConfiguratorBase::~ZenServerConfiguratorBase() { } void ZenServerConfiguratorBase::Configure(int argc, char* argv[]) { for (int i = 0; i < argc; ++i) { if (i) { m_ServerOptions.CommandLine.push_back(' '); } m_ServerOptions.CommandLine += argv[i]; } cxxopts::Options options("zenserver", "Zen Storage Server"); ZenServerCmdLineOptions BaseOptions; BaseOptions.AddCliOptions(options, m_ServerOptions); // Add subclass' command line options AddCliOptions(options); try { cxxopts::ParseResult Result; try { Result = options.parse(argc, argv); } catch (const std::exception& Ex) { throw OptionParseException(Ex.what(), options.help()); } if (Result.count("help")) { ZEN_CONSOLE("{}", options.help()); #if ZEN_PLATFORM_WINDOWS ZEN_CONSOLE("Press any key to exit!"); _getch(); #else // Assume the user's in a terminal on all other platforms and that // they'll use less/more/etc. if need be. #endif exit(0); } #if ZEN_WITH_TRACE if (!m_ServerOptions.HasTraceCommandlineOptions) { // Apply any Lua settings if we don't have them set from the command line TraceConfigure(m_ServerOptions.TraceCmdLineOptions); } #endif ApplyLoggingOptions(options, m_ServerOptions.LoggingConfig); BaseOptions.ApplyOptions(options, m_ServerOptions); ApplyOptions(options); // Handle any environment variables ParseEnvVariables(Result); ZEN_TRACE_CPU("ConfigParse"); if (!m_ServerOptions.ConfigFile.empty()) { ParseConfigFile(m_ServerOptions.ConfigFile, Result, BaseOptions.OutputConfigFile); } else { ParseConfigFile(m_ServerOptions.DataDir / "zen_cfg.lua", Result, BaseOptions.OutputConfigFile); } ValidateOptions(); // subclass validation } catch (const OptionParseException& e) { ZEN_CONSOLE("{}\n", options.help()); ZEN_CONSOLE_ERROR("Invalid zenserver arguments: {}", e.what()); throw; } if (m_ServerOptions.SystemRootDir.empty()) { m_ServerOptions.SystemRootDir = PickDefaultSystemRootDirectory(); } if (m_ServerOptions.DataDir.empty()) { m_ServerOptions.DataDir = PickDefaultStateDirectory(m_ServerOptions.SystemRootDir); } if (m_ServerOptions.LoggingConfig.AbsLogFile.empty()) { m_ServerOptions.LoggingConfig.AbsLogFile = m_ServerOptions.DataDir / "logs" / "zenserver.log"; } m_ServerOptions.HttpConfig.IsDedicatedServer = m_ServerOptions.IsDedicated; } void ZenServerConfiguratorBase::ParseConfigFile(const std::filesystem::path& Path, const cxxopts::ParseResult& CmdLineResult, std::string_view OutputConfigFile) { ZEN_TRACE_CPU("ParseConfigFile"); LuaConfig::Options LuaOptions; AddCommonConfigOptions(LuaOptions); AddConfigOptions(LuaOptions); // subclass options LuaOptions.Parse(Path, CmdLineResult); OnConfigFileParsed(LuaOptions); // subclass hook if (!OutputConfigFile.empty()) { ExtendableStringBuilder<512> ConfigStringBuilder; LuaOptions.Print(ConfigStringBuilder, CmdLineResult); BasicFile Output; std::filesystem::path WritePath(MakeSafeAbsolutePath(OutputConfigFile)); Output.Open(WritePath, BasicFile::Mode::kTruncate); Output.Write(ConfigStringBuilder.Data(), ConfigStringBuilder.Size(), 0); } } } // namespace zen