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.
  */

Reply via email to