boourns-unicorn 4.4.1

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 (155) hide show
  1. data/.CHANGELOG.old +25 -0
  2. data/.document +29 -0
  3. data/.gitignore +24 -0
  4. data/.mailmap +26 -0
  5. data/.wrongdoc.yml +10 -0
  6. data/Application_Timeouts +77 -0
  7. data/CONTRIBUTORS +35 -0
  8. data/COPYING +674 -0
  9. data/DESIGN +97 -0
  10. data/Documentation/.gitignore +5 -0
  11. data/Documentation/GNUmakefile +30 -0
  12. data/Documentation/unicorn.1.txt +174 -0
  13. data/Documentation/unicorn_rails.1.txt +175 -0
  14. data/FAQ +53 -0
  15. data/GIT-VERSION-GEN +40 -0
  16. data/GNUmakefile +267 -0
  17. data/HACKING +134 -0
  18. data/ISSUES +36 -0
  19. data/KNOWN_ISSUES +79 -0
  20. data/LICENSE +64 -0
  21. data/Links +56 -0
  22. data/PHILOSOPHY +145 -0
  23. data/README +149 -0
  24. data/Rakefile +97 -0
  25. data/SIGNALS +114 -0
  26. data/Sandbox +96 -0
  27. data/TODO +5 -0
  28. data/TUNING +98 -0
  29. data/bin/unicorn +121 -0
  30. data/bin/unicorn_rails +209 -0
  31. data/examples/big_app_gc.rb +2 -0
  32. data/examples/echo.ru +27 -0
  33. data/examples/git.ru +13 -0
  34. data/examples/init.sh +74 -0
  35. data/examples/logger_mp_safe.rb +25 -0
  36. data/examples/logrotate.conf +29 -0
  37. data/examples/nginx.conf +156 -0
  38. data/examples/unicorn.conf.minimal.rb +13 -0
  39. data/examples/unicorn.conf.rb +94 -0
  40. data/ext/unicorn_http/CFLAGS +13 -0
  41. data/ext/unicorn_http/c_util.h +124 -0
  42. data/ext/unicorn_http/common_field_optimization.h +111 -0
  43. data/ext/unicorn_http/ext_help.h +86 -0
  44. data/ext/unicorn_http/extconf.rb +10 -0
  45. data/ext/unicorn_http/global_variables.h +97 -0
  46. data/ext/unicorn_http/httpdate.c +82 -0
  47. data/ext/unicorn_http/unicorn_http.rl +1036 -0
  48. data/ext/unicorn_http/unicorn_http_common.rl +76 -0
  49. data/lib/unicorn.rb +107 -0
  50. data/lib/unicorn/app/exec_cgi.rb +154 -0
  51. data/lib/unicorn/app/inetd.rb +109 -0
  52. data/lib/unicorn/app/old_rails.rb +35 -0
  53. data/lib/unicorn/app/old_rails/static.rb +59 -0
  54. data/lib/unicorn/cgi_wrapper.rb +147 -0
  55. data/lib/unicorn/configurator.rb +630 -0
  56. data/lib/unicorn/const.rb +40 -0
  57. data/lib/unicorn/http_request.rb +83 -0
  58. data/lib/unicorn/http_response.rb +45 -0
  59. data/lib/unicorn/http_server.rb +755 -0
  60. data/lib/unicorn/launcher.rb +62 -0
  61. data/lib/unicorn/oob_gc.rb +71 -0
  62. data/lib/unicorn/preread_input.rb +33 -0
  63. data/lib/unicorn/socket_helper.rb +208 -0
  64. data/lib/unicorn/ssl_client.rb +11 -0
  65. data/lib/unicorn/ssl_configurator.rb +104 -0
  66. data/lib/unicorn/ssl_server.rb +42 -0
  67. data/lib/unicorn/stream_input.rb +149 -0
  68. data/lib/unicorn/tee_input.rb +126 -0
  69. data/lib/unicorn/tmpio.rb +29 -0
  70. data/lib/unicorn/util.rb +69 -0
  71. data/lib/unicorn/worker.rb +88 -0
  72. data/local.mk.sample +59 -0
  73. data/script/isolate_for_tests +32 -0
  74. data/setup.rb +1586 -0
  75. data/t/.gitignore +5 -0
  76. data/t/GNUmakefile +82 -0
  77. data/t/README +42 -0
  78. data/t/bin/content-md5-put +36 -0
  79. data/t/bin/sha1sum.rb +17 -0
  80. data/t/bin/unused_listen +40 -0
  81. data/t/bin/utee +12 -0
  82. data/t/broken-app.ru +12 -0
  83. data/t/detach.ru +11 -0
  84. data/t/env.ru +3 -0
  85. data/t/heartbeat-timeout.ru +12 -0
  86. data/t/listener_names.ru +4 -0
  87. data/t/my-tap-lib.sh +201 -0
  88. data/t/oob_gc.ru +21 -0
  89. data/t/oob_gc_path.ru +21 -0
  90. data/t/pid.ru +3 -0
  91. data/t/preread_input.ru +17 -0
  92. data/t/rack-input-tests.ru +21 -0
  93. data/t/sslgen.sh +71 -0
  94. data/t/t0000-http-basic.sh +50 -0
  95. data/t/t0001-reload-bad-config.sh +53 -0
  96. data/t/t0002-config-conflict.sh +49 -0
  97. data/t/t0002-parser-error.sh +94 -0
  98. data/t/t0003-working_directory.sh +51 -0
  99. data/t/t0004-heartbeat-timeout.sh +69 -0
  100. data/t/t0004-working_directory_broken.sh +24 -0
  101. data/t/t0005-working_directory_app.rb.sh +37 -0
  102. data/t/t0006-reopen-logs.sh +83 -0
  103. data/t/t0006.ru +13 -0
  104. data/t/t0007-working_directory_no_embed_cli.sh +44 -0
  105. data/t/t0008-back_out_of_upgrade.sh +110 -0
  106. data/t/t0009-broken-app.sh +56 -0
  107. data/t/t0009-winch_ttin.sh +59 -0
  108. data/t/t0010-reap-logging.sh +55 -0
  109. data/t/t0011-active-unix-socket.sh +79 -0
  110. data/t/t0012-reload-empty-config.sh +85 -0
  111. data/t/t0013-rewindable-input-false.sh +24 -0
  112. data/t/t0013.ru +12 -0
  113. data/t/t0014-rewindable-input-true.sh +24 -0
  114. data/t/t0014.ru +12 -0
  115. data/t/t0015-configurator-internals.sh +25 -0
  116. data/t/t0016-trust-x-forwarded-false.sh +30 -0
  117. data/t/t0017-trust-x-forwarded-true.sh +30 -0
  118. data/t/t0018-write-on-close.sh +23 -0
  119. data/t/t0019-max_header_len.sh +49 -0
  120. data/t/t0020-at_exit-handler.sh +49 -0
  121. data/t/t0021-process_detach.sh +29 -0
  122. data/t/t0022-listener_names-preload_app.sh +32 -0
  123. data/t/t0100-rack-input-tests.sh +124 -0
  124. data/t/t0116-client_body_buffer_size.sh +80 -0
  125. data/t/t0116.ru +16 -0
  126. data/t/t0600-https-server-basic.sh +48 -0
  127. data/t/t9000-preread-input.sh +48 -0
  128. data/t/t9001-oob_gc.sh +47 -0
  129. data/t/t9002-oob_gc-path.sh +75 -0
  130. data/t/test-lib.sh +113 -0
  131. data/t/write-on-close.ru +11 -0
  132. data/test/aggregate.rb +15 -0
  133. data/test/benchmark/README +50 -0
  134. data/test/benchmark/dd.ru +18 -0
  135. data/test/benchmark/stack.ru +8 -0
  136. data/test/exec/README +5 -0
  137. data/test/exec/test_exec.rb +1041 -0
  138. data/test/test_helper.rb +300 -0
  139. data/test/unit/test_configurator.rb +158 -0
  140. data/test/unit/test_droplet.rb +28 -0
  141. data/test/unit/test_http_parser.rb +860 -0
  142. data/test/unit/test_http_parser_ng.rb +716 -0
  143. data/test/unit/test_http_parser_xftrust.rb +38 -0
  144. data/test/unit/test_request.rb +197 -0
  145. data/test/unit/test_response.rb +99 -0
  146. data/test/unit/test_server.rb +289 -0
  147. data/test/unit/test_signals.rb +207 -0
  148. data/test/unit/test_sni_hostnames.rb +47 -0
  149. data/test/unit/test_socket_helper.rb +192 -0
  150. data/test/unit/test_stream_input.rb +204 -0
  151. data/test/unit/test_tee_input.rb +296 -0
  152. data/test/unit/test_upload.rb +306 -0
  153. data/test/unit/test_util.rb +99 -0
  154. data/unicorn.gemspec +44 -0
  155. metadata +333 -0
@@ -0,0 +1,42 @@
1
+ # -*- encoding: binary -*-
2
+ # :stopdoc:
3
+ # this module is meant to be included in Unicorn::HttpServer
4
+ # It is an implementation detail and NOT meant for users.
5
+ module Unicorn::SSLServer
6
+ attr_accessor :ssl_engine
7
+
8
+ def ssl_enable!
9
+ sni_hostnames = rack_sni_hostnames(@app)
10
+ seen = {} # we map a single SSLContext to multiple listeners
11
+ listener_ctx = {}
12
+ @listener_opts.each do |address, address_opts|
13
+ ssl_opts = address_opts[:ssl_opts] or next
14
+ listener_ctx[address] = seen[ssl_opts.object_id] ||= begin
15
+ unless sni_hostnames.empty?
16
+ ssl_opts = ssl_opts.dup
17
+ ssl_opts[:sni_hostnames] = sni_hostnames
18
+ end
19
+ ctx = Flipper.ssl_context(ssl_opts)
20
+ # FIXME: make configurable
21
+ ctx.session_cache_mode = OpenSSL::SSL::SSLContext::SESSION_CACHE_OFF
22
+ ctx
23
+ end
24
+ end
25
+ Unicorn::HttpServer::LISTENERS.each do |listener|
26
+ ctx = listener_ctx[sock_name(listener)] or next
27
+ listener.extend(Kgio::SSLServer)
28
+ listener.ssl_ctx = ctx
29
+ listener.kgio_ssl_class = Unicorn::SSLClient
30
+ end
31
+ end
32
+
33
+ # ugh, this depends on Rack internals...
34
+ def rack_sni_hostnames(rack_app) # :nodoc:
35
+ hostnames = {}
36
+ if Rack::URLMap === rack_app
37
+ mapping = rack_app.instance_variable_get(:@mapping)
38
+ mapping.each { |hostname,_,_,_| hostnames[hostname] = true }
39
+ end
40
+ hostnames.keys
41
+ end
42
+ end
@@ -0,0 +1,149 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ # When processing uploads, Unicorn may expose a StreamInput object under
4
+ # "rack.input" of the (future) Rack (2.x) environment.
5
+ class Unicorn::StreamInput
6
+ # The I/O chunk size (in +bytes+) for I/O operations where
7
+ # the size cannot be user-specified when a method is called.
8
+ # The default is 16 kilobytes.
9
+ @@io_chunk_size = Unicorn::Const::CHUNK_SIZE
10
+
11
+ # Initializes a new StreamInput object. You normally do not have to call
12
+ # this unless you are writing an HTTP server.
13
+ def initialize(socket, request)
14
+ @chunked = request.content_length.nil?
15
+ @socket = socket
16
+ @parser = request
17
+ @buf = request.buf
18
+ @rbuf = ''
19
+ @bytes_read = 0
20
+ filter_body(@rbuf, @buf) unless @buf.empty?
21
+ end
22
+
23
+ # :call-seq:
24
+ # ios.read([length [, buffer ]]) => string, buffer, or nil
25
+ #
26
+ # Reads at most length bytes from the I/O stream, or to the end of
27
+ # file if length is omitted or is nil. length must be a non-negative
28
+ # integer or nil. If the optional buffer argument is present, it
29
+ # must reference a String, which will receive the data.
30
+ #
31
+ # At end of file, it returns nil or '' depend on length.
32
+ # ios.read() and ios.read(nil) returns ''.
33
+ # ios.read(length [, buffer]) returns nil.
34
+ #
35
+ # If the Content-Length of the HTTP request is known (as is the common
36
+ # case for POST requests), then ios.read(length [, buffer]) will block
37
+ # until the specified length is read (or it is the last chunk).
38
+ # Otherwise, for uncommon "Transfer-Encoding: chunked" requests,
39
+ # ios.read(length [, buffer]) will return immediately if there is
40
+ # any data and only block when nothing is available (providing
41
+ # IO#readpartial semantics).
42
+ def read(length = nil, rv = '')
43
+ if length
44
+ if length <= @rbuf.size
45
+ length < 0 and raise ArgumentError, "negative length #{length} given"
46
+ rv.replace(@rbuf.slice!(0, length))
47
+ else
48
+ to_read = length - @rbuf.size
49
+ rv.replace(@rbuf.slice!(0, @rbuf.size))
50
+ until to_read == 0 || eof? || (rv.size > 0 && @chunked)
51
+ @socket.kgio_read(to_read, @buf) or eof!
52
+ filter_body(@rbuf, @buf)
53
+ rv << @rbuf
54
+ to_read -= @rbuf.size
55
+ end
56
+ @rbuf.replace('')
57
+ end
58
+ rv = nil if rv.empty? && length != 0
59
+ else
60
+ read_all(rv)
61
+ end
62
+ rv
63
+ end
64
+
65
+ # :call-seq:
66
+ # ios.gets => string or nil
67
+ #
68
+ # Reads the next ``line'' from the I/O stream; lines are separated
69
+ # by the global record separator ($/, typically "\n"). A global
70
+ # record separator of nil reads the entire unread contents of ios.
71
+ # Returns nil if called at the end of file.
72
+ # This takes zero arguments for strict Rack::Lint compatibility,
73
+ # unlike IO#gets.
74
+ def gets
75
+ sep = $/
76
+ if sep.nil?
77
+ read_all(rv = '')
78
+ return rv.empty? ? nil : rv
79
+ end
80
+ re = /\A(.*?#{Regexp.escape(sep)})/
81
+
82
+ begin
83
+ @rbuf.sub!(re, '') and return $1
84
+ return @rbuf.empty? ? nil : @rbuf.slice!(0, @rbuf.size) if eof?
85
+ @socket.kgio_read(@@io_chunk_size, @buf) or eof!
86
+ filter_body(once = '', @buf)
87
+ @rbuf << once
88
+ end while true
89
+ end
90
+
91
+ # :call-seq:
92
+ # ios.each { |line| block } => ios
93
+ #
94
+ # Executes the block for every ``line'' in *ios*, where lines are
95
+ # separated by the global record separator ($/, typically "\n").
96
+ def each
97
+ while line = gets
98
+ yield line
99
+ end
100
+
101
+ self # Rack does not specify what the return value is here
102
+ end
103
+
104
+ private
105
+
106
+ def eof?
107
+ if @parser.body_eof?
108
+ while @chunked && ! @parser.parse
109
+ once = @socket.kgio_read(@@io_chunk_size) or eof!
110
+ @buf << once
111
+ end
112
+ @socket = nil
113
+ true
114
+ else
115
+ false
116
+ end
117
+ end
118
+
119
+ def filter_body(dst, src)
120
+ rv = @parser.filter_body(dst, src)
121
+ @bytes_read += dst.size
122
+ rv
123
+ end
124
+
125
+ def read_all(dst)
126
+ dst.replace(@rbuf)
127
+ @socket or return
128
+ until eof?
129
+ @socket.kgio_read(@@io_chunk_size, @buf) or eof!
130
+ filter_body(@rbuf, @buf)
131
+ dst << @rbuf
132
+ end
133
+ ensure
134
+ @rbuf.replace('')
135
+ end
136
+
137
+ def eof!
138
+ # in case client only did a premature shutdown(SHUT_WR)
139
+ # we do support clients that shutdown(SHUT_WR) after the
140
+ # _entire_ request has been sent, and those will not have
141
+ # raised EOFError on us.
142
+ if @socket
143
+ @socket.shutdown
144
+ @socket.close
145
+ end
146
+ ensure
147
+ raise Unicorn::ClientShutdown, "bytes_read=#{@bytes_read}", []
148
+ end
149
+ end
@@ -0,0 +1,126 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ # acts like tee(1) on an input input to provide a input-like stream
4
+ # while providing rewindable semantics through a File/StringIO backing
5
+ # store. On the first pass, the input is only read on demand so your
6
+ # Rack application can use input notification (upload progress and
7
+ # like). This should fully conform to the Rack::Lint::InputWrapper
8
+ # specification on the public API. This class is intended to be a
9
+ # strict interpretation of Rack::Lint::InputWrapper functionality and
10
+ # will not support any deviations from it.
11
+ #
12
+ # When processing uploads, Unicorn exposes a TeeInput object under
13
+ # "rack.input" of the Rack environment.
14
+ class Unicorn::TeeInput < Unicorn::StreamInput
15
+ # The maximum size (in +bytes+) to buffer in memory before
16
+ # resorting to a temporary file. Default is 112 kilobytes.
17
+ @@client_body_buffer_size = Unicorn::Const::MAX_BODY
18
+
19
+ # sets the maximum size of request bodies to buffer in memory,
20
+ # amounts larger than this are buffered to the filesystem
21
+ def self.client_body_buffer_size=(bytes)
22
+ @@client_body_buffer_size = bytes
23
+ end
24
+
25
+ # returns the maximum size of request bodies to buffer in memory,
26
+ # amounts larger than this are buffered to the filesystem
27
+ def self.client_body_buffer_size
28
+ @@client_body_buffer_size
29
+ end
30
+
31
+ # Initializes a new TeeInput object. You normally do not have to call
32
+ # this unless you are writing an HTTP server.
33
+ def initialize(socket, request)
34
+ @len = request.content_length
35
+ super
36
+ @tmp = @len && @len <= @@client_body_buffer_size ?
37
+ StringIO.new("") : Unicorn::TmpIO.new
38
+ end
39
+
40
+ # :call-seq:
41
+ # ios.size => Integer
42
+ #
43
+ # Returns the size of the input. For requests with a Content-Length
44
+ # header value, this will not read data off the socket and just return
45
+ # the value of the Content-Length header as an Integer.
46
+ #
47
+ # For Transfer-Encoding:chunked requests, this requires consuming
48
+ # all of the input stream before returning since there's no other
49
+ # way to determine the size of the request body beforehand.
50
+ #
51
+ # This method is no longer part of the Rack specification as of
52
+ # Rack 1.2, so its use is not recommended. This method only exists
53
+ # for compatibility with Rack applications designed for Rack 1.1 and
54
+ # earlier. Most applications should only need to call +read+ with a
55
+ # specified +length+ in a loop until it returns +nil+.
56
+ def size
57
+ @len and return @len
58
+ pos = @tmp.pos
59
+ consume!
60
+ @tmp.pos = pos
61
+ @len = @tmp.size
62
+ end
63
+
64
+ # :call-seq:
65
+ # ios.read([length [, buffer ]]) => string, buffer, or nil
66
+ #
67
+ # Reads at most length bytes from the I/O stream, or to the end of
68
+ # file if length is omitted or is nil. length must be a non-negative
69
+ # integer or nil. If the optional buffer argument is present, it
70
+ # must reference a String, which will receive the data.
71
+ #
72
+ # At end of file, it returns nil or "" depend on length.
73
+ # ios.read() and ios.read(nil) returns "".
74
+ # ios.read(length [, buffer]) returns nil.
75
+ #
76
+ # If the Content-Length of the HTTP request is known (as is the common
77
+ # case for POST requests), then ios.read(length [, buffer]) will block
78
+ # until the specified length is read (or it is the last chunk).
79
+ # Otherwise, for uncommon "Transfer-Encoding: chunked" requests,
80
+ # ios.read(length [, buffer]) will return immediately if there is
81
+ # any data and only block when nothing is available (providing
82
+ # IO#readpartial semantics).
83
+ def read(*args)
84
+ @socket ? tee(super) : @tmp.read(*args)
85
+ end
86
+
87
+ # :call-seq:
88
+ # ios.gets => string or nil
89
+ #
90
+ # Reads the next ``line'' from the I/O stream; lines are separated
91
+ # by the global record separator ($/, typically "\n"). A global
92
+ # record separator of nil reads the entire unread contents of ios.
93
+ # Returns nil if called at the end of file.
94
+ # This takes zero arguments for strict Rack::Lint compatibility,
95
+ # unlike IO#gets.
96
+ def gets
97
+ @socket ? tee(super) : @tmp.gets
98
+ end
99
+
100
+ # :call-seq:
101
+ # ios.rewind => 0
102
+ #
103
+ # Positions the *ios* pointer to the beginning of input, returns
104
+ # the offset (zero) of the +ios+ pointer. Subsequent reads will
105
+ # start from the beginning of the previously-buffered input.
106
+ def rewind
107
+ return 0 if 0 == @tmp.size
108
+ consume! if @socket
109
+ @tmp.rewind # Rack does not specify what the return value is here
110
+ end
111
+
112
+ private
113
+
114
+ # consumes the stream of the socket
115
+ def consume!
116
+ junk = ""
117
+ nil while read(@@io_chunk_size, junk)
118
+ end
119
+
120
+ def tee(buffer)
121
+ if buffer && buffer.size > 0
122
+ @tmp.write(buffer)
123
+ end
124
+ buffer
125
+ end
126
+ end
@@ -0,0 +1,29 @@
1
+ # -*- encoding: binary -*-
2
+ # :stopdoc:
3
+ require 'tmpdir'
4
+
5
+ # some versions of Ruby had a broken Tempfile which didn't work
6
+ # well with unlinked files. This one is much shorter, easier
7
+ # to understand, and slightly faster.
8
+ class Unicorn::TmpIO < File
9
+
10
+ # creates and returns a new File object. The File is unlinked
11
+ # immediately, switched to binary mode, and userspace output
12
+ # buffering is disabled
13
+ def self.new
14
+ fp = begin
15
+ super("#{Dir::tmpdir}/#{rand}", RDWR|CREAT|EXCL, 0600)
16
+ rescue Errno::EEXIST
17
+ retry
18
+ end
19
+ unlink(fp.path)
20
+ fp.binmode
21
+ fp.sync = true
22
+ fp
23
+ end
24
+
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
+ end
@@ -0,0 +1,69 @@
1
+ # -*- encoding: binary -*-
2
+
3
+ module Unicorn::Util
4
+
5
+ # :stopdoc:
6
+ def self.is_log?(fp)
7
+ append_flags = File::WRONLY | File::APPEND
8
+
9
+ ! fp.closed? &&
10
+ fp.stat.file? &&
11
+ fp.sync &&
12
+ (fp.fcntl(Fcntl::F_GETFL) & append_flags) == append_flags
13
+ rescue IOError, Errno::EBADF
14
+ false
15
+ end
16
+
17
+ def self.chown_logs(uid, gid)
18
+ ObjectSpace.each_object(File) do |fp|
19
+ fp.chown(uid, gid) if is_log?(fp)
20
+ end
21
+ end
22
+ # :startdoc:
23
+
24
+ # This reopens ALL logfiles in the process that have been rotated
25
+ # using logrotate(8) (without copytruncate) or similar tools.
26
+ # A +File+ object is considered for reopening if it is:
27
+ # 1) opened with the O_APPEND and O_WRONLY flags
28
+ # 2) the current open file handle does not match its original open path
29
+ # 3) unbuffered (as far as userspace buffering goes, not O_SYNC)
30
+ # Returns the number of files reopened
31
+ #
32
+ # In Unicorn 3.5.x and earlier, files must be opened with an absolute
33
+ # path to be considered a log file.
34
+ def self.reopen_logs
35
+ to_reopen = []
36
+ nr = 0
37
+ ObjectSpace.each_object(File) { |fp| is_log?(fp) and to_reopen << fp }
38
+
39
+ to_reopen.each do |fp|
40
+ orig_st = begin
41
+ fp.stat
42
+ rescue IOError, Errno::EBADF
43
+ next
44
+ end
45
+
46
+ begin
47
+ b = File.stat(fp.path)
48
+ next if orig_st.ino == b.ino && orig_st.dev == b.dev
49
+ rescue Errno::ENOENT
50
+ end
51
+
52
+ begin
53
+ File.open(fp.path, 'a') { |tmpfp| fp.reopen(tmpfp) }
54
+ fp.sync = true
55
+ new_st = fp.stat
56
+
57
+ # this should only happen in the master:
58
+ if orig_st.uid != new_st.uid || orig_st.gid != new_st.gid
59
+ fp.chown(orig_st.uid, orig_st.gid)
60
+ end
61
+
62
+ nr += 1
63
+ rescue IOError, Errno::EBADF
64
+ # not much we can do...
65
+ end
66
+ end
67
+ nr
68
+ end
69
+ end
@@ -0,0 +1,88 @@
1
+ # -*- encoding: binary -*-
2
+ require "raindrops"
3
+
4
+ # This class and its members can be considered a stable interface
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.
8
+ #
9
+ # Some users may want to access it in the before_fork/after_fork hooks.
10
+ # See the Unicorn::Configurator RDoc for examples.
11
+ class Unicorn::Worker
12
+ # :stopdoc:
13
+ attr_accessor :nr, :switched
14
+ attr_writer :tmp
15
+
16
+ PER_DROP = Raindrops::PAGE_SIZE / Raindrops::SIZE
17
+ DROPS = []
18
+
19
+ def initialize(nr)
20
+ drop_index = nr / PER_DROP
21
+ @raindrop = DROPS[drop_index] ||= Raindrops.new(PER_DROP)
22
+ @offset = nr % PER_DROP
23
+ @raindrop[@offset] = 0
24
+ @nr = nr
25
+ @tmp = @switched = false
26
+ end
27
+
28
+ # worker objects may be compared to just plain Integers
29
+ def ==(other_nr) # :nodoc:
30
+ @nr == other_nr
31
+ end
32
+
33
+ # called in the worker process
34
+ def tick=(value) # :nodoc:
35
+ @raindrop[@offset] = value
36
+ end
37
+
38
+ # called in the master process
39
+ def tick # :nodoc:
40
+ @raindrop[@offset]
41
+ end
42
+
43
+ # only exists for compatibility
44
+ def tmp # :nodoc:
45
+ @tmp ||= begin
46
+ tmp = Unicorn::TmpIO.new
47
+ tmp.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
48
+ tmp
49
+ end
50
+ end
51
+
52
+ def close # :nodoc:
53
+ @tmp.close if @tmp
54
+ end
55
+
56
+ # :startdoc:
57
+
58
+ # In most cases, you should be using the Unicorn::Configurator#user
59
+ # directive instead. This method should only be used if you need
60
+ # fine-grained control of exactly when you want to change permissions
61
+ # in your after_fork hooks.
62
+ #
63
+ # Changes the worker process to the specified +user+ and +group+
64
+ # This is only intended to be called from within the worker
65
+ # process from the +after_fork+ hook. This should be called in
66
+ # the +after_fork+ hook after any privileged functions need to be
67
+ # run (e.g. to set per-worker CPU affinity, niceness, etc)
68
+ #
69
+ # Any and all errors raised within this method will be propagated
70
+ # directly back to the caller (usually the +after_fork+ hook.
71
+ # These errors commonly include ArgumentError for specifying an
72
+ # invalid user/group and Errno::EPERM for insufficient privileges
73
+ def user(user, group = nil)
74
+ # we do not protect the caller, checking Process.euid == 0 is
75
+ # insufficient because modern systems have fine-grained
76
+ # capabilities. Let the caller handle any and all errors.
77
+ uid = Etc.getpwnam(user).uid
78
+ gid = Etc.getgrnam(group).gid if group
79
+ Unicorn::Util.chown_logs(uid, gid)
80
+ @tmp.chown(uid, gid) if @tmp
81
+ if gid && Process.egid != gid
82
+ Process.initgroups(user, gid)
83
+ Process::GID.change_privilege(gid)
84
+ end
85
+ Process.euid != uid and Process::UID.change_privilege(uid)
86
+ @switched = true
87
+ end
88
+ end