childprocess 4.1.0 → 5.0.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 (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