diff options
-rw-r--r-- | cmogstored.x | 7 | ||||
-rw-r--r-- | inherit.c | 56 | ||||
-rw-r--r-- | test/inherit.rb | 32 |
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. @@ -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 |