unicorn 4.9.0 → 6.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (108) hide show
  1. checksums.yaml +5 -5
  2. data/.gitattributes +5 -0
  3. data/.manifest +14 -15
  4. data/.olddoc.yml +16 -6
  5. data/Application_Timeouts +7 -7
  6. data/CONTRIBUTORS +6 -2
  7. data/DESIGN +2 -4
  8. data/Documentation/.gitignore +1 -3
  9. data/Documentation/unicorn.1 +222 -0
  10. data/Documentation/unicorn_rails.1 +207 -0
  11. data/FAQ +17 -8
  12. data/GIT-VERSION-FILE +1 -1
  13. data/GIT-VERSION-GEN +1 -1
  14. data/GNUmakefile +121 -56
  15. data/HACKING +2 -10
  16. data/ISSUES +40 -43
  17. data/KNOWN_ISSUES +11 -11
  18. data/LATEST +16 -22
  19. data/LICENSE +2 -2
  20. data/Links +24 -25
  21. data/NEWS +771 -0
  22. data/PHILOSOPHY +0 -6
  23. data/README +46 -40
  24. data/SIGNALS +2 -2
  25. data/Sandbox +11 -10
  26. data/TODO +0 -2
  27. data/TUNING +30 -9
  28. data/archive/slrnpull.conf +1 -1
  29. data/bin/unicorn +4 -2
  30. data/bin/unicorn_rails +3 -3
  31. data/examples/big_app_gc.rb +1 -1
  32. data/examples/init.sh +36 -8
  33. data/examples/logrotate.conf +17 -2
  34. data/examples/nginx.conf +14 -14
  35. data/examples/unicorn.conf.minimal.rb +2 -2
  36. data/examples/unicorn.conf.rb +3 -6
  37. data/examples/unicorn.socket +11 -0
  38. data/examples/unicorn@.service +40 -0
  39. data/ext/unicorn_http/c_util.h +5 -13
  40. data/ext/unicorn_http/common_field_optimization.h +22 -5
  41. data/ext/unicorn_http/epollexclusive.h +124 -0
  42. data/ext/unicorn_http/ext_help.h +0 -44
  43. data/ext/unicorn_http/extconf.rb +32 -5
  44. data/ext/unicorn_http/global_variables.h +2 -2
  45. data/ext/unicorn_http/httpdate.c +3 -2
  46. data/ext/unicorn_http/unicorn_http.c +926 -638
  47. data/ext/unicorn_http/unicorn_http.rl +159 -170
  48. data/ext/unicorn_http/unicorn_http_common.rl +1 -1
  49. data/lib/unicorn/configurator.rb +110 -44
  50. data/lib/unicorn/const.rb +2 -25
  51. data/lib/unicorn/http_request.rb +110 -31
  52. data/lib/unicorn/http_response.rb +17 -31
  53. data/lib/unicorn/http_server.rb +255 -179
  54. data/lib/unicorn/launcher.rb +1 -1
  55. data/lib/unicorn/oob_gc.rb +6 -6
  56. data/lib/unicorn/select_waiter.rb +6 -0
  57. data/lib/unicorn/socket_helper.rb +58 -78
  58. data/lib/unicorn/stream_input.rb +8 -7
  59. data/lib/unicorn/tee_input.rb +8 -10
  60. data/lib/unicorn/tmpio.rb +8 -7
  61. data/lib/unicorn/util.rb +5 -4
  62. data/lib/unicorn/version.rb +1 -1
  63. data/lib/unicorn/worker.rb +36 -23
  64. data/lib/unicorn.rb +64 -46
  65. data/man/man1/unicorn.1 +123 -119
  66. data/man/man1/unicorn_rails.1 +106 -107
  67. data/t/GNUmakefile +3 -72
  68. data/t/README +4 -4
  69. data/t/t0011-active-unix-socket.sh +1 -1
  70. data/t/t0012-reload-empty-config.sh +2 -1
  71. data/t/t0301-no-default-middleware-ignored-in-config.sh +25 -0
  72. data/t/t0301.ru +13 -0
  73. data/t/test-lib.sh +4 -3
  74. data/test/benchmark/README +14 -4
  75. data/test/benchmark/ddstream.ru +50 -0
  76. data/test/benchmark/readinput.ru +40 -0
  77. data/test/benchmark/uconnect.perl +66 -0
  78. data/test/exec/test_exec.rb +73 -19
  79. data/test/test_helper.rb +40 -31
  80. data/test/unit/test_ccc.rb +91 -0
  81. data/test/unit/test_droplet.rb +1 -1
  82. data/test/unit/test_http_parser.rb +46 -16
  83. data/test/unit/test_http_parser_ng.rb +97 -114
  84. data/test/unit/test_request.rb +10 -10
  85. data/test/unit/test_response.rb +28 -16
  86. data/test/unit/test_server.rb +86 -12
  87. data/test/unit/test_signals.rb +8 -8
  88. data/test/unit/test_socket_helper.rb +14 -10
  89. data/test/unit/test_upload.rb +9 -14
  90. data/test/unit/test_util.rb +31 -5
  91. data/test/unit/test_waiter.rb +34 -0
  92. data/unicorn.gemspec +27 -19
  93. metadata +28 -45
  94. data/Documentation/GNUmakefile +0 -30
  95. data/Documentation/unicorn.1.txt +0 -185
  96. data/Documentation/unicorn_rails.1.txt +0 -175
  97. data/examples/git.ru +0 -13
  98. data/lib/unicorn/app/exec_cgi.rb +0 -154
  99. data/lib/unicorn/app/inetd.rb +0 -109
  100. data/lib/unicorn/ssl_client.rb +0 -11
  101. data/lib/unicorn/ssl_configurator.rb +0 -104
  102. data/lib/unicorn/ssl_server.rb +0 -42
  103. data/t/hijack.ru +0 -42
  104. data/t/t0016-trust-x-forwarded-false.sh +0 -30
  105. data/t/t0017-trust-x-forwarded-true.sh +0 -30
  106. data/t/t0200-rack-hijack.sh +0 -27
  107. data/test/unit/test_http_parser_xftrust.rb +0 -38
  108. data/test/unit/test_sni_hostnames.rb +0 -47
@@ -31,7 +31,7 @@ module Unicorn::Launcher
31
31
  # \_ parent - exits immediately ASAP
32
32
  # \_ unicorn master - writes to pipe when ready
33
33
 
34
- rd, wr = IO.pipe
34
+ rd, wr = Unicorn.pipe
35
35
  grandparent = $$
36
36
  if fork
37
37
  wr.close # grandparent does not write
@@ -43,8 +43,9 @@
43
43
  # use Unicorn::OobGC, 2, %r{\A/(?:expensive/foo|more_expensive/foo)}
44
44
  #
45
45
  # Feedback from users of early implementations of this module:
46
- # * http://comments.gmane.org/gmane.comp.lang.ruby.unicorn.general/486
47
- # * http://article.gmane.org/gmane.comp.lang.ruby.unicorn.general/596
46
+ # * https://yhbt.net/unicorn-public/0BFC98E9-072B-47EE-9A70-05478C20141B@lukemelia.com/
47
+ # * https://yhbt.net/unicorn-public/AANLkTilUbgdyDv9W1bi-s_W6kq9sOhWfmuYkKLoKGOLj@mail.gmail.com/
48
+
48
49
  module Unicorn::OobGC
49
50
 
50
51
  # this pretends to be Rack middleware because it used to be
@@ -59,18 +60,17 @@ module Unicorn::OobGC
59
60
  self.const_set :OOBGC_INTERVAL, interval
60
61
  ObjectSpace.each_object(Unicorn::HttpServer) do |s|
61
62
  s.extend(self)
62
- self.const_set :OOBGC_ENV, s.instance_variable_get(:@request).env
63
63
  end
64
64
  app # pretend to be Rack middleware since it was in the past
65
65
  end
66
66
 
67
67
  #:stopdoc:
68
- PATH_INFO = "PATH_INFO"
69
68
  def process_client(client)
70
69
  super(client) # Unicorn::HttpServer#process_client
71
- if OOBGC_PATH =~ OOBGC_ENV[PATH_INFO] && ((@@nr -= 1) <= 0)
70
+ env = instance_variable_get(:@request).env
71
+ if OOBGC_PATH =~ env['PATH_INFO'] && ((@@nr -= 1) <= 0)
72
72
  @@nr = OOBGC_INTERVAL
73
- OOBGC_ENV.clear
73
+ env.clear
74
74
  disabled = GC.enable
75
75
  GC.start
76
76
  GC.disable if disabled
@@ -0,0 +1,6 @@
1
+ # fallback for non-Linux and Linux <4.5 systems w/o EPOLLEXCLUSIVE
2
+ class Unicorn::SelectWaiter # :nodoc:
3
+ def get_readers(ready, readers, timeout) # :nodoc:
4
+ ret = IO.select(readers, nil, nil, timeout) and ready.replace(ret[0])
5
+ end
6
+ end
@@ -3,26 +3,30 @@
3
3
  require 'socket'
4
4
 
5
5
  module Unicorn
6
- module SocketHelper
7
- # :stopdoc:
8
- include Socket::Constants
9
6
 
10
- # prevents IO objects in here from being GC-ed
11
- # kill this when we drop 1.8 support
12
- IO_PURGATORY = []
7
+ # Instead of using a generic Kgio::Socket for everything,
8
+ # tag TCP sockets so we can use TCP_INFO under Linux without
9
+ # incurring extra syscalls for Unix domain sockets.
10
+ # TODO: remove these when we remove kgio
11
+ TCPClient = Class.new(Kgio::Socket) # :nodoc:
12
+ class TCPSrv < Kgio::TCPServer # :nodoc:
13
+ def kgio_tryaccept # :nodoc:
14
+ super(TCPClient)
15
+ end
16
+ end
17
+
18
+ module SocketHelper
13
19
 
14
- # internal interface, only used by Rainbows!/Zbatery
20
+ # internal interface
15
21
  DEFAULTS = {
16
22
  # The semantics for TCP_DEFER_ACCEPT changed in Linux 2.6.32+
17
23
  # with commit d1b99ba41d6c5aa1ed2fc634323449dd656899e9
18
- # This change shouldn't affect Unicorn users behind nginx (a
19
- # value of 1 remains an optimization), but Rainbows! users may
20
- # want to use a higher value on Linux 2.6.32+ to protect against
21
- # denial-of-service attacks
24
+ # This change shouldn't affect unicorn users behind nginx (a
25
+ # value of 1 remains an optimization).
22
26
  :tcp_defer_accept => 1,
23
27
 
24
28
  # FreeBSD, we need to override this to 'dataready' if we
25
- # eventually get HTTPS support
29
+ # eventually support non-HTTP/1.x
26
30
  :accept_filter => 'httpready',
27
31
 
28
32
  # same default value as Mongrel
@@ -32,80 +36,55 @@ module Unicorn
32
36
  :tcp_nopush => nil,
33
37
  :tcp_nodelay => true,
34
38
  }
35
- #:startdoc:
36
39
 
37
40
  # configure platform-specific options (only tested on Linux 2.6 so far)
38
- case RUBY_PLATFORM
39
- when /linux/
40
- # from /usr/include/linux/tcp.h
41
- TCP_DEFER_ACCEPT = 9 unless defined?(TCP_DEFER_ACCEPT)
42
-
43
- # do not send out partial frames (Linux)
44
- TCP_CORK = 3 unless defined?(TCP_CORK)
45
-
46
- # Linux got SO_REUSEPORT in 3.9, BSDs have had it for ages
47
- unless defined?(SO_REUSEPORT)
48
- if RUBY_PLATFORM =~ /(?:alpha|mips|parisc|sparc)/
49
- SO_REUSEPORT = 0x0200 # untested
50
- else
51
- SO_REUSEPORT = 15 # only tested on x86_64 and i686
52
- end
53
- end
54
- when /freebsd/
55
- # do not send out partial frames (FreeBSD)
56
- TCP_NOPUSH = 4 unless defined?(TCP_NOPUSH)
57
-
58
- def accf_arg(af_name)
59
- [ af_name, nil ].pack('a16a240')
60
- end if defined?(SO_ACCEPTFILTER)
61
- end
62
-
63
- def prevent_autoclose(io)
64
- if io.respond_to?(:autoclose=)
65
- io.autoclose = false
66
- else
67
- IO_PURGATORY << io
68
- end
69
- end
41
+ def accf_arg(af_name)
42
+ [ af_name, nil ].pack('a16a240')
43
+ end if RUBY_PLATFORM =~ /freebsd/ && Socket.const_defined?(:SO_ACCEPTFILTER)
70
44
 
71
45
  def set_tcp_sockopt(sock, opt)
72
46
  # just in case, even LANs can break sometimes. Linux sysadmins
73
47
  # can lower net.ipv4.tcp_keepalive_* sysctl knobs to very low values.
74
- sock.setsockopt(SOL_SOCKET, SO_KEEPALIVE, 1) if defined?(SO_KEEPALIVE)
48
+ Socket.const_defined?(:SO_KEEPALIVE) and
49
+ sock.setsockopt(:SOL_SOCKET, :SO_KEEPALIVE, 1)
75
50
 
76
- if defined?(TCP_NODELAY)
51
+ if Socket.const_defined?(:TCP_NODELAY)
77
52
  val = opt[:tcp_nodelay]
78
- val = DEFAULTS[:tcp_nodelay] if nil == val
79
- sock.setsockopt(IPPROTO_TCP, TCP_NODELAY, val ? 1 : 0)
53
+ val = DEFAULTS[:tcp_nodelay] if val.nil?
54
+ sock.setsockopt(:IPPROTO_TCP, :TCP_NODELAY, val ? 1 : 0)
80
55
  end
81
56
 
82
57
  val = opt[:tcp_nopush]
83
58
  unless val.nil?
84
- if defined?(TCP_CORK) # Linux
85
- sock.setsockopt(IPPROTO_TCP, TCP_CORK, val)
86
- elsif defined?(TCP_NOPUSH) # TCP_NOPUSH is lightly tested (FreeBSD)
87
- sock.setsockopt(IPPROTO_TCP, TCP_NOPUSH, val)
59
+ if Socket.const_defined?(:TCP_CORK) # Linux
60
+ sock.setsockopt(:IPPROTO_TCP, :TCP_CORK, val)
61
+ elsif Socket.const_defined?(:TCP_NOPUSH) # FreeBSD
62
+ sock.setsockopt(:IPPROTO_TCP, :TCP_NOPUSH, val)
88
63
  end
89
64
  end
90
65
 
91
- # No good reason to ever have deferred accepts off
92
- # (except maybe benchmarking)
93
- if defined?(TCP_DEFER_ACCEPT)
66
+ # No good reason to ever have deferred accepts off in single-threaded
67
+ # servers (except maybe benchmarking)
68
+ if Socket.const_defined?(:TCP_DEFER_ACCEPT)
94
69
  # this differs from nginx, since nginx doesn't allow us to
95
70
  # configure the the timeout...
96
71
  seconds = opt[:tcp_defer_accept]
97
72
  seconds = DEFAULTS[:tcp_defer_accept] if [true,nil].include?(seconds)
98
73
  seconds = 0 unless seconds # nil/false means disable this
99
- sock.setsockopt(SOL_TCP, TCP_DEFER_ACCEPT, seconds)
74
+ sock.setsockopt(:IPPROTO_TCP, :TCP_DEFER_ACCEPT, seconds)
100
75
  elsif respond_to?(:accf_arg)
101
76
  name = opt[:accept_filter]
102
- name = DEFAULTS[:accept_filter] if nil == name
77
+ name = DEFAULTS[:accept_filter] if name.nil?
78
+ sock.listen(opt[:backlog])
79
+ got = (sock.getsockopt(:SOL_SOCKET, :SO_ACCEPTFILTER) rescue nil).to_s
80
+ arg = accf_arg(name)
103
81
  begin
104
- sock.setsockopt(SOL_SOCKET, SO_ACCEPTFILTER, accf_arg(name))
82
+ sock.setsockopt(:SOL_SOCKET, :SO_ACCEPTFILTER, arg)
105
83
  rescue => e
106
84
  logger.error("#{sock_name(sock)} " \
107
85
  "failed to set accept_filter=#{name} (#{e.inspect})")
108
- end
86
+ logger.error("perhaps accf_http(9) needs to be loaded".freeze)
87
+ end if arg != got
109
88
  end
110
89
  end
111
90
 
@@ -114,20 +93,21 @@ module Unicorn
114
93
 
115
94
  TCPSocket === sock and set_tcp_sockopt(sock, opt)
116
95
 
117
- if opt[:rcvbuf] || opt[:sndbuf]
96
+ rcvbuf, sndbuf = opt.values_at(:rcvbuf, :sndbuf)
97
+ if rcvbuf || sndbuf
118
98
  log_buffer_sizes(sock, "before: ")
119
- sock.setsockopt(SOL_SOCKET, SO_RCVBUF, opt[:rcvbuf]) if opt[:rcvbuf]
120
- sock.setsockopt(SOL_SOCKET, SO_SNDBUF, opt[:sndbuf]) if opt[:sndbuf]
99
+ sock.setsockopt(:SOL_SOCKET, :SO_RCVBUF, rcvbuf) if rcvbuf
100
+ sock.setsockopt(:SOL_SOCKET, :SO_SNDBUF, sndbuf) if sndbuf
121
101
  log_buffer_sizes(sock, " after: ")
122
102
  end
123
103
  sock.listen(opt[:backlog])
124
- rescue => e
125
- Unicorn.log_error(logger, "#{sock_name(sock)} #{opt.inspect}", e)
104
+ rescue => e
105
+ Unicorn.log_error(logger, "#{sock_name(sock)} #{opt.inspect}", e)
126
106
  end
127
107
 
128
108
  def log_buffer_sizes(sock, pfx = '')
129
- rcvbuf = sock.getsockopt(SOL_SOCKET, SO_RCVBUF).unpack('i')
130
- sndbuf = sock.getsockopt(SOL_SOCKET, SO_SNDBUF).unpack('i')
109
+ rcvbuf = sock.getsockopt(:SOL_SOCKET, :SO_RCVBUF).int
110
+ sndbuf = sock.getsockopt(:SOL_SOCKET, :SO_SNDBUF).int
131
111
  logger.info "#{pfx}#{sock_name(sock)} rcvbuf=#{rcvbuf} sndbuf=#{sndbuf}"
132
112
  end
133
113
 
@@ -137,7 +117,7 @@ module Unicorn
137
117
  def bind_listen(address = '0.0.0.0:8080', opt = {})
138
118
  return address unless String === address
139
119
 
140
- sock = if address[0] == ?/
120
+ sock = if address.start_with?('/')
141
121
  if File.exist?(address)
142
122
  if File.socket?(address)
143
123
  begin
@@ -172,25 +152,25 @@ module Unicorn
172
152
 
173
153
  def new_tcp_server(addr, port, opt)
174
154
  # n.b. we set FD_CLOEXEC in the workers
175
- sock = Socket.new(opt[:ipv6] ? AF_INET6 : AF_INET, SOCK_STREAM, 0)
155
+ sock = Socket.new(opt[:ipv6] ? :AF_INET6 : :AF_INET, :SOCK_STREAM)
176
156
  if opt.key?(:ipv6only)
177
- defined?(IPV6_V6ONLY) or
157
+ Socket.const_defined?(:IPV6_V6ONLY) or
178
158
  abort "Socket::IPV6_V6ONLY not defined, upgrade Ruby and/or your OS"
179
- sock.setsockopt(IPPROTO_IPV6, IPV6_V6ONLY, opt[:ipv6only] ? 1 : 0)
159
+ sock.setsockopt(:IPPROTO_IPV6, :IPV6_V6ONLY, opt[:ipv6only] ? 1 : 0)
180
160
  end
181
- sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
182
- if defined?(SO_REUSEPORT) && opt[:reuseport]
183
- sock.setsockopt(SOL_SOCKET, SO_REUSEPORT, 1)
161
+ sock.setsockopt(:SOL_SOCKET, :SO_REUSEADDR, 1)
162
+ if Socket.const_defined?(:SO_REUSEPORT) && opt[:reuseport]
163
+ sock.setsockopt(:SOL_SOCKET, :SO_REUSEPORT, 1)
184
164
  end
185
165
  sock.bind(Socket.pack_sockaddr_in(port, addr))
186
- prevent_autoclose(sock)
187
- Kgio::TCPServer.for_fd(sock.fileno)
166
+ sock.autoclose = false
167
+ TCPSrv.for_fd(sock.fileno)
188
168
  end
189
169
 
190
170
  # returns rfc2732-style (e.g. "[::1]:666") addresses for IPv6
191
171
  def tcp_name(sock)
192
172
  port, addr = Socket.unpack_sockaddr_in(sock.getsockname)
193
- /:/ =~ addr ? "[#{addr}]:#{port}" : "#{addr}:#{port}"
173
+ addr.include?(':') ? "[#{addr}]:#{port}" : "#{addr}:#{port}"
194
174
  end
195
175
  module_function :tcp_name
196
176
 
@@ -221,7 +201,7 @@ module Unicorn
221
201
  def server_cast(sock)
222
202
  begin
223
203
  Socket.unpack_sockaddr_in(sock.getsockname)
224
- Kgio::TCPServer.for_fd(sock.fileno)
204
+ TCPSrv.for_fd(sock.fileno)
225
205
  rescue ArgumentError
226
206
  Kgio::UNIXServer.for_fd(sock.fileno)
227
207
  end
@@ -1,16 +1,17 @@
1
1
  # -*- encoding: binary -*-
2
2
 
3
- # When processing uploads, Unicorn may expose a StreamInput object under
4
- # "rack.input" of the (future) Rack (2.x) environment.
3
+ # When processing uploads, unicorn may expose a StreamInput object under
4
+ # "rack.input" of the Rack environment when
5
+ # Unicorn::Configurator#rewindable_input is set to +false+
5
6
  class Unicorn::StreamInput
6
7
  # The I/O chunk size (in +bytes+) for I/O operations where
7
8
  # the size cannot be user-specified when a method is called.
8
9
  # The default is 16 kilobytes.
9
- @@io_chunk_size = Unicorn::Const::CHUNK_SIZE
10
+ @@io_chunk_size = Unicorn::Const::CHUNK_SIZE # :nodoc:
10
11
 
11
12
  # Initializes a new StreamInput object. You normally do not have to call
12
13
  # this unless you are writing an HTTP server.
13
- def initialize(socket, request)
14
+ def initialize(socket, request) # :nodoc:
14
15
  @chunked = request.content_length.nil?
15
16
  @socket = socket
16
17
  @parser = request
@@ -53,7 +54,7 @@ class Unicorn::StreamInput
53
54
  rv << @rbuf
54
55
  to_read -= @rbuf.size
55
56
  end
56
- @rbuf.replace('')
57
+ @rbuf.clear
57
58
  end
58
59
  rv = nil if rv.empty? && length != 0
59
60
  else
@@ -130,8 +131,8 @@ private
130
131
  filter_body(@rbuf, @buf)
131
132
  dst << @rbuf
132
133
  end
133
- ensure
134
- @rbuf.replace('')
134
+ ensure
135
+ @rbuf.clear
135
136
  end
136
137
 
137
138
  def eof!
@@ -1,6 +1,6 @@
1
1
  # -*- encoding: binary -*-
2
2
 
3
- # acts like tee(1) on an input input to provide a input-like stream
3
+ # Acts like tee(1) on an input input to provide a input-like stream
4
4
  # while providing rewindable semantics through a File/StringIO backing
5
5
  # store. On the first pass, the input is only read on demand so your
6
6
  # Rack application can use input notification (upload progress and
@@ -9,22 +9,22 @@
9
9
  # strict interpretation of Rack::Lint::InputWrapper functionality and
10
10
  # will not support any deviations from it.
11
11
  #
12
- # When processing uploads, Unicorn exposes a TeeInput object under
13
- # "rack.input" of the Rack environment.
12
+ # When processing uploads, unicorn exposes a TeeInput object under
13
+ # "rack.input" of the Rack environment by default.
14
14
  class Unicorn::TeeInput < Unicorn::StreamInput
15
15
  # The maximum size (in +bytes+) to buffer in memory before
16
16
  # resorting to a temporary file. Default is 112 kilobytes.
17
- @@client_body_buffer_size = Unicorn::Const::MAX_BODY
17
+ @@client_body_buffer_size = Unicorn::Const::MAX_BODY # :nodoc:
18
18
 
19
19
  # sets the maximum size of request bodies to buffer in memory,
20
20
  # amounts larger than this are buffered to the filesystem
21
- def self.client_body_buffer_size=(bytes)
21
+ def self.client_body_buffer_size=(bytes) # :nodoc:
22
22
  @@client_body_buffer_size = bytes
23
23
  end
24
24
 
25
25
  # returns the maximum size of request bodies to buffer in memory,
26
26
  # amounts larger than this are buffered to the filesystem
27
- def self.client_body_buffer_size
27
+ def self.client_body_buffer_size # :nodoc:
28
28
  @@client_body_buffer_size
29
29
  end
30
30
 
@@ -37,7 +37,7 @@ class Unicorn::TeeInput < Unicorn::StreamInput
37
37
 
38
38
  # Initializes a new TeeInput object. You normally do not have to call
39
39
  # this unless you are writing an HTTP server.
40
- def initialize(socket, request)
40
+ def initialize(socket, request) # :nodoc:
41
41
  @len = request.content_length
42
42
  super
43
43
  @tmp = @len && @len <= @@client_body_buffer_size ?
@@ -125,9 +125,7 @@ private
125
125
  end
126
126
 
127
127
  def tee(buffer)
128
- if buffer && buffer.size > 0
129
- @tmp.write(buffer)
130
- end
128
+ @tmp.write(buffer) if buffer
131
129
  buffer
132
130
  end
133
131
  end
data/lib/unicorn/tmpio.rb CHANGED
@@ -11,22 +11,23 @@ class Unicorn::TmpIO < File
11
11
  # immediately, switched to binary mode, and userspace output
12
12
  # buffering is disabled
13
13
  def self.new
14
+ path = nil
15
+
16
+ # workaround File#path being tainted:
17
+ # https://bugs.ruby-lang.org/issues/14485
14
18
  fp = begin
15
- super("#{Dir::tmpdir}/#{rand}", RDWR|CREAT|EXCL, 0600)
19
+ path = "#{Dir::tmpdir}/#{rand}"
20
+ super(path, RDWR|CREAT|EXCL, 0600)
16
21
  rescue Errno::EEXIST
17
22
  retry
18
23
  end
19
- unlink(fp.path)
24
+
25
+ unlink(path)
20
26
  fp.binmode
21
27
  fp.sync = true
22
28
  fp
23
29
  end
24
30
 
25
- # for easier env["rack.input"] compatibility with Rack <= 1.1
26
- def size
27
- stat.size
28
- end unless File.method_defined?(:size)
29
-
30
31
  # pretend we're Tempfile for Rack::TempfileReaper
31
32
  alias close! close
32
33
  end
data/lib/unicorn/util.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # -*- encoding: binary -*-
2
2
 
3
- module Unicorn::Util
3
+ require 'fcntl'
4
+ module Unicorn::Util # :nodoc:
4
5
 
5
6
  # :stopdoc:
6
7
  def self.is_log?(fp)
@@ -10,8 +11,8 @@ module Unicorn::Util
10
11
  fp.stat.file? &&
11
12
  fp.sync &&
12
13
  (fp.fcntl(Fcntl::F_GETFL) & append_flags) == append_flags
13
- rescue IOError, Errno::EBADF
14
- false
14
+ rescue IOError, Errno::EBADF
15
+ false
15
16
  end
16
17
 
17
18
  def self.chown_logs(uid, gid)
@@ -63,7 +64,7 @@ module Unicorn::Util
63
64
  fp.reopen(fp.path, "a")
64
65
  else
65
66
  # We should not need this workaround, Ruby can be fixed:
66
- # http://bugs.ruby-lang.org/issues/9036
67
+ # https://bugs.ruby-lang.org/issues/9036
67
68
  # MRI will not call call fclose(3) or freopen(3) here
68
69
  # since there's no associated std{in,out,err} FILE * pointer
69
70
  # This should atomically use dup3(2) (or dup2(2)) syscall
@@ -1 +1 @@
1
- Unicorn::Const::UNICORN_VERSION = '4.9.0'
1
+ Unicorn::Const::UNICORN_VERSION = '6.1.0'
@@ -3,28 +3,28 @@ require "raindrops"
3
3
 
4
4
  # This class and its members can be considered a stable interface
5
5
  # and will not change in a backwards-incompatible fashion between
6
- # releases of \Unicorn. Knowledge of this class is generally not
7
- # not needed for most users of \Unicorn.
6
+ # releases of unicorn. Knowledge of this class is generally not
7
+ # not needed for most users of unicorn.
8
8
  #
9
9
  # Some users may want to access it in the before_fork/after_fork hooks.
10
10
  # See the Unicorn::Configurator RDoc for examples.
11
11
  class Unicorn::Worker
12
12
  # :stopdoc:
13
13
  attr_accessor :nr, :switched
14
- attr_writer :tmp
15
14
  attr_reader :to_io # IO.select-compatible
15
+ attr_reader :master
16
16
 
17
17
  PER_DROP = Raindrops::PAGE_SIZE / Raindrops::SIZE
18
18
  DROPS = []
19
19
 
20
- def initialize(nr)
20
+ def initialize(nr, pipe=nil)
21
21
  drop_index = nr / PER_DROP
22
22
  @raindrop = DROPS[drop_index] ||= Raindrops.new(PER_DROP)
23
23
  @offset = nr % PER_DROP
24
24
  @raindrop[@offset] = 0
25
25
  @nr = nr
26
- @tmp = @switched = false
27
- @to_io, @master = Unicorn.pipe
26
+ @switched = false
27
+ @to_io, @master = pipe || Unicorn.pipe
28
28
  end
29
29
 
30
30
  def atfork_child # :nodoc:
@@ -101,18 +101,8 @@ class Unicorn::Worker
101
101
  @raindrop[@offset]
102
102
  end
103
103
 
104
- # only exists for compatibility
105
- def tmp # :nodoc:
106
- @tmp ||= begin
107
- tmp = Unicorn::TmpIO.new
108
- tmp.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
109
- tmp
110
- end
111
- end
112
-
113
104
  # called in both the master (reaping worker) and worker (SIGQUIT handler)
114
105
  def close # :nodoc:
115
- @tmp.close if @tmp
116
106
  @master.close if @master
117
107
  @to_io.close if @to_io
118
108
  end
@@ -122,30 +112,53 @@ class Unicorn::Worker
122
112
  # In most cases, you should be using the Unicorn::Configurator#user
123
113
  # directive instead. This method should only be used if you need
124
114
  # fine-grained control of exactly when you want to change permissions
125
- # in your after_fork hooks.
115
+ # in your after_fork or after_worker_ready hooks, or if you want to
116
+ # use the chroot support.
126
117
  #
127
- # Changes the worker process to the specified +user+ and +group+
118
+ # Changes the worker process to the specified +user+ and +group+,
119
+ # and chroots to the current working directory if +chroot+ is set.
128
120
  # This is only intended to be called from within the worker
129
121
  # process from the +after_fork+ hook. This should be called in
130
122
  # the +after_fork+ hook after any privileged functions need to be
131
123
  # run (e.g. to set per-worker CPU affinity, niceness, etc)
132
124
  #
125
+ # +group+ can be specified as a string, or as an array of two
126
+ # strings. If an array of two strings is given, the first string
127
+ # is used as the primary group of the process, and the second is
128
+ # used as the group of the log files.
129
+ #
133
130
  # Any and all errors raised within this method will be propagated
134
131
  # directly back to the caller (usually the +after_fork+ hook.
135
132
  # These errors commonly include ArgumentError for specifying an
136
- # invalid user/group and Errno::EPERM for insufficient privileges
137
- def user(user, group = nil)
133
+ # invalid user/group and Errno::EPERM for insufficient privileges.
134
+ #
135
+ # chroot support is only available in unicorn 5.3.0+
136
+ # user and group switching appeared in unicorn 0.94.0 (2009-11-05)
137
+ def user(user, group = nil, chroot = false)
138
138
  # we do not protect the caller, checking Process.euid == 0 is
139
139
  # insufficient because modern systems have fine-grained
140
140
  # capabilities. Let the caller handle any and all errors.
141
141
  uid = Etc.getpwnam(user).uid
142
- gid = Etc.getgrnam(group).gid if group
143
- Unicorn::Util.chown_logs(uid, gid)
144
- @tmp.chown(uid, gid) if @tmp
142
+
143
+ if group
144
+ if group.is_a?(Array)
145
+ group, log_group = group
146
+ log_gid = Etc.getgrnam(log_group).gid
147
+ end
148
+ gid = Etc.getgrnam(group).gid
149
+ log_gid ||= gid
150
+ end
151
+
152
+ Unicorn::Util.chown_logs(uid, log_gid)
145
153
  if gid && Process.egid != gid
146
154
  Process.initgroups(user, gid)
147
155
  Process::GID.change_privilege(gid)
148
156
  end
157
+ if chroot
158
+ chroot = Dir.pwd if chroot == true
159
+ Dir.chroot(chroot)
160
+ Dir.chdir('/')
161
+ end
149
162
  Process.euid != uid and Process::UID.change_privilege(uid)
150
163
  @switched = true
151
164
  end