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 CHANGED
@@ -3,4 +3,7 @@ rvm:
3
3
  - 1.9.2
4
4
  - 1.9.3
5
5
  - jruby
6
- - rbx
6
+ # - rbx
7
+ env:
8
+ - CHILDPROCESS_POSIX_SPAWN=true
9
+ - CHILDPROCESS_POSIX_SPAWN=false
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
- Unix::Process.new(args)
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
@@ -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
@@ -4,4 +4,6 @@ module ChildProcess
4
4
  end
5
5
 
6
6
  require "childprocess/unix/io"
7
- require "childprocess/unix/process"
7
+ require "childprocess/unix/process"
8
+ require "childprocess/unix/fork_exec_process"
9
+ # PosixSpawnProcess + ffi is required on demand.
@@ -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,11 @@
1
+ module ChildProcess::Unix::Platform
2
+ SIZEOF = {
3
+ :posix_spawn_file_actions_t => 8,
4
+ :posix_spawnattr_t => 8,
5
+ :sigset_t => 4
6
+ }
7
+ POSIX_SPAWN_RESETIDS = 1
8
+ POSIX_SPAWN_SETPGROUP = 2
9
+ POSIX_SPAWN_SETSIGDEF = 4
10
+ POSIX_SPAWN_SETSIGMASK = 8
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
@@ -1,3 +1,3 @@
1
1
  module ChildProcess
2
- VERSION = "0.2.6"
2
+ VERSION = "0.2.7"
3
3
  end
@@ -20,11 +20,10 @@ module ChildProcess
20
20
  DETACHED_PROCESS = 0x00000008
21
21
 
22
22
  STARTF_USESTDHANDLES = 0x00000100
23
- INVALID_HANDLE_VALUE = 0xFFFFFFFF
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, [:long, :ulong, :ulong], :bool
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, [:long, :pointer], :bool
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
- FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ARGUMENT_ARRAY,
252
- nil, errnum, 0, buf, buf.size, nil
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.write_bytes env_str, 0, env_str.bytesize
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
- Lib.set_handle_inheritance @write_pipe.address, false
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.fileno)
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
 
@@ -10,7 +10,14 @@ describe ChildProcess do
10
10
  process.should be_started
11
11
  end
12
12
 
13
- it "raises ChildProcess::LaunchError if the process can't be started" do
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
@@ -128,4 +128,8 @@ RSpec.configure do |c|
128
128
  if defined?(JRUBY_VERSION)
129
129
  c.filter_run_excluding :jruby => false
130
130
  end
131
+
132
+ if ChildProcess.linux? && ChildProcess.posix_spawn?
133
+ c.filter_run_excluding :posix_spawn_on_linux => false
134
+ end
131
135
  end
data/spec/unix_spec.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  require File.expand_path('../spec_helper', __FILE__)
2
2
  require "pid_behavior"
3
3
 
4
- if ChildProcess.unix?
4
+ if ChildProcess.unix? && !ChildProcess.posix_spawn?
5
5
  describe ChildProcess::Unix::Process do
6
6
  it_behaves_like "a platform that provides the child's pid"
7
7
 
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: 27
4
+ hash: 25
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 2
9
- - 6
10
- version: 0.2.6
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-07 00:00:00 Z
18
+ date: 2012-01-09 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
- type: :development
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
- prerelease: false
34
- name: rspec
34
+ type: :development
35
35
  version_requirements: *id001
36
36
  - !ruby/object:Gem::Dependency
37
- type: :development
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
- prerelease: false
48
- name: yard
48
+ type: :development
49
49
  version_requirements: *id002
50
50
  - !ruby/object:Gem::Dependency
51
- type: :development
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
- prerelease: false
64
- name: rake
64
+ type: :development
65
65
  version_requirements: *id003
66
66
  - !ruby/object:Gem::Dependency
67
- type: :runtime
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
- prerelease: false
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