1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
|
# Copyright (C) 2013-2015 all contributors <dtas-all@nongnu.org>
# License: GPLv3 or later (https://www.gnu.org/licenses/gpl-3.0.txt)
require 'io/wait'
require_relative '../dtas'
require_relative 'xs'
# process management helpers
module DTAS::Process # :nodoc:
PIDS = {}
include DTAS::XS
include DTAS::SpawnFix
def self.reaper
begin
pid, status = Process.waitpid2(-1, Process::WNOHANG)
pid or return
obj = PIDS.delete(pid)
yield status, obj
rescue Errno::ECHILD
return
end while true
end
# expand common shell constructs based on environment variables
# this is order-dependent, but Ruby 1.9+ hashes are already order-dependent
def env_expand(env, opts)
env = env.dup
if false == opts.delete(:expand)
env.each do |key, val|
Numeric === val and env[key] = val.to_s
end
else
env.each do |key, val|
case val
when Numeric # stringify numeric values to simplify users' lives
env[key] = val.to_s
when /[\`\$]/ # perform variable/command expansion
tmp = env.dup
tmp.delete(key)
val = qx(tmp, "echo #{val}", expand: false)
env[key] = val.chomp
end
end
end
end
# for long-running processes (sox/play/ecasound filters)
def dtas_spawn(env, cmd, opts)
opts = { close_others: true, pgroup: true }.merge!(opts)
env = env_expand(env, opts)
pid = spawn(env, cmd, opts)
warn [ :spawn, pid, cmd ].inspect if $DEBUG
@spawn_at = DTAS.now
PIDS[pid] = self
pid
end
# this is like backtick, but takes an array instead of a string
# This will also raise on errors
def qx(env, cmd = {}, opts = {})
unless Hash === env
cmd, opts = env, cmd
env = {}
end
r, w = IO.pipe
opts = opts.merge(out: w)
r.binmode
no_raise = opts.delete(:no_raise)
if err_str = opts.delete(:err_str)
re, we = IO.pipe
re.binmode
opts[:err] = we
end
env = env_expand(env, opts)
pid = spawn(env, *cmd, opts)
w.close
if err_str
we.close
res = "".b
want = { r => res, re => err_str }
begin
readable = IO.select(want.keys) or next
readable[0].each do |io|
begin
want[io] << io.read_nonblock(2000)
rescue Errno::EAGAIN
# spurious wakeup, bytes may be zero
rescue EOFError
want.delete(io)
end
end
end until want.empty?
re.close
else
res = r.read # read until EOF
end
r.close
_, status = Process.waitpid2(pid)
return res if status.success?
return status if no_raise
raise RuntimeError, "`#{xs(cmd)}' failed: #{status.inspect}"
end
end
|