childprocess 0.8.0 → 2.0.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.
- checksums.yaml +5 -5
- data/.document +6 -6
- data/.gitignore +28 -28
- data/.rspec +1 -1
- data/.travis.yml +42 -36
- data/CHANGELOG.md +67 -44
- data/Gemfile +18 -15
- data/LICENSE +20 -20
- data/README.md +216 -192
- data/Rakefile +61 -61
- data/appveyor.yml +42 -43
- data/childprocess.gemspec +32 -30
- data/ext/mkrf_conf.rb +24 -0
- data/lib/childprocess/abstract_io.rb +36 -36
- data/lib/childprocess/abstract_process.rb +192 -192
- data/lib/childprocess/errors.rb +37 -26
- data/lib/childprocess/jruby/io.rb +16 -16
- data/lib/childprocess/jruby/process.rb +184 -159
- data/lib/childprocess/jruby/pump.rb +53 -53
- data/lib/childprocess/jruby.rb +56 -56
- data/lib/childprocess/tools/generator.rb +145 -145
- data/lib/childprocess/unix/fork_exec_process.rb +78 -70
- data/lib/childprocess/unix/io.rb +21 -21
- data/lib/childprocess/unix/lib.rb +186 -186
- data/lib/childprocess/unix/platform/i386-linux.rb +12 -12
- data/lib/childprocess/unix/platform/i386-solaris.rb +11 -11
- data/lib/childprocess/unix/platform/x86_64-linux.rb +12 -12
- data/lib/childprocess/unix/platform/x86_64-macosx.rb +11 -11
- data/lib/childprocess/unix/posix_spawn_process.rb +134 -134
- data/lib/childprocess/unix/process.rb +90 -89
- data/lib/childprocess/unix.rb +9 -9
- data/lib/childprocess/version.rb +3 -3
- data/lib/childprocess/windows/handle.rb +91 -91
- data/lib/childprocess/windows/io.rb +25 -25
- data/lib/childprocess/windows/lib.rb +416 -416
- data/lib/childprocess/windows/process.rb +130 -130
- data/lib/childprocess/windows/process_builder.rb +178 -175
- data/lib/childprocess/windows/structs.rb +148 -148
- data/lib/childprocess/windows.rb +33 -33
- data/lib/childprocess.rb +210 -205
- data/spec/abstract_io_spec.rb +12 -12
- data/spec/childprocess_spec.rb +447 -391
- data/spec/get_env.ps1 +13 -0
- data/spec/io_spec.rb +228 -228
- data/spec/jruby_spec.rb +24 -24
- data/spec/pid_behavior.rb +12 -12
- data/spec/platform_detection_spec.rb +86 -86
- data/spec/spec_helper.rb +270 -261
- data/spec/unix_spec.rb +57 -57
- data/spec/windows_spec.rb +23 -23
- 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(
|
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
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
end #
|
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
|
data/lib/childprocess/unix.rb
CHANGED
@@ -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.
|
data/lib/childprocess/version.rb
CHANGED
@@ -1,3 +1,3 @@
|
|
1
|
-
module ChildProcess
|
2
|
-
VERSION = '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
|