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 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