~awstools-dev/ubuntu/maverick/ec2-ami-tools/maverick

« back to all changes in this revision

Viewing changes to lib/ec2/oem/open4.rb

  • Committer: Bazaar Package Importer
  • Author(s): Chuck Short
  • Date: 2008-10-14 08:35:25 UTC
  • Revision ID: james.westby@ubuntu.com-20081014083525-c0n69wr7r7aqfb8w
Tags: 1.3-26357-0ubuntu2
* New upstream version.
* Update the debian copyright file.
* Added quilt patch system to make it easier to maintain. 

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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.
 
10
 
 
11
# ------------------------------------------------------------------------
 
12
# Name: Open4
 
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
 
18
require 'fcntl'
 
19
require 'timeout'
 
20
require 'thread'
 
21
 
 
22
module Open4
 
23
#--{{{
 
24
  VERSION = '0.9.6'
 
25
  def self.version() VERSION end
 
26
 
 
27
  class Error < ::StandardError; end
 
28
 
 
29
  def popen4(*cmd, &b)
 
30
#--{{{
 
31
    pw, pr, pe, ps = IO.pipe, IO.pipe, IO.pipe, IO.pipe
 
32
 
 
33
    verbose = $VERBOSE
 
34
    begin
 
35
      $VERBOSE = nil
 
36
      ps.last.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
 
37
 
 
38
      cid = fork {
 
39
        pw.last.close
 
40
        STDIN.reopen pw.first
 
41
        pw.first.close
 
42
 
 
43
        pr.first.close
 
44
        STDOUT.reopen pr.last
 
45
        pr.last.close
 
46
 
 
47
        pe.first.close
 
48
        STDERR.reopen pe.last
 
49
        pe.last.close
 
50
 
 
51
        STDOUT.sync = STDERR.sync = true
 
52
 
 
53
        begin
 
54
          exec(*cmd)
 
55
          raise 'forty-two' 
 
56
        rescue Exception => e
 
57
          Marshal.dump(e, ps.last)
 
58
          ps.last.flush
 
59
        end
 
60
        ps.last.close unless (ps.last.closed?)
 
61
        exit!
 
62
      }
 
63
    ensure
 
64
      $VERBOSE = verbose
 
65
    end
 
66
 
 
67
    [pw.first, pr.last, pe.last, ps.last].each{|fd| fd.close}
 
68
 
 
69
    begin
 
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
 
73
      42
 
74
    ensure
 
75
      ps.first.close
 
76
    end
 
77
 
 
78
    pw.last.sync = true
 
79
 
 
80
    pi = [pw.last, pr.first, pe.first]
 
81
 
 
82
    if b 
 
83
      begin
 
84
        b[cid, *pi]
 
85
        Process.waitpid2(cid).last
 
86
      ensure
 
87
        pi.each{|fd| fd.close unless fd.closed?}
 
88
      end
 
89
    else
 
90
      [cid, pw.last, pr.first, pe.first]
 
91
    end
 
92
#--}}}
 
93
  end
 
94
  alias open4 popen4
 
95
  module_function :popen4
 
96
  module_function :open4
 
97
 
 
98
  class SpawnError < Error
 
99
#--{{{
 
100
    attr 'cmd'
 
101
    attr 'status'
 
102
    attr 'signals'
 
103
    def exitstatus
 
104
      @status.exitstatus
 
105
    end
 
106
    def initialize cmd, status
 
107
      @cmd, @status = cmd, status
 
108
      @signals = {} 
 
109
      if status.signaled?
 
110
        @signals['termsig'] = status.termsig
 
111
        @signals['stopsig'] = status.stopsig
 
112
      end
 
113
      sigs = @signals.map{|k,v| "#{ k }:#{ v.inspect }"}.join(' ')
 
114
      super "cmd <#{ cmd }> failed with status <#{ exitstatus.inspect }> signals <#{ sigs }>"
 
115
    end
 
116
#--}}}
 
117
  end
 
118
 
 
119
  class ThreadEnsemble
 
120
#--{{{
 
121
    attr 'threads'
 
122
 
 
123
    def initialize cid
 
124
      @cid, @threads, @argv, @done, @running = cid, [], [], Queue.new, false
 
125
      @killed = false
 
126
    end
 
127
 
 
128
    def add_thread *a, &b
 
129
      @running ? raise : (@argv << [a, b])
 
130
    end
 
131
 
 
132
#
 
133
# take down process more nicely
 
134
#
 
135
    def killall
 
136
      c = Thread.critical
 
137
      return nil if @killed
 
138
      Thread.critical = true
 
139
      (@threads - [Thread.current]).each{|t| t.kill rescue nil}
 
140
      @killed = true
 
141
    ensure
 
142
      Thread.critical = c
 
143
    end
 
144
 
 
145
    def run
 
146
      @running = true
 
147
 
 
148
      begin
 
149
        @argv.each do |a, b|
 
150
          @threads << Thread.new(*a) do |*a|
 
151
            begin
 
152
              b[*a]
 
153
            ensure
 
154
              killall rescue nil if $!
 
155
              @done.push Thread.current
 
156
            end
 
157
          end
 
158
        end
 
159
      rescue
 
160
        killall
 
161
        raise
 
162
      ensure
 
163
        all_done
 
164
      end
 
165
 
 
166
      @threads.map{|t| t.value}
 
167
    end
 
168
 
 
169
    def all_done
 
170
      @threads.size.times{ @done.pop }
 
171
    end
 
172
#--}}}
 
173
  end
 
174
 
 
175
  def to timeout = nil
 
176
#--{{{
 
177
    Timeout.timeout(timeout){ yield }
 
178
#--}}}
 
179
  end
 
180
  module_function :to
 
181
 
 
182
  def new_thread *a, &b
 
183
#--{{{
 
184
    cur = Thread.current
 
185
    Thread.new(*a) do |*a|
 
186
      begin
 
187
        b[*a]
 
188
      rescue Exception => e
 
189
        cur.raise e
 
190
      end
 
191
    end
 
192
#--}}}
 
193
  end
 
194
  module_function :new_thread
 
195
 
 
196
  def getopts opts = {}
 
197
#--{{{
 
198
    lambda do |*args|
 
199
      keys, default, ignored = args
 
200
      catch('opt') do
 
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)
 
204
          end
 
205
        end
 
206
        default
 
207
      end
 
208
    end
 
209
#--}}}
 
210
  end
 
211
  module_function :getopts
 
212
 
 
213
  def relay src, dst = nil, t = nil
 
214
#--{{{
 
215
    unless src.nil?
 
216
      if src.respond_to? :gets
 
217
        while buf = to(t){ src.gets }
 
218
          dst << buf if dst
 
219
        end
 
220
 
 
221
      elsif src.respond_to? :each
 
222
        q = Queue.new
 
223
        th = nil
 
224
 
 
225
        timer_set = lambda do |t|
 
226
          th = new_thread{ to(t){ q.pop } }
 
227
        end
 
228
 
 
229
        timer_cancel = lambda do |t|
 
230
          th.kill if th rescue nil
 
231
        end
 
232
 
 
233
        timer_set[t]
 
234
        begin
 
235
          src.each do |buf|
 
236
            timer_cancel[t]
 
237
            dst << buf if dst
 
238
            timer_set[t]
 
239
          end
 
240
        ensure
 
241
          timer_cancel[t]
 
242
        end
 
243
 
 
244
      elsif src.respond_to? :read
 
245
        buf = to(t){ src.read }
 
246
        dst << buf if dst 
 
247
 
 
248
      else
 
249
        buf = to(t){ src.to_s }
 
250
        dst << buf if dst 
 
251
      end
 
252
    end
 
253
#--}}}
 
254
  end
 
255
  module_function :relay
 
256
 
 
257
  def spawn arg, *argv 
 
258
#--{{{
 
259
    argv.unshift(arg)
 
260
    opts = ((argv.size > 1 and Hash === argv.last) ? argv.pop : {})
 
261
    argv.flatten!
 
262
    cmd = argv.join(' ')
 
263
 
 
264
 
 
265
    getopt = getopts opts
 
266
 
 
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 ) ]
 
280
 
 
281
    exitstatus =
 
282
      case exitstatus
 
283
        when TrueClass, FalseClass
 
284
          ignore_exit_failure = true if exitstatus
 
285
          [0]
 
286
        else
 
287
          [*(exitstatus || 0)].map{|i| Integer i}
 
288
      end
 
289
 
 
290
    stdin ||= '' if stdin_timeout
 
291
    stdout ||= '' if stdout_timeout
 
292
    stderr ||= '' if stderr_timeout
 
293
 
 
294
    started = false
 
295
 
 
296
    status =
 
297
      begin
 
298
        chdir(cwd) do
 
299
          Timeout::timeout(timeout) do
 
300
            popen4(*argv) do |c, i, o, e|
 
301
              started = true
 
302
 
 
303
              %w( replace pid= << push update ).each do |msg|
 
304
                break(pid.send(msg, c)) if pid.respond_to? msg 
 
305
              end
 
306
 
 
307
              te = ThreadEnsemble.new c
 
308
 
 
309
              te.add_thread(i, stdin) do |i, stdin|
 
310
                relay stdin, i, stdin_timeout
 
311
                i.close rescue nil
 
312
              end
 
313
 
 
314
              te.add_thread(o, stdout) do |o, stdout|
 
315
                relay o, stdout, stdout_timeout
 
316
              end
 
317
 
 
318
              te.add_thread(e, stderr) do |o, stderr|
 
319
                relay e, stderr, stderr_timeout
 
320
              end
 
321
 
 
322
              te.run
 
323
            end
 
324
          end
 
325
        end
 
326
      rescue
 
327
        raise unless(not started and ignore_exec_failure)
 
328
      end
 
329
 
 
330
    raise SpawnError.new(cmd, status) unless
 
331
      (ignore_exit_failure or (status.nil? and ignore_exec_failure) or exitstatus.include?(status.exitstatus))
 
332
 
 
333
    status
 
334
#--}}}
 
335
  end
 
336
  module_function :spawn
 
337
 
 
338
  def chdir cwd, &block
 
339
    return(block.call Dir.pwd) unless cwd
 
340
    Dir.chdir cwd, &block
 
341
  end
 
342
  module_function :chdir
 
343
 
 
344
  def background arg, *argv 
 
345
#--{{{
 
346
    require 'thread'
 
347
    q = Queue.new
 
348
    opts = { 'pid' => q, :pid => q }
 
349
    case argv.last
 
350
      when Hash
 
351
        argv.last.update opts
 
352
      else
 
353
        argv.push opts
 
354
    end
 
355
    thread = Thread.new(arg, argv){|arg, argv| spawn arg, *argv}
 
356
    sc = class << thread; self; end
 
357
    sc.module_eval {
 
358
      define_method(:pid){ @pid ||= q.pop }
 
359
      define_method(:spawn_status){ @spawn_status ||= value }
 
360
      define_method(:exitstatus){ @exitstatus ||= spawn_status.exitstatus }
 
361
    }
 
362
    thread
 
363
#--}}}
 
364
  end
 
365
  alias bg background
 
366
  module_function :background
 
367
  module_function :bg
 
368
 
 
369
  def maim pid, opts = {}
 
370
#--{{{
 
371
    getopt = getopts opts
 
372
    sigs = getopt[ 'signals', %w(SIGTERM SIGQUIT SIGKILL) ]
 
373
    suspend = getopt[ 'suspend', 4 ]
 
374
    pid = Integer pid
 
375
    existed = false
 
376
    sigs.each do |sig|
 
377
      begin
 
378
        Process.kill sig, pid
 
379
        existed = true 
 
380
      rescue Errno::ESRCH
 
381
        return(existed ? nil : true)
 
382
      end
 
383
      return true unless alive? pid
 
384
      sleep suspend
 
385
      return true unless alive? pid
 
386
    end
 
387
    return(not alive?(pid)) 
 
388
#--}}}
 
389
  end
 
390
  module_function :maim
 
391
 
 
392
  def alive pid
 
393
#--{{{
 
394
    pid = Integer pid
 
395
    begin
 
396
      Process.kill 0, pid
 
397
      true
 
398
    rescue Errno::ESRCH
 
399
      false
 
400
    end
 
401
#--}}}
 
402
  end
 
403
  alias alive? alive
 
404
  module_function :alive
 
405
  module_function :'alive?'
 
406
#--}}}
 
407
end
 
408
 
 
409
def open4(*cmd, &b) cmd.size == 0 ? Open4 : Open4::popen4(*cmd, &b) end