childprocess 0.8.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +5 -5
  2. data/.document +6 -6
  3. data/.gitignore +28 -28
  4. data/.rspec +1 -1
  5. data/.travis.yml +42 -36
  6. data/CHANGELOG.md +67 -44
  7. data/Gemfile +18 -15
  8. data/LICENSE +20 -20
  9. data/README.md +216 -192
  10. data/Rakefile +61 -61
  11. data/appveyor.yml +42 -43
  12. data/childprocess.gemspec +32 -30
  13. data/ext/mkrf_conf.rb +24 -0
  14. data/lib/childprocess/abstract_io.rb +36 -36
  15. data/lib/childprocess/abstract_process.rb +192 -192
  16. data/lib/childprocess/errors.rb +37 -26
  17. data/lib/childprocess/jruby/io.rb +16 -16
  18. data/lib/childprocess/jruby/process.rb +184 -159
  19. data/lib/childprocess/jruby/pump.rb +53 -53
  20. data/lib/childprocess/jruby.rb +56 -56
  21. data/lib/childprocess/tools/generator.rb +145 -145
  22. data/lib/childprocess/unix/fork_exec_process.rb +78 -70
  23. data/lib/childprocess/unix/io.rb +21 -21
  24. data/lib/childprocess/unix/lib.rb +186 -186
  25. data/lib/childprocess/unix/platform/i386-linux.rb +12 -12
  26. data/lib/childprocess/unix/platform/i386-solaris.rb +11 -11
  27. data/lib/childprocess/unix/platform/x86_64-linux.rb +12 -12
  28. data/lib/childprocess/unix/platform/x86_64-macosx.rb +11 -11
  29. data/lib/childprocess/unix/posix_spawn_process.rb +134 -134
  30. data/lib/childprocess/unix/process.rb +90 -89
  31. data/lib/childprocess/unix.rb +9 -9
  32. data/lib/childprocess/version.rb +3 -3
  33. data/lib/childprocess/windows/handle.rb +91 -91
  34. data/lib/childprocess/windows/io.rb +25 -25
  35. data/lib/childprocess/windows/lib.rb +416 -416
  36. data/lib/childprocess/windows/process.rb +130 -130
  37. data/lib/childprocess/windows/process_builder.rb +178 -175
  38. data/lib/childprocess/windows/structs.rb +148 -148
  39. data/lib/childprocess/windows.rb +33 -33
  40. data/lib/childprocess.rb +210 -205
  41. data/spec/abstract_io_spec.rb +12 -12
  42. data/spec/childprocess_spec.rb +447 -391
  43. data/spec/get_env.ps1 +13 -0
  44. data/spec/io_spec.rb +228 -228
  45. data/spec/jruby_spec.rb +24 -24
  46. data/spec/pid_behavior.rb +12 -12
  47. data/spec/platform_detection_spec.rb +86 -86
  48. data/spec/spec_helper.rb +270 -261
  49. data/spec/unix_spec.rb +57 -57
  50. data/spec/windows_spec.rb +23 -23
  51. metadata +18 -33
@@ -1,134 +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
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.to_s.include?("\0")
115
+ raise InvalidEnvironmentVariable, "#{key.inspect} => #{val.to_s.inspect}"
116
+ end
117
+
118
+ FFI::MemoryPointer.from_string("#{key}=#{val.to_s}")
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
@@ -1,89 +1,90 @@
1
- module ChildProcess
2
- module Unix
3
- class Process < AbstractProcess
4
- attr_reader :pid
5
-
6
- def io
7
- @io ||= Unix::IO.new
8
- end
9
-
10
- def stop(timeout = 3)
11
- assert_started
12
- send_term
13
-
14
- begin
15
- return poll_for_exit(timeout)
16
- rescue TimeoutError
17
- # try next
18
- end
19
-
20
- send_kill
21
- wait
22
- rescue Errno::ECHILD, Errno::ESRCH
23
- # handle race condition where process dies between timeout
24
- # and send_kill
25
- true
26
- end
27
-
28
- def exited?
29
- return true if @exit_code
30
-
31
- assert_started
32
- pid, status = ::Process.waitpid2(_pid, ::Process::WNOHANG | ::Process::WUNTRACED)
33
- pid = nil if pid == 0 # may happen on jruby
34
-
35
- log(:pid => pid, :status => status)
36
-
37
- if pid
38
- set_exit_code(status)
39
- end
40
-
41
- !!pid
42
- rescue Errno::ECHILD
43
- # may be thrown for detached processes
44
- true
45
- end
46
-
47
- def wait
48
- assert_started
49
-
50
- if exited?
51
- exit_code
52
- else
53
- _, status = ::Process.waitpid2 _pid
54
- set_exit_code(status)
55
- end
56
- end
57
-
58
- private
59
-
60
- def send_term
61
- send_signal 'TERM'
62
- end
63
-
64
- def send_kill
65
- send_signal 'KILL'
66
- end
67
-
68
- def send_signal(sig)
69
- assert_started
70
-
71
- log "sending #{sig}"
72
- ::Process.kill sig, _pid
73
- end
74
-
75
- def set_exit_code(status)
76
- @exit_code = status.exitstatus || status.termsig
77
- end
78
-
79
- def _pid
80
- if leader?
81
- -@pid # negative pid == process group
82
- else
83
- @pid
84
- end
85
- end
86
-
87
- end # Process
88
- end # Unix
89
- end # ChildProcess
1
+ module ChildProcess
2
+ module Unix
3
+ class Process < AbstractProcess
4
+ attr_reader :pid
5
+
6
+ def io
7
+ @io ||= Unix::IO.new
8
+ end
9
+
10
+ def stop(timeout = 3)
11
+ assert_started
12
+ send_term
13
+
14
+ begin
15
+ return poll_for_exit(timeout)
16
+ rescue TimeoutError
17
+ # try next
18
+ end
19
+
20
+ send_kill
21
+ wait
22
+ rescue Errno::ECHILD, Errno::ESRCH
23
+ # handle race condition where process dies between timeout
24
+ # and send_kill
25
+ true
26
+ end
27
+
28
+ def exited?
29
+ return true if @exit_code
30
+
31
+ assert_started
32
+ pid, status = ::Process.waitpid2(@pid, ::Process::WNOHANG | ::Process::WUNTRACED)
33
+ pid = nil if pid == 0 # may happen on jruby
34
+
35
+ log(:pid => pid, :status => status)
36
+
37
+ if pid
38
+ set_exit_code(status)
39
+ end
40
+
41
+ !!pid
42
+ rescue Errno::ECHILD
43
+ # may be thrown for detached processes
44
+ true
45
+ end
46
+
47
+ def wait
48
+ assert_started
49
+
50
+ if exited?
51
+ exit_code
52
+ else
53
+ _, status = ::Process.waitpid2(@pid)
54
+
55
+ set_exit_code(status)
56
+ end
57
+ end
58
+
59
+ private
60
+
61
+ def send_term
62
+ send_signal 'TERM'
63
+ end
64
+
65
+ def send_kill
66
+ send_signal 'KILL'
67
+ end
68
+
69
+ def send_signal(sig)
70
+ assert_started
71
+
72
+ log "sending #{sig}"
73
+ ::Process.kill sig, _pid
74
+ end
75
+
76
+ def set_exit_code(status)
77
+ @exit_code = status.exitstatus || status.termsig
78
+ end
79
+
80
+ def _pid
81
+ if leader?
82
+ -@pid # negative pid == process group
83
+ else
84
+ @pid
85
+ end
86
+ end
87
+
88
+ end # Process
89
+ end # Unix
90
+ end # ChildProcess
@@ -1,9 +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.
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.
@@ -1,3 +1,3 @@
1
- module ChildProcess
2
- VERSION = '0.8.0'
3
- end
1
+ module ChildProcess
2
+ VERSION = '2.0.0'
3
+ end
@@ -1,91 +1,91 @@
1
- module ChildProcess
2
- module Windows
3
- class Handle
4
-
5
- class << self
6
- private :new
7
-
8
- def open(pid, access = PROCESS_ALL_ACCESS)
9
- handle = Lib.open_process(access, false, pid)
10
-
11
- if handle.null?
12
- raise Error, Lib.last_error_message
13
- end
14
-
15
- h = new(handle, pid)
16
- return h unless block_given?
17
-
18
- begin
19
- yield h
20
- ensure
21
- h.close
22
- end
23
- end
24
- end
25
-
26
- attr_reader :pointer
27
-
28
- def initialize(pointer, pid)
29
- unless pointer.kind_of?(FFI::Pointer)
30
- raise TypeError, "invalid handle: #{pointer.inspect}"
31
- end
32
-
33
- if pointer.null?
34
- raise ArgumentError, "handle is null: #{pointer.inspect}"
35
- end
36
-
37
- @pid = pid
38
- @pointer = pointer
39
- @closed = false
40
- end
41
-
42
- def exit_code
43
- code_pointer = FFI::MemoryPointer.new :ulong
44
- ok = Lib.get_exit_code(@pointer, code_pointer)
45
-
46
- if ok
47
- code_pointer.get_ulong(0)
48
- else
49
- close
50
- raise Error, Lib.last_error_message
51
- end
52
- end
53
-
54
- def send(signal)
55
- case signal
56
- when 0
57
- exit_code == PROCESS_STILL_ALIVE
58
- when WIN_SIGINT
59
- Lib.generate_console_ctrl_event(CTRL_C_EVENT, @pid)
60
- when WIN_SIGBREAK
61
- Lib.generate_console_ctrl_event(CTRL_BREAK_EVENT, @pid)
62
- when WIN_SIGKILL
63
- ok = Lib.terminate_process(@pointer, @pid)
64
- Lib.check_error ok
65
- else
66
- thread_id = FFI::MemoryPointer.new(:ulong)
67
- module_handle = Lib.get_module_handle("kernel32")
68
- proc_address = Lib.get_proc_address(module_handle, "ExitProcess")
69
-
70
- thread = Lib.create_remote_thread(@pointer, 0, 0, proc_address, 0, 0, thread_id)
71
- check_error thread
72
-
73
- Lib.wait_for_single_object(thread, 5)
74
- true
75
- end
76
- end
77
-
78
- def close
79
- return if @closed
80
-
81
- Lib.close_handle(@pointer)
82
- @closed = true
83
- end
84
-
85
- def wait(milliseconds = nil)
86
- Lib.wait_for_single_object(@pointer, milliseconds || INFINITE)
87
- end
88
-
89
- end # Handle
90
- end # Windows
91
- end # ChildProcess
1
+ module ChildProcess
2
+ module Windows
3
+ class Handle
4
+
5
+ class << self
6
+ private :new
7
+
8
+ def open(pid, access = PROCESS_ALL_ACCESS)
9
+ handle = Lib.open_process(access, false, pid)
10
+
11
+ if handle.null?
12
+ raise Error, Lib.last_error_message
13
+ end
14
+
15
+ h = new(handle, pid)
16
+ return h unless block_given?
17
+
18
+ begin
19
+ yield h
20
+ ensure
21
+ h.close
22
+ end
23
+ end
24
+ end
25
+
26
+ attr_reader :pointer
27
+
28
+ def initialize(pointer, pid)
29
+ unless pointer.kind_of?(FFI::Pointer)
30
+ raise TypeError, "invalid handle: #{pointer.inspect}"
31
+ end
32
+
33
+ if pointer.null?
34
+ raise ArgumentError, "handle is null: #{pointer.inspect}"
35
+ end
36
+
37
+ @pid = pid
38
+ @pointer = pointer
39
+ @closed = false
40
+ end
41
+
42
+ def exit_code
43
+ code_pointer = FFI::MemoryPointer.new :ulong
44
+ ok = Lib.get_exit_code(@pointer, code_pointer)
45
+
46
+ if ok
47
+ code_pointer.get_ulong(0)
48
+ else
49
+ close
50
+ raise Error, Lib.last_error_message
51
+ end
52
+ end
53
+
54
+ def send(signal)
55
+ case signal
56
+ when 0
57
+ exit_code == PROCESS_STILL_ALIVE
58
+ when WIN_SIGINT
59
+ Lib.generate_console_ctrl_event(CTRL_C_EVENT, @pid)
60
+ when WIN_SIGBREAK
61
+ Lib.generate_console_ctrl_event(CTRL_BREAK_EVENT, @pid)
62
+ when WIN_SIGKILL
63
+ ok = Lib.terminate_process(@pointer, @pid)
64
+ Lib.check_error ok
65
+ else
66
+ thread_id = FFI::MemoryPointer.new(:ulong)
67
+ module_handle = Lib.get_module_handle("kernel32")
68
+ proc_address = Lib.get_proc_address(module_handle, "ExitProcess")
69
+
70
+ thread = Lib.create_remote_thread(@pointer, 0, 0, proc_address, 0, 0, thread_id)
71
+ check_error thread
72
+
73
+ Lib.wait_for_single_object(thread, 5)
74
+ true
75
+ end
76
+ end
77
+
78
+ def close
79
+ return if @closed
80
+
81
+ Lib.close_handle(@pointer)
82
+ @closed = true
83
+ end
84
+
85
+ def wait(milliseconds = nil)
86
+ Lib.wait_for_single_object(@pointer, milliseconds || INFINITE)
87
+ end
88
+
89
+ end # Handle
90
+ end # Windows
91
+ end # ChildProcess