childprocess 0.2.5 → 0.2.6

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