childprocess 0.2.6 → 0.2.7
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.
- data/.travis.yml +4 -1
- data/Rakefile +11 -0
- data/lib/childprocess.rb +70 -2
- data/lib/childprocess/errors.rb +12 -0
- data/lib/childprocess/tools/generator.rb +140 -0
- data/lib/childprocess/unix.rb +3 -1
- data/lib/childprocess/unix/fork_exec_process.rb +60 -0
- data/lib/childprocess/unix/lib.rb +171 -0
- data/lib/childprocess/unix/platform/i386-linux.rb +12 -0
- data/lib/childprocess/unix/platform/x86_64-linux.rb +11 -0
- data/lib/childprocess/unix/platform/x86_64-macosx.rb +11 -0
- data/lib/childprocess/unix/posix_spawn_process.rb +99 -0
- data/lib/childprocess/unix/process.rb +1 -52
- data/lib/childprocess/version.rb +1 -1
- data/lib/childprocess/windows/lib.rb +7 -8
- data/lib/childprocess/windows/process_builder.rb +16 -14
- data/spec/childprocess_spec.rb +8 -1
- data/spec/spec_helper.rb +4 -0
- data/spec/unix_spec.rb +1 -1
- metadata +23 -16
data/.travis.yml
CHANGED
data/Rakefile
CHANGED
@@ -49,3 +49,14 @@ task :jar => [:clean, :build] do
|
|
49
49
|
sh "jar cf childprocess.jar -C #{tmpdir}/gems/#{gem_name}/lib ."
|
50
50
|
sh "jar tf childprocess.jar"
|
51
51
|
end
|
52
|
+
|
53
|
+
task :env do
|
54
|
+
$:.unshift File.expand_path("../lib", __FILE__)
|
55
|
+
require 'childprocess'
|
56
|
+
end
|
57
|
+
|
58
|
+
desc 'Calculate size of posix_spawn structs for the current platform'
|
59
|
+
task :generate => :env do
|
60
|
+
require 'childprocess/tools/generator'
|
61
|
+
ChildProcess::Tools::Generator.generate
|
62
|
+
end
|
data/lib/childprocess.rb
CHANGED
@@ -16,7 +16,11 @@ module ChildProcess
|
|
16
16
|
when :windows
|
17
17
|
Windows::Process.new(args)
|
18
18
|
when :macosx, :linux, :unix, :cygwin
|
19
|
-
|
19
|
+
if posix_spawn?
|
20
|
+
Unix::PosixSpawnProcess.new(args)
|
21
|
+
else
|
22
|
+
Unix::ForkExecProcess.new(args)
|
23
|
+
end
|
20
24
|
else
|
21
25
|
raise Error, "unsupported platform #{platform.inspect}"
|
22
26
|
end
|
@@ -37,10 +41,18 @@ module ChildProcess
|
|
37
41
|
end
|
38
42
|
end
|
39
43
|
|
44
|
+
def platform_name
|
45
|
+
@platform_name ||= "#{arch}-#{os}"
|
46
|
+
end
|
47
|
+
|
40
48
|
def unix?
|
41
49
|
!jruby? && [:macosx, :linux, :unix].include?(os)
|
42
50
|
end
|
43
51
|
|
52
|
+
def linux?
|
53
|
+
os == :linux
|
54
|
+
end
|
55
|
+
|
44
56
|
def jruby?
|
45
57
|
platform == :jruby
|
46
58
|
end
|
@@ -53,10 +65,34 @@ module ChildProcess
|
|
53
65
|
!jruby? && os == :windows
|
54
66
|
end
|
55
67
|
|
68
|
+
def posix_spawn?
|
69
|
+
enabled = @posix_spawn || %w[1 true].include?(ENV['CHILDPROCESS_POSIX_SPAWN'])
|
70
|
+
return false unless enabled
|
71
|
+
|
72
|
+
require 'ffi'
|
73
|
+
begin
|
74
|
+
require "childprocess/unix/platform/#{ChildProcess.platform_name}"
|
75
|
+
rescue LoadError
|
76
|
+
raise ChildProcess::MissingPlatformError
|
77
|
+
end
|
78
|
+
|
79
|
+
require "childprocess/unix/lib"
|
80
|
+
require 'childprocess/unix/posix_spawn_process'
|
81
|
+
|
82
|
+
true
|
83
|
+
rescue ChildProcess::MissingPlatformError => ex
|
84
|
+
warn_once ex.message
|
85
|
+
false
|
86
|
+
end
|
87
|
+
|
88
|
+
def posix_spawn=(bool)
|
89
|
+
@posix_spawn = bool
|
90
|
+
end
|
91
|
+
|
56
92
|
def os
|
57
93
|
@os ||= (
|
58
94
|
require "rbconfig"
|
59
|
-
host_os = RbConfig::CONFIG['host_os']
|
95
|
+
host_os = RbConfig::CONFIG['host_os'].downcase
|
60
96
|
|
61
97
|
case host_os
|
62
98
|
when /mswin|msys|mingw32|cygwin/
|
@@ -73,6 +109,27 @@ module ChildProcess
|
|
73
109
|
)
|
74
110
|
end
|
75
111
|
|
112
|
+
def arch
|
113
|
+
@arch ||= (
|
114
|
+
host_cpu = RbConfig::CONFIG['host_cpu'].downcase
|
115
|
+
case host_cpu
|
116
|
+
when /i[3456]86/
|
117
|
+
# Darwin always reports i686, even when running in 64bit mod
|
118
|
+
if os == :macosx && 0xfee1deadbeef.is_a?(Fixnum)
|
119
|
+
"x86_64"
|
120
|
+
else
|
121
|
+
"i386"
|
122
|
+
end
|
123
|
+
when /amd64|x86_64/
|
124
|
+
"x86_64"
|
125
|
+
when /ppc|powerpc/
|
126
|
+
"powerpc"
|
127
|
+
else
|
128
|
+
host_cpu
|
129
|
+
end
|
130
|
+
)
|
131
|
+
end
|
132
|
+
|
76
133
|
#
|
77
134
|
# By default, a child process will inherit open file descriptors from the
|
78
135
|
# parent process. This helper provides a cross-platform way of making sure
|
@@ -91,5 +148,16 @@ module ChildProcess
|
|
91
148
|
end
|
92
149
|
end
|
93
150
|
|
151
|
+
private
|
152
|
+
|
153
|
+
def warn_once(msg)
|
154
|
+
@warnings ||= {}
|
155
|
+
|
156
|
+
unless @warnings[msg]
|
157
|
+
@warnings[msg] = true
|
158
|
+
$stderr.puts msg
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
94
162
|
end # class << self
|
95
163
|
end # ChildProcess
|
data/lib/childprocess/errors.rb
CHANGED
@@ -4,4 +4,16 @@ module ChildProcess
|
|
4
4
|
class SubclassResponsibility < StandardError; end
|
5
5
|
class InvalidEnvironmentVariableName < StandardError; end
|
6
6
|
class LaunchError < StandardError; end
|
7
|
+
|
8
|
+
class MissingPlatformError < StandardError
|
9
|
+
def initialize
|
10
|
+
platform = ChildProcess.platform_name
|
11
|
+
|
12
|
+
message = "posix_spawn is not yet supported on #{ChildProcess.platform_name} (#{RUBY_PLATFORM}), falling back to fork/exec. " +
|
13
|
+
"Please file a bug at http://github.com/jarib/childprocess/issues"
|
14
|
+
|
15
|
+
super(message)
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
7
19
|
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
require 'ffi'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
module ChildProcess
|
5
|
+
module Tools
|
6
|
+
class Generator
|
7
|
+
EXE_NAME = "childprocess-sizeof-generator"
|
8
|
+
TMP_PROGRAM = "childprocess-sizeof-generator.c"
|
9
|
+
DEFAULT_INCLUDES = %w[stdio.h stddef.h]
|
10
|
+
|
11
|
+
def self.generate
|
12
|
+
new.generate
|
13
|
+
end
|
14
|
+
|
15
|
+
def initialize
|
16
|
+
@cc = ENV['CC'] || 'gcc'
|
17
|
+
@out = File.expand_path("../../unix/platform/#{ChildProcess.platform_name}.rb", __FILE__)
|
18
|
+
@sizeof = {}
|
19
|
+
@constants = {}
|
20
|
+
end
|
21
|
+
|
22
|
+
def generate
|
23
|
+
fetch_size 'posix_spawn_file_actions_t', :include => "spawn.h"
|
24
|
+
fetch_size 'posix_spawnattr_t', :include => "spawn.h"
|
25
|
+
fetch_size 'sigset_t', :include => "signal.h"
|
26
|
+
|
27
|
+
fetch_constant 'POSIX_SPAWN_RESETIDS', :include => 'spawn.h'
|
28
|
+
fetch_constant 'POSIX_SPAWN_SETPGROUP', :include => 'spawn.h'
|
29
|
+
fetch_constant 'POSIX_SPAWN_SETSIGDEF', :include => 'spawn.h'
|
30
|
+
fetch_constant 'POSIX_SPAWN_SETSIGMASK', :include => 'spawn.h'
|
31
|
+
|
32
|
+
if ChildProcess.linux?
|
33
|
+
fetch_constant 'POSIX_SPAWN_USEVFORK', :include => 'spawn.h', :define => {'_GNU_SOURCE' => nil}
|
34
|
+
end
|
35
|
+
|
36
|
+
write
|
37
|
+
end
|
38
|
+
|
39
|
+
def write
|
40
|
+
FileUtils.mkdir_p(File.dirname(@out))
|
41
|
+
File.open(@out, 'w') do |io|
|
42
|
+
io.puts result
|
43
|
+
end
|
44
|
+
|
45
|
+
puts "wrote #{@out}"
|
46
|
+
end
|
47
|
+
|
48
|
+
def fetch_size(type_name, opts = {})
|
49
|
+
src = <<-EOF
|
50
|
+
int main() {
|
51
|
+
printf("%d", (unsigned int)sizeof(#{type_name}));
|
52
|
+
return 0;
|
53
|
+
}
|
54
|
+
EOF
|
55
|
+
|
56
|
+
output = execute(src, opts)
|
57
|
+
|
58
|
+
if output.to_i < 1
|
59
|
+
raise "sizeof(#{type_name}) == #{output.to_i} (output=#{output})"
|
60
|
+
end
|
61
|
+
|
62
|
+
@sizeof[type_name] = output.to_i
|
63
|
+
end
|
64
|
+
|
65
|
+
def fetch_constant(name, opts)
|
66
|
+
src = <<-EOF
|
67
|
+
int main() {
|
68
|
+
printf("%d", (unsigned int)#{name});
|
69
|
+
return 0;
|
70
|
+
}
|
71
|
+
EOF
|
72
|
+
|
73
|
+
output = execute(src, opts)
|
74
|
+
@constants[name] = Integer(output)
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
def execute(src, opts)
|
79
|
+
|
80
|
+
program = Array(opts[:define]).map do |key, value|
|
81
|
+
<<-SRC
|
82
|
+
#ifndef #{key}
|
83
|
+
#define #{key} #{value}
|
84
|
+
#endif
|
85
|
+
SRC
|
86
|
+
end.join("\n")
|
87
|
+
program << "\n"
|
88
|
+
|
89
|
+
includes = Array(opts[:include]) + DEFAULT_INCLUDES
|
90
|
+
program << includes.map { |include| "#include <#{include}>" }.join("\n")
|
91
|
+
program << "\n#{src}"
|
92
|
+
|
93
|
+
File.open(TMP_PROGRAM, 'w') do |file|
|
94
|
+
file << program
|
95
|
+
end
|
96
|
+
|
97
|
+
cmd = "#{@cc} #{TMP_PROGRAM} -o #{EXE_NAME}"
|
98
|
+
system cmd
|
99
|
+
unless $?.success?
|
100
|
+
raise "failed to compile program: #{cmd.inspect}\n#{program}"
|
101
|
+
end
|
102
|
+
|
103
|
+
output = `./#{EXE_NAME} 2>&1`
|
104
|
+
|
105
|
+
unless $?.success?
|
106
|
+
raise "failed to run program: #{cmd.inspect}\n#{output}"
|
107
|
+
end
|
108
|
+
|
109
|
+
output.chomp
|
110
|
+
ensure
|
111
|
+
File.delete TMP_PROGRAM if File.exist?(TMP_PROGRAM)
|
112
|
+
File.delete EXE_NAME if File.exist?(EXE_NAME)
|
113
|
+
end
|
114
|
+
|
115
|
+
def result
|
116
|
+
if @sizeof.empty?
|
117
|
+
raise "no sizes collected, nothing to do"
|
118
|
+
end
|
119
|
+
|
120
|
+
out = ['module ChildProcess::Unix::Platform']
|
121
|
+
out << ' SIZEOF = {'
|
122
|
+
|
123
|
+
max = @sizeof.keys.map { |e| e.length }.max
|
124
|
+
@sizeof.each_with_index do |(type, size), idx|
|
125
|
+
out << " :#{type.ljust max} => #{size}#{',' unless idx == @sizeof.size - 1}"
|
126
|
+
end
|
127
|
+
out << ' }'
|
128
|
+
|
129
|
+
max = @constants.keys.map { |e| e.length }.max
|
130
|
+
@constants.each do |name, val|
|
131
|
+
out << " #{name.ljust max} = #{val}"
|
132
|
+
end
|
133
|
+
out << 'end'
|
134
|
+
|
135
|
+
out.join "\n"
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
data/lib/childprocess/unix.rb
CHANGED
@@ -0,0 +1,60 @@
|
|
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 = fork {
|
21
|
+
exec_r.close
|
22
|
+
set_env
|
23
|
+
|
24
|
+
STDOUT.reopen(stdout || "/dev/null")
|
25
|
+
STDERR.reopen(stderr || "/dev/null")
|
26
|
+
|
27
|
+
if duplex?
|
28
|
+
STDIN.reopen(reader)
|
29
|
+
writer.close
|
30
|
+
end
|
31
|
+
|
32
|
+
begin
|
33
|
+
exec(*@args)
|
34
|
+
rescue SystemCallError => ex
|
35
|
+
exec_w << ex.message
|
36
|
+
end
|
37
|
+
}
|
38
|
+
|
39
|
+
exec_w.close
|
40
|
+
|
41
|
+
if duplex?
|
42
|
+
io._stdin = writer
|
43
|
+
reader.close
|
44
|
+
end
|
45
|
+
|
46
|
+
# if we don't eventually get EOF, exec() failed
|
47
|
+
unless exec_r.eof?
|
48
|
+
raise LaunchError, exec_r.read || "executing command with #{@args.inspect} failed"
|
49
|
+
end
|
50
|
+
|
51
|
+
::Process.detach(@pid) if detach?
|
52
|
+
end
|
53
|
+
|
54
|
+
def set_env
|
55
|
+
@environment.each { |k, v| ENV[k.to_s] = v.to_s }
|
56
|
+
end
|
57
|
+
|
58
|
+
end # Process
|
59
|
+
end # Unix
|
60
|
+
end # ChildProcess
|
@@ -0,0 +1,171 @@
|
|
1
|
+
module ChildProcess
|
2
|
+
module Unix
|
3
|
+
module Lib
|
4
|
+
extend FFI::Library
|
5
|
+
|
6
|
+
ffi_lib FFI::Library::LIBC
|
7
|
+
|
8
|
+
attach_function :strerror, [:int], :string
|
9
|
+
|
10
|
+
# int posix_spawnp(
|
11
|
+
# pid_t *restrict pid,
|
12
|
+
# const char *restrict file,
|
13
|
+
# const posix_spawn_file_actions_t *file_actions,
|
14
|
+
# const posix_spawnattr_t *restrict attrp,
|
15
|
+
# char *const argv[restrict],
|
16
|
+
# char *const envp[restrict]
|
17
|
+
# );
|
18
|
+
|
19
|
+
attach_function :posix_spawnp, [
|
20
|
+
:pointer,
|
21
|
+
:string,
|
22
|
+
:pointer,
|
23
|
+
:pointer,
|
24
|
+
:pointer,
|
25
|
+
:pointer
|
26
|
+
], :int
|
27
|
+
|
28
|
+
# int posix_spawn_file_actions_init(posix_spawn_file_actions_t *file_actions);
|
29
|
+
attach_function :posix_spawn_file_actions_init, [:pointer], :int
|
30
|
+
|
31
|
+
# int posix_spawn_file_actions_destroy(posix_spawn_file_actions_t *file_actions);
|
32
|
+
attach_function :posix_spawn_file_actions_destroy, [:pointer], :int
|
33
|
+
|
34
|
+
# int posix_spawn_file_actions_addclose(posix_spawn_file_actions_t *file_actions, int filedes);
|
35
|
+
attach_function :posix_spawn_file_actions_addclose, [:pointer, :int], :int
|
36
|
+
|
37
|
+
# int posix_spawn_file_actions_addopen(
|
38
|
+
# posix_spawn_file_actions_t *restrict file_actions,
|
39
|
+
# int filedes,
|
40
|
+
# const char *restrict path,
|
41
|
+
# int oflag,
|
42
|
+
# mode_t mode
|
43
|
+
# );
|
44
|
+
attach_function :posix_spawn_file_actions_addopen, [:pointer, :int, :string, :int, :mode_t], :int
|
45
|
+
|
46
|
+
# int posix_spawn_file_actions_adddup2(
|
47
|
+
# posix_spawn_file_actions_t *file_actions,
|
48
|
+
# int filedes,
|
49
|
+
# int newfiledes
|
50
|
+
# );
|
51
|
+
attach_function :posix_spawn_file_actions_adddup2, [:pointer, :int, :int], :int
|
52
|
+
|
53
|
+
# int posix_spawnattr_init(posix_spawnattr_t *attr);
|
54
|
+
attach_function :posix_spawnattr_init, [:pointer], :int
|
55
|
+
|
56
|
+
# int posix_spawnattr_destroy(posix_spawnattr_t *attr);
|
57
|
+
attach_function :posix_spawnattr_destroy, [:pointer], :int
|
58
|
+
|
59
|
+
# int posix_spawnattr_setflags(posix_spawnattr_t *attr, short flags);
|
60
|
+
attach_function :posix_spawnattr_setflags, [:pointer, :short], :int
|
61
|
+
|
62
|
+
# int posix_spawnattr_getflags(const posix_spawnattr_t *restrict attr, short *restrict flags);
|
63
|
+
attach_function :posix_spawnattr_getflags, [:pointer, :pointer], :int
|
64
|
+
|
65
|
+
# int posix_spawnattr_setpgroup(posix_spawnattr_t *attr, pid_t pgroup);
|
66
|
+
attach_function :posix_spawnattr_setpgroup, [:pointer, :pid_t], :int
|
67
|
+
|
68
|
+
# int posix_spawnattr_getpgroup(const posix_spawnattr_t *restrict attr, pid_t *restrict pgroup);
|
69
|
+
attach_function :posix_spawnattr_getpgroup, [:pointer, :pointer], :int
|
70
|
+
|
71
|
+
# int posix_spawnattr_setsigdefault(posix_spawnattr_t *restrict attr, const sigset_t *restrict sigdefault);
|
72
|
+
attach_function :posix_spawnattr_setsigdefault, [:pointer, :pointer], :int
|
73
|
+
|
74
|
+
# int posix_spawnattr_getsigdefault(const posix_spawnattr_t *restrict attr, sigset_t *restrict sigdefault);
|
75
|
+
attach_function :posix_spawnattr_getsigdefault, [:pointer, :pointer], :int
|
76
|
+
|
77
|
+
# int posix_spawnattr_setsigmask(posix_spawnattr_t *restrict attr, const sigset_t *restrict sigmask);
|
78
|
+
attach_function :posix_spawnattr_setsigmask, [:pointer, :pointer], :int
|
79
|
+
|
80
|
+
# int posix_spawnattr_getsigmask(const posix_spawnattr_t *restrict attr, sigset_t *restrict sigmask);
|
81
|
+
attach_function :posix_spawnattr_getsigmask, [:pointer, :pointer], :int
|
82
|
+
|
83
|
+
def self.check(errno)
|
84
|
+
if errno != 0
|
85
|
+
raise Error, Lib.strerror(errno)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
class FileActions
|
90
|
+
def initialize
|
91
|
+
@ptr = FFI::MemoryPointer.new(1, Platform::SIZEOF.fetch(:posix_spawn_file_actions_t), false)
|
92
|
+
Lib.check Lib.posix_spawn_file_actions_init(@ptr)
|
93
|
+
end
|
94
|
+
|
95
|
+
def add_close(fileno)
|
96
|
+
Lib.check Lib.posix_spawn_file_actions_addclose(
|
97
|
+
@ptr,
|
98
|
+
fileno
|
99
|
+
)
|
100
|
+
end
|
101
|
+
|
102
|
+
def add_open(fileno, path, oflag, mode)
|
103
|
+
Lib.check Lib.posix_spawn_file_actions_addopen(
|
104
|
+
@ptr,
|
105
|
+
fileno,
|
106
|
+
path,
|
107
|
+
oflag,
|
108
|
+
mode
|
109
|
+
)
|
110
|
+
end
|
111
|
+
|
112
|
+
def add_dup(fileno, new_fileno)
|
113
|
+
Lib.check Lib.posix_spawn_file_actions_adddup2(
|
114
|
+
@ptr,
|
115
|
+
fileno,
|
116
|
+
new_fileno
|
117
|
+
)
|
118
|
+
end
|
119
|
+
|
120
|
+
def free
|
121
|
+
Lib.check Lib.posix_spawn_file_actions_destroy(@ptr)
|
122
|
+
@ptr = nil
|
123
|
+
end
|
124
|
+
|
125
|
+
def to_ptr
|
126
|
+
@ptr
|
127
|
+
end
|
128
|
+
end # FileActions
|
129
|
+
|
130
|
+
class Attrs
|
131
|
+
def initialize
|
132
|
+
@ptr = FFI::MemoryPointer.new(1, Platform::SIZEOF.fetch(:posix_spawnattr_t), false)
|
133
|
+
Lib.check Lib.posix_spawnattr_init(@ptr)
|
134
|
+
end
|
135
|
+
|
136
|
+
def free
|
137
|
+
Lib.check Lib.posix_spawnattr_destroy(@ptr)
|
138
|
+
@ptr = nil
|
139
|
+
end
|
140
|
+
|
141
|
+
def flags=(flags)
|
142
|
+
Lib.check Lib.posix_spawnattr_setflags(@ptr, flags)
|
143
|
+
end
|
144
|
+
|
145
|
+
def flags
|
146
|
+
ptr = FFI::MemoryPointer.new(:short)
|
147
|
+
Lib.check Lib.posix_spawnattr_getflags(@ptr, ptr)
|
148
|
+
|
149
|
+
ptr.read_short
|
150
|
+
end
|
151
|
+
|
152
|
+
def to_ptr
|
153
|
+
@ptr
|
154
|
+
end
|
155
|
+
end # Attrs
|
156
|
+
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
# missing on rubinius
|
162
|
+
class FFI::MemoryPointer
|
163
|
+
unless method_defined?(:from_string)
|
164
|
+
def self.from_string(str)
|
165
|
+
ptr = new(1, str.bytesize + 1)
|
166
|
+
ptr.write_string("#{str}\0")
|
167
|
+
|
168
|
+
ptr
|
169
|
+
end
|
170
|
+
end
|
171
|
+
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,11 @@
|
|
1
|
+
module ChildProcess::Unix::Platform
|
2
|
+
SIZEOF = {
|
3
|
+
:posix_spawn_file_actions_t => 80,
|
4
|
+
:posix_spawnattr_t => 336
|
5
|
+
}
|
6
|
+
POSIX_SPAWN_RESETIDS = 1
|
7
|
+
POSIX_SPAWN_SETPGROUP = 2
|
8
|
+
POSIX_SPAWN_SETSIGDEF = 4
|
9
|
+
POSIX_SPAWN_SETSIGMASK = 8
|
10
|
+
POSIX_SPAWN_USEVFORK = 64
|
11
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'ffi'
|
2
|
+
|
3
|
+
module ChildProcess
|
4
|
+
module Unix
|
5
|
+
class PosixSpawnProcess < Process
|
6
|
+
private
|
7
|
+
|
8
|
+
def launch_process
|
9
|
+
pid_ptr = FFI::MemoryPointer.new(:pid_t)
|
10
|
+
actions = Lib::FileActions.new
|
11
|
+
attrs = Lib::Attrs.new
|
12
|
+
flags = 0
|
13
|
+
|
14
|
+
if @io
|
15
|
+
if @io.stdout
|
16
|
+
actions.add_dup @io.stdout.fileno, $stdout.fileno
|
17
|
+
else
|
18
|
+
actions.add_open $stdout.fileno, "/dev/null", File::WRONLY, 0644
|
19
|
+
end
|
20
|
+
|
21
|
+
if @io.stderr
|
22
|
+
actions.add_dup @io.stderr.fileno, $stderr.fileno
|
23
|
+
else
|
24
|
+
actions.add_open $stderr.fileno, "/dev/null", File::WRONLY, 0644
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
if duplex?
|
29
|
+
reader, writer = ::IO.pipe
|
30
|
+
actions.add_dup reader.fileno, $stdin.fileno
|
31
|
+
actions.add_close writer.fileno
|
32
|
+
end
|
33
|
+
|
34
|
+
if defined? Platform::POSIX_SPAWN_USEVFORK
|
35
|
+
flags |= Platform::POSIX_SPAWN_USEVFORK
|
36
|
+
end
|
37
|
+
|
38
|
+
attrs.flags = flags
|
39
|
+
|
40
|
+
ret = Lib.posix_spawnp(
|
41
|
+
pid_ptr,
|
42
|
+
@args.first, # TODO: not sure this matches exec() behaviour
|
43
|
+
actions,
|
44
|
+
attrs,
|
45
|
+
argv,
|
46
|
+
env
|
47
|
+
)
|
48
|
+
|
49
|
+
if duplex?
|
50
|
+
io._stdin = writer
|
51
|
+
reader.close
|
52
|
+
end
|
53
|
+
|
54
|
+
actions.free
|
55
|
+
attrs.free
|
56
|
+
|
57
|
+
if ret != 0
|
58
|
+
raise LaunchError, "#{Lib.strerror(ret)} (#{ret})"
|
59
|
+
end
|
60
|
+
|
61
|
+
@pid = pid_ptr.read_int
|
62
|
+
::Process.detach(@pid) if detach?
|
63
|
+
end
|
64
|
+
|
65
|
+
def argv
|
66
|
+
arg_ptrs = @args.map do |e|
|
67
|
+
if e.include?("\0")
|
68
|
+
raise ArgumentError, "argument cannot contain null bytes: #{e.inspect}"
|
69
|
+
end
|
70
|
+
FFI::MemoryPointer.from_string(e.to_s)
|
71
|
+
end
|
72
|
+
arg_ptrs << nil
|
73
|
+
|
74
|
+
argv = FFI::MemoryPointer.new(:pointer, arg_ptrs.size)
|
75
|
+
argv.put_array_of_pointer(0, arg_ptrs)
|
76
|
+
|
77
|
+
argv
|
78
|
+
end
|
79
|
+
|
80
|
+
def env
|
81
|
+
env_ptrs = ENV.to_hash.merge(@environment).map do |key, val|
|
82
|
+
if key.include?("=")
|
83
|
+
raise InvalidEnvironmentVariableName, key
|
84
|
+
end
|
85
|
+
|
86
|
+
FFI::MemoryPointer.from_string("#{key}=#{val}")
|
87
|
+
end
|
88
|
+
|
89
|
+
env_ptrs << nil
|
90
|
+
|
91
|
+
env = FFI::MemoryPointer.new(:pointer, env_ptrs.size)
|
92
|
+
env.put_array_of_pointer(0, env_ptrs)
|
93
|
+
|
94
|
+
env
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -41,6 +41,7 @@ module ChildProcess
|
|
41
41
|
end
|
42
42
|
|
43
43
|
def wait
|
44
|
+
assert_started
|
44
45
|
pid, status = ::Process.waitpid2 @pid
|
45
46
|
|
46
47
|
@exit_code = status.exitstatus || status.termsig
|
@@ -63,58 +64,6 @@ module ChildProcess
|
|
63
64
|
::Process.kill sig, @pid
|
64
65
|
end
|
65
66
|
|
66
|
-
def launch_process
|
67
|
-
if @io
|
68
|
-
stdout = @io.stdout
|
69
|
-
stderr = @io.stderr
|
70
|
-
end
|
71
|
-
|
72
|
-
# pipe used to detect exec() failure
|
73
|
-
exec_r, exec_w = ::IO.pipe
|
74
|
-
ChildProcess.close_on_exec exec_w
|
75
|
-
|
76
|
-
if duplex?
|
77
|
-
reader, writer = ::IO.pipe
|
78
|
-
end
|
79
|
-
|
80
|
-
@pid = fork {
|
81
|
-
exec_r.close
|
82
|
-
set_env
|
83
|
-
|
84
|
-
STDOUT.reopen(stdout || "/dev/null")
|
85
|
-
STDERR.reopen(stderr || "/dev/null")
|
86
|
-
|
87
|
-
if duplex?
|
88
|
-
STDIN.reopen(reader)
|
89
|
-
writer.close
|
90
|
-
end
|
91
|
-
|
92
|
-
begin
|
93
|
-
exec(*@args)
|
94
|
-
rescue SystemCallError => ex
|
95
|
-
exec_w << ex.message
|
96
|
-
end
|
97
|
-
}
|
98
|
-
|
99
|
-
exec_w.close
|
100
|
-
|
101
|
-
if duplex?
|
102
|
-
io._stdin = writer
|
103
|
-
reader.close
|
104
|
-
end
|
105
|
-
|
106
|
-
# if we don't eventually get EOF, exec() failed
|
107
|
-
unless exec_r.eof?
|
108
|
-
raise LaunchError, exec_r.read || "executing command with #{@args.inspect} failed"
|
109
|
-
end
|
110
|
-
|
111
|
-
::Process.detach(@pid) if detach?
|
112
|
-
end
|
113
|
-
|
114
|
-
def set_env
|
115
|
-
@environment.each { |k, v| ENV[k.to_s] = v.to_s }
|
116
|
-
end
|
117
|
-
|
118
67
|
end # Process
|
119
68
|
end # Unix
|
120
69
|
end # ChildProcess
|
data/lib/childprocess/version.rb
CHANGED
@@ -20,11 +20,10 @@ module ChildProcess
|
|
20
20
|
DETACHED_PROCESS = 0x00000008
|
21
21
|
|
22
22
|
STARTF_USESTDHANDLES = 0x00000100
|
23
|
-
INVALID_HANDLE_VALUE =
|
23
|
+
INVALID_HANDLE_VALUE = -1
|
24
24
|
HANDLE_FLAG_INHERIT = 0x00000001
|
25
25
|
|
26
26
|
DUPLICATE_SAME_ACCESS = 0x00000002
|
27
|
-
|
28
27
|
CREATE_UNICODE_ENVIRONMENT = 0x00000400
|
29
28
|
|
30
29
|
module Lib
|
@@ -161,14 +160,14 @@ module ChildProcess
|
|
161
160
|
# __in DWORD dwFlags
|
162
161
|
# );
|
163
162
|
|
164
|
-
attach_function :set_handle_information, :SetHandleInformation, [:
|
163
|
+
attach_function :set_handle_information, :SetHandleInformation, [:pointer, :ulong, :ulong], :bool
|
165
164
|
|
166
165
|
# BOOL WINAPI GetHandleInformation(
|
167
166
|
# __in HANDLE hObject,
|
168
167
|
# __out LPDWORD lpdwFlags
|
169
168
|
# );
|
170
169
|
|
171
|
-
attach_function :get_handle_information, :GetHandleInformation, [:
|
170
|
+
attach_function :get_handle_information, :GetHandleInformation, [:pointer, :pointer], :bool
|
172
171
|
|
173
172
|
# BOOL WINAPI CreatePipe(
|
174
173
|
# __out PHANDLE hReadPipe,
|
@@ -248,8 +247,8 @@ module ChildProcess
|
|
248
247
|
buf = FFI::MemoryPointer.new :char, 512
|
249
248
|
|
250
249
|
size = format_message(
|
251
|
-
|
252
|
-
|
250
|
+
FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ARGUMENT_ARRAY,
|
251
|
+
nil, errnum, 0, buf, buf.size, nil
|
253
252
|
)
|
254
253
|
|
255
254
|
str = buf.read_string(size).strip
|
@@ -280,7 +279,7 @@ module ChildProcess
|
|
280
279
|
raise Error, last_error_message
|
281
280
|
end
|
282
281
|
|
283
|
-
handle
|
282
|
+
FFI::Pointer.new handle
|
284
283
|
end
|
285
284
|
|
286
285
|
def io_for(handle, flags = File::RDONLY)
|
@@ -289,7 +288,7 @@ module ChildProcess
|
|
289
288
|
raise Error, last_error_message
|
290
289
|
end
|
291
290
|
|
292
|
-
::IO.for_fd fd, flags
|
291
|
+
FFI::IO.for_fd fd, flags
|
293
292
|
end
|
294
293
|
|
295
294
|
def duplicate_handle(handle)
|
@@ -60,7 +60,7 @@ module ChildProcess
|
|
60
60
|
env_str = strings.join
|
61
61
|
|
62
62
|
@env_ptr = FFI::MemoryPointer.new(:long, env_str.bytesize)
|
63
|
-
@env_ptr.
|
63
|
+
@env_ptr.put_bytes 0, env_str, 0, env_str.bytesize
|
64
64
|
end
|
65
65
|
|
66
66
|
def create_process
|
@@ -124,24 +124,15 @@ module ChildProcess
|
|
124
124
|
@read_pipe = read_pipe_ptr.read_pointer
|
125
125
|
@write_pipe = write_pipe_ptr.read_pointer
|
126
126
|
|
127
|
-
|
127
|
+
@inherit = true
|
128
|
+
Lib.set_handle_inheritance @read_pipe, true
|
129
|
+
Lib.set_handle_inheritance @write_pipe, false
|
128
130
|
|
129
131
|
startup_info[:hStdInput] = @read_pipe
|
130
132
|
end
|
131
133
|
|
132
|
-
def close_handles
|
133
|
-
Lib.close_handle process_info[:hProcess]
|
134
|
-
Lib.close_handle process_info[:hThread]
|
135
|
-
|
136
|
-
if @duplex
|
137
|
-
@stdin = Lib.io_for(Lib.duplicate_handle(@write_pipe), File::WRONLY)
|
138
|
-
Lib.close_handle @read_pipe
|
139
|
-
Lib.close_handle @write_pipe
|
140
|
-
end
|
141
|
-
end
|
142
|
-
|
143
134
|
def std_stream_handle_for(io)
|
144
|
-
handle = Lib.handle_for(io
|
135
|
+
handle = Lib.handle_for(io)
|
145
136
|
|
146
137
|
begin
|
147
138
|
Lib.set_handle_inheritance handle, true
|
@@ -155,6 +146,17 @@ module ChildProcess
|
|
155
146
|
handle
|
156
147
|
end
|
157
148
|
|
149
|
+
def close_handles
|
150
|
+
Lib.close_handle process_info[:hProcess]
|
151
|
+
Lib.close_handle process_info[:hThread]
|
152
|
+
|
153
|
+
if @duplex
|
154
|
+
@stdin = Lib.io_for(Lib.duplicate_handle(@write_pipe), File::WRONLY)
|
155
|
+
Lib.close_handle @read_pipe
|
156
|
+
Lib.close_handle @write_pipe
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
158
160
|
def quote_if_necessary(str)
|
159
161
|
quote = str.start_with?('"') ? "'" : '"'
|
160
162
|
|
data/spec/childprocess_spec.rb
CHANGED
@@ -10,7 +10,14 @@ describe ChildProcess do
|
|
10
10
|
process.should be_started
|
11
11
|
end
|
12
12
|
|
13
|
-
|
13
|
+
# We can't detect failure to execve() when using posix_spawn() on Linux
|
14
|
+
# without waiting for the child to exit with code 127.
|
15
|
+
#
|
16
|
+
# See e.g. http://repo.or.cz/w/glibc.git/blob/669704fd:/sysdeps/posix/spawni.c#l34
|
17
|
+
#
|
18
|
+
# We could work around this by doing the PATH search ourselves, but not sure
|
19
|
+
# it's worth it.
|
20
|
+
it "raises ChildProcess::LaunchError if the process can't be started", :posix_spawn_on_linux => false do
|
14
21
|
lambda { invalid_process.start }.should raise_error(ChildProcess::LaunchError)
|
15
22
|
end
|
16
23
|
|
data/spec/spec_helper.rb
CHANGED
data/spec/unix_spec.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: childprocess
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 25
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 2
|
9
|
-
-
|
10
|
-
version: 0.2.
|
9
|
+
- 7
|
10
|
+
version: 0.2.7
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Jari Bakken
|
@@ -15,10 +15,11 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2012-01-
|
18
|
+
date: 2012-01-09 00:00:00 Z
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
21
|
-
|
21
|
+
name: rspec
|
22
|
+
prerelease: false
|
22
23
|
requirement: &id001 !ruby/object:Gem::Requirement
|
23
24
|
none: false
|
24
25
|
requirements:
|
@@ -30,11 +31,11 @@ dependencies:
|
|
30
31
|
- 0
|
31
32
|
- 0
|
32
33
|
version: 2.0.0
|
33
|
-
|
34
|
-
name: rspec
|
34
|
+
type: :development
|
35
35
|
version_requirements: *id001
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
|
-
|
37
|
+
name: yard
|
38
|
+
prerelease: false
|
38
39
|
requirement: &id002 !ruby/object:Gem::Requirement
|
39
40
|
none: false
|
40
41
|
requirements:
|
@@ -44,11 +45,11 @@ dependencies:
|
|
44
45
|
segments:
|
45
46
|
- 0
|
46
47
|
version: "0"
|
47
|
-
|
48
|
-
name: yard
|
48
|
+
type: :development
|
49
49
|
version_requirements: *id002
|
50
50
|
- !ruby/object:Gem::Dependency
|
51
|
-
|
51
|
+
name: rake
|
52
|
+
prerelease: false
|
52
53
|
requirement: &id003 !ruby/object:Gem::Requirement
|
53
54
|
none: false
|
54
55
|
requirements:
|
@@ -60,11 +61,11 @@ dependencies:
|
|
60
61
|
- 9
|
61
62
|
- 2
|
62
63
|
version: 0.9.2
|
63
|
-
|
64
|
-
name: rake
|
64
|
+
type: :development
|
65
65
|
version_requirements: *id003
|
66
66
|
- !ruby/object:Gem::Dependency
|
67
|
-
|
67
|
+
name: ffi
|
68
|
+
prerelease: false
|
68
69
|
requirement: &id004 !ruby/object:Gem::Requirement
|
69
70
|
none: false
|
70
71
|
requirements:
|
@@ -76,8 +77,7 @@ dependencies:
|
|
76
77
|
- 0
|
77
78
|
- 6
|
78
79
|
version: 1.0.6
|
79
|
-
|
80
|
-
name: ffi
|
80
|
+
type: :runtime
|
81
81
|
version_requirements: *id004
|
82
82
|
description: This gem aims at being a simple and reliable solution for controlling external programs running in the background on any Ruby / OS combination.
|
83
83
|
email:
|
@@ -106,8 +106,15 @@ files:
|
|
106
106
|
- lib/childprocess/jruby/io.rb
|
107
107
|
- lib/childprocess/jruby/process.rb
|
108
108
|
- lib/childprocess/jruby/pump.rb
|
109
|
+
- lib/childprocess/tools/generator.rb
|
109
110
|
- lib/childprocess/unix.rb
|
111
|
+
- lib/childprocess/unix/fork_exec_process.rb
|
110
112
|
- lib/childprocess/unix/io.rb
|
113
|
+
- lib/childprocess/unix/lib.rb
|
114
|
+
- lib/childprocess/unix/platform/i386-linux.rb
|
115
|
+
- lib/childprocess/unix/platform/x86_64-linux.rb
|
116
|
+
- lib/childprocess/unix/platform/x86_64-macosx.rb
|
117
|
+
- lib/childprocess/unix/posix_spawn_process.rb
|
111
118
|
- lib/childprocess/unix/process.rb
|
112
119
|
- lib/childprocess/version.rb
|
113
120
|
- lib/childprocess/windows.rb
|