engineyard-serverside 1.5.33 → 1.5.35.pre.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -8,7 +8,7 @@ else
8
8
  end
9
9
 
10
10
  $LOAD_PATH.unshift File.expand_path('vendor/thor/lib', File.dirname(__FILE__))
11
- $LOAD_PATH.unshift File.expand_path('vendor/open4/lib', File.dirname(__FILE__))
11
+ $LOAD_PATH.unshift File.expand_path('vendor/systemu/lib', File.dirname(__FILE__))
12
12
  $LOAD_PATH.unshift File.expand_path('vendor/escape/lib', File.dirname(__FILE__))
13
13
  $LOAD_PATH.unshift File.expand_path('vendor/json_pure/lib', File.dirname(__FILE__))
14
14
 
@@ -193,11 +193,10 @@ To fix this problem, commit your Gemfile.lock to your repository and redeploy.
193
193
  # create it on all the servers that will need it.
194
194
  # TODO - This logic likely fails when people change deploy keys.
195
195
  def ssh_executable
196
- path = ssh_wrapper_path
197
196
  roles :app_master, :app, :solo, :util do
198
197
  run(generate_ssh_wrapper)
199
198
  end
200
- path
199
+ ssh_wrapper_path
201
200
  end
202
201
 
203
202
  # We specify 'IdentitiesOnly' to avoid failures on systems with > 5 private keys available.
@@ -206,16 +205,12 @@ To fix this problem, commit your Gemfile.lock to your repository and redeploy.
206
205
  # Learned this at http://lists.mindrot.org/pipermail/openssh-unix-dev/2009-February/027271.html
207
206
  # (Thanks Jim L.)
208
207
  def generate_ssh_wrapper
209
- path = ssh_wrapper_path
210
208
  identity_file = "~/.ssh/#{c.app}-deploy-key"
211
- <<-WRAP
212
- [[ -x #{path} ]] || cat > #{path} <<'SSH'
213
- #!/bin/sh
214
- unset SSH_AUTH_SOCK
215
- ssh -o 'CheckHostIP no' -o 'StrictHostKeyChecking no' -o 'PasswordAuthentication no' -o 'LogLevel DEBUG' -o 'IdentityFile #{identity_file}' -o 'IdentitiesOnly yes' -o 'UserKnownHostsFile /dev/null' $*
216
- SSH
217
- chmod 0700 #{path}
218
- WRAP
209
+ %{ echo "#{wrapper_for(identity_file)}" > #{ssh_wrapper_path} && chmod 0700 #{ssh_wrapper_path} }
210
+ end
211
+
212
+ def wrapper_for(identity)
213
+ "#!/bin/sh\nunset SSH_AUTH_SOCK; ssh -o CheckHostIP=no -o StrictHostKeyChecking=no -o PasswordAuthentication=no -o LogLevel=DEBUG -o IdentityFile=#{identity} -o IdentitiesOnly=yes -o UserKnownHostsFile=/dev/null $*"
219
214
  end
220
215
 
221
216
  def ssh_wrapper_path
@@ -387,7 +382,7 @@ WRAP
387
382
  end
388
383
 
389
384
  def callback(what)
390
- @callbacks_reached ||= true
385
+ @callbacks_reached = true
391
386
  if File.exist?("#{c.release_path}/deploy/#{what}.rb")
392
387
  run Escape.shell_command(base_callback_command_for(what)) do |server, cmd|
393
388
  per_instance_args = [
@@ -1,4 +1,4 @@
1
- require 'open4'
1
+ require 'systemu'
2
2
 
3
3
  module EY
4
4
  module Serverside
@@ -62,10 +62,11 @@ module EY
62
62
  out = verbose? ? Tee.new($stdout, log) : log
63
63
  err = Tee.new($stderr, log) # we always want to see errors
64
64
 
65
+ cmd = "sh -c #{Escape.shell_command([cmd])}"
66
+ puts "running #{cmd}" if ENV['DEBUG']
65
67
  out << with_timestamp(":: running #{cmd}\n")
66
-
67
- # :quiet means don't raise an error on nonzero exit status
68
- status = Open4.spawn cmd, :stdin => '', :stdout => out, :quiet => true # leaving :stderr out for now
68
+ status = systemu cmd, 'stdout' => out, 'stderr' => err
69
+ puts "exit status= #{status.exitstatus}" if ENV['DEBUG']
69
70
  status.exitstatus == 0
70
71
  end
71
72
  end
@@ -83,12 +83,12 @@ module EY
83
83
  if local?
84
84
  logged_system(command)
85
85
  else
86
- logged_system(ssh_command + " " + Escape.shell_command(["#{user}@#{hostname}", command]))
86
+ logged_system("#{ssh_command} #{user}@#{hostname} #{command}")
87
87
  end
88
88
  end
89
89
 
90
90
  def ssh_command
91
- "ssh -i #{ENV['HOME']}/.ssh/internal -o StrictHostKeyChecking=no -o PasswordAuthentication=no"
91
+ "ssh -i #{ENV['HOME']}/.ssh/internal -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o PasswordAuthentication=no"
92
92
  end
93
93
 
94
94
  end
@@ -50,11 +50,11 @@ module EY
50
50
 
51
51
  private
52
52
 
53
- def run_on_roles(cmd, wrapper=%w[sh -l -c], &block)
53
+ def run_on_roles(cmd, &block)
54
54
  servers = EY::Serverside::Server.from_roles(@roles)
55
55
  futures = EY::Serverside::Future.call(servers, block_given?) do |server, exec_block|
56
56
  to_run = exec_block ? block.call(server, cmd.dup) : cmd
57
- server.run(Escape.shell_command(wrapper + [to_run]))
57
+ server.run(to_run)
58
58
  end
59
59
 
60
60
  unless EY::Serverside::Future.success?(futures)
@@ -1,5 +1,5 @@
1
1
  module EY
2
2
  module Serverside
3
- VERSION = '1.5.33'
3
+ VERSION = '1.5.35.pre.1'
4
4
  end
5
5
  end
@@ -0,0 +1,3 @@
1
+ same as Ruby's
2
+
3
+ http://www.ruby-lang.org/en/LICENSE.txt
@@ -0,0 +1,363 @@
1
+ # encoding: utf-8
2
+
3
+ require 'tmpdir'
4
+ require 'socket'
5
+ require 'fileutils'
6
+ require 'rbconfig'
7
+ require 'thread'
8
+
9
+ class Object
10
+ def systemu(*a, &b) SystemUniversal.new(*a, &b).systemu end
11
+ end
12
+
13
+ class SystemUniversal
14
+ #
15
+ # constants
16
+ #
17
+ SystemUniversal::VERSION = '2.5.0' unless SystemUniversal.send(:const_defined?, :VERSION)
18
+ def SystemUniversal.version() SystemUniversal::VERSION end
19
+ def version() SystemUniversal::VERSION end
20
+ #
21
+ # class methods
22
+ #
23
+
24
+ @host = Socket.gethostname
25
+ @ppid = Process.ppid
26
+ @pid = Process.pid
27
+ @turd = ENV['SYSTEMU_TURD']
28
+
29
+ c = begin; ::RbConfig::CONFIG; rescue NameError; ::Config::CONFIG; end
30
+ ruby = File.join(c['bindir'], c['ruby_install_name']) << c['EXEEXT']
31
+ @ruby = if system('%s -e 42' % ruby)
32
+ ruby
33
+ else
34
+ system('%s -e 42' % 'ruby') ? 'ruby' : warn('no ruby in PATH/CONFIG')
35
+ end
36
+
37
+ class << SystemUniversal
38
+ %w( host ppid pid ruby turd ).each{|a| attr_accessor a}
39
+
40
+ def quote(*words)
41
+ words.map{|word| word.inspect}.join(' ')
42
+ end
43
+ end
44
+
45
+ #
46
+ # instance methods
47
+ #
48
+
49
+ def initialize argv, opts = {}, &block
50
+ getopt = getopts opts
51
+
52
+ @argv = argv
53
+ @block = block
54
+
55
+ @stdin = getopt[ ['stdin', 'in', '0', 0] ]
56
+ @stdout = getopt[ ['stdout', 'out', '1', 1] ]
57
+ @stderr = getopt[ ['stderr', 'err', '2', 2] ]
58
+ @env = getopt[ 'env' ]
59
+ @cwd = getopt[ 'cwd' ]
60
+
61
+ @host = getopt[ 'host', self.class.host ]
62
+ @ppid = getopt[ 'ppid', self.class.ppid ]
63
+ @pid = getopt[ 'pid', self.class.pid ]
64
+ @ruby = getopt[ 'ruby', self.class.ruby ]
65
+ end
66
+
67
+ def systemu
68
+ tmpdir do |tmp|
69
+ c = child_setup tmp
70
+ status = nil
71
+
72
+ begin
73
+ thread = nil
74
+
75
+ quietly{
76
+ IO.popen "#{ quote(@ruby) } #{ quote(c['program']) }", 'r+' do |pipe|
77
+ line = pipe.gets
78
+ case line
79
+ when %r/^pid: \d+$/
80
+ cid = Integer line[%r/\d+/]
81
+ else
82
+ begin
83
+ buf = pipe.read
84
+ buf = "#{ line }#{ buf }"
85
+ e = Marshal.load buf
86
+ raise unless Exception === e
87
+ raise e
88
+ rescue
89
+ raise "wtf?\n#{ buf }\n"
90
+ end
91
+ end
92
+ thread = new_thread cid, @block if @block
93
+ pipe.read rescue nil
94
+ end
95
+ }
96
+ status = $?
97
+ ensure
98
+ if thread
99
+ begin
100
+ class << status
101
+ attr 'thread'
102
+ end
103
+ status.instance_eval{ @thread = thread }
104
+ rescue
105
+ 42
106
+ end
107
+ end
108
+ end
109
+
110
+ if @stdout or @stderr
111
+ open(c['stdout']){|f| relay f => @stdout} if @stdout
112
+ open(c['stderr']){|f| relay f => @stderr} if @stderr
113
+ status
114
+ else
115
+ [status, IO.read(c['stdout']), IO.read(c['stderr'])]
116
+ end
117
+ end
118
+ end
119
+
120
+ def quote *args, &block
121
+ SystemUniversal.quote(*args, &block)
122
+ end
123
+
124
+ def new_thread cid, block
125
+ q = Queue.new
126
+ Thread.new(cid) do |cid|
127
+ current = Thread.current
128
+ current.abort_on_exception = true
129
+ q.push current
130
+ block.call cid
131
+ end
132
+ q.pop
133
+ end
134
+
135
+ def child_setup tmp
136
+ stdin = File.expand_path(File.join(tmp, 'stdin'))
137
+ stdout = File.expand_path(File.join(tmp, 'stdout'))
138
+ stderr = File.expand_path(File.join(tmp, 'stderr'))
139
+ program = File.expand_path(File.join(tmp, 'program'))
140
+ config = File.expand_path(File.join(tmp, 'config'))
141
+
142
+ if @stdin
143
+ open(stdin, 'w'){|f| relay @stdin => f}
144
+ else
145
+ FileUtils.touch stdin
146
+ end
147
+ FileUtils.touch stdout
148
+ FileUtils.touch stderr
149
+
150
+ c = {}
151
+ c['argv'] = @argv
152
+ c['env'] = @env
153
+ c['cwd'] = @cwd
154
+ c['stdin'] = stdin
155
+ c['stdout'] = stdout
156
+ c['stderr'] = stderr
157
+ c['program'] = program
158
+ open(config, 'w'){|f| Marshal.dump(c, f)}
159
+
160
+ open(program, 'w'){|f| f.write child_program(config)}
161
+
162
+ c
163
+ end
164
+
165
+ def quietly
166
+ v = $VERBOSE
167
+ $VERBOSE = nil
168
+ yield
169
+ ensure
170
+ $VERBOSE = v
171
+ end
172
+
173
+ def child_program config
174
+ <<-program
175
+ # encoding: utf-8
176
+
177
+ PIPE = STDOUT.dup
178
+ begin
179
+ config = Marshal.load(IO.read('#{ config }'))
180
+
181
+ argv = config['argv']
182
+ env = config['env']
183
+ cwd = config['cwd']
184
+ stdin = config['stdin']
185
+ stdout = config['stdout']
186
+ stderr = config['stderr']
187
+
188
+ Dir.chdir cwd if cwd
189
+ env.each{|k,v| ENV[k.to_s] = v.to_s} if env
190
+
191
+ STDIN.reopen stdin
192
+ STDOUT.reopen stdout
193
+ STDERR.reopen stderr
194
+
195
+ PIPE.puts "pid: \#{ Process.pid }"
196
+ PIPE.flush ### the process is ready yo!
197
+ PIPE.close
198
+ if RUBY_VERSION >= "1.9"
199
+ exec *argv
200
+ else
201
+ exec argv
202
+ end
203
+ rescue Exception => e
204
+ PIPE.write Marshal.dump(e) rescue nil
205
+ exit 42
206
+ end
207
+ program
208
+ end
209
+
210
+ def relay srcdst
211
+ src, dst, ignored = srcdst.to_a.first
212
+ if src.respond_to? 'read'
213
+ while((buf = src.read(8192))); dst << buf; end
214
+ else
215
+ if src.respond_to?(:each_line)
216
+ src.each_line{|buf| dst << buf}
217
+ else
218
+ src.each{|buf| dst << buf}
219
+ end
220
+ end
221
+ end
222
+
223
+ def tmpdir d = Dir.tmpdir, max = 42, &b
224
+ i = -1 and loop{
225
+ i += 1
226
+
227
+ tmp = File.join d, "systemu_#{ @host }_#{ @ppid }_#{ @pid }_#{ rand }_#{ i += 1 }"
228
+
229
+ begin
230
+ Dir.mkdir tmp
231
+ rescue Errno::EEXIST
232
+ raise if i >= max
233
+ next
234
+ end
235
+
236
+ break(
237
+ if b
238
+ begin
239
+ b.call tmp
240
+ ensure
241
+ FileUtils.rm_rf tmp unless SystemU.turd
242
+ end
243
+ else
244
+ tmp
245
+ end
246
+ )
247
+ }
248
+ end
249
+
250
+ def getopts opts = {}
251
+ lambda do |*args|
252
+ keys, default, ignored = args
253
+ catch(:opt) do
254
+ [keys].flatten.each do |key|
255
+ [key, key.to_s, key.to_s.intern].each do |key|
256
+ throw :opt, opts[key] if opts.has_key?(key)
257
+ end
258
+ end
259
+ default
260
+ end
261
+ end
262
+ end
263
+ end
264
+
265
+ # some monkeypatching for JRuby
266
+ if defined? JRUBY_VERSION
267
+ require 'jruby'
268
+ java_import org.jruby.RubyProcess
269
+
270
+ class SystemUniversal
271
+ def systemu
272
+ split_argv = JRuby::PathHelper.smart_split_command @argv
273
+ process = java.lang.Runtime.runtime.exec split_argv.to_java(:string)
274
+
275
+ stdout, stderr = [process.input_stream, process.error_stream].map do |stream|
276
+ StreamReader.new(stream)
277
+ end
278
+
279
+ exit_code = process.wait_for
280
+ field = process.get_class.get_declared_field("pid")
281
+ field.set_accessible(true)
282
+ pid = field.get(process)
283
+ [
284
+ RubyProcess::RubyStatus.new_process_status(JRuby.runtime, exit_code, pid),
285
+ stdout.join,
286
+ stderr.join
287
+ ]
288
+ end
289
+
290
+ class StreamReader
291
+ def initialize(stream)
292
+ @data = ""
293
+ @thread = Thread.new do
294
+ reader = java.io.BufferedReader.new java.io.InputStreamReader.new(stream)
295
+
296
+ while line = reader.read_line
297
+ @data << line << "\n"
298
+ end
299
+ end
300
+ end
301
+
302
+ def join
303
+ @thread.join
304
+ @data
305
+ end
306
+ end
307
+ end
308
+ end
309
+
310
+
311
+
312
+ SystemU = SystemUniversal unless defined? SystemU
313
+ Systemu = SystemUniversal unless defined? Systemu
314
+
315
+
316
+
317
+
318
+
319
+
320
+
321
+
322
+
323
+
324
+
325
+
326
+
327
+ if $0 == __FILE__
328
+ #
329
+ # date
330
+ #
331
+ date = %q( ruby -e" t = Time.now; STDOUT.puts t; STDERR.puts t " )
332
+
333
+ status, stdout, stderr = systemu date
334
+ p [status, stdout, stderr]
335
+
336
+ status = systemu date, 1=>(stdout = '')
337
+ p [status, stdout]
338
+
339
+ status = systemu date, 2=>(stderr = '')
340
+ p [status, stderr]
341
+ #
342
+ # sleep
343
+ #
344
+ sleep = %q( ruby -e" p(sleep(1)) " )
345
+ status, stdout, stderr = systemu sleep
346
+ p [status, stdout, stderr]
347
+
348
+ sleep = %q( ruby -e" p(sleep(42)) " )
349
+ status, stdout, stderr = systemu(sleep){|cid| Process.kill 9, cid}
350
+ p [status, stdout, stderr]
351
+ #
352
+ # env
353
+ #
354
+ env = %q( ruby -e" p ENV['A'] " )
355
+ status, stdout, stderr = systemu env, :env => {'A' => 42}
356
+ p [status, stdout, stderr]
357
+ #
358
+ # cwd
359
+ #
360
+ env = %q( ruby -e" p Dir.pwd " )
361
+ status, stdout, stderr = systemu env, :cwd => Dir.tmpdir
362
+ p [status, stdout, stderr]
363
+ end
@@ -0,0 +1,45 @@
1
+ ## systemu.gemspec
2
+ #
3
+
4
+ Gem::Specification::new do |spec|
5
+ spec.name = "systemu"
6
+ spec.version = "2.5.0"
7
+ spec.platform = Gem::Platform::RUBY
8
+ spec.summary = "systemu"
9
+ spec.description = "description: systemu kicks the ass"
10
+
11
+ spec.files =
12
+ ["LICENSE",
13
+ "README",
14
+ "README.erb",
15
+ "Rakefile",
16
+ "lib",
17
+ "lib/systemu.rb",
18
+ "samples",
19
+ "samples/a.rb",
20
+ "samples/b.rb",
21
+ "samples/c.rb",
22
+ "samples/d.rb",
23
+ "samples/e.rb",
24
+ "samples/f.rb",
25
+ "systemu.gemspec",
26
+ "test",
27
+ "test/systemu_test.rb",
28
+ "test/testing.rb"]
29
+
30
+ spec.executables = []
31
+
32
+ spec.require_path = "lib"
33
+
34
+ spec.test_files = nil
35
+
36
+ ### spec.add_dependency 'lib', '>= version'
37
+ #### spec.add_dependency 'map'
38
+
39
+ spec.extensions.push(*[])
40
+
41
+ spec.rubyforge_project = "codeforpeople"
42
+ spec.author = "Ara T. Howard"
43
+ spec.email = "ara.t.howard@gmail.com"
44
+ spec.homepage = "https://github.com/ahoward/systemu"
45
+ end
data/spec/spec_helper.rb CHANGED
@@ -36,19 +36,6 @@ module EY
36
36
  old_info(*args)
37
37
  end
38
38
  end
39
-
40
- def logged_system(cmd)
41
- output = `#{cmd} 2>&1`
42
- successful = ($? == 0)
43
- if ENV['VERBOSE']
44
- if successful
45
- $stdout.puts "#{cmd}\n#{output.strip}".chomp
46
- else
47
- $stderr.puts "\nCommand `#{cmd}` exited with status #{$?.exitstatus}: '#{output.strip}'"
48
- end
49
- end
50
- successful
51
- end
52
39
  end
53
40
 
54
41
  class Strategies::Git
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: engineyard-serverside
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.5.33
5
- prerelease:
4
+ version: 1.5.35.pre.1
5
+ prerelease: 7
6
6
  platform: ruby
7
7
  authors:
8
8
  - EY Cloud Team
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-03-19 00:00:00.000000000 Z
12
+ date: 2012-03-23 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rspec
@@ -279,7 +279,9 @@ files:
279
279
  - lib/vendor/json_pure/tools/fuzz.rb
280
280
  - lib/vendor/json_pure/tools/server.rb
281
281
  - lib/vendor/json_pure/VERSION
282
- - lib/vendor/open4/lib/open4.rb
282
+ - lib/vendor/systemu/lib/systemu.rb
283
+ - lib/vendor/systemu/LICENSE
284
+ - lib/vendor/systemu/systemu.gemspec
283
285
  - lib/vendor/thor/bin/rake2thor
284
286
  - lib/vendor/thor/bin/thor
285
287
  - lib/vendor/thor/CHANGELOG.rdoc
@@ -321,7 +323,6 @@ files:
321
323
  - spec/deploy_hook_spec.rb
322
324
  - spec/deprecation_spec.rb
323
325
  - spec/fixtures/gemfiles/1.0.21-rails-31-with-sqlite
324
- - spec/fixtures/gitrepo/bar
325
326
  - spec/fixtures/gitrepo/foo
326
327
  - spec/fixtures/gitrepo.tar.gz
327
328
  - spec/fixtures/invalid_hook.rb
@@ -363,9 +364,6 @@ required_ruby_version: !ruby/object:Gem::Requirement
363
364
  - - ! '>='
364
365
  - !ruby/object:Gem::Version
365
366
  version: '0'
366
- segments:
367
- - 0
368
- hash: -3202821505931209678
369
367
  required_rubygems_version: !ruby/object:Gem::Requirement
370
368
  none: false
371
369
  requirements:
@@ -385,7 +383,6 @@ test_files:
385
383
  - spec/deploy_hook_spec.rb
386
384
  - spec/deprecation_spec.rb
387
385
  - spec/fixtures/gemfiles/1.0.21-rails-31-with-sqlite
388
- - spec/fixtures/gitrepo/bar
389
386
  - spec/fixtures/gitrepo/foo
390
387
  - spec/fixtures/gitrepo.tar.gz
391
388
  - spec/fixtures/invalid_hook.rb
@@ -1,403 +0,0 @@
1
- # vim: ts=2:sw=2:sts=2:et:fdm=marker
2
- require 'fcntl'
3
- require 'timeout'
4
- require 'thread'
5
-
6
- module Open4
7
- #--{{{
8
- VERSION = '1.1.0'
9
- def self.version() VERSION end
10
-
11
- class Error < ::StandardError; end
12
-
13
- def popen4(*cmd, &b)
14
- #--{{{
15
- pw, pr, pe, ps = IO.pipe, IO.pipe, IO.pipe, IO.pipe
16
-
17
- verbose = $VERBOSE
18
- begin
19
- $VERBOSE = nil
20
- ps.first.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
21
- ps.last.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
22
-
23
- cid = fork {
24
- pw.last.close
25
- STDIN.reopen pw.first
26
- pw.first.close
27
-
28
- pr.first.close
29
- STDOUT.reopen pr.last
30
- pr.last.close
31
-
32
- pe.first.close
33
- STDERR.reopen pe.last
34
- pe.last.close
35
-
36
- STDOUT.sync = STDERR.sync = true
37
-
38
- begin
39
- exec(*cmd)
40
- raise 'forty-two'
41
- rescue Exception => e
42
- Marshal.dump(e, ps.last)
43
- ps.last.flush
44
- end
45
- ps.last.close unless (ps.last.closed?)
46
- exit!
47
- }
48
- ensure
49
- $VERBOSE = verbose
50
- end
51
-
52
- [pw.first, pr.last, pe.last, ps.last].each{|fd| fd.close}
53
-
54
- begin
55
- e = Marshal.load ps.first
56
- raise(Exception === e ? e : "unknown failure!")
57
- rescue EOFError # If we get an EOF error, then the exec was successful
58
- 42
59
- ensure
60
- ps.first.close
61
- end
62
-
63
- pw.last.sync = true
64
-
65
- pi = [pw.last, pr.first, pe.first]
66
-
67
- if b
68
- begin
69
- b[cid, *pi]
70
- Process.waitpid2(cid).last
71
- ensure
72
- pi.each{|fd| fd.close unless fd.closed?}
73
- end
74
- else
75
- [cid, pw.last, pr.first, pe.first]
76
- end
77
- #--}}}
78
- end
79
- alias open4 popen4
80
- module_function :popen4
81
- module_function :open4
82
-
83
- class SpawnError < Error
84
- #--{{{
85
- attr 'cmd'
86
- attr 'status'
87
- attr 'signals'
88
- def exitstatus
89
- @status.exitstatus
90
- end
91
- def initialize cmd, status
92
- @cmd, @status = cmd, status
93
- @signals = {}
94
- if status.signaled?
95
- @signals['termsig'] = status.termsig
96
- @signals['stopsig'] = status.stopsig
97
- end
98
- sigs = @signals.map{|k,v| "#{ k }:#{ v.inspect }"}.join(' ')
99
- super "cmd <#{ cmd }> failed with status <#{ exitstatus.inspect }> signals <#{ sigs }>"
100
- end
101
- #--}}}
102
- end
103
-
104
- class ThreadEnsemble
105
- #--{{{
106
- attr 'threads'
107
-
108
- def initialize cid
109
- @cid, @threads, @argv, @done, @running = cid, [], [], Queue.new, false
110
- @killed = false
111
- end
112
-
113
- def add_thread *a, &b
114
- @running ? raise : (@argv << [a, b])
115
- end
116
-
117
- #
118
- # take down process more nicely
119
- #
120
- def killall
121
- c = Thread.critical
122
- return nil if @killed
123
- Thread.critical = true
124
- (@threads - [Thread.current]).each{|t| t.kill rescue nil}
125
- @killed = true
126
- ensure
127
- Thread.critical = c
128
- end
129
-
130
- def run
131
- @running = true
132
-
133
- begin
134
- @argv.each do |a, b|
135
- @threads << Thread.new(*a) do |*a|
136
- begin
137
- b[*a]
138
- ensure
139
- killall rescue nil if $!
140
- @done.push Thread.current
141
- end
142
- end
143
- end
144
- rescue
145
- killall
146
- raise
147
- ensure
148
- all_done
149
- end
150
-
151
- @threads.map{|t| t.value}
152
- end
153
-
154
- def all_done
155
- @threads.size.times{ @done.pop }
156
- end
157
- #--}}}
158
- end
159
-
160
- def to timeout = nil
161
- #--{{{
162
- Timeout.timeout(timeout){ yield }
163
- #--}}}
164
- end
165
- module_function :to
166
-
167
- def new_thread *a, &b
168
- #--{{{
169
- cur = Thread.current
170
- Thread.new(*a) do |*a|
171
- begin
172
- b[*a]
173
- rescue Exception => e
174
- cur.raise e
175
- end
176
- end
177
- #--}}}
178
- end
179
- module_function :new_thread
180
-
181
- def getopts opts = {}
182
- #--{{{
183
- lambda do |*args|
184
- keys, default, ignored = args
185
- catch('opt') do
186
- [keys].flatten.each do |key|
187
- [key, key.to_s, key.to_s.intern].each do |key|
188
- throw 'opt', opts[key] if opts.has_key?(key)
189
- end
190
- end
191
- default
192
- end
193
- end
194
- #--}}}
195
- end
196
- module_function :getopts
197
-
198
- def relay src, dst = nil, t = nil
199
- #--{{{
200
- send_dst =
201
- if dst.respond_to?(:call)
202
- lambda{|buf| dst.call(buf)}
203
- elsif dst.respond_to?(:<<)
204
- lambda{|buf| dst << buf }
205
- else
206
- lambda{|buf| buf }
207
- end
208
-
209
- unless src.nil?
210
- if src.respond_to? :gets
211
- while buf = to(t){ src.gets }
212
- send_dst[buf]
213
- end
214
-
215
- elsif src.respond_to? :each
216
- q = Queue.new
217
- th = nil
218
-
219
- timer_set = lambda do |t|
220
- th = new_thread{ to(t){ q.pop } }
221
- end
222
-
223
- timer_cancel = lambda do |t|
224
- th.kill if th rescue nil
225
- end
226
-
227
- timer_set[t]
228
- begin
229
- src.each do |buf|
230
- timer_cancel[t]
231
- send_dst[buf]
232
- timer_set[t]
233
- end
234
- ensure
235
- timer_cancel[t]
236
- end
237
-
238
- elsif src.respond_to? :read
239
- buf = to(t){ src.read }
240
- send_dst[buf]
241
-
242
- else
243
- buf = to(t){ src.to_s }
244
- send_dst[buf]
245
- end
246
- end
247
- #--}}}
248
- end
249
- module_function :relay
250
-
251
- def spawn arg, *argv
252
- #--{{{
253
- argv.unshift(arg)
254
- opts = ((argv.size > 1 and Hash === argv.last) ? argv.pop : {})
255
- argv.flatten!
256
- cmd = argv.join(' ')
257
-
258
-
259
- getopt = getopts opts
260
-
261
- ignore_exit_failure = getopt[ 'ignore_exit_failure', getopt['quiet', false] ]
262
- ignore_exec_failure = getopt[ 'ignore_exec_failure', !getopt['raise', true] ]
263
- exitstatus = getopt[ %w( exitstatus exit_status status ) ]
264
- stdin = getopt[ %w( stdin in i 0 ) << 0 ]
265
- stdout = getopt[ %w( stdout out o 1 ) << 1 ]
266
- stderr = getopt[ %w( stderr err e 2 ) << 2 ]
267
- pid = getopt[ 'pid' ]
268
- timeout = getopt[ %w( timeout spawn_timeout ) ]
269
- stdin_timeout = getopt[ %w( stdin_timeout ) ]
270
- stdout_timeout = getopt[ %w( stdout_timeout io_timeout ) ]
271
- stderr_timeout = getopt[ %w( stderr_timeout ) ]
272
- status = getopt[ %w( status ) ]
273
- cwd = getopt[ %w( cwd dir ) ]
274
-
275
- exitstatus =
276
- case exitstatus
277
- when TrueClass, FalseClass
278
- ignore_exit_failure = true if exitstatus
279
- [0]
280
- else
281
- [*(exitstatus || 0)].map{|i| Integer i}
282
- end
283
-
284
- stdin ||= '' if stdin_timeout
285
- stdout ||= '' if stdout_timeout
286
- stderr ||= '' if stderr_timeout
287
-
288
- started = false
289
-
290
- status =
291
- begin
292
- chdir(cwd) do
293
- Timeout::timeout(timeout) do
294
- popen4(*argv) do |c, i, o, e|
295
- started = true
296
-
297
- %w( replace pid= << push update ).each do |msg|
298
- break(pid.send(msg, c)) if pid.respond_to? msg
299
- end
300
-
301
- te = ThreadEnsemble.new c
302
-
303
- te.add_thread(i, stdin) do |i, stdin|
304
- relay stdin, i, stdin_timeout
305
- i.close rescue nil
306
- end
307
-
308
- te.add_thread(o, stdout) do |o, stdout|
309
- relay o, stdout, stdout_timeout
310
- end
311
-
312
- te.add_thread(e, stderr) do |o, stderr|
313
- relay e, stderr, stderr_timeout
314
- end
315
-
316
- te.run
317
- end
318
- end
319
- end
320
- rescue
321
- raise unless(not started and ignore_exec_failure)
322
- end
323
-
324
- raise SpawnError.new(cmd, status) unless
325
- (ignore_exit_failure or (status.nil? and ignore_exec_failure) or exitstatus.include?(status.exitstatus))
326
-
327
- status
328
- #--}}}
329
- end
330
- module_function :spawn
331
-
332
- def chdir cwd, &block
333
- return(block.call Dir.pwd) unless cwd
334
- Dir.chdir cwd, &block
335
- end
336
- module_function :chdir
337
-
338
- def background arg, *argv
339
- #--{{{
340
- require 'thread'
341
- q = Queue.new
342
- opts = { 'pid' => q, :pid => q }
343
- case argv.last
344
- when Hash
345
- argv.last.update opts
346
- else
347
- argv.push opts
348
- end
349
- thread = Thread.new(arg, argv){|arg, argv| spawn arg, *argv}
350
- sc = class << thread; self; end
351
- sc.module_eval {
352
- define_method(:pid){ @pid ||= q.pop }
353
- define_method(:spawn_status){ @spawn_status ||= value }
354
- define_method(:exitstatus){ @exitstatus ||= spawn_status.exitstatus }
355
- }
356
- thread
357
- #--}}}
358
- end
359
- alias bg background
360
- module_function :background
361
- module_function :bg
362
-
363
- def maim pid, opts = {}
364
- #--{{{
365
- getopt = getopts opts
366
- sigs = getopt[ 'signals', %w(SIGTERM SIGQUIT SIGKILL) ]
367
- suspend = getopt[ 'suspend', 4 ]
368
- pid = Integer pid
369
- existed = false
370
- sigs.each do |sig|
371
- begin
372
- Process.kill sig, pid
373
- existed = true
374
- rescue Errno::ESRCH
375
- return(existed ? nil : true)
376
- end
377
- return true unless alive? pid
378
- sleep suspend
379
- return true unless alive? pid
380
- end
381
- return(not alive?(pid))
382
- #--}}}
383
- end
384
- module_function :maim
385
-
386
- def alive pid
387
- #--{{{
388
- pid = Integer pid
389
- begin
390
- Process.kill 0, pid
391
- true
392
- rescue Errno::ESRCH
393
- false
394
- end
395
- #--}}}
396
- end
397
- alias alive? alive
398
- module_function :alive
399
- module_function :'alive?'
400
- #--}}}
401
- end
402
-
403
- def open4(*cmd, &b) cmd.size == 0 ? Open4 : Open4::popen4(*cmd, &b) end
File without changes