1
# Copyright 2008 Amazon.com, Inc. or its affiliates. All Rights
2
# Reserved. Licensed under the Amazon Software License (the
3
# "License"). You may not use this file except in compliance with the
4
# License. A copy of the License is located at
5
# http://aws.amazon.com/asl or in the "license" file accompanying this
6
# file. This file is distributed on an "AS IS" BASIS, WITHOUT
7
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
8
# the License for the specific language governing permissions and
9
# limitations under the License.
11
# ------------------------------------------------------------------------
13
# Version: 4-0.9.6 <http://codeforpeople.com/lib/ruby/open4/open4-0.9.6.tgz>
14
# Author: Ara T. Howard <ara[dot]t[dot]howard[at]noaa[dot]gov>
15
# License: Ruby License <http://www.ruby-lang.org/en/LICENSE.txt>
16
# ------------------------------------------------------------------------
17
# vim: ts=2:sw=2:sts=2:et:fdm=marker
25
def self.version() VERSION end
27
class Error < ::StandardError; end
31
pw, pr, pe, ps = IO.pipe, IO.pipe, IO.pipe, IO.pipe
36
ps.last.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
51
STDOUT.sync = STDERR.sync = true
57
Marshal.dump(e, ps.last)
60
ps.last.close unless (ps.last.closed?)
67
[pw.first, pr.last, pe.last, ps.last].each{|fd| fd.close}
70
e = Marshal.load ps.first
71
raise(Exception === e ? e : "unknown failure!")
72
rescue EOFError # If we get an EOF error, then the exec was successful
80
pi = [pw.last, pr.first, pe.first]
85
Process.waitpid2(cid).last
87
pi.each{|fd| fd.close unless fd.closed?}
90
[cid, pw.last, pr.first, pe.first]
95
module_function :popen4
96
module_function :open4
98
class SpawnError < Error
106
def initialize cmd, status
107
@cmd, @status = cmd, status
110
@signals['termsig'] = status.termsig
111
@signals['stopsig'] = status.stopsig
113
sigs = @signals.map{|k,v| "#{ k }:#{ v.inspect }"}.join(' ')
114
super "cmd <#{ cmd }> failed with status <#{ exitstatus.inspect }> signals <#{ sigs }>"
124
@cid, @threads, @argv, @done, @running = cid, [], [], Queue.new, false
128
def add_thread *a, &b
129
@running ? raise : (@argv << [a, b])
133
# take down process more nicely
137
return nil if @killed
138
Thread.critical = true
139
(@threads - [Thread.current]).each{|t| t.kill rescue nil}
150
@threads << Thread.new(*a) do |*a|
154
killall rescue nil if $!
155
@done.push Thread.current
166
@threads.map{|t| t.value}
170
@threads.size.times{ @done.pop }
177
Timeout.timeout(timeout){ yield }
182
def new_thread *a, &b
185
Thread.new(*a) do |*a|
188
rescue Exception => e
194
module_function :new_thread
196
def getopts opts = {}
199
keys, default, ignored = args
201
[keys].flatten.each do |key|
202
[key, key.to_s, key.to_s.intern].each do |key|
203
throw 'opt', opts[key] if opts.has_key?(key)
211
module_function :getopts
213
def relay src, dst = nil, t = nil
216
if src.respond_to? :gets
217
while buf = to(t){ src.gets }
221
elsif src.respond_to? :each
225
timer_set = lambda do |t|
226
th = new_thread{ to(t){ q.pop } }
229
timer_cancel = lambda do |t|
230
th.kill if th rescue nil
244
elsif src.respond_to? :read
245
buf = to(t){ src.read }
249
buf = to(t){ src.to_s }
255
module_function :relay
260
opts = ((argv.size > 1 and Hash === argv.last) ? argv.pop : {})
265
getopt = getopts opts
267
ignore_exit_failure = getopt[ 'ignore_exit_failure', getopt['quiet', false] ]
268
ignore_exec_failure = getopt[ 'ignore_exec_failure', !getopt['raise', true] ]
269
exitstatus = getopt[ %w( exitstatus exit_status status ) ]
270
stdin = getopt[ %w( stdin in i 0 ) << 0 ]
271
stdout = getopt[ %w( stdout out o 1 ) << 1 ]
272
stderr = getopt[ %w( stderr err e 2 ) << 2 ]
273
pid = getopt[ 'pid' ]
274
timeout = getopt[ %w( timeout spawn_timeout ) ]
275
stdin_timeout = getopt[ %w( stdin_timeout ) ]
276
stdout_timeout = getopt[ %w( stdout_timeout io_timeout ) ]
277
stderr_timeout = getopt[ %w( stderr_timeout ) ]
278
status = getopt[ %w( status ) ]
279
cwd = getopt[ %w( cwd dir ) ]
283
when TrueClass, FalseClass
284
ignore_exit_failure = true if exitstatus
287
[*(exitstatus || 0)].map{|i| Integer i}
290
stdin ||= '' if stdin_timeout
291
stdout ||= '' if stdout_timeout
292
stderr ||= '' if stderr_timeout
299
Timeout::timeout(timeout) do
300
popen4(*argv) do |c, i, o, e|
303
%w( replace pid= << push update ).each do |msg|
304
break(pid.send(msg, c)) if pid.respond_to? msg
307
te = ThreadEnsemble.new c
309
te.add_thread(i, stdin) do |i, stdin|
310
relay stdin, i, stdin_timeout
314
te.add_thread(o, stdout) do |o, stdout|
315
relay o, stdout, stdout_timeout
318
te.add_thread(e, stderr) do |o, stderr|
319
relay e, stderr, stderr_timeout
327
raise unless(not started and ignore_exec_failure)
330
raise SpawnError.new(cmd, status) unless
331
(ignore_exit_failure or (status.nil? and ignore_exec_failure) or exitstatus.include?(status.exitstatus))
336
module_function :spawn
338
def chdir cwd, &block
339
return(block.call Dir.pwd) unless cwd
340
Dir.chdir cwd, &block
342
module_function :chdir
344
def background arg, *argv
348
opts = { 'pid' => q, :pid => q }
351
argv.last.update opts
355
thread = Thread.new(arg, argv){|arg, argv| spawn arg, *argv}
356
sc = class << thread; self; end
358
define_method(:pid){ @pid ||= q.pop }
359
define_method(:spawn_status){ @spawn_status ||= value }
360
define_method(:exitstatus){ @exitstatus ||= spawn_status.exitstatus }
366
module_function :background
369
def maim pid, opts = {}
371
getopt = getopts opts
372
sigs = getopt[ 'signals', %w(SIGTERM SIGQUIT SIGKILL) ]
373
suspend = getopt[ 'suspend', 4 ]
378
Process.kill sig, pid
381
return(existed ? nil : true)
383
return true unless alive? pid
385
return true unless alive? pid
387
return(not alive?(pid))
390
module_function :maim
404
module_function :alive
405
module_function :'alive?'
409
def open4(*cmd, &b) cmd.size == 0 ? Open4 : Open4::popen4(*cmd, &b) end