childprocess 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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 +36 -0
  6. data/CHANGELOG.md +44 -0
  7. data/Gemfile +15 -0
  8. data/LICENSE +20 -0
  9. data/README.md +192 -0
  10. data/Rakefile +61 -0
  11. data/appveyor.yml +43 -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 +391 -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