childprocess 0.9.0

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