open4 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/README CHANGED
@@ -5,10 +5,45 @@ URIS
5
5
 
6
6
  SYNOPSIS
7
7
 
8
- open child process with handles on pid, stdin, stdout, and stderr
8
+ open child process with handles on pid, stdin, stdout, and stderr: manage
9
+ child processes and their io handles easily.
9
10
 
10
11
  HISTORY
11
12
 
13
+ 0.5.0:
14
+ - on the suggestion of tim pease (thanks tim!), i added timeout features
15
+ to open4. the command run may have an overall timeout and individual
16
+ timeouts set for each of the io handles. for example
17
+
18
+ cmd = 'command_that_produce_out_at_one_second_intervals'
19
+
20
+ open4.spawn cmd, :stdout_timeout => 2
21
+
22
+ or
23
+
24
+ cmd = 'command_that_should_complete_in_about_one_minute'
25
+
26
+ open4.spawn cmd, :timeout => 60
27
+
28
+ or
29
+
30
+ cmd = 'consumes_input_at_one_line_per_second_rate'
31
+
32
+ input = %w( 42 forty-two 42.0 )
33
+
34
+ open4.spawn cmd, :stdin=>input, :stdin_timeout=>1
35
+
36
+ - added 'open4' alias so one can write
37
+
38
+ open4.spawn vs Open4.spawn
39
+
40
+ or even
41
+
42
+ open4(cmd) do |pid,i,o,e|
43
+ end
44
+
45
+ - added signal info to SpawnError
46
+
12
47
  0.4.0:
13
48
  - improved error handling contributed by jordan breeding.
14
49
  - introduction of background/bg method
@@ -185,6 +220,32 @@ SAMPLES
185
220
  ---
186
221
  stdout: "42\n"
187
222
 
223
+ ----------------------------------------------------------------------------
224
+ the timeout methods can be used to ensure execution is preceding at the
225
+ desired interval. note also how to setup a 'pipeline'
226
+ ----------------------------------------------------------------------------
227
+
228
+ harp: > cat sample/stdin_timeout.rb
229
+ require 'open4'
230
+
231
+ producer = 'ruby -e" STDOUT.sync = true; loop{sleep(rand+rand) and puts 42} "'
232
+
233
+ consumer = 'ruby -e" STDOUT.sync = true; STDIN.each{|line| puts line} "'
234
+
235
+ open4(producer) do |pid, i, o, e|
236
+
237
+ open4.spawn consumer, :stdin=>o, :stdout=>STDOUT, :stdin_timeout => 1.4
238
+
239
+ end
240
+
241
+
242
+ harp: > ruby sample/stdin_timeout.rb
243
+ 42
244
+ 42
245
+ 42
246
+ 42
247
+ 42
248
+ /dmsp/reference/ruby-1.8.1//lib/ruby/1.8/timeout.rb:42:in `relay': execution expired (Timeout::Error)
188
249
 
189
250
  AUTHOR
190
251
 
@@ -0,0 +1,311 @@
1
+ require 'fcntl'
2
+ require 'timeout'
3
+ require 'thread'
4
+
5
+ module Open4
6
+ #--{{{
7
+ VERSION = '0.5.0'
8
+ def self.version() VERSION end
9
+
10
+ class Error < ::StandardError; end
11
+
12
+ def popen4(*cmd, &b)
13
+ #--{{{
14
+ pw, pr, pe, ps = IO.pipe, IO.pipe, IO.pipe, IO.pipe
15
+
16
+ verbose = $VERBOSE
17
+ begin
18
+ $VERBOSE = nil
19
+ ps.last.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
20
+
21
+ cid = fork {
22
+ pw.last.close
23
+ STDIN.reopen pw.first
24
+ pw.first.close
25
+
26
+ pr.first.close
27
+ STDOUT.reopen pr.last
28
+ pr.last.close
29
+
30
+ pe.first.close
31
+ STDERR.reopen pe.last
32
+ pe.last.close
33
+
34
+ STDOUT.sync = STDERR.sync = true
35
+
36
+ begin
37
+ exec(*cmd)
38
+ raise 'forty-two'
39
+ rescue Exception => e
40
+ Marshal.dump(e, ps.last)
41
+ ps.last.flush
42
+ end
43
+ ps.last.close unless (ps.last.closed?)
44
+ exit!
45
+ }
46
+ ensure
47
+ $VERBOSE = verbose
48
+ end
49
+
50
+ [pw.first, pr.last, pe.last, ps.last].each{|fd| fd.close}
51
+
52
+ begin
53
+ e = Marshal.load ps.first
54
+ raise(Exception === e ? e : "unknown failure!")
55
+ rescue EOFError # If we get an EOF error, then the exec was successful
56
+ 42
57
+ end
58
+
59
+ pw.last.sync = true
60
+
61
+ pi = [pw.last, pr.first, pe.first]
62
+
63
+ if b
64
+ begin
65
+ b[cid, *pi]
66
+ Process.waitpid2(cid).last
67
+ ensure
68
+ pi.each{|fd| fd.close unless fd.closed?}
69
+ end
70
+ else
71
+ [cid, pw.last, pr.first, pe.first]
72
+ end
73
+ #--}}}
74
+ end
75
+ alias open4 popen4
76
+ module_function :popen4
77
+ module_function :open4
78
+
79
+
80
+
81
+ class SpawnError < Error
82
+ #--{{{
83
+ attr 'cmd'
84
+ attr 'status'
85
+ attr 'signals'
86
+ def exitstatus
87
+ @status.exitstatus
88
+ end
89
+ def initialize cmd, status
90
+ @cmd, @status = cmd, status
91
+ @signals = {}
92
+ if status.signaled?
93
+ @signals['termsig'] = status.termsig
94
+ @signals['stopsig'] = status.stopsig
95
+ end
96
+ sigs = @signals.map{|k,v| "#{ k }:#{ v.inspect }"}.join(' ')
97
+ super "cmd <#{ cmd }> failed with status <#{ exitstatus.inspect }> signals <#{ sigs }>"
98
+ end
99
+ #--}}}
100
+ end
101
+
102
+ class ThreadEnsemble
103
+ #--{{{
104
+ def initialize
105
+ @group = ThreadGroup.new
106
+ @kill = Queue.new
107
+ @killer = Thread.new{ k = @kill.pop and others.map{|t| t.kill unless t == k rescue next}}
108
+ @running = false
109
+ end
110
+
111
+ def add_thread *a, &b
112
+ raise if @running
113
+ @group.add(Thread.new(*a) do |*a|
114
+ Thread.stop
115
+ begin
116
+ b[*a]
117
+ ensure
118
+ kill if $!
119
+ end
120
+ end)
121
+ end
122
+
123
+ def kill
124
+ @kill.push Thread.current
125
+ end
126
+
127
+ def others
128
+ threads - [Thread.current]
129
+ end
130
+
131
+ def threads
132
+ @group.list
133
+ end
134
+
135
+ def value
136
+ others.map{|t| t.value}
137
+ end
138
+
139
+ def run
140
+ @running = true
141
+ others.map{|t| t.wakeup}
142
+ value
143
+ end
144
+ #--}}}
145
+ end
146
+
147
+ def to timeout = nil
148
+ #--{{{
149
+ Timeout.timeout(timeout){ yield }
150
+ #--}}}
151
+ end
152
+ module_function :to
153
+
154
+ def new_thread *a, &b
155
+ #--{{{
156
+ cur = Thread.current
157
+ Thread.new(*a) do |*a|
158
+ begin
159
+ b[*a]
160
+ rescue Exception => e
161
+ cur.raise e
162
+ end
163
+ end
164
+ #--}}}
165
+ end
166
+ module_function :new_thread
167
+
168
+ def getopts opts = {}
169
+ #--{{{
170
+ lambda do |*args|
171
+ keys, default, ignored = args
172
+ catch('opt') do
173
+ [keys].flatten.each do |key|
174
+ [key, key.to_s, key.to_s.intern].each do |key|
175
+ throw 'opt', opts[key] if opts.has_key?(key)
176
+ end
177
+ end
178
+ default
179
+ end
180
+ end
181
+ #--}}}
182
+ end
183
+ module_function :getopts
184
+
185
+ def relay src, dst = nil, t = nil
186
+ #--{{{
187
+ if src.respond_to? :gets
188
+ while buf = to(t){ src.gets }
189
+ dst << buf if dst
190
+ end
191
+
192
+ elsif src.respond_to? :each
193
+ q = Queue.new
194
+ th = nil
195
+
196
+ timer_set = lambda do |t|
197
+ th = new_thread{ to(t){ q.pop } }
198
+ end
199
+
200
+ timer_cancel = lambda do |t|
201
+ th.kill if th rescue nil
202
+ end
203
+
204
+ timer_set[t]
205
+ begin
206
+ src.each do |buf|
207
+ timer_cancel[t]
208
+ dst << buf if dst
209
+ timer_set[t]
210
+ end
211
+ ensure
212
+ timer_cancel[t]
213
+ end
214
+
215
+ elsif src.respond_to? :read
216
+ buf = to(t){ src.read }
217
+ dst << buf if dst
218
+
219
+ else
220
+ buf = to(t){ src.to_s }
221
+ dst << buf if dst
222
+ end
223
+ #--}}}
224
+ end
225
+ module_function :relay
226
+
227
+ def spawn cmd, opts = {}
228
+ #--{{{
229
+ getopt = getopts opts
230
+
231
+ ignore_exit_failure = getopt[ 'ignore_exit_failure', getopt['quiet', false] ]
232
+ ignore_exec_failure = getopt[ 'ignore_exec_failure', !getopt['raise', true] ]
233
+ exitstatus = getopt[ %w( exitstatus exit_status status ), 0 ]
234
+ stdin = getopt[ %w( stdin in i 0 ) << 0 ]
235
+ stdout = getopt[ %w( stdout out o 1 ) << 1 ]
236
+ stderr = getopt[ %w( stderr err e 2 ) << 2 ]
237
+ pid = getopt[ 'pid' ]
238
+ timeout = getopt[ %w( timeout spawn_timeout ) ]
239
+ stdin_timeout = getopt[ %w( stdin_timeout ) ]
240
+ stdout_timeout = getopt[ %w( stdout_timeout io_timeout ) ]
241
+ stderr_timeout = getopt[ %w( stderr_timeout ) ]
242
+
243
+ stdin ||= '' if stdin_timeout
244
+ stdout ||= '' if stdout_timeout
245
+ stderr ||= '' if stderr_timeout
246
+
247
+ started = false
248
+
249
+ status =
250
+ begin
251
+ Timeout::timeout(timeout) do
252
+ popen4(cmd) do |c, i, o, e|
253
+ started = true
254
+
255
+ %w( pid= << push update ).each do |msg|
256
+ break(pid.send(msg, c)) if pid.respond_to? msg
257
+ end
258
+
259
+ te = ThreadEnsemble.new
260
+
261
+ te.add_thread(i, stdin) do |i, stdin|
262
+ relay stdin, i, stdin_timeout if stdin
263
+ i.close rescue nil
264
+ end
265
+
266
+ te.add_thread(o, stdout) do |o, stdout|
267
+ relay o, stdout, stdout_timeout if stdout
268
+ end
269
+
270
+ te.add_thread(e, stderr) do |o, stderr|
271
+ relay e, stderr, stderr_timeout if stderr
272
+ end
273
+
274
+ te.run
275
+ end
276
+ end
277
+ rescue
278
+ raise unless(not started and ignore_exec_failure)
279
+ end
280
+
281
+ raise SpawnError.new(cmd, status) unless
282
+ (ignore_exit_failure or (status.nil? and ignore_exec_failure) or (status.exitstatus == exitstatus))
283
+
284
+ status
285
+ #--}}}
286
+ end
287
+ module_function :spawn
288
+
289
+ def background cmd, opts = {}
290
+ #--{{{
291
+ require 'thread'
292
+ q = Queue.new
293
+ opts['pid'] = opts[:pid] = q
294
+ thread = Thread.new(cmd, opts){|cmd, opts| spawn cmd, opts}
295
+ pid = q.pop
296
+ sc = class << thread; self; end
297
+ sc.module_eval {
298
+ define_method(:pid){ pid }
299
+ define_method(:spawn_status){ @spawn_status ||= value }
300
+ define_method(:exitstatus){ spawn_status.exitstatus }
301
+ }
302
+ thread
303
+ #--}}}
304
+ end
305
+ alias bg background
306
+ module_function :background
307
+ module_function :bg
308
+ #--}}}
309
+ end
310
+
311
+ def open4(*cmd, &b) cmd.size == 0 ? Open4 : Open4::popen4(*cmd, &b) end
data/lib/open4.rb CHANGED
@@ -1,12 +1,15 @@
1
1
  require 'fcntl'
2
+ require 'timeout'
3
+ require 'thread'
2
4
 
3
5
  module Open4
4
6
  #--{{{
5
- def self.version
6
- '0.4.0'
7
- end
7
+ VERSION = '0.5.0'
8
+ def self.version() VERSION end
9
+
10
+ class Error < ::StandardError; end
8
11
 
9
- def popen4(*cmd)
12
+ def popen4(*cmd, &b)
10
13
  #--{{{
11
14
  pw, pr, pe, ps = IO.pipe, IO.pipe, IO.pipe, IO.pipe
12
15
 
@@ -32,7 +35,7 @@ module Open4
32
35
 
33
36
  begin
34
37
  exec(*cmd)
35
- raise "exec failed!"
38
+ raise 'forty-two'
36
39
  rescue Exception => e
37
40
  Marshal.dump(e, ps.last)
38
41
  ps.last.flush
@@ -57,9 +60,9 @@ module Open4
57
60
 
58
61
  pi = [pw.last, pr.first, pe.first]
59
62
 
60
- if defined? yield
63
+ if b
61
64
  begin
62
- yield(cid, *pi)
65
+ b[cid, *pi]
63
66
  Process.waitpid2(cid).last
64
67
  ensure
65
68
  pi.each{|fd| fd.close unless fd.closed?}
@@ -73,23 +76,98 @@ module Open4
73
76
  module_function :popen4
74
77
  module_function :open4
75
78
 
76
- class Error < ::StandardError; end
79
+
80
+
77
81
  class SpawnError < Error
78
82
  #--{{{
79
83
  attr 'cmd'
80
84
  attr 'status'
85
+ attr 'signals'
81
86
  def exitstatus
82
87
  @status.exitstatus
83
88
  end
84
89
  def initialize cmd, status
85
90
  @cmd, @status = cmd, status
86
- super "cmd <#{ cmd }> failed with <#{ exitstatus }>"
91
+ @signals = {}
92
+ if status.signaled?
93
+ @signals['termsig'] = status.termsig
94
+ @signals['stopsig'] = status.stopsig
95
+ end
96
+ sigs = @signals.map{|k,v| "#{ k }:#{ v.inspect }"}.join(' ')
97
+ super "cmd <#{ cmd }> failed with status <#{ exitstatus.inspect }> signals <#{ sigs }>"
87
98
  end
88
99
  #--}}}
89
100
  end
90
- def spawn cmd, opts = {}
101
+
102
+ class ThreadEnsemble
103
+ #--{{{
104
+ def initialize
105
+ @group = ThreadGroup.new
106
+ @kill = Queue.new
107
+ @killer = Thread.new{ k = @kill.pop and others.map{|t| t.kill unless t == k rescue next}}
108
+ @running = false
109
+ end
110
+
111
+ def add_thread *a, &b
112
+ raise if @running
113
+ @group.add(Thread.new(*a) do |*a|
114
+ Thread.stop
115
+ begin
116
+ b[*a]
117
+ ensure
118
+ kill if $!
119
+ end
120
+ end)
121
+ end
122
+
123
+ def kill
124
+ @kill.push Thread.current
125
+ end
126
+
127
+ def others
128
+ threads - [Thread.current]
129
+ end
130
+
131
+ def threads
132
+ @group.list
133
+ end
134
+
135
+ def value
136
+ others.map{|t| t.value}
137
+ end
138
+
139
+ def run
140
+ @running = true
141
+ others.map{|t| t.wakeup}
142
+ value
143
+ end
144
+ #--}}}
145
+ end
146
+
147
+ def to timeout = nil
148
+ #--{{{
149
+ Timeout.timeout(timeout){ yield }
150
+ #--}}}
151
+ end
152
+ module_function :to
153
+
154
+ def new_thread *a, &b
155
+ #--{{{
156
+ cur = Thread.current
157
+ Thread.new(*a) do |*a|
158
+ begin
159
+ b[*a]
160
+ rescue Exception => e
161
+ cur.raise e
162
+ end
163
+ end
164
+ #--}}}
165
+ end
166
+ module_function :new_thread
167
+
168
+ def getopts opts = {}
91
169
  #--{{{
92
- getopt = lambda do |*args|
170
+ lambda do |*args|
93
171
  keys, default, ignored = args
94
172
  catch('opt') do
95
173
  [keys].flatten.each do |key|
@@ -100,45 +178,101 @@ module Open4
100
178
  default
101
179
  end
102
180
  end
181
+ #--}}}
182
+ end
183
+ module_function :getopts
184
+
185
+ def relay src, dst = nil, t = nil
186
+ #--{{{
187
+ if src.respond_to? :gets
188
+ while buf = to(t){ src.gets }
189
+ dst << buf if dst
190
+ end
191
+
192
+ elsif src.respond_to? :each
193
+ q = Queue.new
194
+ th = nil
195
+
196
+ timer_set = lambda do |t|
197
+ th = new_thread{ to(t){ q.pop } }
198
+ end
199
+
200
+ timer_cancel = lambda do |t|
201
+ th.kill if th rescue nil
202
+ end
203
+
204
+ timer_set[t]
205
+ begin
206
+ src.each do |buf|
207
+ timer_cancel[t]
208
+ dst << buf if dst
209
+ timer_set[t]
210
+ end
211
+ ensure
212
+ timer_cancel[t]
213
+ end
214
+
215
+ elsif src.respond_to? :read
216
+ buf = to(t){ src.read }
217
+ dst << buf if dst
218
+
219
+ else
220
+ buf = to(t){ src.to_s }
221
+ dst << buf if dst
222
+ end
223
+ #--}}}
224
+ end
225
+ module_function :relay
226
+
227
+ def spawn cmd, opts = {}
228
+ #--{{{
229
+ getopt = getopts opts
103
230
 
104
231
  ignore_exit_failure = getopt[ 'ignore_exit_failure', getopt['quiet', false] ]
105
232
  ignore_exec_failure = getopt[ 'ignore_exec_failure', !getopt['raise', true] ]
106
233
  exitstatus = getopt[ %w( exitstatus exit_status status ), 0 ]
107
- stdin = getopt[ ['stdin', 'in', '0', 0] ]
108
- stdout = getopt[ ['stdout', 'out', '1', 1] ]
109
- stderr = getopt[ ['stderr', 'err', '2', 2] ]
234
+ stdin = getopt[ %w( stdin in i 0 ) << 0 ]
235
+ stdout = getopt[ %w( stdout out o 1 ) << 1 ]
236
+ stderr = getopt[ %w( stderr err e 2 ) << 2 ]
110
237
  pid = getopt[ 'pid' ]
238
+ timeout = getopt[ %w( timeout spawn_timeout ) ]
239
+ stdin_timeout = getopt[ %w( stdin_timeout ) ]
240
+ stdout_timeout = getopt[ %w( stdout_timeout io_timeout ) ]
241
+ stderr_timeout = getopt[ %w( stderr_timeout ) ]
242
+
243
+ stdin ||= '' if stdin_timeout
244
+ stdout ||= '' if stdout_timeout
245
+ stderr ||= '' if stderr_timeout
111
246
 
112
247
  started = false
113
248
 
114
249
  status =
115
250
  begin
116
- popen4(cmd) do |c, i, o, e|
117
- started = true
251
+ Timeout::timeout(timeout) do
252
+ popen4(cmd) do |c, i, o, e|
253
+ started = true
118
254
 
119
- if pid.respond_to? '<<'
120
- pid << c
121
- end
255
+ %w( pid= << push update ).each do |msg|
256
+ break(pid.send(msg, c)) if pid.respond_to? msg
257
+ end
258
+
259
+ te = ThreadEnsemble.new
122
260
 
123
- it = Thread.new(i,stdin) do |i,stdin|
124
- if stdin
125
- if stdin.respond_to? :each
126
- stdin.each{|buf| i << buf}
127
- elsif stdin.respond_to? :read
128
- i << stdin.read
129
- else
130
- i << stdin.to_s
261
+ te.add_thread(i, stdin) do |i, stdin|
262
+ relay stdin, i, stdin_timeout if stdin
263
+ i.close rescue nil
131
264
  end
132
- end
133
- i.close
134
- end
135
265
 
136
- ot = Thread.new(o,stdout){|o,stdout| o.each{|buf| stdout << buf if stdout}}
137
- et = Thread.new(e,stderr){|e,stderr| e.each{|buf| stderr << buf if stderr}}
266
+ te.add_thread(o, stdout) do |o, stdout|
267
+ relay o, stdout, stdout_timeout if stdout
268
+ end
138
269
 
139
- it.join
140
- ot.join if ot
141
- et.join if et
270
+ te.add_thread(e, stderr) do |o, stderr|
271
+ relay e, stderr, stderr_timeout if stderr
272
+ end
273
+
274
+ te.run
275
+ end
142
276
  end
143
277
  rescue
144
278
  raise unless(not started and ignore_exec_failure)
@@ -173,3 +307,5 @@ module Open4
173
307
  module_function :bg
174
308
  #--}}}
175
309
  end
310
+
311
+ def open4(*cmd, &b) cmd.size == 0 ? Open4 : Open4::popen4(*cmd, &b) end
@@ -0,0 +1,9 @@
1
+ require 'open4'
2
+
3
+ producer = 'ruby -e" STDOUT.sync = true; loop{sleep(rand+rand) and puts 42} " 2>/dev/null'
4
+
5
+ consumer = 'ruby -e" STDOUT.sync = true; STDIN.each{|line| puts line} "'
6
+
7
+ open4(producer) do |pid, i, o, e|
8
+ open4.spawn consumer, 0=>o, 1=>STDOUT, :stdin_timeout => 1.4
9
+ end
data/sample/timeout.rb ADDED
@@ -0,0 +1,37 @@
1
+
2
+ require 'open4'
3
+
4
+ def show_failure
5
+ fork{ yield }
6
+ Process.wait
7
+ puts
8
+ end
9
+
10
+ #
11
+ # command timeout
12
+ #
13
+ show_failure{
14
+ open4.spawn 'sleep 42', 'timeout' => 1
15
+ }
16
+
17
+ #
18
+ # stdin timeout
19
+ #
20
+ show_failure{
21
+
22
+ producer = 'ruby -e" STDOUT.sync = true; loop{sleep(rand+rand) and puts 42} " 2>/dev/null'
23
+
24
+ consumer = 'ruby -e" STDOUT.sync = true; STDIN.each{|line| puts line} "'
25
+
26
+ open4(producer) do |pid, i, o, e|
27
+ open4.spawn consumer, 0=>o, 1=>STDOUT, :stdin_timeout => 1.4
28
+ end
29
+ }
30
+
31
+ #
32
+ # stdout timeout (stderr is similar)
33
+ #
34
+
35
+ show_failure{
36
+ open4.spawn 'ruby -e" sleep 2 and puts 42 "', 'stdout_timeout' => 1
37
+ }
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.8.11
3
3
  specification_version: 1
4
4
  name: open4
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.4.0
7
- date: 2006-04-26 00:00:00.000000 -06:00
6
+ version: 0.5.0
7
+ date: 2006-08-07 00:00:00.000000 -06:00
8
8
  summary: open4
9
9
  require_paths:
10
10
  - lib
@@ -36,14 +36,15 @@ files:
36
36
  - build
37
37
  - install
38
38
  - gemspec.rb
39
- - open4-0.4.0.gem
40
39
  - sample/block.rb
41
40
  - sample/simple.rb
42
41
  - sample/exception.rb
43
42
  - sample/spawn.rb
44
43
  - sample/bg.rb
45
- - lib/open4-0.4.0.rb
44
+ - sample/timeout.rb
45
+ - sample/stdin_timeout.rb
46
46
  - lib/open4.rb
47
+ - lib/open4-0.5.0.rb
47
48
  test_files: []
48
49
  rdoc_options: []
49
50
  extra_rdoc_files: []
data/lib/open4-0.4.0.rb DELETED
@@ -1,175 +0,0 @@
1
- require 'fcntl'
2
-
3
- module Open4
4
- #--{{{
5
- def self.version
6
- '0.4.0'
7
- end
8
-
9
- def popen4(*cmd)
10
- #--{{{
11
- pw, pr, pe, ps = IO.pipe, IO.pipe, IO.pipe, IO.pipe
12
-
13
- verbose = $VERBOSE
14
- begin
15
- $VERBOSE = nil
16
- ps.last.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
17
-
18
- cid = fork {
19
- pw.last.close
20
- STDIN.reopen pw.first
21
- pw.first.close
22
-
23
- pr.first.close
24
- STDOUT.reopen pr.last
25
- pr.last.close
26
-
27
- pe.first.close
28
- STDERR.reopen pe.last
29
- pe.last.close
30
-
31
- STDOUT.sync = STDERR.sync = true
32
-
33
- begin
34
- exec(*cmd)
35
- raise "exec failed!"
36
- rescue Exception => e
37
- Marshal.dump(e, ps.last)
38
- ps.last.flush
39
- end
40
- ps.last.close unless (ps.last.closed?)
41
- exit!
42
- }
43
- ensure
44
- $VERBOSE = verbose
45
- end
46
-
47
- [pw.first, pr.last, pe.last, ps.last].each{|fd| fd.close}
48
-
49
- begin
50
- e = Marshal.load ps.first
51
- raise(Exception === e ? e : "unknown failure!")
52
- rescue EOFError # If we get an EOF error, then the exec was successful
53
- 42
54
- end
55
-
56
- pw.last.sync = true
57
-
58
- pi = [pw.last, pr.first, pe.first]
59
-
60
- if defined? yield
61
- begin
62
- yield(cid, *pi)
63
- Process.waitpid2(cid).last
64
- ensure
65
- pi.each{|fd| fd.close unless fd.closed?}
66
- end
67
- else
68
- [cid, pw.last, pr.first, pe.first]
69
- end
70
- #--}}}
71
- end
72
- alias open4 popen4
73
- module_function :popen4
74
- module_function :open4
75
-
76
- class Error < ::StandardError; end
77
- class SpawnError < Error
78
- #--{{{
79
- attr 'cmd'
80
- attr 'status'
81
- def exitstatus
82
- @status.exitstatus
83
- end
84
- def initialize cmd, status
85
- @cmd, @status = cmd, status
86
- super "cmd <#{ cmd }> failed with <#{ exitstatus }>"
87
- end
88
- #--}}}
89
- end
90
- def spawn cmd, opts = {}
91
- #--{{{
92
- getopt = lambda do |*args|
93
- keys, default, ignored = args
94
- catch('opt') do
95
- [keys].flatten.each do |key|
96
- [key, key.to_s, key.to_s.intern].each do |key|
97
- throw 'opt', opts[key] if opts.has_key?(key)
98
- end
99
- end
100
- default
101
- end
102
- end
103
-
104
- ignore_exit_failure = getopt[ 'ignore_exit_failure', getopt['quiet', false] ]
105
- ignore_exec_failure = getopt[ 'ignore_exec_failure', !getopt['raise', true] ]
106
- exitstatus = getopt[ %w( exitstatus exit_status status ), 0 ]
107
- stdin = getopt[ ['stdin', 'in', '0', 0] ]
108
- stdout = getopt[ ['stdout', 'out', '1', 1] ]
109
- stderr = getopt[ ['stderr', 'err', '2', 2] ]
110
- pid = getopt[ 'pid' ]
111
-
112
- started = false
113
-
114
- status =
115
- begin
116
- popen4(cmd) do |c, i, o, e|
117
- started = true
118
-
119
- if pid.respond_to? '<<'
120
- pid << c
121
- end
122
-
123
- it = Thread.new(i,stdin) do |i,stdin|
124
- if stdin
125
- if stdin.respond_to? :each
126
- stdin.each{|buf| i << buf}
127
- elsif stdin.respond_to? :read
128
- i << stdin.read
129
- else
130
- i << stdin.to_s
131
- end
132
- end
133
- i.close
134
- end
135
-
136
- ot = Thread.new(o,stdout){|o,stdout| o.each{|buf| stdout << buf if stdout}}
137
- et = Thread.new(e,stderr){|e,stderr| e.each{|buf| stderr << buf if stderr}}
138
-
139
- it.join
140
- ot.join if ot
141
- et.join if et
142
- end
143
- rescue
144
- raise unless(not started and ignore_exec_failure)
145
- end
146
-
147
- raise SpawnError.new(cmd, status) unless
148
- (ignore_exit_failure or (status.nil? and ignore_exec_failure) or (status.exitstatus == exitstatus))
149
-
150
- status
151
- #--}}}
152
- end
153
- module_function :spawn
154
-
155
- def background cmd, opts = {}
156
- #--{{{
157
- require 'thread'
158
- q = Queue.new
159
- opts['pid'] = opts[:pid] = q
160
- thread = Thread.new(cmd, opts){|cmd, opts| spawn cmd, opts}
161
- pid = q.pop
162
- sc = class << thread; self; end
163
- sc.module_eval {
164
- define_method(:pid){ pid }
165
- define_method(:spawn_status){ @spawn_status ||= value }
166
- define_method(:exitstatus){ spawn_status.exitstatus }
167
- }
168
- thread
169
- #--}}}
170
- end
171
- alias bg background
172
- module_function :background
173
- module_function :bg
174
- #--}}}
175
- end
data/open4-0.4.0.gem DELETED
File without changes