// Copyright Epic Games, Inc. All Rights Reserved. #include "config.h" #include "diag/logging.h" #include #include #include #pragma warning(push) #pragma warning(disable : 4267) // warning C4267: '=': conversion from 'size_t' to 'US', possible loss of data #include #pragma warning(pop) #include #include #include #include #if ZEN_PLATFORM_WINDOWS // Used for getting My Documents for default data directory # include # pragma comment(lib, "shell32.lib") std::filesystem::path PickDefaultStateDirectory() { // Pick sensible default WCHAR myDocumentsDir[MAX_PATH]; HRESULT hRes = SHGetFolderPathW(NULL, CSIDL_PERSONAL /* My Documents */, NULL, SHGFP_TYPE_CURRENT, /* out */ myDocumentsDir); if (SUCCEEDED(hRes)) { wcscat_s(myDocumentsDir, L"\\zen"); return myDocumentsDir; } return L""; } #else std::filesystem::path PickDefaultStateDirectory() { return std::filesystem::path("~/.zen"); } #endif UpstreamCachePolicy ParseUpstreamCachePolicy(std::string_view Options) { if (Options == "readonly") { return UpstreamCachePolicy::Read; } else if (Options == "writeonly") { return UpstreamCachePolicy::Write; } else if (Options == "disabled") { return UpstreamCachePolicy::Disabled; } else { return UpstreamCachePolicy::ReadWrite; } } void ParseGlobalCliOptions(int argc, char* argv[], ZenServerOptions& GlobalOptions, ZenServiceConfig& ServiceConfig) { #if ZEN_WITH_HTTPSYS const char* DefaultHttp = "httpsys"; #else const char* DefaultHttp = "asio"; #endif cxxopts::Options options("zenserver", "Zen Server"); options.add_options()("dedicated", "Enable dedicated server mode", cxxopts::value(GlobalOptions.IsDedicated)->default_value("false")); options.add_options()("d, debug", "Enable debugging", cxxopts::value(GlobalOptions.IsDebug)->default_value("false")); options.add_options()("help", "Show command line help"); options.add_options()("t, test", "Enable test mode", cxxopts::value(GlobalOptions.IsTest)->default_value("false")); options.add_options()("log-id", "Specify id for adding context to log output", cxxopts::value(GlobalOptions.LogId)); options.add_options()("data-dir", "Specify persistence root", cxxopts::value(GlobalOptions.DataDir)); options.add_options()("content-dir", "Frontend content directory", cxxopts::value(GlobalOptions.ContentDir)); options.add_options()("abslog", "Path to log file", cxxopts::value(GlobalOptions.AbsLogFile)); options .add_option("lifetime", "", "owner-pid", "Specify owning process id", cxxopts::value(GlobalOptions.OwnerPid), ""); options.add_option("lifetime", "", "child-id", "Specify id which can be used to signal parent", cxxopts::value(GlobalOptions.ChildId), ""); #if ZEN_PLATFORM_WINDOWS options.add_option("lifetime", "", "install", "Install zenserver as a Windows service", cxxopts::value(GlobalOptions.InstallService), ""); options.add_option("lifetime", "", "uninstall", "Uninstall zenserver as a Windows service", cxxopts::value(GlobalOptions.UninstallService), ""); #endif options.add_option("network", "", "http", "Select HTTP server implementation (asio|httpsys|null)", cxxopts::value(GlobalOptions.HttpServerClass)->default_value(DefaultHttp), ""); options.add_option("network", "p", "port", "Select HTTP port", cxxopts::value(GlobalOptions.BasePort)->default_value("1337"), ""); #if ZEN_ENABLE_MESH options.add_option("network", "m", "mesh", "Enable mesh network", cxxopts::value(ServiceConfig.MeshEnabled)->default_value("false"), ""); #endif options.add_option("diagnostics", "", "crash", "Simulate a crash", cxxopts::value(ServiceConfig.ShouldCrash)->default_value("false"), ""); std::string UpstreamCachePolicyOptions; options.add_option("cache", "", "upstream-cache-policy", "", cxxopts::value(UpstreamCachePolicyOptions)->default_value(""), "Upstream cache policy (readwrite|readonly|writeonly|disabled)"); options.add_option("cache", "", "upstream-jupiter-url", "URL to a Jupiter instance", cxxopts::value(ServiceConfig.UpstreamCacheConfig.JupiterConfig.Url)->default_value(""), ""); options.add_option("cache", "", "upstream-jupiter-oauth-url", "URL to the OAuth provier", cxxopts::value(ServiceConfig.UpstreamCacheConfig.JupiterConfig.OAuthProvider)->default_value(""), ""); options.add_option("cache", "", "upstream-jupiter-oauth-clientid", "The OAuth client ID", cxxopts::value(ServiceConfig.UpstreamCacheConfig.JupiterConfig.OAuthClientId)->default_value(""), ""); options.add_option("cache", "", "upstream-jupiter-oauth-clientsecret", "The OAuth client secret", cxxopts::value(ServiceConfig.UpstreamCacheConfig.JupiterConfig.OAuthClientSecret)->default_value(""), ""); options.add_option("cache", "", "upstream-jupiter-namespace", "The Common Blob Store API namespace", cxxopts::value(ServiceConfig.UpstreamCacheConfig.JupiterConfig.Namespace)->default_value(""), ""); options.add_option("cache", "", "upstream-jupiter-namespace-ddc", "The lecacy DDC namespace", cxxopts::value(ServiceConfig.UpstreamCacheConfig.JupiterConfig.DdcNamespace)->default_value(""), ""); options.add_option("cache", "", "upstream-jupiter-prod", "Enable Jupiter upstream caching using production settings", cxxopts::value(ServiceConfig.UpstreamCacheConfig.JupiterConfig.UseProductionSettings)->default_value("false"), ""); options.add_option("cache", "", "upstream-jupiter-dev", "Enable Jupiter upstream caching using development settings", cxxopts::value(ServiceConfig.UpstreamCacheConfig.JupiterConfig.UseDevelopmentSettings)->default_value("false"), ""); options.add_option("cache", "", "upstream-jupiter-use-legacy-ddc", "Whether to store derived data using the legacy endpoint", cxxopts::value(ServiceConfig.UpstreamCacheConfig.JupiterConfig.UseLegacyDdc)->default_value("false"), ""); options.add_option("cache", "", "upstream-zen-url", "URL to remote Zen server. Use a comma separated list to choose the one with the best latency.", cxxopts::value>(ServiceConfig.UpstreamCacheConfig.ZenConfig.Urls)->default_value(""), ""); options.add_option("cache", "", "upstream-thread-count", "Number of threads used for upstream procsssing", cxxopts::value(ServiceConfig.UpstreamCacheConfig.UpstreamThreadCount)->default_value("4"), ""); options.add_option("cache", "", "upstream-stats", "Collect performance metrics for upstream endpoints", cxxopts::value(ServiceConfig.UpstreamCacheConfig.StatsEnabled)->default_value("false"), ""); try { auto result = options.parse(argc, argv); if (result.count("help")) { zen::logging::ConsoleLog().info("{}", options.help()); zen::logging::ConsoleLog().info("Press any key to exit!"); _getch(); exit(0); } ServiceConfig.UpstreamCacheConfig.CachePolicy = ParseUpstreamCachePolicy(UpstreamCachePolicyOptions); } catch (cxxopts::OptionParseException& e) { zen::logging::ConsoleLog().error("Error parsing zenserver arguments: {}\n\n{}", e.what(), options.help()); throw; } if (GlobalOptions.DataDir.empty()) { GlobalOptions.DataDir = PickDefaultStateDirectory(); } } void ParseServiceConfig(const std::filesystem::path& DataRoot, ZenServiceConfig& ServiceConfig) { using namespace fmt::literals; std::filesystem::path ConfigScript = DataRoot / "zen_cfg.lua"; zen::IoBuffer LuaScript = zen::IoBufferBuilder::MakeFromFile(ConfigScript.native().c_str()); if (LuaScript) { sol::state lua; // Provide some context to help derive defaults lua.set("dataroot", DataRoot.native()); lua.open_libraries(sol::lib::base); // We probably want to limit the scope of this so the script won't see // any more than it needs to lua.set_function("getenv", [&](const std::string env) -> sol::object { std::wstring EnvVarValue; size_t RequiredSize = 0; std::wstring EnvWide = zen::Utf8ToWide(env); _wgetenv_s(&RequiredSize, nullptr, 0, EnvWide.c_str()); if (RequiredSize == 0) return sol::make_object(lua, sol::lua_nil); EnvVarValue.resize(RequiredSize); _wgetenv_s(&RequiredSize, EnvVarValue.data(), RequiredSize, EnvWide.c_str()); return sol::make_object(lua, zen::WideToUtf8(EnvVarValue.c_str())); }); try { sol::load_result config = lua.load(std::string_view((const char*)LuaScript.Data(), LuaScript.Size()), "zen_cfg"); if (!config.valid()) { sol::error err = config; std::string ErrorString = sol::to_string(config.status()); throw std::runtime_error("{} error: {}"_format(ErrorString, err.what())); } config(); } catch (std::exception& e) { ZEN_ERROR("config failure: {}", e.what()); throw std::runtime_error("failed to run global config script ('{}'): {}"_format(ConfigScript, e.what()).c_str()); } #if ZEN_ENABLE_MESH ServiceConfig.MeshEnabled = lua["mesh"]["enable"].get_or(ServiceConfig.MeshEnabled); #endif auto UpdateStringValueFromConfig = [](const sol::table& Table, std::string_view Key, std::string& OutValue) { // Update the specified config value unless it has been set, i.e. from command line if (auto MaybeValue = Table.get>(Key); MaybeValue.has_value() && OutValue.empty()) { OutValue = MaybeValue.value(); } }; if (sol::optional StructuredCacheConfig = lua["structuredcache"]) { ServiceConfig.StructuredCacheEnabled = StructuredCacheConfig->get_or("enable", ServiceConfig.StructuredCacheEnabled); if (auto UpstreamConfig = StructuredCacheConfig->get>("upstream")) { std::string Policy = UpstreamConfig->get_or("policy", std::string()); ServiceConfig.UpstreamCacheConfig.CachePolicy = ParseUpstreamCachePolicy(Policy); ServiceConfig.UpstreamCacheConfig.UpstreamThreadCount = UpstreamConfig->get_or("upstreamthreadcount", 4); if (auto JupiterConfig = UpstreamConfig->get>("jupiter")) { UpdateStringValueFromConfig(JupiterConfig.value(), std::string_view("url"), ServiceConfig.UpstreamCacheConfig.JupiterConfig.Url); UpdateStringValueFromConfig(JupiterConfig.value(), std::string_view("oauthprovider"), ServiceConfig.UpstreamCacheConfig.JupiterConfig.OAuthProvider); UpdateStringValueFromConfig(JupiterConfig.value(), std::string_view("oauthclientid"), ServiceConfig.UpstreamCacheConfig.JupiterConfig.OAuthClientId); UpdateStringValueFromConfig(JupiterConfig.value(), std::string_view("oauthclientsecret"), ServiceConfig.UpstreamCacheConfig.JupiterConfig.OAuthClientSecret); UpdateStringValueFromConfig(JupiterConfig.value(), std::string_view("namespace"), ServiceConfig.UpstreamCacheConfig.JupiterConfig.Namespace); UpdateStringValueFromConfig(JupiterConfig.value(), std::string_view("ddcnamespace"), ServiceConfig.UpstreamCacheConfig.JupiterConfig.DdcNamespace); ServiceConfig.UpstreamCacheConfig.JupiterConfig.UseDevelopmentSettings = JupiterConfig->get_or("usedevelopmentsettings", ServiceConfig.UpstreamCacheConfig.JupiterConfig.UseDevelopmentSettings); ServiceConfig.UpstreamCacheConfig.JupiterConfig.UseLegacyDdc = JupiterConfig->get_or("uselegacyddc", ServiceConfig.UpstreamCacheConfig.JupiterConfig.UseLegacyDdc); }; if (auto ZenConfig = UpstreamConfig->get>("zen")) { if (auto Url = ZenConfig.value().get>("url")) { ServiceConfig.UpstreamCacheConfig.ZenConfig.Urls.push_back(Url.value()); } else if (auto Urls = ZenConfig.value().get>("url")) { for (const auto& Kv : Urls.value()) { ServiceConfig.UpstreamCacheConfig.ZenConfig.Urls.push_back(Kv.second.as()); } } } } } } }