childprocess 0.2.5 → 0.2.6

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.
data/lib/childprocess.rb CHANGED
@@ -46,11 +46,7 @@ module ChildProcess
46
46
  end
47
47
 
48
48
  def jruby_on_unix?
49
- return false unless jruby?
50
- # patterns grabbed from http://lopica.sourceforge.net/os.html
51
- require "java"
52
- name = java.lang.System.getProperty("os.name").downcase
53
- name =~ /mac os|linux|solaris|bsd/
49
+ jruby? and [:macosx, :linux, :unix].include? os
54
50
  end
55
51
 
56
52
  def windows?
@@ -78,8 +74,9 @@ module ChildProcess
78
74
  end
79
75
 
80
76
  #
81
- # By default, a child process will inherit open file descriptors from the parent process.
82
- # This helper provides a cross-platform way of making sure that doesn't happen for the given file/io.
77
+ # By default, a child process will inherit open file descriptors from the
78
+ # parent process. This helper provides a cross-platform way of making sure
79
+ # that doesn't happen for the given file/io.
83
80
  #
84
81
 
85
82
  def close_on_exec(file)
@@ -88,7 +85,7 @@ module ChildProcess
88
85
  elsif file.respond_to?(:fcntl) && defined?(Fcntl::FD_CLOEXEC)
89
86
  file.fcntl Fcntl::F_SETFD, Fcntl::FD_CLOEXEC
90
87
  elsif windows?
91
- Windows.dont_inherit file
88
+ Windows::Lib.dont_inherit file
92
89
  else
93
90
  raise Error, "not sure how to set close-on-exec for #{file.inspect} on #{platform.inspect}"
94
91
  end
@@ -1,3 +1,3 @@
1
1
  module ChildProcess
2
- VERSION = "0.2.5"
2
+ VERSION = "0.2.6"
3
3
  end
@@ -25,10 +25,9 @@ module ChildProcess
25
25
  end # Windows
26
26
  end # ChildProcess
27
27
 
28
- require "childprocess/windows/constants"
28
+ require "childprocess/windows/lib"
29
29
  require "childprocess/windows/structs"
30
- require "childprocess/windows/functions"
31
30
  require "childprocess/windows/handle"
32
- require "childprocess/windows/api"
33
31
  require "childprocess/windows/io"
32
+ require "childprocess/windows/process_builder"
34
33
  require "childprocess/windows/process"
@@ -59,14 +59,14 @@ module ChildProcess
59
59
  Lib.generate_console_ctrl_event(CTRL_BREAK_EVENT, @pid)
60
60
  when WIN_SIGKILL
61
61
  ok = Lib.terminate_process(@handle, @pid)
62
- ok or raise Error, Lib.last_error_message
62
+ Lib.check_error ok
63
63
  else
64
64
  thread_id = FFI::MemoryPointer.new(:ulong)
65
65
  module_handle = Lib.get_module_handle("kernel32")
66
66
  proc_address = Lib.get_proc_address(module_handle, "ExitProcess")
67
67
 
68
68
  thread = Lib.create_remote_thread(@handle, 0, 0, proc_address, 0, 0, thread_id)
69
- thread or raise Error, Lib.last_error_message
69
+ check_error thread
70
70
 
71
71
  Lib.wait_for_single_object(thread, 5)
72
72
  true
@@ -0,0 +1,359 @@
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
+ DETACHED_PROCESS = 0x00000008
21
+
22
+ STARTF_USESTDHANDLES = 0x00000100
23
+ INVALID_HANDLE_VALUE = 0xFFFFFFFF
24
+ HANDLE_FLAG_INHERIT = 0x00000001
25
+
26
+ DUPLICATE_SAME_ACCESS = 0x00000002
27
+
28
+ CREATE_UNICODE_ENVIRONMENT = 0x00000400
29
+
30
+ module Lib
31
+ enum :wait_status, [
32
+ :wait_object_0, 0,
33
+ :wait_timeout, 0x102,
34
+ :wait_abandoned, 0x80,
35
+ :wait_failed, 0xFFFFFFFF
36
+ ]
37
+
38
+ #
39
+ # BOOL WINAPI CreateProcess(
40
+ # __in_opt LPCTSTR lpApplicationName,
41
+ # __inout_opt LPTSTR lpCommandLine,
42
+ # __in_opt LPSECURITY_ATTRIBUTES lpProcessAttributes,
43
+ # __in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes,
44
+ # __in BOOL bInheritHandles,
45
+ # __in DWORD dwCreationFlags,
46
+ # __in_opt LPVOID lpEnvironment,
47
+ # __in_opt LPCTSTR lpCurrentDirectory,
48
+ # __in LPSTARTUPINFO lpStartupInfo,
49
+ # __out LPPROCESS_INFORMATION lpProcessInformation
50
+ # );
51
+ #
52
+
53
+ attach_function :create_process, :CreateProcessA, [
54
+ :pointer,
55
+ :pointer,
56
+ :pointer,
57
+ :pointer,
58
+ :bool,
59
+ :ulong,
60
+ :pointer,
61
+ :pointer,
62
+ :pointer,
63
+ :pointer], :bool
64
+
65
+ #
66
+ # DWORD WINAPI GetLastError(void);
67
+ #
68
+
69
+ attach_function :get_last_error, :GetLastError, [], :ulong
70
+
71
+ #
72
+ # DWORD WINAPI FormatMessage(
73
+ # __in DWORD dwFlags,
74
+ # __in_opt LPCVOID lpSource,
75
+ # __in DWORD dwMessageId,
76
+ # __in DWORD dwLanguageId,
77
+ # __out LPTSTR lpBuffer,
78
+ # __in DWORD nSize,
79
+ # __in_opt va_list *Arguments
80
+ # );
81
+ #
82
+
83
+ attach_function :format_message, :FormatMessageA, [
84
+ :ulong,
85
+ :pointer,
86
+ :ulong,
87
+ :ulong,
88
+ :pointer,
89
+ :ulong,
90
+ :pointer], :ulong
91
+
92
+
93
+ attach_function :close_handle, :CloseHandle, [:pointer], :bool
94
+
95
+ #
96
+ # HANDLE WINAPI OpenProcess(
97
+ # __in DWORD dwDesiredAccess,
98
+ # __in BOOL bInheritHandle,
99
+ # __in DWORD dwProcessId
100
+ # );
101
+ #
102
+
103
+ attach_function :open_process, :OpenProcess, [:ulong, :bool, :ulong], :pointer
104
+
105
+ #
106
+ # DWORD WINAPI WaitForSingleObject(
107
+ # __in HANDLE hHandle,
108
+ # __in DWORD dwMilliseconds
109
+ # );
110
+ #
111
+
112
+ attach_function :wait_for_single_object, :WaitForSingleObject, [:pointer, :ulong], :wait_status
113
+
114
+ #
115
+ # BOOL WINAPI GetExitCodeProcess(
116
+ # __in HANDLE hProcess,
117
+ # __out LPDWORD lpExitCode
118
+ # );
119
+ #
120
+
121
+ attach_function :get_exit_code, :GetExitCodeProcess, [:pointer, :pointer], :bool
122
+
123
+ #
124
+ # BOOL WINAPI GenerateConsoleCtrlEvent(
125
+ # __in DWORD dwCtrlEvent,
126
+ # __in DWORD dwProcessGroupId
127
+ # );
128
+ #
129
+
130
+ attach_function :generate_console_ctrl_event, :GenerateConsoleCtrlEvent, [:ulong, :ulong], :bool
131
+
132
+ #
133
+ # BOOL WINAPI TerminateProcess(
134
+ # __in HANDLE hProcess,
135
+ # __in UINT uExitCode
136
+ # );
137
+ #
138
+
139
+ attach_function :terminate_process, :TerminateProcess, [:pointer, :uint], :bool
140
+
141
+ #
142
+ # long _get_osfhandle(
143
+ # int fd
144
+ # );
145
+ #
146
+
147
+ attach_function :get_osfhandle, :_get_osfhandle, [:int], :long
148
+
149
+ #
150
+ # int _open_osfhandle (
151
+ # intptr_t osfhandle,
152
+ # int flags
153
+ # );
154
+ #
155
+
156
+ attach_function :open_osfhandle, :_open_osfhandle, [:pointer, :int], :int
157
+
158
+ # BOOL WINAPI SetHandleInformation(
159
+ # __in HANDLE hObject,
160
+ # __in DWORD dwMask,
161
+ # __in DWORD dwFlags
162
+ # );
163
+
164
+ attach_function :set_handle_information, :SetHandleInformation, [:long, :ulong, :ulong], :bool
165
+
166
+ # BOOL WINAPI GetHandleInformation(
167
+ # __in HANDLE hObject,
168
+ # __out LPDWORD lpdwFlags
169
+ # );
170
+
171
+ attach_function :get_handle_information, :GetHandleInformation, [:long, :pointer], :bool
172
+
173
+ # BOOL WINAPI CreatePipe(
174
+ # __out PHANDLE hReadPipe,
175
+ # __out PHANDLE hWritePipe,
176
+ # __in_opt LPSECURITY_ATTRIBUTES lpPipeAttributes,
177
+ # __in DWORD nSize
178
+ # );
179
+
180
+ attach_function :create_pipe, :CreatePipe, [:pointer, :pointer, :pointer, :ulong], :bool
181
+
182
+ #
183
+ # HANDLE WINAPI GetCurrentProcess(void);
184
+ #
185
+
186
+ attach_function :current_process, :GetCurrentProcess, [], :pointer
187
+
188
+ #
189
+ # BOOL WINAPI DuplicateHandle(
190
+ # __in HANDLE hSourceProcessHandle,
191
+ # __in HANDLE hSourceHandle,
192
+ # __in HANDLE hTargetProcessHandle,
193
+ # __out LPHANDLE lpTargetHandle,
194
+ # __in DWORD dwDesiredAccess,
195
+ # __in BOOL bInheritHandle,
196
+ # __in DWORD dwOptions
197
+ # );
198
+ #
199
+
200
+ attach_function :_duplicate_handle, :DuplicateHandle, [
201
+ :pointer,
202
+ :pointer,
203
+ :pointer,
204
+ :pointer,
205
+ :ulong,
206
+ :bool,
207
+ :ulong
208
+ ], :bool
209
+
210
+ class << self
211
+ def kill(signal, *pids)
212
+ case signal
213
+ when 'SIGINT', 'INT', :SIGINT, :INT
214
+ signal = WIN_SIGINT
215
+ when 'SIGBRK', 'BRK', :SIGBREAK, :BRK
216
+ signal = WIN_SIGBREAK
217
+ when 'SIGKILL', 'KILL', :SIGKILL, :KILL
218
+ signal = WIN_SIGKILL
219
+ when 0..9
220
+ # Do nothing
221
+ else
222
+ raise Error, "invalid signal #{signal.inspect}"
223
+ end
224
+
225
+ pids.map { |pid| pid if Lib.send_signal(signal, pid) }.compact
226
+ end
227
+
228
+ def waitpid(pid, flags = 0)
229
+ wait_for_pid(pid, no_hang?(flags))
230
+ end
231
+
232
+ def waitpid2(pid, flags = 0)
233
+ code = wait_for_pid(pid, no_hang?(flags))
234
+
235
+ [pid, code] if code
236
+ end
237
+
238
+ def dont_inherit(file)
239
+ unless file.respond_to?(:fileno)
240
+ raise ArgumentError, "expected #{file.inspect} to respond to :fileno"
241
+ end
242
+
243
+ set_handle_inheritance(handle_for(file.fileno), false)
244
+ end
245
+
246
+ def last_error_message
247
+ errnum = get_last_error
248
+ buf = FFI::MemoryPointer.new :char, 512
249
+
250
+ size = format_message(
251
+ FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ARGUMENT_ARRAY,
252
+ nil, errnum, 0, buf, buf.size, nil
253
+ )
254
+
255
+ str = buf.read_string(size).strip
256
+ "#{str} (#{errnum})"
257
+ end
258
+
259
+ def handle_for(fd_or_io)
260
+ case fd_or_io
261
+ when IO
262
+ handle = get_osfhandle(fd_or_io.fileno)
263
+ when Fixnum
264
+ handle = get_osfhandle(fd_or_io)
265
+ else
266
+ if fd_or_io.respond_to?(:to_io)
267
+ io = fd_or_io.to_io
268
+
269
+ unless io.kind_of?(IO)
270
+ raise TypeError, "expected #to_io to return an instance of IO"
271
+ end
272
+
273
+ handle = get_osfhandle(io.fileno)
274
+ else
275
+ raise TypeError, "invalid type: #{fd_or_io.inspect}"
276
+ end
277
+ end
278
+
279
+ if handle == INVALID_HANDLE_VALUE
280
+ raise Error, last_error_message
281
+ end
282
+
283
+ handle
284
+ end
285
+
286
+ def io_for(handle, flags = File::RDONLY)
287
+ fd = open_osfhandle(handle, flags)
288
+ if fd == -1
289
+ raise Error, last_error_message
290
+ end
291
+
292
+ ::IO.for_fd fd, flags
293
+ end
294
+
295
+ def duplicate_handle(handle)
296
+ dup = FFI::MemoryPointer.new(:pointer)
297
+ proc = current_process
298
+
299
+ ok = Lib._duplicate_handle(
300
+ proc,
301
+ handle,
302
+ proc,
303
+ dup,
304
+ 0,
305
+ false,
306
+ DUPLICATE_SAME_ACCESS
307
+ )
308
+
309
+ check_error ok
310
+
311
+ dup.read_pointer
312
+ ensure
313
+ close_handle proc
314
+ end
315
+
316
+ def set_handle_inheritance(handle, bool)
317
+ status = set_handle_information(
318
+ handle,
319
+ HANDLE_FLAG_INHERIT,
320
+ bool ? HANDLE_FLAG_INHERIT : 0
321
+ )
322
+
323
+ check_error status
324
+ end
325
+
326
+ def get_handle_inheritance(handle)
327
+ flags = FFI::MemoryPointer.new(:uint)
328
+
329
+ status = get_handle_information(
330
+ handle,
331
+ flags
332
+ )
333
+
334
+ check_error status
335
+
336
+ flags.read_uint
337
+ end
338
+
339
+ def check_error(bool)
340
+ bool or raise Error, last_error_message
341
+ end
342
+
343
+ def no_hang?(flags)
344
+ (flags & Process::WNOHANG) == Process::WNOHANG
345
+ end
346
+
347
+ def wait_for_pid(pid, no_hang)
348
+ code = Handle.open(pid) { |handle|
349
+ handle.wait unless no_hang
350
+ handle.exit_code
351
+ }
352
+
353
+ code if code != PROCESS_STILL_ACTIVE
354
+ end
355
+ end
356
+
357
+ end # Lib
358
+ end # Windows
359
+ end # ChildProcess
@@ -23,6 +23,9 @@ module ChildProcess
23
23
  def wait
24
24
  @handle.wait
25
25
  @exit_code = @handle.exit_code
26
+ @handle.close
27
+
28
+ @exit_code
26
29
  end
27
30
 
28
31
  def exited?
@@ -36,6 +39,7 @@ module ChildProcess
36
39
 
37
40
  if exited
38
41
  @exit_code = code
42
+ @handle.close
39
43
  end
40
44
 
41
45
  exited
@@ -44,45 +48,28 @@ module ChildProcess
44
48
  private
45
49
 
46
50
  def launch_process
47
- opts = {
48
- :inherit => false,
49
- :detach => detach?,
50
- :duplex => duplex?,
51
- :environment => (@environment unless @environment.empty?)
52
- }
51
+ builder = ProcessBuilder.new(@args)
52
+ builder.inherit = false
53
+ builder.detach = detach?
54
+ builder.duplex = duplex?
55
+ builder.environment = @environment unless @environment.empty?
53
56
 
54
57
  if @io
55
- opts[:stdout] = @io.stdout
56
- opts[:stderr] = @io.stderr
58
+ builder.stdout = @io.stdout
59
+ builder.stderr = @io.stderr
57
60
  end
58
61
 
59
- @pid = Lib.create_proc(command_string, opts)
60
- @handle = Handle.open(@pid)
62
+ @pid = builder.start
63
+ @handle = Handle.open @pid
61
64
 
62
65
  if duplex?
63
- io._stdin = opts[:stdin]
66
+ raise Error, "no stdin stream" unless builder.stdin
67
+ io._stdin = builder.stdin
64
68
  end
65
69
 
66
70
  self
67
71
  end
68
72
 
69
- def command_string
70
- @command_string ||= (
71
- @args.map { |arg| quote_if_necessary(arg.to_s) }.join ' '
72
- )
73
- end
74
-
75
- def quote_if_necessary(str)
76
- quote = str.start_with?('"') ? "'" : '"'
77
-
78
- case str
79
- when /[\s\\'"]/
80
- %{#{quote}#{str}#{quote}}
81
- else
82
- str
83
- end
84
- end
85
-
86
73
  end # Process
87
74
  end # Windows
88
75
  end # ChildProcess