alib 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,379 @@
1
+ require 'fcntl'
2
+ require 'timeout'
3
+ require 'thread'
4
+
5
+ module Open4
6
+ #--{{{
7
+ VERSION = '0.9.1'
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
+ class SpawnError < Error
80
+ #--{{{
81
+ attr 'cmd'
82
+ attr 'status'
83
+ attr 'signals'
84
+ def exitstatus
85
+ @status.exitstatus
86
+ end
87
+ def initialize cmd, status
88
+ @cmd, @status = cmd, status
89
+ @signals = {}
90
+ if status.signaled?
91
+ @signals['termsig'] = status.termsig
92
+ @signals['stopsig'] = status.stopsig
93
+ end
94
+ sigs = @signals.map{|k,v| "#{ k }:#{ v.inspect }"}.join(' ')
95
+ super "cmd <#{ cmd }> failed with status <#{ exitstatus.inspect }> signals <#{ sigs }>"
96
+ end
97
+ #--}}}
98
+ end
99
+
100
+ class ThreadEnsemble
101
+ #--{{{
102
+ attr 'threads'
103
+
104
+ def initialize cid
105
+ @cid, @threads, @argv, @done, @running = cid, [], [], Queue.new, false
106
+ @killed = false
107
+ end
108
+
109
+ def add_thread *a, &b
110
+ @running ? raise : (@argv << [a, b])
111
+ end
112
+
113
+ #
114
+ # take down process more nicely
115
+ #
116
+ def killall
117
+ c = Thread.critical
118
+ return nil if @killed
119
+ Thread.critical = true
120
+ (@threads - [Thread.current]).each{|t| t.kill rescue nil}
121
+ @killed = true
122
+ ensure
123
+ Thread.critical = c
124
+ end
125
+
126
+ def run
127
+ @running = true
128
+
129
+ begin
130
+ @argv.each do |a, b|
131
+ @threads << Thread.new(*a) do |*a|
132
+ begin
133
+ b[*a]
134
+ ensure
135
+ killall rescue nil if $!
136
+ @done.push Thread.current
137
+ end
138
+ end
139
+ end
140
+ rescue
141
+ killall
142
+ raise
143
+ ensure
144
+ all_done
145
+ end
146
+
147
+ @threads.map{|t| t.value}
148
+ end
149
+
150
+ def all_done
151
+ @threads.size.times{ @done.pop }
152
+ end
153
+ #--}}}
154
+ end
155
+
156
+ def to timeout = nil
157
+ #--{{{
158
+ Timeout.timeout(timeout){ yield }
159
+ #--}}}
160
+ end
161
+ module_function :to
162
+
163
+ def new_thread *a, &b
164
+ #--{{{
165
+ cur = Thread.current
166
+ Thread.new(*a) do |*a|
167
+ begin
168
+ b[*a]
169
+ rescue Exception => e
170
+ cur.raise e
171
+ end
172
+ end
173
+ #--}}}
174
+ end
175
+ module_function :new_thread
176
+
177
+ def getopts opts = {}
178
+ #--{{{
179
+ lambda do |*args|
180
+ keys, default, ignored = args
181
+ catch('opt') do
182
+ [keys].flatten.each do |key|
183
+ [key, key.to_s, key.to_s.intern].each do |key|
184
+ throw 'opt', opts[key] if opts.has_key?(key)
185
+ end
186
+ end
187
+ default
188
+ end
189
+ end
190
+ #--}}}
191
+ end
192
+ module_function :getopts
193
+
194
+ def relay src, dst = nil, t = nil
195
+ #--{{{
196
+ unless src.nil?
197
+ if src.respond_to? :gets
198
+ while buf = to(t){ src.gets }
199
+ dst << buf if dst
200
+ end
201
+
202
+ elsif src.respond_to? :each
203
+ q = Queue.new
204
+ th = nil
205
+
206
+ timer_set = lambda do |t|
207
+ th = new_thread{ to(t){ q.pop } }
208
+ end
209
+
210
+ timer_cancel = lambda do |t|
211
+ th.kill if th rescue nil
212
+ end
213
+
214
+ timer_set[t]
215
+ begin
216
+ src.each do |buf|
217
+ timer_cancel[t]
218
+ dst << buf if dst
219
+ timer_set[t]
220
+ end
221
+ ensure
222
+ timer_cancel[t]
223
+ end
224
+
225
+ elsif src.respond_to? :read
226
+ buf = to(t){ src.read }
227
+ dst << buf if dst
228
+
229
+ else
230
+ buf = to(t){ src.to_s }
231
+ dst << buf if dst
232
+ end
233
+ end
234
+ #--}}}
235
+ end
236
+ module_function :relay
237
+
238
+ def spawn arg, *argv
239
+ #--{{{
240
+ argv.unshift(arg)
241
+ opts = ((argv.size > 1 and Hash === argv.last) ? argv.pop : {})
242
+ argv.flatten!
243
+ cmd = argv.join(' ')
244
+
245
+
246
+ getopt = getopts opts
247
+
248
+ ignore_exit_failure = getopt[ 'ignore_exit_failure', getopt['quiet', false] ]
249
+ ignore_exec_failure = getopt[ 'ignore_exec_failure', !getopt['raise', true] ]
250
+ exitstatus = getopt[ %w( exitstatus exit_status status ) ]
251
+ stdin = getopt[ %w( stdin in i 0 ) << 0 ]
252
+ stdout = getopt[ %w( stdout out o 1 ) << 1 ]
253
+ stderr = getopt[ %w( stderr err e 2 ) << 2 ]
254
+ pid = getopt[ 'pid' ]
255
+ timeout = getopt[ %w( timeout spawn_timeout ) ]
256
+ stdin_timeout = getopt[ %w( stdin_timeout ) ]
257
+ stdout_timeout = getopt[ %w( stdout_timeout io_timeout ) ]
258
+ stderr_timeout = getopt[ %w( stderr_timeout ) ]
259
+ status = getopt[ %w( status ) ]
260
+ cwd = getopt[ %w( cwd dir ), Dir.pwd ]
261
+
262
+ exitstatus =
263
+ case exitstatus
264
+ when TrueClass, FalseClass
265
+ ignore_exit_failure = true if exitstatus
266
+ [0]
267
+ else
268
+ [*(exitstatus || 0)].map{|i| Integer i}
269
+ end
270
+
271
+ stdin ||= '' if stdin_timeout
272
+ stdout ||= '' if stdout_timeout
273
+ stderr ||= '' if stderr_timeout
274
+
275
+ started = false
276
+
277
+ status =
278
+ begin
279
+ Dir.chdir(cwd) do
280
+ Timeout::timeout(timeout) do
281
+ popen4(*argv) do |c, i, o, e|
282
+ started = true
283
+
284
+ %w( replace pid= << push update ).each do |msg|
285
+ break(pid.send(msg, c)) if pid.respond_to? msg
286
+ end
287
+
288
+ te = ThreadEnsemble.new c
289
+
290
+ te.add_thread(i, stdin) do |i, stdin|
291
+ relay stdin, i, stdin_timeout
292
+ i.close rescue nil
293
+ end
294
+
295
+ te.add_thread(o, stdout) do |o, stdout|
296
+ relay o, stdout, stdout_timeout
297
+ end
298
+
299
+ te.add_thread(e, stderr) do |o, stderr|
300
+ relay e, stderr, stderr_timeout
301
+ end
302
+
303
+ te.run
304
+ end
305
+ end
306
+ end
307
+ rescue
308
+ raise unless(not started and ignore_exec_failure)
309
+ end
310
+
311
+ raise SpawnError.new(cmd, status) unless
312
+ (ignore_exit_failure or (status.nil? and ignore_exec_failure) or exitstatus.include?(status.exitstatus))
313
+
314
+ status
315
+ #--}}}
316
+ end
317
+ module_function :spawn
318
+
319
+ def background arg, *argv
320
+ #--{{{
321
+ require 'thread'
322
+ q = Queue.new
323
+ opts['pid'] = opts[:pid] = q
324
+ thread = Thread.new(arg, argv){|arg, argv| spawn arg, *argv}
325
+ pid = q.pop
326
+ sc = class << thread; self; end
327
+ sc.module_eval {
328
+ define_method(:pid){ pid }
329
+ define_method(:spawn_status){ @spawn_status ||= value }
330
+ define_method(:exitstatus){ spawn_status.exitstatus }
331
+ }
332
+ thread
333
+ #--}}}
334
+ end
335
+ alias bg background
336
+ module_function :background
337
+ module_function :bg
338
+
339
+ def maim pid, opts = {}
340
+ #--{{{
341
+ getopt = getopts opts
342
+ sigs = getopt[ 'signals', %w(SIGTERM SIGQUIT SIGKILL) ]
343
+ suspend = getopt[ 'suspend', 4 ]
344
+ pid = Integer pid
345
+ existed = false
346
+ sigs.each do |sig|
347
+ begin
348
+ Process.kill sig, pid
349
+ existed = true
350
+ rescue Errno::ESRCH
351
+ return(existed ? nil : true)
352
+ end
353
+ return true unless alive? pid
354
+ sleep suspend
355
+ return true unless alive? pid
356
+ end
357
+ return(not alive?(pid))
358
+ #--}}}
359
+ end
360
+ module_function :maim
361
+
362
+ def alive pid
363
+ #--{{{
364
+ pid = Integer pid
365
+ begin
366
+ Process.kill 0, pid
367
+ true
368
+ rescue Errno::ESRCH
369
+ false
370
+ end
371
+ #--}}}
372
+ end
373
+ alias alive? alive
374
+ module_function :alive
375
+ module_function :'alive?'
376
+ #--}}}
377
+ end
378
+
379
+ def open4(*cmd, &b) cmd.size == 0 ? Open4 : Open4::popen4(*cmd, &b) end
@@ -0,0 +1,379 @@
1
+ require 'fcntl'
2
+ require 'timeout'
3
+ require 'thread'
4
+
5
+ module Open4
6
+ #--{{{
7
+ VERSION = '0.9.1'
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
+ class SpawnError < Error
80
+ #--{{{
81
+ attr 'cmd'
82
+ attr 'status'
83
+ attr 'signals'
84
+ def exitstatus
85
+ @status.exitstatus
86
+ end
87
+ def initialize cmd, status
88
+ @cmd, @status = cmd, status
89
+ @signals = {}
90
+ if status.signaled?
91
+ @signals['termsig'] = status.termsig
92
+ @signals['stopsig'] = status.stopsig
93
+ end
94
+ sigs = @signals.map{|k,v| "#{ k }:#{ v.inspect }"}.join(' ')
95
+ super "cmd <#{ cmd }> failed with status <#{ exitstatus.inspect }> signals <#{ sigs }>"
96
+ end
97
+ #--}}}
98
+ end
99
+
100
+ class ThreadEnsemble
101
+ #--{{{
102
+ attr 'threads'
103
+
104
+ def initialize cid
105
+ @cid, @threads, @argv, @done, @running = cid, [], [], Queue.new, false
106
+ @killed = false
107
+ end
108
+
109
+ def add_thread *a, &b
110
+ @running ? raise : (@argv << [a, b])
111
+ end
112
+
113
+ #
114
+ # take down process more nicely
115
+ #
116
+ def killall
117
+ c = Thread.critical
118
+ return nil if @killed
119
+ Thread.critical = true
120
+ (@threads - [Thread.current]).each{|t| t.kill rescue nil}
121
+ @killed = true
122
+ ensure
123
+ Thread.critical = c
124
+ end
125
+
126
+ def run
127
+ @running = true
128
+
129
+ begin
130
+ @argv.each do |a, b|
131
+ @threads << Thread.new(*a) do |*a|
132
+ begin
133
+ b[*a]
134
+ ensure
135
+ killall rescue nil if $!
136
+ @done.push Thread.current
137
+ end
138
+ end
139
+ end
140
+ rescue
141
+ killall
142
+ raise
143
+ ensure
144
+ all_done
145
+ end
146
+
147
+ @threads.map{|t| t.value}
148
+ end
149
+
150
+ def all_done
151
+ @threads.size.times{ @done.pop }
152
+ end
153
+ #--}}}
154
+ end
155
+
156
+ def to timeout = nil
157
+ #--{{{
158
+ Timeout.timeout(timeout){ yield }
159
+ #--}}}
160
+ end
161
+ module_function :to
162
+
163
+ def new_thread *a, &b
164
+ #--{{{
165
+ cur = Thread.current
166
+ Thread.new(*a) do |*a|
167
+ begin
168
+ b[*a]
169
+ rescue Exception => e
170
+ cur.raise e
171
+ end
172
+ end
173
+ #--}}}
174
+ end
175
+ module_function :new_thread
176
+
177
+ def getopts opts = {}
178
+ #--{{{
179
+ lambda do |*args|
180
+ keys, default, ignored = args
181
+ catch('opt') do
182
+ [keys].flatten.each do |key|
183
+ [key, key.to_s, key.to_s.intern].each do |key|
184
+ throw 'opt', opts[key] if opts.has_key?(key)
185
+ end
186
+ end
187
+ default
188
+ end
189
+ end
190
+ #--}}}
191
+ end
192
+ module_function :getopts
193
+
194
+ def relay src, dst = nil, t = nil
195
+ #--{{{
196
+ unless src.nil?
197
+ if src.respond_to? :gets
198
+ while buf = to(t){ src.gets }
199
+ dst << buf if dst
200
+ end
201
+
202
+ elsif src.respond_to? :each
203
+ q = Queue.new
204
+ th = nil
205
+
206
+ timer_set = lambda do |t|
207
+ th = new_thread{ to(t){ q.pop } }
208
+ end
209
+
210
+ timer_cancel = lambda do |t|
211
+ th.kill if th rescue nil
212
+ end
213
+
214
+ timer_set[t]
215
+ begin
216
+ src.each do |buf|
217
+ timer_cancel[t]
218
+ dst << buf if dst
219
+ timer_set[t]
220
+ end
221
+ ensure
222
+ timer_cancel[t]
223
+ end
224
+
225
+ elsif src.respond_to? :read
226
+ buf = to(t){ src.read }
227
+ dst << buf if dst
228
+
229
+ else
230
+ buf = to(t){ src.to_s }
231
+ dst << buf if dst
232
+ end
233
+ end
234
+ #--}}}
235
+ end
236
+ module_function :relay
237
+
238
+ def spawn arg, *argv
239
+ #--{{{
240
+ argv.unshift(arg)
241
+ opts = ((argv.size > 1 and Hash === argv.last) ? argv.pop : {})
242
+ argv.flatten!
243
+ cmd = argv.join(' ')
244
+
245
+
246
+ getopt = getopts opts
247
+
248
+ ignore_exit_failure = getopt[ 'ignore_exit_failure', getopt['quiet', false] ]
249
+ ignore_exec_failure = getopt[ 'ignore_exec_failure', !getopt['raise', true] ]
250
+ exitstatus = getopt[ %w( exitstatus exit_status status ) ]
251
+ stdin = getopt[ %w( stdin in i 0 ) << 0 ]
252
+ stdout = getopt[ %w( stdout out o 1 ) << 1 ]
253
+ stderr = getopt[ %w( stderr err e 2 ) << 2 ]
254
+ pid = getopt[ 'pid' ]
255
+ timeout = getopt[ %w( timeout spawn_timeout ) ]
256
+ stdin_timeout = getopt[ %w( stdin_timeout ) ]
257
+ stdout_timeout = getopt[ %w( stdout_timeout io_timeout ) ]
258
+ stderr_timeout = getopt[ %w( stderr_timeout ) ]
259
+ status = getopt[ %w( status ) ]
260
+ cwd = getopt[ %w( cwd dir ), Dir.pwd ]
261
+
262
+ exitstatus =
263
+ case exitstatus
264
+ when TrueClass, FalseClass
265
+ ignore_exit_failure = true if exitstatus
266
+ [0]
267
+ else
268
+ [*(exitstatus || 0)].map{|i| Integer i}
269
+ end
270
+
271
+ stdin ||= '' if stdin_timeout
272
+ stdout ||= '' if stdout_timeout
273
+ stderr ||= '' if stderr_timeout
274
+
275
+ started = false
276
+
277
+ status =
278
+ begin
279
+ Dir.chdir(cwd) do
280
+ Timeout::timeout(timeout) do
281
+ popen4(*argv) do |c, i, o, e|
282
+ started = true
283
+
284
+ %w( replace pid= << push update ).each do |msg|
285
+ break(pid.send(msg, c)) if pid.respond_to? msg
286
+ end
287
+
288
+ te = ThreadEnsemble.new c
289
+
290
+ te.add_thread(i, stdin) do |i, stdin|
291
+ relay stdin, i, stdin_timeout
292
+ i.close rescue nil
293
+ end
294
+
295
+ te.add_thread(o, stdout) do |o, stdout|
296
+ relay o, stdout, stdout_timeout
297
+ end
298
+
299
+ te.add_thread(e, stderr) do |o, stderr|
300
+ relay e, stderr, stderr_timeout
301
+ end
302
+
303
+ te.run
304
+ end
305
+ end
306
+ end
307
+ rescue
308
+ raise unless(not started and ignore_exec_failure)
309
+ end
310
+
311
+ raise SpawnError.new(cmd, status) unless
312
+ (ignore_exit_failure or (status.nil? and ignore_exec_failure) or exitstatus.include?(status.exitstatus))
313
+
314
+ status
315
+ #--}}}
316
+ end
317
+ module_function :spawn
318
+
319
+ def background arg, *argv
320
+ #--{{{
321
+ require 'thread'
322
+ q = Queue.new
323
+ opts['pid'] = opts[:pid] = q
324
+ thread = Thread.new(arg, argv){|arg, argv| spawn arg, *argv}
325
+ pid = q.pop
326
+ sc = class << thread; self; end
327
+ sc.module_eval {
328
+ define_method(:pid){ pid }
329
+ define_method(:spawn_status){ @spawn_status ||= value }
330
+ define_method(:exitstatus){ spawn_status.exitstatus }
331
+ }
332
+ thread
333
+ #--}}}
334
+ end
335
+ alias bg background
336
+ module_function :background
337
+ module_function :bg
338
+
339
+ def maim pid, opts = {}
340
+ #--{{{
341
+ getopt = getopts opts
342
+ sigs = getopt[ 'signals', %w(SIGTERM SIGQUIT SIGKILL) ]
343
+ suspend = getopt[ 'suspend', 4 ]
344
+ pid = Integer pid
345
+ existed = false
346
+ sigs.each do |sig|
347
+ begin
348
+ Process.kill sig, pid
349
+ existed = true
350
+ rescue Errno::ESRCH
351
+ return(existed ? nil : true)
352
+ end
353
+ return true unless alive? pid
354
+ sleep suspend
355
+ return true unless alive? pid
356
+ end
357
+ return(not alive?(pid))
358
+ #--}}}
359
+ end
360
+ module_function :maim
361
+
362
+ def alive pid
363
+ #--{{{
364
+ pid = Integer pid
365
+ begin
366
+ Process.kill 0, pid
367
+ true
368
+ rescue Errno::ESRCH
369
+ false
370
+ end
371
+ #--}}}
372
+ end
373
+ alias alive? alive
374
+ module_function :alive
375
+ module_function :'alive?'
376
+ #--}}}
377
+ end
378
+
379
+ def open4(*cmd, &b) cmd.size == 0 ? Open4 : Open4::popen4(*cmd, &b) end