about summary refs log tree commit homepage
diff options
context:
space:
mode:
-rw-r--r--cmogstored.x7
-rw-r--r--inherit.c56
-rw-r--r--test/inherit.rb32
3 files changed, 91 insertions, 4 deletions
diff --git a/cmogstored.x b/cmogstored.x
index bd5956d..a6b96e6 100644
--- a/cmogstored.x
+++ b/cmogstored.x
@@ -15,6 +15,13 @@ MOG_IOSTAT_CMD - command-line for invoking iostat(1).  This is used
 by the sidechannel to report disk utilization to trackers.
 Default: "iostat -dx 1 30"
 
+LISTEN_FDS, LISTEN_PID - may be used for systemd-style socket activation
+regardless of whether systemd is on the system.  See sd_listen_fds(3)
+for more information.  Since cmogstored supports two protocols on
+different sockets, the command line or config file must still specify
+which addresses to listen on.  This feature is available in cmogstored
+1.5.0 and later.
+
 See MALLOC TUNING for environment variables which may affect memory
 usage.
 
diff --git a/inherit.c b/inherit.c
index ca7afa7..0229a04 100644
--- a/inherit.c
+++ b/inherit.c
@@ -89,6 +89,55 @@ static bool listener_close_each(void *_l, void *unused)
         return true;
 }
 
+static void listeners_init(void)
+{
+        if (listeners) return;
+        listeners = hash_initialize(3, NULL, listener_hash, listener_cmp, free);
+        mog_oom_if_null(listeners);
+        atexit(listeners_cleanup);
+}
+
+static unsigned long listen_env(const char *env)
+{
+        const char *e = getenv(env);
+        unsigned long tmp;
+        char *end;
+
+        if (!e) return ULONG_MAX;
+        errno = 0;
+        tmp = strtoul(e, &end, 10);
+        if (errno) die_errno("failed to parse %s: %s", env, e);
+        if (*end) die("trailing byte in %s: %s", env, e);
+
+        return tmp;
+}
+
+/* systemd-style socket activation in the vein of sd_listen_fds(3) */
+static void systemd_inherit_fds(void)
+{
+        const int listen_fds_start = 3; /* SD_LISTEN_FDS_START */
+        int fd, listen_fds_end;
+        unsigned long tmp = listen_env("LISTEN_PID");
+
+        if (getpid() != (pid_t)tmp) goto out;
+
+        tmp = listen_env("LISTEN_FDS");
+        if (tmp > INT_MAX) die("LISTEN_FDS out of range: %lu", tmp);
+
+        listeners_init();
+        listen_fds_end = listen_fds_start + (int)tmp;
+        for (fd = listen_fds_start; fd < listen_fds_end; fd++) {
+                if (mog_set_cloexec(fd, true) == 0)
+                        register_listen_fd(fd);
+                else
+                        die("inherited out %d of %lu LISTEN_FDS",
+                            fd - listen_fds_start, tmp);
+        }
+out:
+        unsetenv("LISTEN_FDS");
+        unsetenv("LISTEN_PID");
+}
+
 /* close all inherited listeners we do not need */
 void mog_inherit_cleanup(void)
 {
@@ -131,13 +180,12 @@ void mog_inherit_init(void)
         unsigned long fd;
         unsigned endbyte;
 
+        systemd_inherit_fds();
+
         if (orig == NULL)
                 return;
 
-        listeners = hash_initialize(3, NULL, listener_hash, listener_cmp, free);
-        mog_oom_if_null(listeners);
-        atexit(listeners_cleanup);
-
+        listeners_init();
         fds = xstrdup(orig);
         tip = fds;
 
diff --git a/test/inherit.rb b/test/inherit.rb
index b342ab0..34aa52f 100644
--- a/test/inherit.rb
+++ b/test/inherit.rb
@@ -142,4 +142,36 @@ class TestInherit < Test::Unit::TestCase
     @err.rewind
     assert_match(/failed to parse/, @err.read)
   end
+
+  def test_inherit_systemd
+    # disabled test on old Rubies: https://bugs.ruby-lang.org/issues/11336
+    # [ruby-core:69895] [Bug #11336] fixed by r51576
+    return unless RUBY_VERSION.to_f >= 2.3
+
+    mgmt = TCPServer.new(@host, 0)
+    @to_close << mgmt
+    mport = mgmt.addr[1]
+    cmd = %W(cmogstored --docroot=#@tmpdir --httplisten=#@host:#@port
+             --mgmtlisten=#@host:#{mport} --maxconns=100)
+    @pid = fork do
+      ENV['LISTEN_PID'] = "#$$"
+      ENV['LISTEN_FDS'] = '2'
+      exec(*cmd, 3 => mgmt.fileno, 4 => @srv.fileno)
+    end
+
+    # just ensure HTTP works after being inherited
+    Net::HTTP.start(@host, @port) do |http|
+      [ Net::HTTP::Get, Net::HTTP::Head ].each do |meth|
+        resp = http.request(meth.new("/"))
+        assert_kind_of Net::HTTPOK, resp
+      end
+    end
+
+    # still works since drop is open in _this_ process
+    c = TCPSocket.new(@host, mport)
+    assert_instance_of(TCPSocket, c)
+    @to_close << c
+    c.write "hello\n"
+    assert_match /ERROR: unknown command/, c.gets
+  end
 end