// Copyright Epic Games, Inc. All Rights Reserved. #include "up_cmd.h" #include #include #include #include #include #include #include namespace zen { UpCommand::UpCommand() { m_Options.add_option("", "p", "port", "Host port", cxxopts::value(m_Port)->default_value("0"), ""); m_Options.add_option("", "b", "base-dir", "Parent folder of server executable", cxxopts::value(m_ProgramBaseDir), ""); m_Options.add_option("", "c", "show-console", "Open a console window for the zenserver process", cxxopts::value(m_ShowConsole), ""); m_Options.add_option("", "l", "show-log", "Show the output log of the zenserver process after successful start", cxxopts::value(m_ShowLog), ""); } UpCommand::~UpCommand() = default; void UpCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) { if (!ParseOptions(argc, argv)) { return; } if (m_ShowConsole && m_ShowLog) { throw OptionParseException("'--show-console' conflicts with '--show-log'", m_Options.help()); } std::optional StartResult = StartupZenServer(ConsoleLog(), {.ProgramBaseDir = m_ProgramBaseDir, .Port = m_Port, .OpenConsole = m_ShowConsole, .ShowLog = m_ShowLog, .ExtraArgs = GlobalOptions.PassthroughCommandLine}); if (!StartResult.has_value()) { ZEN_CONSOLE("Zen server already running"); return; } if (*StartResult != 0) { throw ErrorWithReturnCode("Zen server failed to start", *StartResult); } ZEN_CONSOLE("Zen server up"); } ////////////////////////////////////////////////////////////////////////// AttachCommand::AttachCommand() { m_Options.add_option("", "p", "port", "Host port", cxxopts::value(m_Port)->default_value("8558"), ""); m_Options.add_option("lifetime", "", "owner-pid", "Specify owning process id", cxxopts::value(m_OwnerPid), ""); m_Options.add_option("", "", "data-dir", "Path to data directory to inspect for running server", cxxopts::value(m_DataDir), ""); } AttachCommand::~AttachCommand() = default; void AttachCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) { ZEN_UNUSED(GlobalOptions); if (!ParseOptions(argc, argv)) { return; } ZenServerState Instance; Instance.Initialize(); Instance.Sweep(); ZenServerState::ZenServerEntry* Entry = Instance.Lookup(m_Port); if (!m_DataDir.empty()) { if (!IsFile(m_DataDir / ".lock")) { throw std::runtime_error(fmt::format("Lock file does not exist in directory '{}'", m_DataDir)); } CbValidateError ValidateResult = CbValidateError::None; if (CbObject LockFileObject = ValidateAndReadCompactBinaryObject(IoBufferBuilder::MakeFromFile(m_DataDir / ".lock"), ValidateResult); ValidateResult == CbValidateError::None && LockFileObject) { LockFileInfo Info = ReadLockFilePayload(LockFileObject); std::string Reason; if (!ValidateLockFileInfo(Info, Reason)) { throw std::runtime_error(fmt::format("Lock file in directory '{}' is not valid. Reason: '{}'", m_DataDir, Reason)); } Entry = Instance.LookupByEffectivePort(Info.EffectiveListenPort); } else { throw std::runtime_error( fmt::format("Lock file in directory '{}' is malformed. Reason: '{}'", m_DataDir, ToString(ValidateResult))); } } if (!Entry) { throw std::runtime_error("No zen server instance to add sponsor process to"); } // Sponsor processes are checked every second, so 2 second wait time should be enough if (!Entry->AddSponsorProcess(m_OwnerPid, 2000)) { throw std::runtime_error("Unable to add sponsor process to running zen server instance"); } ZEN_CONSOLE("Added sponsor process {} to running instance {} on port {}", m_OwnerPid, Entry->Pid.load(), m_Port); } ////////////////////////////////////////////////////////////////////////// DownCommand::DownCommand() { m_Options.add_option("", "p", "port", "Host port", cxxopts::value(m_Port)->default_value("0"), ""); m_Options.add_option("", "a", "all", "Shut down all running zen server instances", cxxopts::value(m_All), ""); m_Options.add_option("", "f", "force", "Force terminate if graceful shutdown fails", cxxopts::value(m_ForceTerminate), ""); m_Options.add_option("", "b", "base-dir", "Parent folder of server executable", cxxopts::value(m_ProgramBaseDir), ""); m_Options.add_option("", "", "data-dir", "Path to data directory to inspect for running server", cxxopts::value(m_DataDir), ""); } DownCommand::~DownCommand() = default; void DownCommand::Run(const ZenCliOptions& GlobalOptions, int argc, char** argv) { ZEN_UNUSED(GlobalOptions); if (!ParseOptions(argc, argv)) { return; } if (m_ProgramBaseDir.empty()) { std::filesystem::path ExePath = GetRunningExecutablePath(); m_ProgramBaseDir = ExePath.parent_path(); } // Discover executing instances ZenServerState Instance; Instance.Initialize(); if (m_All) { struct EntryInfo { uint16_t Port = 0; uint32_t Pid = 0; }; std::vector Entries; Instance.Snapshot([&Entries](const ZenServerState::ZenServerEntry& Entry) { uint16_t Port = Entry.DesiredListenPort.load(); uint32_t Pid = Entry.Pid.load(); if (Port != 0 && Pid != 0) { Entries.push_back({Port, Pid}); } }); if (Entries.empty()) { ZEN_CONSOLE("No zen server instances to bring down"); return; } int FailCount = 0; for (const EntryInfo& Info : Entries) { Instance.Sweep(); ZenServerState::ZenServerEntry* Entry = Instance.Lookup(Info.Port); if (Entry && Entry->Pid.load() == Info.Pid) { if (!ShutdownZenServer(ConsoleLog(), Instance, Entry, m_ProgramBaseDir)) { ZEN_CONSOLE_WARN("Failed to shutdown server on port {} (pid {})", Info.Port, Info.Pid); ++FailCount; } } } if (FailCount > 0 && !m_ForceTerminate) { throw std::runtime_error(fmt::format("Failed to shutdown {} instance(s), use --force to hard terminate", FailCount)); } return; } ZenServerState::ZenServerEntry* Entry = Instance.Lookup(m_Port); if (!m_DataDir.empty()) { if (!IsFile(m_DataDir / ".lock")) { throw std::runtime_error(fmt::format("Lock file does not exist in directory '{}'", m_DataDir)); } CbValidateError ValidateResult = CbValidateError::None; if (CbObject LockFileObject = ValidateAndReadCompactBinaryObject(IoBufferBuilder::MakeFromFile(m_DataDir / ".lock"), ValidateResult); ValidateResult == CbValidateError::None && LockFileObject) { LockFileInfo Info = ReadLockFilePayload(LockFileObject); std::string Reason; if (!ValidateLockFileInfo(Info, Reason)) { throw std::runtime_error(fmt::format("Lock file in directory '{}' is not valid. Reason: '{}'", m_DataDir, Reason)); } Entry = Instance.LookupByEffectivePort(Info.EffectiveListenPort); } else { throw std::runtime_error( fmt::format("Lock file in directory '{}' is malformed. Reason: '{}'", m_DataDir, ToString(ValidateResult))); } } if (Entry) { if (ShutdownZenServer(ConsoleLog(), Instance, Entry, m_ProgramBaseDir)) { return; } } if (m_ForceTerminate) { // Try to find the running executable by path name std::filesystem::path ServerExePath = m_ProgramBaseDir / "zenserver" ZEN_EXE_SUFFIX_LITERAL; ProcessHandle RunningProcess; if (std::error_code Ec = FindProcess(ServerExePath, RunningProcess, /*IncludeSelf*/ false); !Ec) { ZEN_CONSOLE_WARN("Attempting hard terminate of zen process with pid ({})", RunningProcess.Pid()); if (RunningProcess.Terminate(0)) { ZEN_CONSOLE("Terminate complete"); return; } throw std::runtime_error("Failed to terminate server, still running"); } else { ZEN_CONSOLE_WARN("Failed to find process '{}', reason: {}", ServerExePath.string(), Ec.message()); } } else if (Entry) { throw std::runtime_error( fmt::format("Failed to shut down server on port {}, use --force to hard terminate process", Entry->DesiredListenPort.load())); } ZEN_CONSOLE("No zen server to bring down"); } } // namespace zen