childprocess 0.5.9 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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