distribustream 0.2.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,11 +1,23 @@
1
1
  #--
2
2
  # Copyright (C) 2006-07 ClickCaster, Inc. (info@clickcaster.com)
3
- # All rights reserved. See COPYING for permissions.
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
4
16
  #
5
17
  # This source file is distributed as part of the
6
18
  # DistribuStream file transfer system.
7
19
  #
8
- # See http://distribustream.rubyforge.org/
20
+ # See http://distribustream.org/
9
21
  #++
10
22
 
11
23
  require 'rubygems'
@@ -1,18 +1,25 @@
1
1
  #--
2
2
  # Copyright (C) 2006-07 ClickCaster, Inc. (info@clickcaster.com)
3
- # All rights reserved. See COPYING for permissions.
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
4
16
  #
5
17
  # This source file is distributed as part of the
6
18
  # DistribuStream file transfer system.
7
19
  #
8
- # See http://distribustream.rubyforge.org/
20
+ # See http://distribustream.org/
9
21
  #++
10
22
 
11
- require 'rubygems'
12
- require 'mongrel'
13
- require 'uri'
14
- require 'pathname'
15
-
16
23
  require File.dirname(__FILE__) + '/../common/file_service.rb'
17
24
  require File.dirname(__FILE__) + '/file_buffer.rb'
18
25
 
@@ -23,25 +30,24 @@ module PDTP
23
30
  class FileInfo < PDTP::FileInfo
24
31
  def initialize(filename, io = nil)
25
32
  @buffer = FileBuffer.new io || open(filename, 'w')
26
- @lock = Mutex.new
27
33
  end
28
34
 
29
35
  # Write data into buffer starting at start_pos
30
36
  def write(start_pos,data)
31
- @lock.synchronize { @buffer.write start_pos, data }
37
+ @buffer.write start_pos, data
32
38
  end
33
39
 
34
40
  # Read a range of data out of buffer. Takes a ruby Range object
35
41
  def read(range)
36
42
  begin
37
- @lock.synchronize { @buffer.read range }
43
+ @buffer.read range
38
44
  rescue nil
39
45
  end
40
46
  end
41
47
 
42
48
  # Return the number of bytes currently stored
43
49
  def bytes_downloaded
44
- @lock.synchronize { @buffer.bytes_stored }
50
+ @buffer.bytes_stored
45
51
  end
46
52
  end
47
53
 
@@ -1,34 +1,33 @@
1
- # $Id: httpclient.rb 518 2007-08-30 10:17:02Z blackhedd $
2
- #
3
- # Author:: Francis Cianfrocca (gmail: blackhedd)
4
- # Homepage:: http://rubyeventmachine.com
5
- # Date:: 16 July 2006
6
- #
7
- # See EventMachine and EventMachine::Connection for documentation and
8
- # usage examples.
9
- #
10
- #----------------------------------------------------------------------------
11
- #
1
+ #--
12
2
  # Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
13
3
  # Gmail: blackhedd
4
+ # Modifications (C) 2007 ClickCaster, Inc.
5
+ #
6
+ # This program is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
14
15
  #
15
- # This program is free software; you can redistribute it and/or modify
16
- # it under the terms of either: 1) the GNU General Public License
17
- # as published by the Free Software Foundation; either version 2 of the
18
- # License, or (at your option) any later version; or 2) Ruby's License.
16
+ # You should have received a copy of the GNU General Public License
17
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
19
18
  #
20
- # See the file COPYING for complete licensing information.
19
+ # This source file is distributed as part of the
20
+ # DistribuStream file transfer system.
21
21
  #
22
- #---------------------------------------------------------------------------
22
+ # See http://distribustream.org/
23
23
  #
24
- #
25
-
26
24
  # This version of HttpClient has been modified for use in DistribuStream
27
25
  # Notable changes:
28
26
  # - Moved into PDTP::Client namespace
29
27
  # - HTTP/1.1 switched to HTTP/1.0
30
28
  # - Introduced support for HTTP ranges
31
29
  # - Support for X headers
30
+ #++
32
31
 
33
32
  module PDTP
34
33
  class Client
@@ -1,11 +1,23 @@
1
1
  #--
2
2
  # Copyright (C) 2006-07 ClickCaster, Inc. (info@clickcaster.com)
3
- # All rights reserved. See COPYING for permissions.
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
4
16
  #
5
17
  # This source file is distributed as part of the
6
18
  # DistribuStream file transfer system.
7
19
  #
8
- # See http://distribustream.rubyforge.org/
20
+ # See http://distribustream.org/
9
21
  #++
10
22
 
11
23
  require 'rubygems'
@@ -27,12 +39,12 @@ module PDTP
27
39
  # This method is called after a connection to the server
28
40
  # has been successfully established.
29
41
  def connection_created(connection)
30
- @@log.debug("[mongrel] Opened connection...");
42
+ #@@log.debug("[mongrel] Opened connection...");
31
43
  end
32
44
 
33
45
  # This method is called when the server connection is destroyed
34
46
  def connection_destroyed(connection)
35
- @@log.debug("[mongrel] Closed connection...")
47
+ #@@log.debug("[mongrel] Closed connection...")
36
48
  end
37
49
 
38
50
  # Returns a transfer object if the given connection is a peer associated with
@@ -42,11 +54,10 @@ module PDTP
42
54
  nil
43
55
  end
44
56
 
45
- # This method is called when an HTTP request is received. It is called in
46
- # a separate thread, one for each request.
47
- def process(request,response)
57
+ # This method is called when an HTTP request is received.
58
+ def process(request,response)
48
59
  begin
49
- @@log.debug "Creating Transfer::Listener"
60
+ #@@log.debug "Creating Transfer::Listener"
50
61
  transfer = Transfer::Listener.new(
51
62
  @client.connection,
52
63
  request,
@@ -54,9 +65,8 @@ module PDTP
54
65
  client.file_service
55
66
  )
56
67
 
57
- #Needs to be locked because multiple threads could attempt to append a transfer at once
58
- @client.lock.synchronize { @client.transfers << transfer }
59
- transfer.handle_header
68
+ @client.transfers << transfer
69
+ transfer.handle_header
60
70
  rescue Exception => e
61
71
  raise e if transfer.nil?
62
72
  transfer.write_http_exception(e)
@@ -1,15 +1,25 @@
1
1
  #--
2
2
  # Copyright (C) 2006-07 ClickCaster, Inc. (info@clickcaster.com)
3
- # All rights reserved. See COPYING for permissions.
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
4
16
  #
5
17
  # This source file is distributed as part of the
6
18
  # DistribuStream file transfer system.
7
19
  #
8
- # See http://distribustream.rubyforge.org/
20
+ # See http://distribustream.org/
9
21
  #++
10
22
 
11
- require "thread"
12
- #require "net/http"
13
23
  require "uri"
14
24
  require "digest/sha2"
15
25
 
@@ -92,20 +102,16 @@ module PDTP
92
102
  out.write(e.to_s + "\n\n" + e.backtrace.join("\n") )
93
103
  end
94
104
  else
95
- @@log.info("MONGREL SERVER ERROR: exception:" + e.to_s+"\n\n"+e.backtrace.join("\n"))
105
+ STDERR.write "MONGREL SERVER ERROR: exception: " + e.to_s+"\n\n"+e.backtrace.join("\n")+"\n"
96
106
  @response.start(500) do |head,out|
97
- out.write("Server error, unknown exception:"+e.to_s + "\n\n" + e.backtrace.join("\n") )
107
+ out.write("Server error, unknown exception: "+e.to_s + "\n\n" + e.backtrace.join("\n") )
98
108
  end
99
109
  end
100
110
  end
101
111
 
102
112
  # Parse the HTTP header and ask for verification of transfer
103
- # Thread is stopped after asking for verification and will
104
- # be restarted when verification arrives
105
113
  def handle_header
106
- @thread=Thread.current
107
-
108
- @@log.debug "params=#{@request.params.inspect}"
114
+ #@@log.debug "params=#{@request.params.inspect}"
109
115
 
110
116
  @method=@request.params["REQUEST_METHOD"].downcase
111
117
  @peer=@request.params["REMOTE_ADDR"]
@@ -124,33 +130,24 @@ module PDTP
124
130
  raise HTTPException.new(400, "Missing Range header") if @byte_range.nil?
125
131
 
126
132
  send_ask_verify_message
127
- Thread.stop
128
- after_verification
133
+ @response.pending = true
129
134
  end
130
135
 
131
136
  # Called after receiving verification message from the server
132
- # Set the authorized status and restart the thread
133
- # This throws us into after_verification
134
137
  def tell_verify(authorized)
135
- @authorized=authorized
136
- @thread.run
137
- end
138
-
139
- # Perform the transfer if verification was successful
140
- def after_verification
141
138
  #check if the server authorized us
142
- unless @authorized
139
+ unless authorized
143
140
  raise HTTPException.new(403,"Forbidden: the server did not authorize this transfer")
144
141
  end
145
142
 
146
143
  info = @file_service.get_info(@url)
147
144
  if @method == "put"
148
145
  #we are the taker
149
- @@log.debug("Body Downloaded: url=#{@url} range=#{@byte_range} peer=#{@peer}")
146
+ #@@log.debug("Body Downloaded: url=#{@url} range=#{@byte_range} peer=#{@peer}")
150
147
 
151
148
  @file_service.set_info(FileInfo.new) if info.nil?
152
- info.write(@byte_range.first, @request.body.read)
153
- @hash=Digest::SHA256.hexdigest(res.body) rescue nil
149
+ info.write @byte_range.first, @request.body.read
150
+ @hash = Digest::SHA256.hexdigest(res.body) rescue nil
154
151
 
155
152
  # Stock HTTP OK response
156
153
  @response.start(200) do |head,out|
@@ -158,7 +155,7 @@ module PDTP
158
155
  elsif @method=="get"
159
156
  #we are the giver
160
157
  raise HTTPException.new(404,"File not found: #{@url}") if info.nil?
161
- data=info.read(@byte_range)
158
+ data = info.read @byte_range
162
159
  raise HTTPException.new(416,"Invalid range: #{@byte_range.inspect}") if data.nil?
163
160
 
164
161
  #Request was GET, so now we need to send the data
@@ -167,8 +164,11 @@ module PDTP
167
164
  head['Content-Range'] = "bytes #{@byte_range.first}-#{@byte_range.last}/*"
168
165
  #FIXME must include a DATE header according to http
169
166
 
170
- out.write(data)
167
+ out.write data
171
168
  end
169
+
170
+ # Call response.finished ourselves since we left the response pending
171
+ @response.finished
172
172
  else
173
173
  raise HTTPException.new(405,"Invalid method: #{@method}")
174
174
  end
@@ -220,11 +220,11 @@ module PDTP
220
220
  hash = nil
221
221
 
222
222
  if @method == 'get' and response[:status] == 206
223
- @@log.debug("Body Downloaded: url=#{@url} range=#{@byte_range} peer=#{@peer}:#{@port}")
223
+ #@@log.debug("Body Downloaded: url=#{@url} range=#{@byte_range} peer=#{@peer}:#{@port}")
224
224
  info.write(@byte_range.first,response[:content])
225
225
  hash = Digest::SHA256.hexdigest(response[:content]) rescue nil
226
226
  elsif @method == 'put' and response[:status] == 200
227
- @@log.debug("Body Uploaded: url=#{@url} range=#{@byte_range} peer=#{@peer}:#{@port}")
227
+ #@@log.debug("Body Uploaded: url=#{@url} range=#{@byte_range} peer=#{@peer}:#{@port}")
228
228
  else
229
229
  raise RuntimeError, "Invalid method/status combination: #{@method} #{response[:status]}"
230
230
  end
@@ -1,15 +1,30 @@
1
1
  #--
2
2
  # Copyright (C) 2006-07 ClickCaster, Inc. (info@clickcaster.com)
3
- # All rights reserved. See COPYING for permissions.
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
4
16
  #
5
17
  # This source file is distributed as part of the
6
18
  # DistribuStream file transfer system.
7
19
  #
8
- # See http://distribustream.rubyforge.org/
20
+ # See http://distribustream.org/
9
21
  #++
10
22
 
11
23
  # Namespace for all PDTP components
12
24
  module PDTP
13
- PDTP::VERSION = '0.2.1' unless defined? PDTP::VERSION
25
+ PDTP::VERSION = '0.3.0' unless defined? PDTP::VERSION
14
26
  def self.version() VERSION end
15
- end
27
+
28
+ PDTP::DEFAULT_PORT = 6086 unless defined? PDTP::DEFAULT_PORT
29
+ def self.default_port() DEFAULT_PORT end
30
+ end
@@ -1,11 +1,23 @@
1
1
  #--
2
2
  # Copyright (C) 2006-07 ClickCaster, Inc. (info@clickcaster.com)
3
- # All rights reserved. See COPYING for permissions.
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
4
16
  #
5
17
  # This source file is distributed as part of the
6
18
  # DistribuStream file transfer system.
7
19
  #
8
- # See http://distribustream.rubyforge.org/
20
+ # See http://distribustream.org/
9
21
  #++
10
22
 
11
23
  module PDTP
@@ -0,0 +1,217 @@
1
+ # This module rewrites pieces of the very good Mongrel web server in
2
+ # order to change it from a threaded application to an event based
3
+ # application running inside an EventMachine event loop. It should
4
+ # be compatible with the existing Mongrel handlers for Rails,
5
+ # Camping, Nitro, etc....
6
+
7
+ require 'rubygems'
8
+ require 'eventmachine'
9
+ require 'mongrel'
10
+
11
+ module Mongrel
12
+ class Protocol < EventMachine::Connection
13
+ def post_init
14
+ @parser = HttpParser.new
15
+ @params = HttpParams.new
16
+ @nparsed = 0
17
+ @request = nil
18
+ @request_len = nil
19
+ @linebuffer = ''
20
+ end
21
+
22
+ def receive_data data
23
+ @linebuffer << data
24
+ @nparsed = @parser.execute(@params, @linebuffer, @nparsed) unless @parser.finished?
25
+ if @parser.finished?
26
+ if @request_len.nil?
27
+ @request_len = @params[::Mongrel::Const::CONTENT_LENGTH].to_i
28
+ script_name, path_info, handlers = ::Mongrel::HttpServer::Instance.classifier.resolve(@params[::Mongrel::Const::REQUEST_PATH])
29
+ if handlers
30
+ @params[::Mongrel::Const::PATH_INFO] = path_info
31
+ @params[::Mongrel::Const::SCRIPT_NAME] = script_name
32
+ @params[::Mongrel::Const::REMOTE_ADDR] = @params[::Mongrel::Const::HTTP_X_FORWARDED_FOR] || ::Socket.unpack_sockaddr_in(get_peername)[1]
33
+ @notifiers = handlers.select { |h| h.request_notify }
34
+ end
35
+ if @request_len > ::Mongrel::Const::MAX_BODY
36
+ new_buffer = Tempfile.new(::Mongrel::Const::MONGREL_TMP_BASE)
37
+ new_buffer.binmode
38
+ new_buffer << @linebuffer[@nparsed..-1]
39
+ @linebuffer = new_buffer
40
+ else
41
+ @linebuffer = StringIO.new(@linebuffer[@nparsed..-1])
42
+ @linebuffer.pos = @linebuffer.length
43
+ end
44
+ end
45
+ if @linebuffer.length >= @request_len
46
+ @linebuffer.rewind
47
+ ::Mongrel::HttpServer::Instance.process_http_request(@params,@linebuffer,self)
48
+ @linebuffer.delete if Tempfile === @linebuffer
49
+ end
50
+ elsif @linebuffer.length > ::Mongrel::Const::MAX_HEADER
51
+ close_connection
52
+ raise ::Mongrel::HttpParserError.new("HEADER is longer than allowed, aborting client early.")
53
+ end
54
+ rescue ::Mongrel::HttpParserError
55
+ if $mongrel_debug_client
56
+ STDERR.puts "#{Time.now}: BAD CLIENT (#{params[Const::HTTP_X_FORWARDED_FOR] || client.peeraddr.last}): #$!"
57
+ STDERR.puts "#{Time.now}: REQUEST DATA: #{data.inspect}\n---\nPARAMS: #{params.inspect}\n---\n"
58
+ end
59
+ close_connection
60
+ rescue Exception => e
61
+ close_connection
62
+ raise e
63
+ end
64
+
65
+ def write data
66
+ send_data data
67
+ end
68
+
69
+ def closed?
70
+ false
71
+ end
72
+
73
+ end
74
+
75
+ class HttpServer
76
+ def initialize(host, port, num_processors = 950, throttle = 0, timeout = 60)
77
+ @socket = nil
78
+ @classifier = Mongrel::URIClassifier.new
79
+ @host = host
80
+ @port = port
81
+ @workers = ThreadGroup.new
82
+ @timeout = timeout
83
+ @num_processors = num_processors
84
+ @death_time = 60
85
+ self.class.const_set(:Instance,self)
86
+ end
87
+
88
+ def run
89
+ trap('INT') { raise StopServer }
90
+ trap('TERM') { raise StopServer }
91
+ @acceptor = Thread.new do
92
+ EventMachine.run do
93
+ begin
94
+ run_evented
95
+ rescue StopServer
96
+ EventMachine.stop_event_loop
97
+ end
98
+ end
99
+ end
100
+ end
101
+
102
+ def run_evented
103
+ EventMachine.start_server @host, @port, Mongrel::Protocol
104
+ end
105
+
106
+ def process_http_request(params,linebuffer,client)
107
+ if not params[Const::REQUEST_PATH]
108
+ uri = URI.parse(params[Const::REQUEST_URI])
109
+ params[Const::REQUEST_PATH] = uri.request_uri
110
+ end
111
+
112
+ raise "No REQUEST PATH" if not params[Const::REQUEST_PATH]
113
+
114
+ script_name, path_info, handlers = @classifier.resolve(params[Const::REQUEST_PATH])
115
+
116
+ if handlers
117
+ notifiers = handlers.select { |h| h.request_notify }
118
+ request = HttpRequest.new(params, linebuffer, notifiers)
119
+
120
+ # request is good so far, continue processing the response
121
+ response = HttpResponse.new(client)
122
+
123
+ # Process each handler in registered order until we run out or one finalizes the response.
124
+ dispatch_to_handlers(handlers,request,response)
125
+
126
+ # And finally, if nobody closed the response off, we finalize it.
127
+ unless response.done
128
+ response.finished unless response.pending
129
+ else
130
+ response.close_connection_after_writing
131
+ end
132
+ else
133
+ # Didn't find it, return a stock 404 response.
134
+ client.send_data(Const::ERROR_404_RESPONSE)
135
+ client.close_connection_after_writing
136
+ end
137
+ end
138
+
139
+ def dispatch_to_handlers(handlers,request,response)
140
+ handlers.each do |handler|
141
+ handler.process(request, response)
142
+ break if response.done
143
+ end
144
+ end
145
+ end
146
+
147
+ class HttpRequest
148
+ def initialize(params, linebuffer, dispatchers)
149
+ @params = params
150
+ @dispatchers = dispatchers
151
+ @body = linebuffer
152
+ end
153
+ end
154
+
155
+ class HttpResponse
156
+ # A flag to prevent response.finished from being called automatically
157
+ attr_accessor :pending
158
+
159
+ def send_file(path, small_file = false)
160
+ File.open(path, "rb") do |f|
161
+ while chunk = f.read(Const::CHUNK_SIZE) and chunk.length > 0
162
+ begin
163
+ write(chunk)
164
+ rescue Object => exc
165
+ break
166
+ end
167
+ end
168
+ end
169
+ @body_sent = true
170
+ end
171
+
172
+ def write(data)
173
+ @socket.send_data data
174
+ end
175
+
176
+ def close_connection_after_writing
177
+ @socket.close_connection_after_writing
178
+ end
179
+
180
+ def socket_error(details)
181
+ @socket.close_connection
182
+ done = true
183
+ raise details
184
+ end
185
+
186
+ def finished
187
+ send_status
188
+ send_header
189
+ send_body
190
+ @socket.close_connection_after_writing
191
+ end
192
+ end
193
+
194
+ class Configurator
195
+ # This version fixes a bug in the regular Mongrel version by adding
196
+ # initialization of groups.
197
+ def change_privilege(user, group)
198
+ if user and group
199
+ log "Initializing groups for {#user}:{#group}."
200
+ Process.initgroups(user,Etc.getgrnam(group).gid)
201
+ end
202
+
203
+ if group
204
+ log "Changing group to #{group}."
205
+ Process::GID.change_privilege(Etc.getgrnam(group).gid)
206
+ end
207
+
208
+ if user
209
+ log "Changing user to #{user}."
210
+ Process::UID.change_privilege(Etc.getpwnam(user).uid)
211
+ end
212
+ rescue Errno::EPERM
213
+ log "FAILED to change user:group #{user}:#{group}: #$!"
214
+ exit 1
215
+ end
216
+ end
217
+ end