This is an automated email from the ASF dual-hosted git repository. bennoe pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/mesos.git
commit b73965de9874559c02bed42cd597e6ec678a461d Author: Benno Evers <[email protected]> AuthorDate: Thu Jan 9 15:43:29 2020 +0100 Added support for systemd socket activation API. Added support for the systemd socket activation api, that allows systemd to pass listening file descriptors to a given service. Review: https://reviews.apache.org/r/71976 --- src/linux/systemd.cpp | 132 +++++++++++++++++++++++++++++++++++++++++++++++++- src/linux/systemd.hpp | 63 ++++++++++++++++++++++++ 2 files changed, 194 insertions(+), 1 deletion(-) diff --git a/src/linux/systemd.cpp b/src/linux/systemd.cpp index 8126b31..d9cb566 100644 --- a/src/linux/systemd.cpp +++ b/src/linux/systemd.cpp @@ -16,6 +16,7 @@ #include "linux/systemd.hpp" +#include <algorithm> #include <string> #include <vector> @@ -27,6 +28,8 @@ #include <stout/os/realpath.hpp> +#include <stout/flags/parse.hpp> + #include "linux/cgroups.hpp" using process::Once; @@ -38,7 +41,6 @@ namespace systemd { int DELEGATE_MINIMUM_VERSION = 218; - Flags::Flags() { add(&Flags::enabled, @@ -332,4 +334,132 @@ Try<Nothing> start(const string& name) } // namespace slices { + +namespace socket_activation { + +static Try<Nothing> setCloexecFlag(int fd, bool cloexec) +{ + CHECK(fd >= 0) << "Invalid file desciptor was passed"; + + int flags = ::fcntl(fd, F_GETFD, 0); + if (flags < 0) { + return ErrnoError(); + } + + int nflags = cloexec == true ? flags | FD_CLOEXEC + : flags & ~FD_CLOEXEC; + + if (nflags == flags) { + return Nothing(); + } + + if (::fcntl(fd, F_SETFD, nflags) < 0) { + return ErrnoError(); + } + + return Nothing(); +} + + +// See `src/libsystemd/sd-daemon/sd-daemon.c` in the systemd source tree +// for the reference implementation. We follow that implementation to +// decide which conditions should result in errors and which should return +// an empty array. +Try<std::vector<int>> listenFds() +{ + vector<int> result; + + Option<std::string> listenPidEnv = os::getenv("LISTEN_PID"); + if (listenPidEnv.isNone()) { + return result; + } + + Try<pid_t> listenPid = flags::parse<pid_t>(listenPidEnv->c_str()); + if (listenPid.isError()) { + return Error("Could not parse $LISTEN_PID=\"" + listenPidEnv.get() + + "\" as integer"); + } + + pid_t pid = ::getpid(); + if (listenPid.get() != pid) { + LOG(WARNING) << "File descriptors were passed for pid " << listenPid.get() + << ", ignoring them because we have pid " << pid; + return result; + } + + Option<std::string> listenFdsEnv = os::getenv("LISTEN_FDS"); + if (listenFdsEnv.isNone()) { + return result; + } + + Try<int> listenFds = flags::parse<int>(listenFdsEnv->c_str()); + if (listenFds.isError()) { + return Error("Could not parse $LISTEN_FDS=\"" + listenFdsEnv.get() + + "\" as integer"); + } + + int n = listenFds.get(); + if (n <= 0 || n > INT_MAX - SD_LISTEN_FDS_START) { + return Error("Too many passed file descriptors"); + } + + for (int fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; ++fd) { + Try<Nothing> cloexec = setCloexecFlag(fd, true); + if (cloexec.isError()) { + return Error( + "Could not set CLOEXEC flag for file descriptor " + stringify(fd) + + cloexec.error()); + } + } + + result.resize(n); + std::iota(result.begin(), result.begin() + n, 3); + + return result; +} + + +Try<std::vector<int>> listenFdsWithName(const std::string& name) +{ + Try<std::vector<int>> fds = listenFds(); + if (fds.isError()) { + return fds; + } + + std::vector<std::string> listenFdnames; + + Option<std::string> listenFdnamesEnv = os::getenv("LISTEN_FDNAMES"); + if (listenFdnamesEnv.isSome()) { + listenFdnames = strings::split(*listenFdnamesEnv, ":"); + + if (listenFdnames.size() != fds->size()) { + return Error("Size mismatch between file descriptors and names"); + } + } else { + // If `LISTEN_FDNAMES` is not set in the environment, libsystemd assigns + // the special name "unknown" to all passed file descriptors. + // We do the same. + listenFdnames.resize(fds->size()); + std::fill_n(listenFdnames.begin(), listenFdnames.size(), "unknown"); + } + + std::vector<int> result; + for (size_t i=0; i < listenFdnames.size(); ++i) { + if (listenFdnames[i] == name) { + result.push_back(fds->at(i)); + } + } + + return result; +} + + +void clearEnvironment() +{ + os::unsetenv("LISTEN_PID"); + os::unsetenv("LISTEN_FDS"); + os::unsetenv("LISTEN_FDNAMES"); +} + +} // namespace socket_activation { } // namespace systemd { diff --git a/src/linux/systemd.hpp b/src/linux/systemd.hpp index f760e2c..ba96000 100644 --- a/src/linux/systemd.hpp +++ b/src/linux/systemd.hpp @@ -53,6 +53,69 @@ Try<Nothing> extendLifetime(pid_t child); } // namespace mesos { +namespace socket_activation { + +// A re-implementation of the systemd socket activation API. +// +// To implement the socket-passing protocol, systemd uses the +// environment variables `$LISTEN_PID`, `$LISTEN_FDS` and `$LISTEN_FDNAMES` +// according to the scheme documented in [1], [2]. +// +// Users of libsystemd can use the following API to interface +// with the socket passing functionality: +// +// #include <systemd/sd-daemon.h> +// int sd_listen_fds(int unset_environment); +// int sd_listen_fds_with_names(int unset_environment, char ***names); +// +// The `sd_listen_fds()` function does the following: +// +// * The return value is the number of listening sockets passed by +// systemd. The actual file descriptors of these sockets are +// numbered 3...n+3. +// * If the current pid is different from the one specified by the +// environment variable $LISTEN_PID, 0 is returned +// * The `CLOEXEC` option will be set on all file descriptors "returned" +// by this function. +// * If `unset_environment` is true, the environment variables $LISTEN_PID, +// $LISTEN_FDS, $LISTEN_FDNAMES will be cleared. +// +// The `sd_listen_fds_with_names()` function does the following: +// +// * If $LISTEN_FDS is set, will return an array of strings with the +// names. By default, the name of a socket will be equal to the +// name of the unit file containing the socket description. +// * The special string "unknown" is used for sockets where no name +// could be determined. +// +// For this reimplementation, the interface was slightly changed to better +// suit the needs of the Mesos codebase. However, we still set the `CLOEXEC` +// flag on all file descriptors passed via socket activation when one of +// these functions is called. +// +// [1] https://www.freedesktop.org/software/systemd/man/sd_listen_fds.html#Notes +// [2] http://0pointer.de/blog/projects/socket-activation.html + +Try<std::vector<int>> listenFds(); + +// The names are set by the `FileDescriptorName=` directive in the unit file. +// This requires systemd 227 or newer. Since any number of unit files can +// specify the same name, this can return more than one file descriptor. +Try<std::vector<int>> listenFdsWithName(const std::string& name); + +// Clear the `$LISTEN_PID`, `$LISTEN_FDS` and `$LISTEN_FDNAMES` environment +// variables. +// +// *NOTE*: This function is not thread-safe, since it modifies the global +// environment. +void clearEnvironment(); + +// Defined in `man(3) sd_listen_fds`. +constexpr int SD_LISTEN_FDS_START = 3; + +} // namespace socket_activation { + + /** * Flags to initialize systemd state. */
