unicorn 4.9.0 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. checksums.yaml +5 -5
  2. data/.gitattributes +5 -0
  3. data/.olddoc.yml +13 -6
  4. data/Application_Timeouts +7 -7
  5. data/DESIGN +2 -4
  6. data/Documentation/.gitignore +1 -3
  7. data/Documentation/unicorn.1 +222 -0
  8. data/Documentation/unicorn_rails.1 +207 -0
  9. data/FAQ +17 -8
  10. data/GIT-VERSION-GEN +1 -1
  11. data/GNUmakefile +121 -56
  12. data/HACKING +1 -2
  13. data/ISSUES +40 -41
  14. data/KNOWN_ISSUES +11 -11
  15. data/LICENSE +2 -2
  16. data/Links +24 -25
  17. data/PHILOSOPHY +0 -6
  18. data/README +46 -39
  19. data/SIGNALS +2 -2
  20. data/Sandbox +10 -9
  21. data/TODO +0 -2
  22. data/TUNING +30 -9
  23. data/archive/slrnpull.conf +1 -1
  24. data/bin/unicorn +4 -2
  25. data/bin/unicorn_rails +3 -3
  26. data/examples/big_app_gc.rb +1 -1
  27. data/examples/init.sh +36 -8
  28. data/examples/logrotate.conf +17 -2
  29. data/examples/nginx.conf +14 -14
  30. data/examples/unicorn.conf.minimal.rb +2 -2
  31. data/examples/unicorn.conf.rb +3 -6
  32. data/examples/unicorn.socket +11 -0
  33. data/examples/unicorn@.service +40 -0
  34. data/ext/unicorn_http/common_field_optimization.h +23 -5
  35. data/ext/unicorn_http/ext_help.h +0 -20
  36. data/ext/unicorn_http/extconf.rb +37 -1
  37. data/ext/unicorn_http/global_variables.h +1 -1
  38. data/ext/unicorn_http/httpdate.c +2 -2
  39. data/ext/unicorn_http/unicorn_http.rl +167 -170
  40. data/ext/unicorn_http/unicorn_http_common.rl +1 -1
  41. data/lib/unicorn.rb +66 -46
  42. data/lib/unicorn/configurator.rb +110 -44
  43. data/lib/unicorn/const.rb +2 -25
  44. data/lib/unicorn/http_request.rb +110 -31
  45. data/lib/unicorn/http_response.rb +17 -31
  46. data/lib/unicorn/http_server.rb +238 -157
  47. data/lib/unicorn/launcher.rb +1 -1
  48. data/lib/unicorn/oob_gc.rb +6 -6
  49. data/lib/unicorn/socket_helper.rb +58 -78
  50. data/lib/unicorn/stream_input.rb +8 -7
  51. data/lib/unicorn/tee_input.rb +8 -10
  52. data/lib/unicorn/tmpio.rb +8 -7
  53. data/lib/unicorn/util.rb +5 -4
  54. data/lib/unicorn/worker.rb +36 -23
  55. data/t/GNUmakefile +3 -72
  56. data/t/README +4 -4
  57. data/t/t0011-active-unix-socket.sh +1 -1
  58. data/t/t0012-reload-empty-config.sh +2 -1
  59. data/t/t0301-no-default-middleware-ignored-in-config.sh +25 -0
  60. data/t/t0301.ru +13 -0
  61. data/t/test-lib.sh +2 -2
  62. data/test/benchmark/README +14 -4
  63. data/test/benchmark/ddstream.ru +50 -0
  64. data/test/benchmark/readinput.ru +40 -0
  65. data/test/benchmark/uconnect.perl +66 -0
  66. data/test/exec/test_exec.rb +73 -19
  67. data/test/test_helper.rb +40 -31
  68. data/test/unit/test_ccc.rb +91 -0
  69. data/test/unit/test_droplet.rb +1 -1
  70. data/test/unit/test_http_parser.rb +46 -16
  71. data/test/unit/test_http_parser_ng.rb +97 -114
  72. data/test/unit/test_request.rb +10 -10
  73. data/test/unit/test_response.rb +28 -16
  74. data/test/unit/test_server.rb +86 -12
  75. data/test/unit/test_signals.rb +8 -8
  76. data/test/unit/test_socket_helper.rb +14 -10
  77. data/test/unit/test_upload.rb +9 -14
  78. data/test/unit/test_util.rb +27 -2
  79. data/unicorn.gemspec +27 -19
  80. metadata +24 -45
  81. data/Documentation/GNUmakefile +0 -30
  82. data/Documentation/unicorn.1.txt +0 -185
  83. data/Documentation/unicorn_rails.1.txt +0 -175
  84. data/examples/git.ru +0 -13
  85. data/lib/unicorn/app/exec_cgi.rb +0 -154
  86. data/lib/unicorn/app/inetd.rb +0 -109
  87. data/lib/unicorn/ssl_client.rb +0 -11
  88. data/lib/unicorn/ssl_configurator.rb +0 -104
  89. data/lib/unicorn/ssl_server.rb +0 -42
  90. data/t/hijack.ru +0 -42
  91. data/t/t0016-trust-x-forwarded-false.sh +0 -30
  92. data/t/t0017-trust-x-forwarded-true.sh +0 -30
  93. data/t/t0200-rack-hijack.sh +0 -27
  94. data/test/unit/test_http_parser_xftrust.rb +0 -38
  95. data/test/unit/test_sni_hostnames.rb +0 -47
@@ -31,7 +31,7 @@ def self.daemonize!(options)
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 @@ def self.new(app, interval = 5, path = %r{\A/})
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
@@ -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 SocketHelper
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 @@ def set_server_sockopt(sock, opt)
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 @@ def log_buffer_sizes(sock, pfx = '')
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 @@ def bind_listen(address = '0.0.0.0:8080', opt = {})
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 @@ def sock_name(sock)
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 @@ def read(length = nil, rv = '')
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 @@ def read_all(dst)
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 @@ def new_tmpio # :nodoc:
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 @@ def consume!
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 @@ def self.is_log?(fp)
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 @@ def self.reopen_logs
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
@@ -3,28 +3,28 @@
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 @@ def tick # :nodoc:
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 @@ def close # :nodoc:
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