childprocess 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.document +6 -0
- data/.gitignore +28 -0
- data/.rspec +1 -0
- data/.travis.yml +44 -0
- data/CHANGELOG.md +49 -0
- data/Gemfile +15 -0
- data/LICENSE +20 -0
- data/README.md +196 -0
- data/Rakefile +61 -0
- data/appveyor.yml +60 -0
- data/childprocess.gemspec +30 -0
- data/lib/childprocess.rb +205 -0
- data/lib/childprocess/abstract_io.rb +36 -0
- data/lib/childprocess/abstract_process.rb +192 -0
- data/lib/childprocess/errors.rb +26 -0
- data/lib/childprocess/jruby.rb +56 -0
- data/lib/childprocess/jruby/io.rb +16 -0
- data/lib/childprocess/jruby/process.rb +159 -0
- data/lib/childprocess/jruby/pump.rb +53 -0
- data/lib/childprocess/tools/generator.rb +146 -0
- data/lib/childprocess/unix.rb +9 -0
- data/lib/childprocess/unix/fork_exec_process.rb +70 -0
- data/lib/childprocess/unix/io.rb +21 -0
- data/lib/childprocess/unix/lib.rb +186 -0
- data/lib/childprocess/unix/platform/i386-linux.rb +12 -0
- data/lib/childprocess/unix/platform/i386-solaris.rb +11 -0
- data/lib/childprocess/unix/platform/x86_64-linux.rb +12 -0
- data/lib/childprocess/unix/platform/x86_64-macosx.rb +11 -0
- data/lib/childprocess/unix/posix_spawn_process.rb +134 -0
- data/lib/childprocess/unix/process.rb +89 -0
- data/lib/childprocess/version.rb +3 -0
- data/lib/childprocess/windows.rb +33 -0
- data/lib/childprocess/windows/handle.rb +91 -0
- data/lib/childprocess/windows/io.rb +25 -0
- data/lib/childprocess/windows/lib.rb +416 -0
- data/lib/childprocess/windows/process.rb +130 -0
- data/lib/childprocess/windows/process_builder.rb +175 -0
- data/lib/childprocess/windows/structs.rb +149 -0
- data/spec/abstract_io_spec.rb +12 -0
- data/spec/childprocess_spec.rb +422 -0
- data/spec/io_spec.rb +228 -0
- data/spec/jruby_spec.rb +24 -0
- data/spec/pid_behavior.rb +12 -0
- data/spec/platform_detection_spec.rb +86 -0
- data/spec/spec_helper.rb +261 -0
- data/spec/unix_spec.rb +57 -0
- data/spec/windows_spec.rb +23 -0
- metadata +179 -0
@@ -0,0 +1,70 @@
|
|
1
|
+
module ChildProcess
|
2
|
+
module Unix
|
3
|
+
class ForkExecProcess < Process
|
4
|
+
private
|
5
|
+
|
6
|
+
def launch_process
|
7
|
+
if @io
|
8
|
+
stdout = @io.stdout
|
9
|
+
stderr = @io.stderr
|
10
|
+
end
|
11
|
+
|
12
|
+
# pipe used to detect exec() failure
|
13
|
+
exec_r, exec_w = ::IO.pipe
|
14
|
+
ChildProcess.close_on_exec exec_w
|
15
|
+
|
16
|
+
if duplex?
|
17
|
+
reader, writer = ::IO.pipe
|
18
|
+
end
|
19
|
+
|
20
|
+
@pid = Kernel.fork {
|
21
|
+
# Children of the forked process will inherit its process group
|
22
|
+
# This is to make sure that all grandchildren dies when this Process instance is killed
|
23
|
+
::Process.setpgid 0, 0 if leader?
|
24
|
+
|
25
|
+
if @cwd
|
26
|
+
Dir.chdir(@cwd)
|
27
|
+
end
|
28
|
+
|
29
|
+
exec_r.close
|
30
|
+
set_env
|
31
|
+
|
32
|
+
STDOUT.reopen(stdout || "/dev/null")
|
33
|
+
STDERR.reopen(stderr || "/dev/null")
|
34
|
+
|
35
|
+
if duplex?
|
36
|
+
STDIN.reopen(reader)
|
37
|
+
writer.close
|
38
|
+
end
|
39
|
+
|
40
|
+
executable, *args = @args
|
41
|
+
|
42
|
+
begin
|
43
|
+
Kernel.exec([executable, executable], *args)
|
44
|
+
rescue SystemCallError => ex
|
45
|
+
exec_w << ex.message
|
46
|
+
end
|
47
|
+
}
|
48
|
+
|
49
|
+
exec_w.close
|
50
|
+
|
51
|
+
if duplex?
|
52
|
+
io._stdin = writer
|
53
|
+
reader.close
|
54
|
+
end
|
55
|
+
|
56
|
+
# if we don't eventually get EOF, exec() failed
|
57
|
+
unless exec_r.eof?
|
58
|
+
raise LaunchError, exec_r.read || "executing command with #{@args.inspect} failed"
|
59
|
+
end
|
60
|
+
|
61
|
+
::Process.detach(@pid) if detach?
|
62
|
+
end
|
63
|
+
|
64
|
+
def set_env
|
65
|
+
@environment.each { |k, v| ENV[k.to_s] = v.nil? ? nil : v.to_s }
|
66
|
+
end
|
67
|
+
|
68
|
+
end # Process
|
69
|
+
end # Unix
|
70
|
+
end # ChildProcess
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module ChildProcess
|
2
|
+
module Unix
|
3
|
+
class IO < AbstractIO
|
4
|
+
private
|
5
|
+
|
6
|
+
def check_type(io)
|
7
|
+
unless io.respond_to? :to_io
|
8
|
+
raise ArgumentError, "expected #{io.inspect} to respond to :to_io"
|
9
|
+
end
|
10
|
+
|
11
|
+
result = io.to_io
|
12
|
+
unless result && result.kind_of?(::IO)
|
13
|
+
raise TypeError, "expected IO, got #{result.inspect}:#{result.class}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
end # IO
|
18
|
+
end # Unix
|
19
|
+
end # ChildProcess
|
20
|
+
|
21
|
+
|
@@ -0,0 +1,186 @@
|
|
1
|
+
module ChildProcess
|
2
|
+
module Unix
|
3
|
+
module Lib
|
4
|
+
extend FFI::Library
|
5
|
+
ffi_lib FFI::Library::LIBC
|
6
|
+
|
7
|
+
if ChildProcess.os == :macosx
|
8
|
+
attach_function :_NSGetEnviron, [], :pointer
|
9
|
+
def self.environ
|
10
|
+
_NSGetEnviron().read_pointer
|
11
|
+
end
|
12
|
+
elsif respond_to? :attach_variable
|
13
|
+
attach_variable :environ, :pointer
|
14
|
+
end
|
15
|
+
|
16
|
+
attach_function :strerror, [:int], :string
|
17
|
+
attach_function :chdir, [:string], :int
|
18
|
+
attach_function :fcntl, [:int, :int, :int], :int # fcntl actually takes varags, but we only need this version.
|
19
|
+
|
20
|
+
# int posix_spawnp(
|
21
|
+
# pid_t *restrict pid,
|
22
|
+
# const char *restrict file,
|
23
|
+
# const posix_spawn_file_actions_t *file_actions,
|
24
|
+
# const posix_spawnattr_t *restrict attrp,
|
25
|
+
# char *const argv[restrict],
|
26
|
+
# char *const envp[restrict]
|
27
|
+
# );
|
28
|
+
|
29
|
+
attach_function :posix_spawnp, [
|
30
|
+
:pointer,
|
31
|
+
:string,
|
32
|
+
:pointer,
|
33
|
+
:pointer,
|
34
|
+
:pointer,
|
35
|
+
:pointer
|
36
|
+
], :int
|
37
|
+
|
38
|
+
# int posix_spawn_file_actions_init(posix_spawn_file_actions_t *file_actions);
|
39
|
+
attach_function :posix_spawn_file_actions_init, [:pointer], :int
|
40
|
+
|
41
|
+
# int posix_spawn_file_actions_destroy(posix_spawn_file_actions_t *file_actions);
|
42
|
+
attach_function :posix_spawn_file_actions_destroy, [:pointer], :int
|
43
|
+
|
44
|
+
# int posix_spawn_file_actions_addclose(posix_spawn_file_actions_t *file_actions, int filedes);
|
45
|
+
attach_function :posix_spawn_file_actions_addclose, [:pointer, :int], :int
|
46
|
+
|
47
|
+
# int posix_spawn_file_actions_addopen(
|
48
|
+
# posix_spawn_file_actions_t *restrict file_actions,
|
49
|
+
# int filedes,
|
50
|
+
# const char *restrict path,
|
51
|
+
# int oflag,
|
52
|
+
# mode_t mode
|
53
|
+
# );
|
54
|
+
attach_function :posix_spawn_file_actions_addopen, [:pointer, :int, :string, :int, :mode_t], :int
|
55
|
+
|
56
|
+
# int posix_spawn_file_actions_adddup2(
|
57
|
+
# posix_spawn_file_actions_t *file_actions,
|
58
|
+
# int filedes,
|
59
|
+
# int newfiledes
|
60
|
+
# );
|
61
|
+
attach_function :posix_spawn_file_actions_adddup2, [:pointer, :int, :int], :int
|
62
|
+
|
63
|
+
# int posix_spawnattr_init(posix_spawnattr_t *attr);
|
64
|
+
attach_function :posix_spawnattr_init, [:pointer], :int
|
65
|
+
|
66
|
+
# int posix_spawnattr_destroy(posix_spawnattr_t *attr);
|
67
|
+
attach_function :posix_spawnattr_destroy, [:pointer], :int
|
68
|
+
|
69
|
+
# int posix_spawnattr_setflags(posix_spawnattr_t *attr, short flags);
|
70
|
+
attach_function :posix_spawnattr_setflags, [:pointer, :short], :int
|
71
|
+
|
72
|
+
# int posix_spawnattr_getflags(const posix_spawnattr_t *restrict attr, short *restrict flags);
|
73
|
+
attach_function :posix_spawnattr_getflags, [:pointer, :pointer], :int
|
74
|
+
|
75
|
+
# int posix_spawnattr_setpgroup(posix_spawnattr_t *attr, pid_t pgroup);
|
76
|
+
attach_function :posix_spawnattr_setpgroup, [:pointer, :pid_t], :int
|
77
|
+
|
78
|
+
# int posix_spawnattr_getpgroup(const posix_spawnattr_t *restrict attr, pid_t *restrict pgroup);
|
79
|
+
attach_function :posix_spawnattr_getpgroup, [:pointer, :pointer], :int
|
80
|
+
|
81
|
+
# int posix_spawnattr_setsigdefault(posix_spawnattr_t *restrict attr, const sigset_t *restrict sigdefault);
|
82
|
+
attach_function :posix_spawnattr_setsigdefault, [:pointer, :pointer], :int
|
83
|
+
|
84
|
+
# int posix_spawnattr_getsigdefault(const posix_spawnattr_t *restrict attr, sigset_t *restrict sigdefault);
|
85
|
+
attach_function :posix_spawnattr_getsigdefault, [:pointer, :pointer], :int
|
86
|
+
|
87
|
+
# int posix_spawnattr_setsigmask(posix_spawnattr_t *restrict attr, const sigset_t *restrict sigmask);
|
88
|
+
attach_function :posix_spawnattr_setsigmask, [:pointer, :pointer], :int
|
89
|
+
|
90
|
+
# int posix_spawnattr_getsigmask(const posix_spawnattr_t *restrict attr, sigset_t *restrict sigmask);
|
91
|
+
attach_function :posix_spawnattr_getsigmask, [:pointer, :pointer], :int
|
92
|
+
|
93
|
+
def self.check(errno)
|
94
|
+
if errno != 0
|
95
|
+
raise Error, Lib.strerror(FFI.errno)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
class FileActions
|
100
|
+
def initialize
|
101
|
+
@ptr = FFI::MemoryPointer.new(1, Platform::SIZEOF.fetch(:posix_spawn_file_actions_t), false)
|
102
|
+
Lib.check Lib.posix_spawn_file_actions_init(@ptr)
|
103
|
+
end
|
104
|
+
|
105
|
+
def add_close(fileno)
|
106
|
+
Lib.check Lib.posix_spawn_file_actions_addclose(
|
107
|
+
@ptr,
|
108
|
+
fileno
|
109
|
+
)
|
110
|
+
end
|
111
|
+
|
112
|
+
def add_open(fileno, path, oflag, mode)
|
113
|
+
Lib.check Lib.posix_spawn_file_actions_addopen(
|
114
|
+
@ptr,
|
115
|
+
fileno,
|
116
|
+
path,
|
117
|
+
oflag,
|
118
|
+
mode
|
119
|
+
)
|
120
|
+
end
|
121
|
+
|
122
|
+
def add_dup(fileno, new_fileno)
|
123
|
+
Lib.check Lib.posix_spawn_file_actions_adddup2(
|
124
|
+
@ptr,
|
125
|
+
fileno,
|
126
|
+
new_fileno
|
127
|
+
)
|
128
|
+
end
|
129
|
+
|
130
|
+
def free
|
131
|
+
Lib.check Lib.posix_spawn_file_actions_destroy(@ptr)
|
132
|
+
@ptr = nil
|
133
|
+
end
|
134
|
+
|
135
|
+
def to_ptr
|
136
|
+
@ptr
|
137
|
+
end
|
138
|
+
end # FileActions
|
139
|
+
|
140
|
+
class Attrs
|
141
|
+
def initialize
|
142
|
+
@ptr = FFI::MemoryPointer.new(1, Platform::SIZEOF.fetch(:posix_spawnattr_t), false)
|
143
|
+
Lib.check Lib.posix_spawnattr_init(@ptr)
|
144
|
+
end
|
145
|
+
|
146
|
+
def free
|
147
|
+
Lib.check Lib.posix_spawnattr_destroy(@ptr)
|
148
|
+
@ptr = nil
|
149
|
+
end
|
150
|
+
|
151
|
+
def flags=(flags)
|
152
|
+
Lib.check Lib.posix_spawnattr_setflags(@ptr, flags)
|
153
|
+
end
|
154
|
+
|
155
|
+
def flags
|
156
|
+
ptr = FFI::MemoryPointer.new(:short)
|
157
|
+
Lib.check Lib.posix_spawnattr_getflags(@ptr, ptr)
|
158
|
+
|
159
|
+
ptr.read_short
|
160
|
+
end
|
161
|
+
|
162
|
+
def pgroup=(pid)
|
163
|
+
self.flags |= Platform::POSIX_SPAWN_SETPGROUP
|
164
|
+
Lib.check Lib.posix_spawnattr_setpgroup(@ptr, pid)
|
165
|
+
end
|
166
|
+
|
167
|
+
def to_ptr
|
168
|
+
@ptr
|
169
|
+
end
|
170
|
+
end # Attrs
|
171
|
+
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
# missing on rubinius
|
177
|
+
class FFI::MemoryPointer
|
178
|
+
unless method_defined?(:from_string)
|
179
|
+
def self.from_string(str)
|
180
|
+
ptr = new(1, str.bytesize + 1)
|
181
|
+
ptr.write_string("#{str}\0")
|
182
|
+
|
183
|
+
ptr
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module ChildProcess::Unix::Platform
|
2
|
+
SIZEOF = {
|
3
|
+
:posix_spawn_file_actions_t => 76,
|
4
|
+
:posix_spawnattr_t => 336,
|
5
|
+
:sigset_t => 128
|
6
|
+
}
|
7
|
+
POSIX_SPAWN_RESETIDS = 1
|
8
|
+
POSIX_SPAWN_SETPGROUP = 2
|
9
|
+
POSIX_SPAWN_SETSIGDEF = 4
|
10
|
+
POSIX_SPAWN_SETSIGMASK = 8
|
11
|
+
POSIX_SPAWN_USEVFORK = 64
|
12
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module ChildProcess::Unix::Platform
|
2
|
+
SIZEOF = {
|
3
|
+
:posix_spawn_file_actions_t => 80,
|
4
|
+
:posix_spawnattr_t => 336,
|
5
|
+
:sigset_t => 128
|
6
|
+
}
|
7
|
+
POSIX_SPAWN_RESETIDS = 1
|
8
|
+
POSIX_SPAWN_SETPGROUP = 2
|
9
|
+
POSIX_SPAWN_SETSIGDEF = 4
|
10
|
+
POSIX_SPAWN_SETSIGMASK = 8
|
11
|
+
POSIX_SPAWN_USEVFORK = 64
|
12
|
+
end
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require 'ffi'
|
2
|
+
require 'thread'
|
3
|
+
|
4
|
+
module ChildProcess
|
5
|
+
module Unix
|
6
|
+
class PosixSpawnProcess < Process
|
7
|
+
private
|
8
|
+
|
9
|
+
@@cwd_lock = Mutex.new
|
10
|
+
|
11
|
+
def launch_process
|
12
|
+
pid_ptr = FFI::MemoryPointer.new(:pid_t)
|
13
|
+
actions = Lib::FileActions.new
|
14
|
+
attrs = Lib::Attrs.new
|
15
|
+
|
16
|
+
if io.stdout
|
17
|
+
actions.add_dup fileno_for(io.stdout), fileno_for(STDOUT)
|
18
|
+
else
|
19
|
+
actions.add_open fileno_for(STDOUT), "/dev/null", File::WRONLY, 0644
|
20
|
+
end
|
21
|
+
|
22
|
+
if io.stderr
|
23
|
+
actions.add_dup fileno_for(io.stderr), fileno_for(STDERR)
|
24
|
+
else
|
25
|
+
actions.add_open fileno_for(STDERR), "/dev/null", File::WRONLY, 0644
|
26
|
+
end
|
27
|
+
|
28
|
+
if duplex?
|
29
|
+
reader, writer = ::IO.pipe
|
30
|
+
actions.add_dup fileno_for(reader), fileno_for(STDIN)
|
31
|
+
actions.add_close fileno_for(writer)
|
32
|
+
end
|
33
|
+
|
34
|
+
attrs.pgroup = 0 if leader?
|
35
|
+
attrs.flags |= Platform::POSIX_SPAWN_USEVFORK if defined? Platform::POSIX_SPAWN_USEVFORK
|
36
|
+
|
37
|
+
# wrap in helper classes in order to avoid GC'ed pointers
|
38
|
+
argv = Argv.new(@args)
|
39
|
+
envp = Envp.new(ENV.to_hash.merge(@environment))
|
40
|
+
|
41
|
+
ret = 0
|
42
|
+
@@cwd_lock.synchronize do
|
43
|
+
Dir.chdir(@cwd || Dir.pwd) do
|
44
|
+
if ChildProcess.jruby?
|
45
|
+
# on JRuby, the current working directory is for some reason not inherited.
|
46
|
+
# We'll work around it by making a chdir call through FFI.
|
47
|
+
# TODO: report this to JRuby
|
48
|
+
Lib.chdir Dir.pwd
|
49
|
+
end
|
50
|
+
|
51
|
+
ret = Lib.posix_spawnp(
|
52
|
+
pid_ptr,
|
53
|
+
@args.first, # TODO: not sure this matches exec() behaviour
|
54
|
+
actions,
|
55
|
+
attrs,
|
56
|
+
argv,
|
57
|
+
envp
|
58
|
+
)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
if duplex?
|
63
|
+
io._stdin = writer
|
64
|
+
reader.close
|
65
|
+
end
|
66
|
+
|
67
|
+
actions.free
|
68
|
+
attrs.free
|
69
|
+
|
70
|
+
if ret != 0
|
71
|
+
raise LaunchError, "#{Lib.strerror(ret)} (#{ret})"
|
72
|
+
end
|
73
|
+
|
74
|
+
@pid = pid_ptr.read_int
|
75
|
+
::Process.detach(@pid) if detach?
|
76
|
+
end
|
77
|
+
|
78
|
+
if ChildProcess.jruby?
|
79
|
+
def fileno_for(obj)
|
80
|
+
ChildProcess::JRuby.posix_fileno_for(obj)
|
81
|
+
end
|
82
|
+
else
|
83
|
+
def fileno_for(obj)
|
84
|
+
obj.fileno
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
class Argv
|
89
|
+
def initialize(args)
|
90
|
+
@ptrs = args.map do |e|
|
91
|
+
if e.include?("\0")
|
92
|
+
raise ArgumentError, "argument cannot contain null bytes: #{e.inspect}"
|
93
|
+
end
|
94
|
+
|
95
|
+
FFI::MemoryPointer.from_string(e.to_s)
|
96
|
+
end
|
97
|
+
|
98
|
+
@ptrs << FFI::Pointer.new(0)
|
99
|
+
end
|
100
|
+
|
101
|
+
def to_ptr
|
102
|
+
argv = FFI::MemoryPointer.new(:pointer, @ptrs.size)
|
103
|
+
argv.put_array_of_pointer(0, @ptrs)
|
104
|
+
|
105
|
+
argv
|
106
|
+
end
|
107
|
+
end # Argv
|
108
|
+
|
109
|
+
class Envp
|
110
|
+
def initialize(env)
|
111
|
+
@ptrs = env.map do |key, val|
|
112
|
+
next if val.nil?
|
113
|
+
|
114
|
+
if key =~ /=|\0/ || val.include?("\0")
|
115
|
+
raise InvalidEnvironmentVariable, "#{key.inspect} => #{val.inspect}"
|
116
|
+
end
|
117
|
+
|
118
|
+
FFI::MemoryPointer.from_string("#{key}=#{val}")
|
119
|
+
end.compact
|
120
|
+
|
121
|
+
@ptrs << FFI::Pointer.new(0)
|
122
|
+
end
|
123
|
+
|
124
|
+
def to_ptr
|
125
|
+
env = FFI::MemoryPointer.new(:pointer, @ptrs.size)
|
126
|
+
env.put_array_of_pointer(0, @ptrs)
|
127
|
+
|
128
|
+
env
|
129
|
+
end
|
130
|
+
end # Envp
|
131
|
+
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|