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