childprocess 0.2.6 → 0.2.7
Sign up to get free protection for your applications and to get access to all the features.
- 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
|