From ebcce68b6095ca9766641ce5dc00aae01d61de06 Mon Sep 17 00:00:00 2001 From: Sadie Powell Date: Fri, 19 Jun 2026 16:25:36 +0100 Subject: Move server restart logic to the core. - Properly clean up the process with Cleanup() instead of using - DieRestart. - Use close_range on modern Linux instead of the CLOEXEC hack. --- include/CMakeLists.txt | 1 + include/config.h.in | 3 ++ include/inspircd.h | 7 +++++ include/modules.h | 3 +- modules/core/core_oper/cmd_die.cpp | 23 +------------- modules/core/core_oper/cmd_restart.cpp | 31 ++----------------- modules/core/core_oper/core_oper.h | 8 ----- modules/spanningtree/main.cpp | 2 +- modules/spanningtree/main.h | 2 +- src/inspircd.cpp | 55 +++++++++++++++++++++++++++++++++- src/modules.cpp | 2 +- 11 files changed, 73 insertions(+), 64 deletions(-) diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt index 3790a782d..f0714cbcf 100644 --- a/include/CMakeLists.txt +++ b/include/CMakeLists.txt @@ -22,6 +22,7 @@ endif() check_function_exists("arc4random_buf" HAS_ARC4RANDOM_BUF) check_function_exists("clock_gettime" HAS_CLOCK_GETTIME) +check_function_exists("close_range" HAS_CLOSE_RANGE) check_function_exists("getentropy" HAS_GETENTROPY) configure_file("config.h.in" "config.h") diff --git a/include/config.h.in b/include/config.h.in index 028c932df..fbb82f367 100644 --- a/include/config.h.in +++ b/include/config.h.in @@ -58,6 +58,9 @@ // Whether the arc4random_buf() function was available at compile time. #cmakedefine HAS_ARC4RANDOM_BUF +// Whether the close_range() function was available at compile time. +#cmakedefine HAS_CLOSE_RANGE + // Whether the clock_gettime() function was available at compile time. #cmakedefine HAS_CLOCK_GETTIME diff --git a/include/inspircd.h b/include/inspircd.h index 470500f64..6c653f04e 100644 --- a/include/inspircd.h +++ b/include/inspircd.h @@ -396,6 +396,13 @@ public: [[noreturn]] static void QuickExit(int status, const std::string& reason = "", const std::string& logtype = ""); + /** Causes the server to restart after unloading modules and closing all open file descriptors. + * @param reason If non-empty then the reason the server is exiting. + * @param logtype If non-empty then the log type to log the exit reason as. + * @return If this function returns then the restart failed and the return value contains the reason why. + */ + std::string Restart(const std::string& reason = "", const std::string& logtype = ""); + /** Stores an incoming signal when received from the operating system. * @param signal The signal received from the operating system. */ diff --git a/include/modules.h b/include/modules.h index dd45c6f09..bbbb209b2 100644 --- a/include/modules.h +++ b/include/modules.h @@ -917,8 +917,9 @@ public: /** Called before a server shuts down. * @param reason The reason the server is shutting down. + * @param restarting Whether the server is restarting. */ - virtual void OnShutdown(const std::string& reason); + virtual void OnShutdown(const std::string& reason, bool restarting); /** Called when a local user is attempting to log in to an server operator account. * @param user The user who is attempting to log in. diff --git a/modules/core/core_oper/cmd_die.cpp b/modules/core/core_oper/cmd_die.cpp index 61d7658bb..f7aa67613 100644 --- a/modules/core/core_oper/cmd_die.cpp +++ b/modules/core/core_oper/cmd_die.cpp @@ -37,32 +37,11 @@ CommandDie::CommandDie(const WeakModulePtr& parent) syntax = { "" }; } -void DieRestart::SendError(const std::string& message) -{ - ClientProtocol::Messages::Error errormsg(message); - ClientProtocol::Event errorevent(ServerInstance->GetRFCEvents().error, errormsg); - - for (auto* user : ServerInstance->Users.GetLocalUsers()) - { - if (user->IsFullyConnected()) - { - user->WriteNotice(message); - } - else - { - // Partially connected users receive ERROR, not a NOTICE - user->Send(errorevent); - } - } -} - CmdResult CommandDie::Handle(User* user, const Params& parameters) { if (insp::casemapped_equals(parameters[0], ServerInstance->Config->ServerName)) { - const std::string diebuf = "*** DIE command from " + user->GetMask() + ". Terminating."; - ServerInstance->Logs.Critical(MODNAME, diebuf); - DieRestart::SendError(diebuf); + ServerInstance->SNO.WriteGlobalSno('a', "DIE command from {}, shutting down server.", user->GetRealMask()); ServerInstance->Exit(EXIT_SUCCESS); } return CmdResult::FAILURE; diff --git a/modules/core/core_oper/cmd_restart.cpp b/modules/core/core_oper/cmd_restart.cpp index 0274bfac3..030f98362 100644 --- a/modules/core/core_oper/cmd_restart.cpp +++ b/modules/core/core_oper/cmd_restart.cpp @@ -23,13 +23,6 @@ */ -#ifdef _WIN32 -# include -#else -# include -# include -#endif - #include "inspircd.h" #include "core_oper.h" @@ -42,31 +35,11 @@ CommandRestart::CommandRestart(const WeakModulePtr& parent) CmdResult CommandRestart::Handle(User* user, const Params& parameters) { - ServerInstance->Logs.Normal(MODNAME, "Restart: {}", user->nick); if (insp::casemapped_equals(parameters[0], ServerInstance->Config->ServerName)) { ServerInstance->SNO.WriteGlobalSno('a', "RESTART command from {}, restarting server.", user->GetRealMask()); - - DieRestart::SendError("Server restarting."); - -#ifdef FD_CLOEXEC - /* XXX: This hack sets FD_CLOEXEC on all possible file descriptors, so they're closed if the execvp() below succeeds. - * Certainly, this is not a nice way to do things and it's slow when the fd limit is high. - * - * A better solution would be to set the close-on-exec flag for each fd we create (or create them with O_CLOEXEC), - * however there is no guarantee that third party libs will do the same. - */ - for (int i = (int)SocketEngine::GetMaxFds(); --i > 2; ) - { - int flags = fcntl(i, F_GETFD); - if (flags != -1) - fcntl(i, F_SETFD, flags | FD_CLOEXEC); - } -#endif - - execvp(ServerInstance->CommandLine.argv[0], ServerInstance->CommandLine.argv); - ServerInstance->SNO.WriteGlobalSno('a', "Failed RESTART - could not execute '{}' ({})", - ServerInstance->CommandLine.argv[0], strerror(errno)); + const auto error = ServerInstance->Restart(); + ServerInstance->SNO.WriteGlobalSno('a', "Failed RESTART: {}", error); } return CmdResult::FAILURE; } diff --git a/modules/core/core_oper/core_oper.h b/modules/core/core_oper/core_oper.h index 48375946e..d6729ad26 100644 --- a/modules/core/core_oper/core_oper.h +++ b/modules/core/core_oper/core_oper.h @@ -29,14 +29,6 @@ enum ERR_NOOPERHOST = 491, }; -namespace DieRestart -{ - /** Send an ERROR to partially connected users and a NOTICE to fully connected users - * @param message Message to send - */ - void SendError(const std::string& message); -} - class CommandDie final : public Command { diff --git a/modules/spanningtree/main.cpp b/modules/spanningtree/main.cpp index 5cd582fe0..6a3bf5495 100644 --- a/modules/spanningtree/main.cpp +++ b/modules/spanningtree/main.cpp @@ -854,7 +854,7 @@ void ModuleSpanningTree::OnMode(User* source, User* u, Channel* c, const Modes:: } } -void ModuleSpanningTree::OnShutdown(const std::string& reason) +void ModuleSpanningTree::OnShutdown(const std::string& reason, bool restarting) { const TreeServer::ChildServers& children = Utils->TreeRoot->GetChildren(); while (!children.empty()) diff --git a/modules/spanningtree/main.h b/modules/spanningtree/main.h index a1d114991..5b2e2d882 100644 --- a/modules/spanningtree/main.h +++ b/modules/spanningtree/main.h @@ -186,7 +186,7 @@ public: void OnUnloadModule(const ModulePtr& mod) override; ModResult OnAcceptConnection(int newsock, ListenSocket* from, const irc::sockets::sockaddrs& client, const irc::sockets::sockaddrs& server) override; void OnMode(User* source, User* u, Channel* c, const Modes::ChangeList& modes, ModeParser::ModeProcessFlag processflags) override; - void OnShutdown(const std::string& reason) override; + void OnShutdown(const std::string& reason, bool restarting) override; void OnDecodeMetadata(Extensible* target, const std::string& extname, const std::string& extdata) override; Cullable::Result Cull() override; ~ModuleSpanningTree() override; diff --git a/src/inspircd.cpp b/src/inspircd.cpp index d75016242..afbd7947d 100644 --- a/src/inspircd.cpp +++ b/src/inspircd.cpp @@ -442,13 +442,66 @@ void InspIRCd::Exit(int status, const std::string& reason, const std::string& lo { // Tell modules that we're shutting down. const auto quitmsg = reason.empty() ? "Server shutting down" : reason; - FOREACH_MOD(OnShutdown, (quitmsg)); + FOREACH_MOD(OnShutdown, (quitmsg, false)); // Clean up all subsystems and then exit. this->Cleanup(quitmsg); InspIRCd::QuickExit(status, reason, logtype); } +std::string InspIRCd::Restart(const std::string& reason, const std::string& logtype) +{ + // Tell modules that we're restarting. + const auto quitmsg = reason.empty() ? "Server restarting" : reason; + FOREACH_MOD(OnShutdown, (quitmsg, true)); + + // Clean up all subsystems. + this->Cleanup(quitmsg); + + // HACK: Because execvp does not close any remaining open file descriptors + // we need ensure they are closed. On modern Linux we can do this with the + // close_range function but on older systems we need to fall back to setting + // the FD_CLOEXEC flag on all possible file descriptors. This is very slow + // but there's not much else we can do because we don't control the fd + // creation of all of our dependencies. + const auto first_fd = fileno(stderr) + 1; +#if defined HAS_CLOSE_RANGE && defined CLOSE_RANGE_CLOEXEC + close_range(first_fd, ~0, CLOSE_RANGE_CLOEXEC); +#elif defined FD_CLOEXEC + for (auto fd = SocketEngine::GetMaxFds(); fd >= size_t(first_fd); ++fd) + { + const auto flags = fcntl(int(fd), F_GETFD); + if (flags != 1) + fcntl(int(fd), F_SETFD, flags | FD_CLOEXEC); + } +#endif + + const auto should_print = isatty(fileno(stdout)); + if (should_print) + fmt::println(""); + + if (!reason.empty()) + { + if (!logtype.empty()) + ServerInstance->Logs.Critical(logtype, reason); + + if (should_print) + fmt::println("Server is restarting: {}.", reason); + } + + execvp(this->CommandLine.argv[0], this->CommandLine.argv); + + const std::string error = strerror(errno); + if (should_print) + { + if (!logtype.empty()) + ServerInstance->Logs.Critical(logtype, "Server restart failed: {}", error); + fmt::println("Server restart failed: {}.", error); + } + + return error; +} + void InspIRCd::WritePID() { if (!CommandLine.writepid) diff --git a/src/modules.cpp b/src/modules.cpp index b465a5044..413757380 100644 --- a/src/modules.cpp +++ b/src/modules.cpp @@ -172,7 +172,7 @@ void Module::OnChangeRemoteAddress(LocalUser*) { DetachEvent(I_OnChangeRemoteAd void Module::OnServiceAdd(Service::Provider&) { DetachEvent(I_OnServiceAdd); } void Module::OnServiceDel(Service::Provider&) { DetachEvent(I_OnServiceDel); } ModResult Module::OnUserWrite(LocalUser*, ClientProtocol::Message&) { DetachEvent(I_OnUserWrite); return MOD_RES_PASSTHRU; } -void Module::OnShutdown(const std::string& reason) { DetachEvent(I_OnShutdown); } +void Module::OnShutdown(const std::string& reason, bool) { DetachEvent(I_OnShutdown); } ModResult Module::OnPreOperLogin(LocalUser*, const std::shared_ptr&, bool) { DetachEvent(I_OnPreOperLogin); return MOD_RES_PASSTHRU; } void Module::OnOperLogin(User*, const std::shared_ptr&, bool) { DetachEvent(I_OnOperLogin); } void Module::OnPostOperLogin(User*, bool) { DetachEvent(I_OnPostOperLogin); } -- cgit v1.3.1-10-gc9f91