diff options
-rw-r--r-- | Links | 2 | ||||
-rw-r--r-- | ext/unicorn_http/common_field_optimization.h | 4 | ||||
-rw-r--r-- | ext/unicorn_http/global_variables.h | 4 | ||||
-rw-r--r-- | ext/unicorn_http/httpdate.c | 4 | ||||
-rw-r--r-- | ext/unicorn_http/unicorn_http.rl | 13 | ||||
-rw-r--r-- | lib/unicorn.rb | 2 | ||||
-rw-r--r-- | lib/unicorn/configurator.rb | 22 | ||||
-rw-r--r-- | lib/unicorn/http_server.rb | 4 | ||||
-rw-r--r-- | lib/unicorn/oob_gc.rb | 3 | ||||
-rw-r--r-- | lib/unicorn/socket_helper.rb | 7 | ||||
-rw-r--r-- | lib/unicorn/worker.rb | 13 | ||||
-rw-r--r-- | t/test-lib.sh | 4 | ||||
-rw-r--r-- | test/exec/test_exec.rb | 9 | ||||
-rw-r--r-- | test/unit/test_http_parser.rb | 18 | ||||
-rw-r--r-- | test/unit/test_socket_helper.rb | 12 | ||||
-rw-r--r-- | test/unit/test_util.rb | 4 |
16 files changed, 74 insertions, 51 deletions
@@ -23,7 +23,7 @@ or services behind them. * {golden_brindle}[https://github.com/simonoff/golden_brindle] - tool to manage multiple unicorn instances/applications on a single server -* {raindrops}[http://raindrops.bogomips.org/] - real-time stats for +* {raindrops}[https://bogomips.org/raindrops/] - real-time stats for preforking Rack servers * {UnXF}[https://bogomips.org/unxf/] Un-X-Forward* the Rack environment, diff --git a/ext/unicorn_http/common_field_optimization.h b/ext/unicorn_http/common_field_optimization.h index 42c5430..251e734 100644 --- a/ext/unicorn_http/common_field_optimization.h +++ b/ext/unicorn_http/common_field_optimization.h @@ -60,7 +60,7 @@ static struct common_field common_http_fields[] = { #define HTTP_PREFIX_LEN (sizeof(HTTP_PREFIX) - 1) /* this function is not performance-critical, called only at load time */ -static void init_common_fields(void) +static void init_common_fields(VALUE mark_ary) { int i; struct common_field *cf = common_http_fields; @@ -77,7 +77,7 @@ static void init_common_fields(void) cf->value = rb_str_new(tmp, HTTP_PREFIX_LEN + cf->len); } cf->value = rb_obj_freeze(cf->value); - rb_global_variable(&cf->value); + rb_ary_push(mark_ary, cf->value); } } diff --git a/ext/unicorn_http/global_variables.h b/ext/unicorn_http/global_variables.h index e1c43c9..c17ee6a 100644 --- a/ext/unicorn_http/global_variables.h +++ b/ext/unicorn_http/global_variables.h @@ -56,7 +56,7 @@ NORETURN(static void parser_raise(VALUE klass, const char *)); /** Defines global strings in the init method. */ #define DEF_GLOBAL(N, val) do { \ g_##N = rb_obj_freeze(rb_str_new(val, sizeof(val) - 1)); \ - rb_global_variable(&g_##N); \ + rb_ary_push(mark_ary, g_##N); \ } while (0) /* Defines the maximum allowed lengths for various input elements.*/ @@ -67,7 +67,7 @@ DEF_MAX_LENGTH(FRAGMENT, 1024); /* Don't know if this length is specified somewh DEF_MAX_LENGTH(REQUEST_PATH, 4096); /* common PATH_MAX on modern systems */ DEF_MAX_LENGTH(QUERY_STRING, (1024 * 10)); -static void init_globals(void) +static void init_globals(VALUE mark_ary) { DEF_GLOBAL(rack_url_scheme, "rack.url_scheme"); DEF_GLOBAL(request_method, "REQUEST_METHOD"); diff --git a/ext/unicorn_http/httpdate.c b/ext/unicorn_http/httpdate.c index 0a1045f..2381cff 100644 --- a/ext/unicorn_http/httpdate.c +++ b/ext/unicorn_http/httpdate.c @@ -64,13 +64,13 @@ static VALUE httpdate(VALUE self) return buf; } -void init_unicorn_httpdate(void) +void init_unicorn_httpdate(VALUE mark_ary) { VALUE mod = rb_define_module("Unicorn"); mod = rb_define_module_under(mod, "HttpResponse"); buf = rb_str_new(0, buf_capa - 1); - rb_global_variable(&buf); + rb_ary_push(mark_ary, buf); buf_ptr = RSTRING_PTR(buf); httpdate(Qnil); diff --git a/ext/unicorn_http/unicorn_http.rl b/ext/unicorn_http/unicorn_http.rl index 957a5e3..6fc3498 100644 --- a/ext/unicorn_http/unicorn_http.rl +++ b/ext/unicorn_http/unicorn_http.rl @@ -13,7 +13,7 @@ #include "global_variables.h" #include "c_util.h" -void init_unicorn_httpdate(void); +void init_unicorn_httpdate(VALUE mark_ary); #define UH_FL_CHUNKED 0x1 #define UH_FL_HASBODY 0x2 @@ -917,8 +917,10 @@ static VALUE HttpParser_rssget(VALUE self) void Init_unicorn_http(void) { + static VALUE mark_ary; VALUE mUnicorn, cHttpParser; + mark_ary = rb_ary_new(); mUnicorn = rb_define_module("Unicorn"); cHttpParser = rb_define_class_under(mUnicorn, "HttpParser", rb_cObject); eHttpParserError = @@ -928,7 +930,7 @@ void Init_unicorn_http(void) e414 = rb_define_class_under(mUnicorn, "RequestURITooLongError", eHttpParserError); - init_globals(); + init_globals(mark_ary); rb_define_alloc_func(cHttpParser, HttpParser_alloc); rb_define_method(cHttpParser, "initialize", HttpParser_init, 0); rb_define_method(cHttpParser, "clear", HttpParser_clear, 0); @@ -964,14 +966,17 @@ void Init_unicorn_http(void) rb_define_singleton_method(cHttpParser, "max_header_len=", set_maxhdrlen, 1); - init_common_fields(); + init_common_fields(mark_ary); SET_GLOBAL(g_http_host, "HOST"); SET_GLOBAL(g_http_trailer, "TRAILER"); SET_GLOBAL(g_http_transfer_encoding, "TRANSFER_ENCODING"); SET_GLOBAL(g_content_length, "CONTENT_LENGTH"); SET_GLOBAL(g_http_connection, "CONNECTION"); id_set_backtrace = rb_intern("set_backtrace"); - init_unicorn_httpdate(); + init_unicorn_httpdate(mark_ary); + + OBJ_FREEZE(mark_ary); + rb_global_variable(&mark_ary); #ifndef HAVE_RB_HASH_CLEAR id_clear = rb_intern("clear"); diff --git a/lib/unicorn.rb b/lib/unicorn.rb index f122563..4bd7bda 100644 --- a/lib/unicorn.rb +++ b/lib/unicorn.rb @@ -95,7 +95,7 @@ module Unicorn # returns an array of strings representing TCP listen socket addresses # and Unix domain socket paths. This is useful for use with - # Raindrops::Middleware under Linux: http://raindrops.bogomips.org/ + # Raindrops::Middleware under Linux: https://bogomips.org/raindrops/ def self.listener_names Unicorn::HttpServer::LISTENERS.map do |io| Unicorn::SocketHelper.sock_name(io) diff --git a/lib/unicorn/configurator.rb b/lib/unicorn/configurator.rb index 1e2b6e4..7ed5ffa 100644 --- a/lib/unicorn/configurator.rb +++ b/lib/unicorn/configurator.rb @@ -49,6 +49,9 @@ class Unicorn::Configurator server.logger.error(m) end }, + :after_worker_ready => lambda { |server, worker| + server.logger.info("worker=#{worker.nr} ready") + }, :pid => nil, :preload_app => false, :check_client_connection => false, @@ -172,6 +175,21 @@ class Unicorn::Configurator set_hook(:after_worker_exit, block_given? ? block : args[0], 3) end + # sets after_worker_ready hook to a given block. This block will be called + # by a worker process after it has been fully loaded, directly before it + # starts responding to requests: + # + # after_worker_ready do |server,worker| + # server.logger.info("worker #{worker.nr} ready, dropping privileges") + # worker.user('username', 'groupname') + # end + # + # Do not use Configurator#user if you rely on changing users in the + # after_worker_ready hook. + def after_worker_ready(*args, &block) + set_hook(:after_worker_ready, block_given? ? block : args[0]) + end + # sets before_fork got be a given Proc object. This Proc # object will be called by the master process before forking # each worker. @@ -569,6 +587,10 @@ class Unicorn::Configurator # This switch will occur after calling the after_fork hook, and only # if the Worker#user method is not called in the after_fork hook # +group+ is optional and will not change if unspecified. + # + # Do not use Configurator#user if you rely on changing users in the + # after_worker_ready hook. Instead, you need to call Worker#user + # directly in after_worker_ready. def user(user, group = nil) # raises ArgumentError on invalid user/group Etc.getpwnam(user) diff --git a/lib/unicorn/http_server.rb b/lib/unicorn/http_server.rb index c2086cb..ef897ad 100644 --- a/lib/unicorn/http_server.rb +++ b/lib/unicorn/http_server.rb @@ -15,7 +15,7 @@ class Unicorn::HttpServer :before_fork, :after_fork, :before_exec, :listener_opts, :preload_app, :orig_app, :config, :ready_pipe, :user - attr_writer :after_worker_exit + attr_writer :after_worker_exit, :after_worker_ready attr_reader :pid, :logger include Unicorn::SocketHelper @@ -644,7 +644,7 @@ class Unicorn::HttpServer trap(:USR1) { nr = -65536 } ready = readers.dup - @logger.info "worker=#{worker.nr} ready" + @after_worker_ready.call(self, worker) begin nr < 0 and reopen_worker_logs(worker.nr) diff --git a/lib/unicorn/oob_gc.rb b/lib/unicorn/oob_gc.rb index 5572e59..c4741a0 100644 --- a/lib/unicorn/oob_gc.rb +++ b/lib/unicorn/oob_gc.rb @@ -66,10 +66,9 @@ module Unicorn::OobGC end #:stopdoc: - PATH_INFO = "PATH_INFO" def process_client(client) super(client) # Unicorn::HttpServer#process_client - if OOBGC_PATH =~ OOBGC_ENV[PATH_INFO] && ((@@nr -= 1) <= 0) + if OOBGC_PATH =~ OOBGC_ENV['PATH_INFO'] && ((@@nr -= 1) <= 0) @@nr = OOBGC_INTERVAL OOBGC_ENV.clear disabled = GC.enable diff --git a/lib/unicorn/socket_helper.rb b/lib/unicorn/socket_helper.rb index 5371413..f52dde2 100644 --- a/lib/unicorn/socket_helper.rb +++ b/lib/unicorn/socket_helper.rb @@ -75,12 +75,15 @@ module Unicorn elsif respond_to?(:accf_arg) name = opt[:accept_filter] name = DEFAULTS[:accept_filter] if name.nil? + sock.listen(opt[:backlog]) + got = (sock.getsockopt(:SOL_SOCKET, :SO_ACCEPTFILTER) rescue nil).to_s + arg = accf_arg(name) begin - sock.setsockopt(:SOL_SOCKET, :SO_ACCEPTFILTER, accf_arg(name)) + sock.setsockopt(:SOL_SOCKET, :SO_ACCEPTFILTER, arg) rescue => e logger.error("#{sock_name(sock)} " \ "failed to set accept_filter=#{name} (#{e.inspect})") - end + end if arg != got end end diff --git a/lib/unicorn/worker.rb b/lib/unicorn/worker.rb index 6748a2f..e22c1bf 100644 --- a/lib/unicorn/worker.rb +++ b/lib/unicorn/worker.rb @@ -111,9 +111,11 @@ class Unicorn::Worker # In most cases, you should be using the Unicorn::Configurator#user # directive instead. This method should only be used if you need # fine-grained control of exactly when you want to change permissions - # in your after_fork hooks. + # in your after_fork or after_worker_ready hooks, or if you want to + # use the chroot support. # - # Changes the worker process to the specified +user+ and +group+ + # Changes the worker process to the specified +user+ and +group+, + # and chroots to the current working directory if +chroot+ is set. # This is only intended to be called from within the worker # process from the +after_fork+ hook. This should be called in # the +after_fork+ hook after any privileged functions need to be @@ -123,7 +125,7 @@ class Unicorn::Worker # directly back to the caller (usually the +after_fork+ hook. # These errors commonly include ArgumentError for specifying an # invalid user/group and Errno::EPERM for insufficient privileges - def user(user, group = nil) + def user(user, group = nil, chroot = false) # we do not protect the caller, checking Process.euid == 0 is # insufficient because modern systems have fine-grained # capabilities. Let the caller handle any and all errors. @@ -134,6 +136,11 @@ class Unicorn::Worker Process.initgroups(user, gid) Process::GID.change_privilege(gid) end + if chroot + chroot = Dir.pwd if chroot == true + Dir.chroot(chroot) + Dir.chdir('/') + end Process.euid != uid and Process::UID.change_privilege(uid) @switched = true end diff --git a/t/test-lib.sh b/t/test-lib.sh index 28d6a88..7f97958 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -106,8 +106,8 @@ check_stderr () { # unicorn_setup unicorn_setup () { eval $(unused_listen) - port=$(expr $listen : '[^:]*:\([0-9]\+\)') - host=$(expr $listen : '\([^:]*\):[0-9]\+') + port=$(expr $listen : '[^:]*:\([0-9]*\)') + host=$(expr $listen : '\([^:][^:]*\):[0-9][0-9]*') rtmpfiles unicorn_config pid r_err r_out fifo tmp ok cat > $unicorn_config <<EOF diff --git a/test/exec/test_exec.rb b/test/exec/test_exec.rb index ca0b7bc..08f92ae 100644 --- a/test/exec/test_exec.rb +++ b/test/exec/test_exec.rb @@ -97,6 +97,9 @@ run lambda { |env| end def test_sd_listen_fds_emulation + # [ruby-core:69895] [Bug #11336] fixed by r51576 + return if RUBY_VERSION.to_f < 2.3 + File.open("config.ru", "wb") { |fp| fp.write(HI) } sock = TCPServer.new(@addr, @port) @@ -124,9 +127,7 @@ run lambda { |env| end ensure sock.close if sock - # disabled test on old Rubies: https://bugs.ruby-lang.org/issues/11336 - # [ruby-core:69895] [Bug #11336] fixed by r51576 - end if RUBY_VERSION.to_f >= 2.3 + end def test_inherit_listener_unspecified File.open("config.ru", "wb") { |fp| fp.write(HI) } @@ -142,7 +143,7 @@ run lambda { |env| res = hit(["http://#@addr:#@port/"]) assert_equal [ "HI\n" ], res assert_shutdown(pid) - assert_equal 1, sock.getsockopt(:SOL_SOCKET, :SO_KEEPALIVE).int, + assert sock.getsockopt(:SOL_SOCKET, :SO_KEEPALIVE).bool, 'unicorn should always set SO_KEEPALIVE on inherited sockets' ensure sock.close if sock diff --git a/test/unit/test_http_parser.rb b/test/unit/test_http_parser.rb index 7cbc0f8..31e6f71 100644 --- a/test/unit/test_http_parser.rb +++ b/test/unit/test_http_parser.rb @@ -851,24 +851,6 @@ class HttpParserTest < Test::Unit::TestCase assert_equal '', parser.env['HTTP_HOST'] end - # so we don't care about the portability of this test - # if it doesn't leak on Linux, it won't leak anywhere else - # unless your C compiler or platform is otherwise broken - LINUX_PROC_PID_STATUS = "/proc/self/status" - def test_memory_leak - match_rss = /^VmRSS:\s+(\d+)/ - if File.read(LINUX_PROC_PID_STATUS) =~ match_rss - before = $1.to_i - 1000000.times { Unicorn::HttpParser.new } - File.read(LINUX_PROC_PID_STATUS) =~ match_rss - after = $1.to_i - diff = after - before - assert(diff < 10000, "memory grew more than 10M: #{diff}") - end - end if RUBY_PLATFORM =~ /linux/ && - File.readable?(LINUX_PROC_PID_STATUS) && - !defined?(RUBY_ENGINE) - def test_memsize require 'objspace' if ObjectSpace.respond_to?(:memsize_of) diff --git a/test/unit/test_socket_helper.rb b/test/unit/test_socket_helper.rb index 7526e82..8699409 100644 --- a/test/unit/test_socket_helper.rb +++ b/test/unit/test_socket_helper.rb @@ -150,28 +150,31 @@ class TestSocketHelper < Test::Unit::TestCase end def test_tcp_defer_accept_default + return unless defined?(TCP_DEFER_ACCEPT) port = unused_port @test_addr name = "#@test_addr:#{port}" sock = bind_listen(name) cur = sock.getsockopt(Socket::SOL_TCP, TCP_DEFER_ACCEPT).unpack('i')[0] assert cur >= 1 - end if defined?(TCP_DEFER_ACCEPT) + end def test_tcp_defer_accept_disable + return unless defined?(TCP_DEFER_ACCEPT) port = unused_port @test_addr name = "#@test_addr:#{port}" sock = bind_listen(name, :tcp_defer_accept => false) cur = sock.getsockopt(Socket::SOL_TCP, TCP_DEFER_ACCEPT).unpack('i')[0] assert_equal 0, cur - end if defined?(TCP_DEFER_ACCEPT) + end def test_tcp_defer_accept_nr + return unless defined?(TCP_DEFER_ACCEPT) port = unused_port @test_addr name = "#@test_addr:#{port}" sock = bind_listen(name, :tcp_defer_accept => 60) cur = sock.getsockopt(Socket::SOL_TCP, TCP_DEFER_ACCEPT).unpack('i')[0] assert cur > 1 - end if defined?(TCP_DEFER_ACCEPT) + end def test_ipv6only port = begin @@ -186,6 +189,7 @@ class TestSocketHelper < Test::Unit::TestCase end def test_reuseport + return unless defined?(Socket::SO_REUSEPORT) port = unused_port @test_addr name = "#@test_addr:#{port}" sock = bind_listen(name, :reuseport => true) @@ -193,5 +197,5 @@ class TestSocketHelper < Test::Unit::TestCase assert_operator cur, :>, 0 rescue Errno::ENOPROTOOPT # kernel does not support SO_REUSEPORT (older Linux) - end if defined?(Socket::SO_REUSEPORT) + end end diff --git a/test/unit/test_util.rb b/test/unit/test_util.rb index 4d17a16..dc6302e 100644 --- a/test/unit/test_util.rb +++ b/test/unit/test_util.rb @@ -69,7 +69,7 @@ class TestUtil < Test::Unit::TestCase } } tmp.close! - end if STDIN.respond_to?(:external_encoding) + end def test_reopen_logs_renamed_with_internal_encoding tmp = Tempfile.new('') @@ -101,5 +101,5 @@ class TestUtil < Test::Unit::TestCase } } tmp.close! - end if STDIN.respond_to?(:external_encoding) + end end |