From 63ea08bde6db8687988f139c8ee8914e308f8455 Mon Sep 17 00:00:00 2001 From: zqmfb <267821100+angularbanjo@users.noreply.github.com> Date: Tue, 24 Mar 2026 20:48:32 -0400 Subject: [PATCH] Add support for SCGI systemd socket activation Add a new command, `network.scgi.open_systemd`, that binds to a file descriptor passed in via systemd socket activation. --- configure.ac | 1 + scripts/checks.m4 | 17 +++++++++++++++++ src/command_network.cc | 42 ++++++++++++++++++++++++++++++++++++++++++ src/rpc/scgi.cc | 15 +++++++++++++++ src/rpc/scgi.h | 1 + 5 files changed, 76 insertions(+) diff --git a/configure.ac b/configure.ac index ec479370..8ae0fde7 100644 --- a/configure.ac +++ b/configure.ac @@ -56,6 +56,7 @@ AC_LANG_POP(C++) TORRENT_WITH_LUA TORRENT_WITH_TINYXML2 +TORRENT_WITH_SYSTEMD if test ${with_xmlrpc_c+y} && test ${with_xmlrpc_tinyxml2+y}; then AC_MSG_ERROR([--with-xmlrpc-c and --with-xmlrpc-tinyxml2 cannot be used together. Please choose only one]) diff --git a/scripts/checks.m4 b/scripts/checks.m4 index 003f0d88..38a490e3 100644 --- a/scripts/checks.m4 +++ b/scripts/checks.m4 @@ -358,3 +358,20 @@ AC_DEFUN([TORRENT_DISABLE_PTHREAD_SETNAME_NP], [ ] ) ]) + + +AC_DEFUN([TORRENT_WITH_SYSTEMD], [ + AC_ARG_WITH(systemd, + AS_HELP_STRING([--with-systemd],[enable systemd socket activation support [[default=no]]]), + [ + if test "$withval" = "yes"; then + PKG_CHECK_MODULES([SYSTEMD], [libsystemd], + [ + CXXFLAGS="$CXXFLAGS $SYSTEMD_CFLAGS" + LIBS="$LIBS $SYSTEMD_LIBS" + AC_DEFINE(HAVE_SYSTEMD, 1, [Support for systemd socket activation.]) + ], + [AC_MSG_ERROR([libsystemd not found. Install libsystemd-dev (or the equivalent for your distribution).])]) + fi + ]) +]) diff --git a/src/command_network.cc b/src/command_network.cc index a42e181e..f20888f6 100644 --- a/src/command_network.cc +++ b/src/command_network.cc @@ -3,6 +3,10 @@ #include #include #include +#ifdef HAVE_SYSTEMD +#include +#include +#endif #include #include #include @@ -143,6 +147,41 @@ apply_scgi(const std::string& arg, int type) { return torrent::Object(); } +#ifdef HAVE_SYSTEMD +torrent::Object +apply_scgi_systemd() { + if (scgi_thread::scgi() != nullptr) + throw torrent::input_error("SCGI already enabled."); + + int n = sd_listen_fds(0); + if (n < 1) + throw torrent::input_error("No systemd socket(s) provided (sd_listen_fds returned " + + std::to_string(n) + ")."); + + // Iterate over all provided fds. Use the first listening stream socket; + // close the rest. The systemd docs say unused fds should be closed. + int selected_fd = -1; + for (int i = 0; i < n; i++) { + int fd = SD_LISTEN_FDS_START + i; + if (selected_fd == -1 && sd_is_socket(fd, AF_UNSPEC, SOCK_STREAM, 1) > 0) + selected_fd = fd; + else + ::close(fd); + } + + if (selected_fd == -1) + throw torrent::input_error("No listening stream socket found among systemd-provided fds."); + + initialize_rpc_handlers(); + + rpc::SCgi* scgi = new rpc::SCgi; + scgi->open_fd(selected_fd); + + scgi_thread::set_scgi(scgi); + return torrent::Object(); +} +#endif + torrent::Object apply_xmlrpc_dialect(const std::string& arg) { int value; @@ -243,6 +282,9 @@ initialize_command_network() { CMD2_ANY_STRING ("network.scgi.open_port", std::bind(&apply_scgi, std::placeholders::_2, 1)); CMD2_ANY_STRING ("network.scgi.open_local", std::bind(&apply_scgi, std::placeholders::_2, 2)); CMD2_VAR_BOOL ("network.scgi.dont_route", false); +#ifdef HAVE_SYSTEMD + CMD2_ANY_VALUE_V ("network.scgi.open_systemd", [](auto, auto& value) { if (value != 0) apply_scgi_systemd(); }); +#endif CMD2_ANY_STRING ("network.xmlrpc.dialect.set", [](const auto&, const auto& arg) { return apply_xmlrpc_dialect(arg); }) CMD2_ANY ("network.xmlrpc.size_limit", [](const auto&, const auto&) { return rpc::rpc.size_limit(); }); diff --git a/src/rpc/scgi.cc b/src/rpc/scgi.cc index 6555d557..44c6cbb4 100644 --- a/src/rpc/scgi.cc +++ b/src/rpc/scgi.cc @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -80,6 +81,20 @@ SCgi::open_named(const std::string& filename) { m_path = filename; } +void +SCgi::open_fd(int fd) { + torrent::runtime::socket_manager()->open_event_or_throw(this, [&]() { + int flags = ::fcntl(fd, F_GETFL, 0); + if (flags == -1 || ::fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) + throw torrent::resource_error("Could not set non-blocking on systemd fd: " + + std::string(std::strerror(errno))); + set_file_descriptor(fd); + // fd is already bound and listening; no bind()/listen() needed. + }); + + torrent::connection_manager()->inc_socket_count(); +} + void SCgi::open(sockaddr* sa, unsigned int length) { try { diff --git a/src/rpc/scgi.h b/src/rpc/scgi.h index 5214149e..d6e8cfd0 100644 --- a/src/rpc/scgi.h +++ b/src/rpc/scgi.h @@ -21,6 +21,7 @@ public: void open_port(sockaddr* sa, unsigned int length, bool dont_route); void open_named(const std::string& filename); + void open_fd(int fd); void activate();