childprocess 4.1.0 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +23 -0
  3. data/CHANGELOG.md +5 -0
  4. data/Gemfile +0 -2
  5. data/README.md +3 -20
  6. data/lib/childprocess/abstract_process.rb +1 -1
  7. data/lib/childprocess/errors.rb +0 -21
  8. data/lib/childprocess/process_spawn_process.rb +127 -0
  9. data/lib/childprocess/unix/process.rb +3 -64
  10. data/lib/childprocess/unix.rb +2 -4
  11. data/lib/childprocess/version.rb +1 -1
  12. data/lib/childprocess/windows/process.rb +13 -116
  13. data/lib/childprocess/windows.rb +0 -29
  14. data/lib/childprocess.rb +16 -53
  15. data/spec/childprocess_spec.rb +39 -15
  16. data/spec/io_spec.rb +1 -1
  17. data/spec/spec_helper.rb +3 -18
  18. data/spec/unix_spec.rb +3 -7
  19. metadata +8 -27
  20. data/.travis.yml +0 -37
  21. data/appveyor.yml +0 -36
  22. data/lib/childprocess/jruby/io.rb +0 -16
  23. data/lib/childprocess/jruby/process.rb +0 -184
  24. data/lib/childprocess/jruby/pump.rb +0 -53
  25. data/lib/childprocess/jruby.rb +0 -56
  26. data/lib/childprocess/tools/generator.rb +0 -146
  27. data/lib/childprocess/unix/fork_exec_process.rb +0 -78
  28. data/lib/childprocess/unix/lib.rb +0 -186
  29. data/lib/childprocess/unix/platform/arm64-macosx.rb +0 -11
  30. data/lib/childprocess/unix/platform/i386-linux.rb +0 -12
  31. data/lib/childprocess/unix/platform/i386-solaris.rb +0 -11
  32. data/lib/childprocess/unix/platform/x86_64-linux.rb +0 -12
  33. data/lib/childprocess/unix/platform/x86_64-macosx.rb +0 -11
  34. data/lib/childprocess/unix/posix_spawn_process.rb +0 -134
  35. data/lib/childprocess/windows/handle.rb +0 -91
  36. data/lib/childprocess/windows/lib.rb +0 -416
  37. data/lib/childprocess/windows/process_builder.rb +0 -178
  38. data/lib/childprocess/windows/structs.rb +0 -149
  39. data/spec/jruby_spec.rb +0 -24
@@ -1,416 +0,0 @@
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
@@ -1,178 +0,0 @@
1
- module ChildProcess
2
- module Windows
3
- class ProcessBuilder
4
- attr_accessor :leader, :detach, :duplex, :environment, :stdout, :stderr, :cwd
5
- attr_reader :stdin
6
-
7
- def initialize(args)
8
- @args = args
9
-
10
- @detach = false
11
- @duplex = false
12
- @environment = nil
13
- @cwd = nil
14
-
15
- @stdout = nil
16
- @stderr = nil
17
- @stdin = nil
18
-
19
- @flags = 0
20
- @job_ptr = nil
21
- @cmd_ptr = nil
22
- @env_ptr = nil
23
- @cwd_ptr = nil
24
- end
25
-
26
- def start
27
- create_command_pointer
28
- create_environment_pointer
29
- create_cwd_pointer
30
-
31
- setup_flags
32
- setup_io
33
-
34
- pid = create_process
35
- close_handles
36
-
37
- pid
38
- end
39
-
40
- private
41
-
42
- def to_wide_string(str)
43
- newstr = str + "\0".encode(str.encoding)
44
- newstr.encode!('UTF-16LE')
45
- end
46
-
47
- def create_command_pointer
48
- string = @args.map { |arg| quote_if_necessary(arg.to_s) }.join(' ')
49
- @cmd_ptr = to_wide_string(string)
50
- end
51
-
52
- def create_environment_pointer
53
- return unless @environment.kind_of?(Hash) && @environment.any?
54
-
55
- strings = []
56
-
57
- ENV.to_hash.merge(@environment).each do |key, val|
58
- next if val.nil?
59
-
60
- if key.to_s =~ /=|\0/ || val.to_s.include?("\0")
61
- raise InvalidEnvironmentVariable, "#{key.inspect} => #{val.inspect}"
62
- end
63
-
64
- strings << "#{key}=#{val}\0"
65
- end
66
-
67
- env_str = to_wide_string(strings.join)
68
- @env_ptr = FFI::MemoryPointer.from_string(env_str)
69
- end
70
-
71
- def create_cwd_pointer
72
- @cwd_ptr = FFI::MemoryPointer.from_string(to_wide_string(@cwd || Dir.pwd))
73
- end
74
-
75
- def create_process
76
- ok = Lib.create_process(
77
- nil, # application name
78
- @cmd_ptr, # command line
79
- nil, # process attributes
80
- nil, # thread attributes
81
- true, # inherit handles
82
- @flags, # creation flags
83
- @env_ptr, # environment
84
- @cwd_ptr, # current directory
85
- startup_info, # startup info
86
- process_info # process info
87
- )
88
-
89
- ok or raise LaunchError, Lib.last_error_message
90
-
91
- process_info[:dwProcessId]
92
- end
93
-
94
- def startup_info
95
- @startup_info ||= StartupInfo.new
96
- end
97
-
98
- def process_info
99
- @process_info ||= ProcessInfo.new
100
- end
101
-
102
- def setup_flags
103
- @flags |= CREATE_UNICODE_ENVIRONMENT
104
- @flags |= DETACHED_PROCESS if @detach
105
- @flags |= CREATE_BREAKAWAY_FROM_JOB if @leader
106
- end
107
-
108
- def setup_io
109
- startup_info[:dwFlags] ||= 0
110
- startup_info[:dwFlags] |= STARTF_USESTDHANDLES
111
-
112
- if @stdout
113
- startup_info[:hStdOutput] = std_stream_handle_for(@stdout)
114
- end
115
-
116
- if @stderr
117
- startup_info[:hStdError] = std_stream_handle_for(@stderr)
118
- end
119
-
120
- if @duplex
121
- read_pipe_ptr = FFI::MemoryPointer.new(:pointer)
122
- write_pipe_ptr = FFI::MemoryPointer.new(:pointer)
123
- sa = SecurityAttributes.new(:inherit => true)
124
-
125
- ok = Lib.create_pipe(read_pipe_ptr, write_pipe_ptr, sa, 0)
126
- Lib.check_error ok
127
-
128
- @read_pipe = read_pipe_ptr.read_pointer
129
- @write_pipe = write_pipe_ptr.read_pointer
130
-
131
- Lib.set_handle_inheritance @read_pipe, true
132
- Lib.set_handle_inheritance @write_pipe, false
133
-
134
- startup_info[:hStdInput] = @read_pipe
135
- else
136
- startup_info[:hStdInput] = std_stream_handle_for(STDIN)
137
- end
138
- end
139
-
140
- def std_stream_handle_for(io)
141
- handle = Lib.handle_for(io)
142
-
143
- begin
144
- Lib.set_handle_inheritance handle, true
145
- rescue ChildProcess::Error
146
- # If the IO was set to close on exec previously, this call will fail.
147
- # That's probably OK, since the user explicitly asked for it to be
148
- # closed (at least I have yet to find other cases where this will
149
- # happen...)
150
- end
151
-
152
- handle
153
- end
154
-
155
- def close_handles
156
- Lib.close_handle process_info[:hProcess]
157
- Lib.close_handle process_info[:hThread]
158
-
159
- if @duplex
160
- @stdin = Lib.io_for(Lib.duplicate_handle(@write_pipe), File::WRONLY)
161
- Lib.close_handle @read_pipe
162
- Lib.close_handle @write_pipe
163
- end
164
- end
165
-
166
- def quote_if_necessary(str)
167
- quote = str.start_with?('"') ? "'" : '"'
168
-
169
- case str
170
- when /[\s\\'"]/
171
- [quote, str, quote].join
172
- else
173
- str
174
- end
175
- end
176
- end # ProcessBuilder
177
- end # Windows
178
- end # ChildProcess