aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Sadie Powell2026-06-19 16:25:36 +0100
committerGravatar Sadie Powell2026-06-19 16:43:54 +0100
commitebcce68b6095ca9766641ce5dc00aae01d61de06 (patch)
tree1031bb5de5cbfd3be5ce37470d593bbe30154c95
parentRework error reporting and shutting down. (diff)
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.
-rw-r--r--include/CMakeLists.txt1
-rw-r--r--include/config.h.in3
-rw-r--r--include/inspircd.h7
-rw-r--r--include/modules.h3
-rw-r--r--modules/core/core_oper/cmd_die.cpp23
-rw-r--r--modules/core/core_oper/cmd_restart.cpp31
-rw-r--r--modules/core/core_oper/core_oper.h8
-rw-r--r--modules/spanningtree/main.cpp2
-rw-r--r--modules/spanningtree/main.h2
-rw-r--r--src/inspircd.cpp55
-rw-r--r--src/modules.cpp2
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 = { "<servername>" };
}
-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 <process.h>
-#else
-# include <fcntl.h>
-# include <unistd.h>
-#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<OperAccount>&, bool) { DetachEvent(I_OnPreOperLogin); return MOD_RES_PASSTHRU; }
void Module::OnOperLogin(User*, const std::shared_ptr<OperAccount>&, bool) { DetachEvent(I_OnOperLogin); }
void Module::OnPostOperLogin(User*, bool) { DetachEvent(I_OnPostOperLogin); }