childprocess 0.8.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) 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 +40 -36
  6. data/CHANGELOG.md +73 -44
  7. data/Gemfile +21 -15
  8. data/LICENSE +20 -20
  9. data/README.md +218 -192
  10. data/Rakefile +61 -61
  11. data/appveyor.yml +42 -43
  12. data/childprocess.gemspec +26 -30
  13. data/lib/childprocess.rb +210 -205
  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.rb +56 -56
  18. data/lib/childprocess/jruby/io.rb +16 -16
  19. data/lib/childprocess/jruby/process.rb +184 -159
  20. data/lib/childprocess/jruby/pump.rb +53 -53
  21. data/lib/childprocess/tools/generator.rb +145 -145
  22. data/lib/childprocess/unix.rb +9 -9
  23. data/lib/childprocess/unix/fork_exec_process.rb +78 -70
  24. data/lib/childprocess/unix/io.rb +21 -21
  25. data/lib/childprocess/unix/lib.rb +186 -186
  26. data/lib/childprocess/unix/platform/i386-linux.rb +12 -12
  27. data/lib/childprocess/unix/platform/i386-solaris.rb +11 -11
  28. data/lib/childprocess/unix/platform/x86_64-linux.rb +12 -12
  29. data/lib/childprocess/unix/platform/x86_64-macosx.rb +11 -11
  30. data/lib/childprocess/unix/posix_spawn_process.rb +134 -134
  31. data/lib/childprocess/unix/process.rb +90 -89
  32. data/lib/childprocess/version.rb +3 -3
  33. data/lib/childprocess/windows.rb +38 -33
  34. data/lib/childprocess/windows/handle.rb +91 -91
  35. data/lib/childprocess/windows/io.rb +25 -25
  36. data/lib/childprocess/windows/lib.rb +416 -416
  37. data/lib/childprocess/windows/process.rb +130 -130
  38. data/lib/childprocess/windows/process_builder.rb +178 -175
  39. data/lib/childprocess/windows/structs.rb +148 -148
  40. data/spec/abstract_io_spec.rb +12 -12
  41. data/spec/childprocess_spec.rb +447 -391
  42. data/spec/get_env.ps1 +13 -0
  43. data/spec/io_spec.rb +228 -228
  44. data/spec/jruby_spec.rb +24 -24
  45. data/spec/pid_behavior.rb +12 -12
  46. data/spec/platform_detection_spec.rb +86 -86
  47. data/spec/spec_helper.rb +270 -261
  48. data/spec/unix_spec.rb +57 -57
  49. data/spec/windows_spec.rb +23 -23
  50. metadata +8 -39
@@ -1,3 +1,3 @@
1
- module ChildProcess
2
- VERSION = '0.8.0'
3
- end
1
+ module ChildProcess
2
+ VERSION = '3.0.0'
3
+ end
@@ -1,33 +1,38 @@
1
- require "ffi"
2
- require "rbconfig"
3
-
4
- module ChildProcess
5
- module Windows
6
- module Lib
7
- extend FFI::Library
8
-
9
- def self.msvcrt_name
10
- host_part = RbConfig::CONFIG['host_os'].split("_")[1]
11
- manifest = File.join(RbConfig::CONFIG['bindir'], 'ruby.exe.manifest')
12
-
13
- if host_part && host_part.to_i > 80 && File.exists?(manifest)
14
- "msvcr#{host_part}"
15
- else
16
- "msvcrt"
17
- end
18
- end
19
-
20
- ffi_lib "kernel32", msvcrt_name
21
- ffi_convention :stdcall
22
-
23
-
24
- end # Library
25
- end # Windows
26
- end # ChildProcess
27
-
28
- require "childprocess/windows/lib"
29
- require "childprocess/windows/structs"
30
- require "childprocess/windows/handle"
31
- require "childprocess/windows/io"
32
- require "childprocess/windows/process_builder"
33
- require "childprocess/windows/process"
1
+ require "rbconfig"
2
+
3
+ begin
4
+ require 'ffi'
5
+ rescue LoadError
6
+ raise ChildProcess::MissingFFIError
7
+ end
8
+
9
+ module ChildProcess
10
+ module Windows
11
+ module Lib
12
+ extend FFI::Library
13
+
14
+ def self.msvcrt_name
15
+ host_part = RbConfig::CONFIG['host_os'].split("_")[1]
16
+ manifest = File.join(RbConfig::CONFIG['bindir'], 'ruby.exe.manifest')
17
+
18
+ if host_part && host_part.to_i > 80 && File.exists?(manifest)
19
+ "msvcr#{host_part}"
20
+ else
21
+ "msvcrt"
22
+ end
23
+ end
24
+
25
+ ffi_lib "kernel32", msvcrt_name
26
+ ffi_convention :stdcall
27
+
28
+
29
+ end # Library
30
+ end # Windows
31
+ end # ChildProcess
32
+
33
+ require "childprocess/windows/lib"
34
+ require "childprocess/windows/structs"
35
+ require "childprocess/windows/handle"
36
+ require "childprocess/windows/io"
37
+ require "childprocess/windows/process_builder"
38
+ require "childprocess/windows/process"
@@ -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
@@ -1,25 +1,25 @@
1
- module ChildProcess
2
- module Windows
3
- class IO < AbstractIO
4
- private
5
-
6
- def check_type(io)
7
- return if has_fileno?(io)
8
- return if has_to_io?(io)
9
-
10
- raise ArgumentError, "#{io.inspect}:#{io.class} must have :fileno or :to_io"
11
- end
12
-
13
- def has_fileno?(io)
14
- io.respond_to?(:fileno) && io.fileno
15
- end
16
-
17
- def has_to_io?(io)
18
- io.respond_to?(:to_io) && io.to_io.kind_of?(::IO)
19
- end
20
-
21
- end # IO
22
- end # Windows
23
- end # ChildProcess
24
-
25
-
1
+ module ChildProcess
2
+ module Windows
3
+ class IO < AbstractIO
4
+ private
5
+
6
+ def check_type(io)
7
+ return if has_fileno?(io)
8
+ return if has_to_io?(io)
9
+
10
+ raise ArgumentError, "#{io.inspect}:#{io.class} must have :fileno or :to_io"
11
+ end
12
+
13
+ def has_fileno?(io)
14
+ io.respond_to?(:fileno) && io.fileno
15
+ end
16
+
17
+ def has_to_io?(io)
18
+ io.respond_to?(:to_io) && io.to_io.kind_of?(::IO)
19
+ end
20
+
21
+ end # IO
22
+ end # Windows
23
+ end # ChildProcess
24
+
25
+
@@ -1,416 +1,416 @@
1
- module ChildProcess
2
- module Windows
3
- FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000
4
- FORMAT_MESSAGE_ARGUMENT_ARRAY = 0x00002000
5
-
6
- PROCESS_ALL_ACCESS = 0x1F0FFF
7
- PROCESS_QUERY_INFORMATION = 0x0400
8
- PROCESS_VM_READ = 0x0010
9
- PROCESS_STILL_ACTIVE = 259
10
-
11
- INFINITE = 0xFFFFFFFF
12
-
13
- WIN_SIGINT = 2
14
- WIN_SIGBREAK = 3
15
- WIN_SIGKILL = 9
16
-
17
- CTRL_C_EVENT = 0
18
- CTRL_BREAK_EVENT = 1
19
-
20
- CREATE_BREAKAWAY_FROM_JOB = 0x01000000
21
- DETACHED_PROCESS = 0x00000008
22
-
23
- STARTF_USESTDHANDLES = 0x00000100
24
- INVALID_HANDLE_VALUE = -1
25
- HANDLE_FLAG_INHERIT = 0x00000001
26
-
27
- DUPLICATE_SAME_ACCESS = 0x00000002
28
- CREATE_UNICODE_ENVIRONMENT = 0x00000400
29
-
30
- JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x2000
31
- JOB_OBJECT_LIMIT_BREAKAWAY_OK = 0x00000800
32
- JOB_OBJECT_EXTENDED_LIMIT_INFORMATION = 9
33
- JOB_OBJECT_BASIC_LIMIT_INFORMATION = 2
34
-
35
- module Lib
36
- enum :wait_status, [
37
- :wait_object_0, 0,
38
- :wait_timeout, 0x102,
39
- :wait_abandoned, 0x80,
40
- :wait_failed, 0xFFFFFFFF
41
- ]
42
-
43
- #
44
- # BOOL WINAPI CreateProcess(
45
- # __in_opt LPCTSTR lpApplicationName,
46
- # __inout_opt LPTSTR lpCommandLine,
47
- # __in_opt LPSECURITY_ATTRIBUTES lpProcessAttributes,
48
- # __in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes,
49
- # __in BOOL bInheritHandles,
50
- # __in DWORD dwCreationFlags,
51
- # __in_opt LPVOID lpEnvironment,
52
- # __in_opt LPCTSTR lpCurrentDirectory,
53
- # __in LPSTARTUPINFO lpStartupInfo,
54
- # __out LPPROCESS_INFORMATION lpProcessInformation
55
- # );
56
- #
57
-
58
- attach_function :create_process, :CreateProcessA, [
59
- :pointer,
60
- :pointer,
61
- :pointer,
62
- :pointer,
63
- :bool,
64
- :ulong,
65
- :pointer,
66
- :pointer,
67
- :pointer,
68
- :pointer], :bool
69
-
70
- #
71
- # DWORD WINAPI FormatMessage(
72
- # __in DWORD dwFlags,
73
- # __in_opt LPCVOID lpSource,
74
- # __in DWORD dwMessageId,
75
- # __in DWORD dwLanguageId,
76
- # __out LPTSTR lpBuffer,
77
- # __in DWORD nSize,
78
- # __in_opt va_list *Arguments
79
- # );
80
- #
81
-
82
- attach_function :format_message, :FormatMessageA, [
83
- :ulong,
84
- :pointer,
85
- :ulong,
86
- :ulong,
87
- :pointer,
88
- :ulong,
89
- :pointer], :ulong
90
-
91
-
92
- attach_function :close_handle, :CloseHandle, [:pointer], :bool
93
-
94
- #
95
- # HANDLE WINAPI OpenProcess(
96
- # __in DWORD dwDesiredAccess,
97
- # __in BOOL bInheritHandle,
98
- # __in DWORD dwProcessId
99
- # );
100
- #
101
-
102
- attach_function :open_process, :OpenProcess, [:ulong, :bool, :ulong], :pointer
103
-
104
- #
105
- # HANDLE WINAPI CreateJobObject(
106
- # _In_opt_ LPSECURITY_ATTRIBUTES lpJobAttributes,
107
- # _In_opt_ LPCTSTR lpName
108
- # );
109
- #
110
-
111
- attach_function :create_job_object, :CreateJobObjectA, [:pointer, :pointer], :pointer
112
-
113
- #
114
- # BOOL WINAPI AssignProcessToJobObject(
115
- # _In_ HANDLE hJob,
116
- # _In_ HANDLE hProcess
117
- # );
118
-
119
- attach_function :assign_process_to_job_object, :AssignProcessToJobObject, [:pointer, :pointer], :bool
120
-
121
- #
122
- # BOOL WINAPI SetInformationJobObject(
123
- # _In_ HANDLE hJob,
124
- # _In_ JOBOBJECTINFOCLASS JobObjectInfoClass,
125
- # _In_ LPVOID lpJobObjectInfo,
126
- # _In_ DWORD cbJobObjectInfoLength
127
- # );
128
- #
129
-
130
- attach_function :set_information_job_object, :SetInformationJobObject, [:pointer, :int, :pointer, :ulong], :bool
131
-
132
- #
133
- #
134
- # DWORD WINAPI WaitForSingleObject(
135
- # __in HANDLE hHandle,
136
- # __in DWORD dwMilliseconds
137
- # );
138
- #
139
-
140
- attach_function :wait_for_single_object, :WaitForSingleObject, [:pointer, :ulong], :wait_status, :blocking => true
141
-
142
- #
143
- # BOOL WINAPI GetExitCodeProcess(
144
- # __in HANDLE hProcess,
145
- # __out LPDWORD lpExitCode
146
- # );
147
- #
148
-
149
- attach_function :get_exit_code, :GetExitCodeProcess, [:pointer, :pointer], :bool
150
-
151
- #
152
- # BOOL WINAPI GenerateConsoleCtrlEvent(
153
- # __in DWORD dwCtrlEvent,
154
- # __in DWORD dwProcessGroupId
155
- # );
156
- #
157
-
158
- attach_function :generate_console_ctrl_event, :GenerateConsoleCtrlEvent, [:ulong, :ulong], :bool
159
-
160
- #
161
- # BOOL WINAPI TerminateProcess(
162
- # __in HANDLE hProcess,
163
- # __in UINT uExitCode
164
- # );
165
- #
166
-
167
- attach_function :terminate_process, :TerminateProcess, [:pointer, :uint], :bool
168
-
169
- #
170
- # intptr_t _get_osfhandle(
171
- # int fd
172
- # );
173
- #
174
-
175
- attach_function :get_osfhandle, :_get_osfhandle, [:int], :intptr_t
176
-
177
- #
178
- # int _open_osfhandle (
179
- # intptr_t osfhandle,
180
- # int flags
181
- # );
182
- #
183
-
184
- attach_function :open_osfhandle, :_open_osfhandle, [:pointer, :int], :int
185
-
186
- # BOOL WINAPI SetHandleInformation(
187
- # __in HANDLE hObject,
188
- # __in DWORD dwMask,
189
- # __in DWORD dwFlags
190
- # );
191
-
192
- attach_function :set_handle_information, :SetHandleInformation, [:pointer, :ulong, :ulong], :bool
193
-
194
- # BOOL WINAPI GetHandleInformation(
195
- # __in HANDLE hObject,
196
- # __out LPDWORD lpdwFlags
197
- # );
198
-
199
- attach_function :get_handle_information, :GetHandleInformation, [:pointer, :pointer], :bool
200
-
201
- # BOOL WINAPI CreatePipe(
202
- # __out PHANDLE hReadPipe,
203
- # __out PHANDLE hWritePipe,
204
- # __in_opt LPSECURITY_ATTRIBUTES lpPipeAttributes,
205
- # __in DWORD nSize
206
- # );
207
-
208
- attach_function :create_pipe, :CreatePipe, [:pointer, :pointer, :pointer, :ulong], :bool
209
-
210
- #
211
- # HANDLE WINAPI GetCurrentProcess(void);
212
- #
213
-
214
- attach_function :current_process, :GetCurrentProcess, [], :pointer
215
-
216
- #
217
- # BOOL WINAPI DuplicateHandle(
218
- # __in HANDLE hSourceProcessHandle,
219
- # __in HANDLE hSourceHandle,
220
- # __in HANDLE hTargetProcessHandle,
221
- # __out LPHANDLE lpTargetHandle,
222
- # __in DWORD dwDesiredAccess,
223
- # __in BOOL bInheritHandle,
224
- # __in DWORD dwOptions
225
- # );
226
- #
227
-
228
- attach_function :_duplicate_handle, :DuplicateHandle, [
229
- :pointer,
230
- :pointer,
231
- :pointer,
232
- :pointer,
233
- :ulong,
234
- :bool,
235
- :ulong
236
- ], :bool
237
-
238
- class << self
239
- def kill(signal, *pids)
240
- case signal
241
- when 'SIGINT', 'INT', :SIGINT, :INT
242
- signal = WIN_SIGINT
243
- when 'SIGBRK', 'BRK', :SIGBREAK, :BRK
244
- signal = WIN_SIGBREAK
245
- when 'SIGKILL', 'KILL', :SIGKILL, :KILL
246
- signal = WIN_SIGKILL
247
- when 0..9
248
- # Do nothing
249
- else
250
- raise Error, "invalid signal #{signal.inspect}"
251
- end
252
-
253
- pids.map { |pid| pid if Lib.send_signal(signal, pid) }.compact
254
- end
255
-
256
- def waitpid(pid, flags = 0)
257
- wait_for_pid(pid, no_hang?(flags))
258
- end
259
-
260
- def waitpid2(pid, flags = 0)
261
- code = wait_for_pid(pid, no_hang?(flags))
262
-
263
- [pid, code] if code
264
- end
265
-
266
- def dont_inherit(file)
267
- unless file.respond_to?(:fileno)
268
- raise ArgumentError, "expected #{file.inspect} to respond to :fileno"
269
- end
270
-
271
- set_handle_inheritance(handle_for(file.fileno), false)
272
- end
273
-
274
- def last_error_message
275
- errnum = FFI.errno
276
-
277
- buf = FFI::MemoryPointer.new :char, 512
278
-
279
- size = format_message(
280
- FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ARGUMENT_ARRAY,
281
- nil, errnum, 0, buf, buf.size, nil
282
- )
283
-
284
- str = buf.read_string(size).strip
285
- if errnum == 0
286
- "Unknown error (Windows says #{str.inspect}, but it did not.)"
287
- else
288
- "#{str} (#{errnum})"
289
- end
290
- end
291
-
292
- def each_child_of(pid, &blk)
293
- raise NotImplementedError
294
-
295
- # http://stackoverflow.com/questions/1173342/terminate-a-process-tree-c-for-windows?rq=1
296
-
297
- # for each process entry
298
- # if pe.th32ParentProcessID == pid
299
- # Handle.open(pe.pe.th32ProcessId, &blk)
300
- # end
301
- #
302
- end
303
-
304
- def handle_for(fd_or_io)
305
- if fd_or_io.kind_of?(IO) || fd_or_io.respond_to?(:fileno)
306
- if ChildProcess.jruby?
307
- handle = ChildProcess::JRuby.windows_handle_for(fd_or_io)
308
- else
309
- handle = get_osfhandle(fd_or_io.fileno)
310
- end
311
- elsif fd_or_io.kind_of?(Integer)
312
- handle = get_osfhandle(fd_or_io)
313
- elsif fd_or_io.respond_to?(:to_io)
314
- io = fd_or_io.to_io
315
-
316
- unless io.kind_of?(IO)
317
- raise TypeError, "expected #to_io to return an instance of IO"
318
- end
319
-
320
- handle = get_osfhandle(io.fileno)
321
- else
322
- raise TypeError, "invalid type: #{fd_or_io.inspect}"
323
- end
324
-
325
- if handle == INVALID_HANDLE_VALUE
326
- raise Error, last_error_message
327
- end
328
-
329
- FFI::Pointer.new handle
330
- end
331
-
332
- def io_for(handle, flags = File::RDONLY)
333
- fd = open_osfhandle(handle, flags)
334
- if fd == -1
335
- raise Error, last_error_message
336
- end
337
-
338
- FFI::IO.for_fd fd, flags
339
- end
340
-
341
- def duplicate_handle(handle)
342
- dup = FFI::MemoryPointer.new(:pointer)
343
- proc = current_process
344
-
345
- ok = Lib._duplicate_handle(
346
- proc,
347
- handle,
348
- proc,
349
- dup,
350
- 0,
351
- false,
352
- DUPLICATE_SAME_ACCESS
353
- )
354
-
355
- check_error ok
356
-
357
- dup.read_pointer
358
- ensure
359
- close_handle proc
360
- end
361
-
362
- def set_handle_inheritance(handle, bool)
363
- status = set_handle_information(
364
- handle,
365
- HANDLE_FLAG_INHERIT,
366
- bool ? HANDLE_FLAG_INHERIT : 0
367
- )
368
-
369
- check_error status
370
- end
371
-
372
- def get_handle_inheritance(handle)
373
- flags = FFI::MemoryPointer.new(:uint)
374
-
375
- status = get_handle_information(
376
- handle,
377
- flags
378
- )
379
-
380
- check_error status
381
-
382
- flags.read_uint
383
- end
384
-
385
- def check_error(bool)
386
- bool or raise Error, last_error_message
387
- end
388
-
389
- def alive?(pid)
390
- handle = Lib.open_process(PROCESS_ALL_ACCESS, false, pid)
391
- if handle.null?
392
- false
393
- else
394
- ptr = FFI::MemoryPointer.new :ulong
395
- Lib.check_error Lib.get_exit_code(handle, ptr)
396
- ptr.read_ulong == PROCESS_STILL_ACTIVE
397
- end
398
- end
399
-
400
- def no_hang?(flags)
401
- (flags & Process::WNOHANG) == Process::WNOHANG
402
- end
403
-
404
- def wait_for_pid(pid, no_hang)
405
- code = Handle.open(pid) { |handle|
406
- handle.wait unless no_hang
407
- handle.exit_code
408
- }
409
-
410
- code if code != PROCESS_STILL_ACTIVE
411
- end
412
- end
413
-
414
- end # Lib
415
- end # Windows
416
- end # ChildProcess
1
+ module ChildProcess
2
+ module Windows
3
+ FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000
4
+ FORMAT_MESSAGE_ARGUMENT_ARRAY = 0x00002000
5
+
6
+ PROCESS_ALL_ACCESS = 0x1F0FFF
7
+ PROCESS_QUERY_INFORMATION = 0x0400
8
+ PROCESS_VM_READ = 0x0010
9
+ PROCESS_STILL_ACTIVE = 259
10
+
11
+ INFINITE = 0xFFFFFFFF
12
+
13
+ WIN_SIGINT = 2
14
+ WIN_SIGBREAK = 3
15
+ WIN_SIGKILL = 9
16
+
17
+ CTRL_C_EVENT = 0
18
+ CTRL_BREAK_EVENT = 1
19
+
20
+ CREATE_BREAKAWAY_FROM_JOB = 0x01000000
21
+ DETACHED_PROCESS = 0x00000008
22
+
23
+ STARTF_USESTDHANDLES = 0x00000100
24
+ INVALID_HANDLE_VALUE = -1
25
+ HANDLE_FLAG_INHERIT = 0x00000001
26
+
27
+ DUPLICATE_SAME_ACCESS = 0x00000002
28
+ CREATE_UNICODE_ENVIRONMENT = 0x00000400
29
+
30
+ JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x2000
31
+ JOB_OBJECT_LIMIT_BREAKAWAY_OK = 0x00000800
32
+ JOB_OBJECT_EXTENDED_LIMIT_INFORMATION = 9
33
+ JOB_OBJECT_BASIC_LIMIT_INFORMATION = 2
34
+
35
+ module Lib
36
+ enum :wait_status, [
37
+ :wait_object_0, 0,
38
+ :wait_timeout, 0x102,
39
+ :wait_abandoned, 0x80,
40
+ :wait_failed, 0xFFFFFFFF
41
+ ]
42
+
43
+ #
44
+ # BOOL WINAPI CreateProcess(
45
+ # __in_opt LPCTSTR lpApplicationName,
46
+ # __inout_opt LPTSTR lpCommandLine,
47
+ # __in_opt LPSECURITY_ATTRIBUTES lpProcessAttributes,
48
+ # __in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes,
49
+ # __in BOOL bInheritHandles,
50
+ # __in DWORD dwCreationFlags,
51
+ # __in_opt LPVOID lpEnvironment,
52
+ # __in_opt LPCTSTR lpCurrentDirectory,
53
+ # __in LPSTARTUPINFO lpStartupInfo,
54
+ # __out LPPROCESS_INFORMATION lpProcessInformation
55
+ # );
56
+ #
57
+
58
+ attach_function :create_process, :CreateProcessW, [
59
+ :pointer,
60
+ :buffer_inout,
61
+ :pointer,
62
+ :pointer,
63
+ :bool,
64
+ :ulong,
65
+ :pointer,
66
+ :pointer,
67
+ :pointer,
68
+ :pointer], :bool
69
+
70
+ #
71
+ # DWORD WINAPI FormatMessage(
72
+ # __in DWORD dwFlags,
73
+ # __in_opt LPCVOID lpSource,
74
+ # __in DWORD dwMessageId,
75
+ # __in DWORD dwLanguageId,
76
+ # __out LPTSTR lpBuffer,
77
+ # __in DWORD nSize,
78
+ # __in_opt va_list *Arguments
79
+ # );
80
+ #
81
+
82
+ attach_function :format_message, :FormatMessageA, [
83
+ :ulong,
84
+ :pointer,
85
+ :ulong,
86
+ :ulong,
87
+ :pointer,
88
+ :ulong,
89
+ :pointer], :ulong
90
+
91
+
92
+ attach_function :close_handle, :CloseHandle, [:pointer], :bool
93
+
94
+ #
95
+ # HANDLE WINAPI OpenProcess(
96
+ # __in DWORD dwDesiredAccess,
97
+ # __in BOOL bInheritHandle,
98
+ # __in DWORD dwProcessId
99
+ # );
100
+ #
101
+
102
+ attach_function :open_process, :OpenProcess, [:ulong, :bool, :ulong], :pointer
103
+
104
+ #
105
+ # HANDLE WINAPI CreateJobObject(
106
+ # _In_opt_ LPSECURITY_ATTRIBUTES lpJobAttributes,
107
+ # _In_opt_ LPCTSTR lpName
108
+ # );
109
+ #
110
+
111
+ attach_function :create_job_object, :CreateJobObjectA, [:pointer, :pointer], :pointer
112
+
113
+ #
114
+ # BOOL WINAPI AssignProcessToJobObject(
115
+ # _In_ HANDLE hJob,
116
+ # _In_ HANDLE hProcess
117
+ # );
118
+
119
+ attach_function :assign_process_to_job_object, :AssignProcessToJobObject, [:pointer, :pointer], :bool
120
+
121
+ #
122
+ # BOOL WINAPI SetInformationJobObject(
123
+ # _In_ HANDLE hJob,
124
+ # _In_ JOBOBJECTINFOCLASS JobObjectInfoClass,
125
+ # _In_ LPVOID lpJobObjectInfo,
126
+ # _In_ DWORD cbJobObjectInfoLength
127
+ # );
128
+ #
129
+
130
+ attach_function :set_information_job_object, :SetInformationJobObject, [:pointer, :int, :pointer, :ulong], :bool
131
+
132
+ #
133
+ #
134
+ # DWORD WINAPI WaitForSingleObject(
135
+ # __in HANDLE hHandle,
136
+ # __in DWORD dwMilliseconds
137
+ # );
138
+ #
139
+
140
+ attach_function :wait_for_single_object, :WaitForSingleObject, [:pointer, :ulong], :wait_status, :blocking => true
141
+
142
+ #
143
+ # BOOL WINAPI GetExitCodeProcess(
144
+ # __in HANDLE hProcess,
145
+ # __out LPDWORD lpExitCode
146
+ # );
147
+ #
148
+
149
+ attach_function :get_exit_code, :GetExitCodeProcess, [:pointer, :pointer], :bool
150
+
151
+ #
152
+ # BOOL WINAPI GenerateConsoleCtrlEvent(
153
+ # __in DWORD dwCtrlEvent,
154
+ # __in DWORD dwProcessGroupId
155
+ # );
156
+ #
157
+
158
+ attach_function :generate_console_ctrl_event, :GenerateConsoleCtrlEvent, [:ulong, :ulong], :bool
159
+
160
+ #
161
+ # BOOL WINAPI TerminateProcess(
162
+ # __in HANDLE hProcess,
163
+ # __in UINT uExitCode
164
+ # );
165
+ #
166
+
167
+ attach_function :terminate_process, :TerminateProcess, [:pointer, :uint], :bool
168
+
169
+ #
170
+ # intptr_t _get_osfhandle(
171
+ # int fd
172
+ # );
173
+ #
174
+
175
+ attach_function :get_osfhandle, :_get_osfhandle, [:int], :intptr_t
176
+
177
+ #
178
+ # int _open_osfhandle (
179
+ # intptr_t osfhandle,
180
+ # int flags
181
+ # );
182
+ #
183
+
184
+ attach_function :open_osfhandle, :_open_osfhandle, [:pointer, :int], :int
185
+
186
+ # BOOL WINAPI SetHandleInformation(
187
+ # __in HANDLE hObject,
188
+ # __in DWORD dwMask,
189
+ # __in DWORD dwFlags
190
+ # );
191
+
192
+ attach_function :set_handle_information, :SetHandleInformation, [:pointer, :ulong, :ulong], :bool
193
+
194
+ # BOOL WINAPI GetHandleInformation(
195
+ # __in HANDLE hObject,
196
+ # __out LPDWORD lpdwFlags
197
+ # );
198
+
199
+ attach_function :get_handle_information, :GetHandleInformation, [:pointer, :pointer], :bool
200
+
201
+ # BOOL WINAPI CreatePipe(
202
+ # __out PHANDLE hReadPipe,
203
+ # __out PHANDLE hWritePipe,
204
+ # __in_opt LPSECURITY_ATTRIBUTES lpPipeAttributes,
205
+ # __in DWORD nSize
206
+ # );
207
+
208
+ attach_function :create_pipe, :CreatePipe, [:pointer, :pointer, :pointer, :ulong], :bool
209
+
210
+ #
211
+ # HANDLE WINAPI GetCurrentProcess(void);
212
+ #
213
+
214
+ attach_function :current_process, :GetCurrentProcess, [], :pointer
215
+
216
+ #
217
+ # BOOL WINAPI DuplicateHandle(
218
+ # __in HANDLE hSourceProcessHandle,
219
+ # __in HANDLE hSourceHandle,
220
+ # __in HANDLE hTargetProcessHandle,
221
+ # __out LPHANDLE lpTargetHandle,
222
+ # __in DWORD dwDesiredAccess,
223
+ # __in BOOL bInheritHandle,
224
+ # __in DWORD dwOptions
225
+ # );
226
+ #
227
+
228
+ attach_function :_duplicate_handle, :DuplicateHandle, [
229
+ :pointer,
230
+ :pointer,
231
+ :pointer,
232
+ :pointer,
233
+ :ulong,
234
+ :bool,
235
+ :ulong
236
+ ], :bool
237
+
238
+ class << self
239
+ def kill(signal, *pids)
240
+ case signal
241
+ when 'SIGINT', 'INT', :SIGINT, :INT
242
+ signal = WIN_SIGINT
243
+ when 'SIGBRK', 'BRK', :SIGBREAK, :BRK
244
+ signal = WIN_SIGBREAK
245
+ when 'SIGKILL', 'KILL', :SIGKILL, :KILL
246
+ signal = WIN_SIGKILL
247
+ when 0..9
248
+ # Do nothing
249
+ else
250
+ raise Error, "invalid signal #{signal.inspect}"
251
+ end
252
+
253
+ pids.map { |pid| pid if Lib.send_signal(signal, pid) }.compact
254
+ end
255
+
256
+ def waitpid(pid, flags = 0)
257
+ wait_for_pid(pid, no_hang?(flags))
258
+ end
259
+
260
+ def waitpid2(pid, flags = 0)
261
+ code = wait_for_pid(pid, no_hang?(flags))
262
+
263
+ [pid, code] if code
264
+ end
265
+
266
+ def dont_inherit(file)
267
+ unless file.respond_to?(:fileno)
268
+ raise ArgumentError, "expected #{file.inspect} to respond to :fileno"
269
+ end
270
+
271
+ set_handle_inheritance(handle_for(file.fileno), false)
272
+ end
273
+
274
+ def last_error_message
275
+ errnum = FFI.errno
276
+
277
+ buf = FFI::MemoryPointer.new :char, 512
278
+
279
+ size = format_message(
280
+ FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ARGUMENT_ARRAY,
281
+ nil, errnum, 0, buf, buf.size, nil
282
+ )
283
+
284
+ str = buf.read_string(size).strip
285
+ if errnum == 0
286
+ "Unknown error (Windows says #{str.inspect}, but it did not.)"
287
+ else
288
+ "#{str} (#{errnum})"
289
+ end
290
+ end
291
+
292
+ def each_child_of(pid, &blk)
293
+ raise NotImplementedError
294
+
295
+ # http://stackoverflow.com/questions/1173342/terminate-a-process-tree-c-for-windows?rq=1
296
+
297
+ # for each process entry
298
+ # if pe.th32ParentProcessID == pid
299
+ # Handle.open(pe.pe.th32ProcessId, &blk)
300
+ # end
301
+ #
302
+ end
303
+
304
+ def handle_for(fd_or_io)
305
+ if fd_or_io.kind_of?(IO) || fd_or_io.respond_to?(:fileno)
306
+ if ChildProcess.jruby?
307
+ handle = ChildProcess::JRuby.windows_handle_for(fd_or_io)
308
+ else
309
+ handle = get_osfhandle(fd_or_io.fileno)
310
+ end
311
+ elsif fd_or_io.kind_of?(Integer)
312
+ handle = get_osfhandle(fd_or_io)
313
+ elsif fd_or_io.respond_to?(:to_io)
314
+ io = fd_or_io.to_io
315
+
316
+ unless io.kind_of?(IO)
317
+ raise TypeError, "expected #to_io to return an instance of IO"
318
+ end
319
+
320
+ handle = get_osfhandle(io.fileno)
321
+ else
322
+ raise TypeError, "invalid type: #{fd_or_io.inspect}"
323
+ end
324
+
325
+ if handle == INVALID_HANDLE_VALUE
326
+ raise Error, last_error_message
327
+ end
328
+
329
+ FFI::Pointer.new handle
330
+ end
331
+
332
+ def io_for(handle, flags = File::RDONLY)
333
+ fd = open_osfhandle(handle, flags)
334
+ if fd == -1
335
+ raise Error, last_error_message
336
+ end
337
+
338
+ FFI::IO.for_fd fd, flags
339
+ end
340
+
341
+ def duplicate_handle(handle)
342
+ dup = FFI::MemoryPointer.new(:pointer)
343
+ proc = current_process
344
+
345
+ ok = Lib._duplicate_handle(
346
+ proc,
347
+ handle,
348
+ proc,
349
+ dup,
350
+ 0,
351
+ false,
352
+ DUPLICATE_SAME_ACCESS
353
+ )
354
+
355
+ check_error ok
356
+
357
+ dup.read_pointer
358
+ ensure
359
+ close_handle proc
360
+ end
361
+
362
+ def set_handle_inheritance(handle, bool)
363
+ status = set_handle_information(
364
+ handle,
365
+ HANDLE_FLAG_INHERIT,
366
+ bool ? HANDLE_FLAG_INHERIT : 0
367
+ )
368
+
369
+ check_error status
370
+ end
371
+
372
+ def get_handle_inheritance(handle)
373
+ flags = FFI::MemoryPointer.new(:uint)
374
+
375
+ status = get_handle_information(
376
+ handle,
377
+ flags
378
+ )
379
+
380
+ check_error status
381
+
382
+ flags.read_uint
383
+ end
384
+
385
+ def check_error(bool)
386
+ bool or raise Error, last_error_message
387
+ end
388
+
389
+ def alive?(pid)
390
+ handle = Lib.open_process(PROCESS_ALL_ACCESS, false, pid)
391
+ if handle.null?
392
+ false
393
+ else
394
+ ptr = FFI::MemoryPointer.new :ulong
395
+ Lib.check_error Lib.get_exit_code(handle, ptr)
396
+ ptr.read_ulong == PROCESS_STILL_ACTIVE
397
+ end
398
+ end
399
+
400
+ def no_hang?(flags)
401
+ (flags & Process::WNOHANG) == Process::WNOHANG
402
+ end
403
+
404
+ def wait_for_pid(pid, no_hang)
405
+ code = Handle.open(pid) { |handle|
406
+ handle.wait unless no_hang
407
+ handle.exit_code
408
+ }
409
+
410
+ code if code != PROCESS_STILL_ACTIVE
411
+ end
412
+ end
413
+
414
+ end # Lib
415
+ end # Windows
416
+ end # ChildProcess