aggkit 0.2.5

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 (58) hide show
  1. checksums.yaml +7 -0
  2. data/.dockerignore +20 -0
  3. data/.gitignore +109 -0
  4. data/.gitlab-ci.yml +66 -0
  5. data/.rspec +4 -0
  6. data/.rubocop.yml +98 -0
  7. data/.travis.yml +30 -0
  8. data/Gemfile +12 -0
  9. data/Gemfile.lock +55 -0
  10. data/README.md +96 -0
  11. data/aggkit.gemspec +38 -0
  12. data/bin/agg +167 -0
  13. data/bin/aggconsul +222 -0
  14. data/bin/agglock +71 -0
  15. data/bin/aggmerge +118 -0
  16. data/bin/aggwait +262 -0
  17. data/bin/consul.rb +222 -0
  18. data/bin/locker.rb +71 -0
  19. data/bin/merger.rb +118 -0
  20. data/bin/terminator.rb +71 -0
  21. data/bin/waiter.rb +262 -0
  22. data/docker/Dockerfile +112 -0
  23. data/docker/docker-compose.yml +12 -0
  24. data/docker/down.sh +4 -0
  25. data/docker/run_tests.sh +23 -0
  26. data/lib/aggkit/childprocess/abstract_io.rb +38 -0
  27. data/lib/aggkit/childprocess/abstract_process.rb +194 -0
  28. data/lib/aggkit/childprocess/errors.rb +28 -0
  29. data/lib/aggkit/childprocess/jruby/io.rb +17 -0
  30. data/lib/aggkit/childprocess/jruby/process.rb +161 -0
  31. data/lib/aggkit/childprocess/jruby/pump.rb +55 -0
  32. data/lib/aggkit/childprocess/jruby.rb +58 -0
  33. data/lib/aggkit/childprocess/tools/generator.rb +148 -0
  34. data/lib/aggkit/childprocess/unix/fork_exec_process.rb +72 -0
  35. data/lib/aggkit/childprocess/unix/io.rb +22 -0
  36. data/lib/aggkit/childprocess/unix/lib.rb +188 -0
  37. data/lib/aggkit/childprocess/unix/platform/i386-linux.rb +14 -0
  38. data/lib/aggkit/childprocess/unix/platform/i386-solaris.rb +13 -0
  39. data/lib/aggkit/childprocess/unix/platform/x86_64-linux.rb +14 -0
  40. data/lib/aggkit/childprocess/unix/platform/x86_64-macosx.rb +13 -0
  41. data/lib/aggkit/childprocess/unix/posix_spawn_process.rb +135 -0
  42. data/lib/aggkit/childprocess/unix/process.rb +91 -0
  43. data/lib/aggkit/childprocess/unix.rb +11 -0
  44. data/lib/aggkit/childprocess/version.rb +5 -0
  45. data/lib/aggkit/childprocess/windows/handle.rb +93 -0
  46. data/lib/aggkit/childprocess/windows/io.rb +25 -0
  47. data/lib/aggkit/childprocess/windows/lib.rb +418 -0
  48. data/lib/aggkit/childprocess/windows/process.rb +132 -0
  49. data/lib/aggkit/childprocess/windows/process_builder.rb +177 -0
  50. data/lib/aggkit/childprocess/windows/structs.rb +151 -0
  51. data/lib/aggkit/childprocess/windows.rb +35 -0
  52. data/lib/aggkit/childprocess.rb +213 -0
  53. data/lib/aggkit/env.rb +219 -0
  54. data/lib/aggkit/runner.rb +80 -0
  55. data/lib/aggkit/version.rb +5 -0
  56. data/lib/aggkit/watcher.rb +239 -0
  57. data/lib/aggkit.rb +15 -0
  58. metadata +196 -0
@@ -0,0 +1,418 @@
1
+ module Aggkit
2
+ module ChildProcess
3
+ module Windows
4
+ FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000
5
+ FORMAT_MESSAGE_ARGUMENT_ARRAY = 0x00002000
6
+
7
+ PROCESS_ALL_ACCESS = 0x1F0FFF
8
+ PROCESS_QUERY_INFORMATION = 0x0400
9
+ PROCESS_VM_READ = 0x0010
10
+ PROCESS_STILL_ACTIVE = 259
11
+
12
+ INFINITE = 0xFFFFFFFF
13
+
14
+ WIN_SIGINT = 2
15
+ WIN_SIGBREAK = 3
16
+ WIN_SIGKILL = 9
17
+
18
+ CTRL_C_EVENT = 0
19
+ CTRL_BREAK_EVENT = 1
20
+
21
+ CREATE_BREAKAWAY_FROM_JOB = 0x01000000
22
+ DETACHED_PROCESS = 0x00000008
23
+
24
+ STARTF_USESTDHANDLES = 0x00000100
25
+ INVALID_HANDLE_VALUE = -1
26
+ HANDLE_FLAG_INHERIT = 0x00000001
27
+
28
+ DUPLICATE_SAME_ACCESS = 0x00000002
29
+ CREATE_UNICODE_ENVIRONMENT = 0x00000400
30
+
31
+ JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x2000
32
+ JOB_OBJECT_LIMIT_BREAKAWAY_OK = 0x00000800
33
+ JOB_OBJECT_EXTENDED_LIMIT_INFORMATION = 9
34
+ JOB_OBJECT_BASIC_LIMIT_INFORMATION = 2
35
+
36
+ module Lib
37
+ enum :wait_status, [
38
+ :wait_object_0, 0,
39
+ :wait_timeout, 0x102,
40
+ :wait_abandoned, 0x80,
41
+ :wait_failed, 0xFFFFFFFF
42
+ ]
43
+
44
+ #
45
+ # BOOL WINAPI CreateProcess(
46
+ # __in_opt LPCTSTR lpApplicationName,
47
+ # __inout_opt LPTSTR lpCommandLine,
48
+ # __in_opt LPSECURITY_ATTRIBUTES lpProcessAttributes,
49
+ # __in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes,
50
+ # __in BOOL bInheritHandles,
51
+ # __in DWORD dwCreationFlags,
52
+ # __in_opt LPVOID lpEnvironment,
53
+ # __in_opt LPCTSTR lpCurrentDirectory,
54
+ # __in LPSTARTUPINFO lpStartupInfo,
55
+ # __out LPPROCESS_INFORMATION lpProcessInformation
56
+ # );
57
+ #
58
+
59
+ attach_function :create_process, :CreateProcessA, [
60
+ :pointer,
61
+ :pointer,
62
+ :pointer,
63
+ :pointer,
64
+ :bool,
65
+ :ulong,
66
+ :pointer,
67
+ :pointer,
68
+ :pointer,
69
+ :pointer], :bool
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
+ # HANDLE WINAPI CreateJobObject(
107
+ # _In_opt_ LPSECURITY_ATTRIBUTES lpJobAttributes,
108
+ # _In_opt_ LPCTSTR lpName
109
+ # );
110
+ #
111
+
112
+ attach_function :create_job_object, :CreateJobObjectA, [:pointer, :pointer], :pointer
113
+
114
+ #
115
+ # BOOL WINAPI AssignProcessToJobObject(
116
+ # _In_ HANDLE hJob,
117
+ # _In_ HANDLE hProcess
118
+ # );
119
+
120
+ attach_function :assign_process_to_job_object, :AssignProcessToJobObject, [:pointer, :pointer], :bool
121
+
122
+ #
123
+ # BOOL WINAPI SetInformationJobObject(
124
+ # _In_ HANDLE hJob,
125
+ # _In_ JOBOBJECTINFOCLASS JobObjectInfoClass,
126
+ # _In_ LPVOID lpJobObjectInfo,
127
+ # _In_ DWORD cbJobObjectInfoLength
128
+ # );
129
+ #
130
+
131
+ attach_function :set_information_job_object, :SetInformationJobObject, [:pointer, :int, :pointer, :ulong], :bool
132
+
133
+ #
134
+ #
135
+ # DWORD WINAPI WaitForSingleObject(
136
+ # __in HANDLE hHandle,
137
+ # __in DWORD dwMilliseconds
138
+ # );
139
+ #
140
+
141
+ attach_function :wait_for_single_object, :WaitForSingleObject, [:pointer, :ulong], :wait_status, :blocking => true
142
+
143
+ #
144
+ # BOOL WINAPI GetExitCodeProcess(
145
+ # __in HANDLE hProcess,
146
+ # __out LPDWORD lpExitCode
147
+ # );
148
+ #
149
+
150
+ attach_function :get_exit_code, :GetExitCodeProcess, [:pointer, :pointer], :bool
151
+
152
+ #
153
+ # BOOL WINAPI GenerateConsoleCtrlEvent(
154
+ # __in DWORD dwCtrlEvent,
155
+ # __in DWORD dwProcessGroupId
156
+ # );
157
+ #
158
+
159
+ attach_function :generate_console_ctrl_event, :GenerateConsoleCtrlEvent, [:ulong, :ulong], :bool
160
+
161
+ #
162
+ # BOOL WINAPI TerminateProcess(
163
+ # __in HANDLE hProcess,
164
+ # __in UINT uExitCode
165
+ # );
166
+ #
167
+
168
+ attach_function :terminate_process, :TerminateProcess, [:pointer, :uint], :bool
169
+
170
+ #
171
+ # intptr_t _get_osfhandle(
172
+ # int fd
173
+ # );
174
+ #
175
+
176
+ attach_function :get_osfhandle, :_get_osfhandle, [:int], :intptr_t
177
+
178
+ #
179
+ # int _open_osfhandle (
180
+ # intptr_t osfhandle,
181
+ # int flags
182
+ # );
183
+ #
184
+
185
+ attach_function :open_osfhandle, :_open_osfhandle, [:pointer, :int], :int
186
+
187
+ # BOOL WINAPI SetHandleInformation(
188
+ # __in HANDLE hObject,
189
+ # __in DWORD dwMask,
190
+ # __in DWORD dwFlags
191
+ # );
192
+
193
+ attach_function :set_handle_information, :SetHandleInformation, [:pointer, :ulong, :ulong], :bool
194
+
195
+ # BOOL WINAPI GetHandleInformation(
196
+ # __in HANDLE hObject,
197
+ # __out LPDWORD lpdwFlags
198
+ # );
199
+
200
+ attach_function :get_handle_information, :GetHandleInformation, [:pointer, :pointer], :bool
201
+
202
+ # BOOL WINAPI CreatePipe(
203
+ # __out PHANDLE hReadPipe,
204
+ # __out PHANDLE hWritePipe,
205
+ # __in_opt LPSECURITY_ATTRIBUTES lpPipeAttributes,
206
+ # __in DWORD nSize
207
+ # );
208
+
209
+ attach_function :create_pipe, :CreatePipe, [:pointer, :pointer, :pointer, :ulong], :bool
210
+
211
+ #
212
+ # HANDLE WINAPI GetCurrentProcess(void);
213
+ #
214
+
215
+ attach_function :current_process, :GetCurrentProcess, [], :pointer
216
+
217
+ #
218
+ # BOOL WINAPI DuplicateHandle(
219
+ # __in HANDLE hSourceProcessHandle,
220
+ # __in HANDLE hSourceHandle,
221
+ # __in HANDLE hTargetProcessHandle,
222
+ # __out LPHANDLE lpTargetHandle,
223
+ # __in DWORD dwDesiredAccess,
224
+ # __in BOOL bInheritHandle,
225
+ # __in DWORD dwOptions
226
+ # );
227
+ #
228
+
229
+ attach_function :_duplicate_handle, :DuplicateHandle, [
230
+ :pointer,
231
+ :pointer,
232
+ :pointer,
233
+ :pointer,
234
+ :ulong,
235
+ :bool,
236
+ :ulong
237
+ ], :bool
238
+
239
+ class << self
240
+ def kill(signal, *pids)
241
+ case signal
242
+ when 'SIGINT', 'INT', :SIGINT, :INT
243
+ signal = WIN_SIGINT
244
+ when 'SIGBRK', 'BRK', :SIGBREAK, :BRK
245
+ signal = WIN_SIGBREAK
246
+ when 'SIGKILL', 'KILL', :SIGKILL, :KILL
247
+ signal = WIN_SIGKILL
248
+ when 0..9
249
+ # Do nothing
250
+ else
251
+ raise Error, "invalid signal #{signal.inspect}"
252
+ end
253
+
254
+ pids.map { |pid| pid if Lib.send_signal(signal, pid) }.compact
255
+ end
256
+
257
+ def waitpid(pid, flags = 0)
258
+ wait_for_pid(pid, no_hang?(flags))
259
+ end
260
+
261
+ def waitpid2(pid, flags = 0)
262
+ code = wait_for_pid(pid, no_hang?(flags))
263
+
264
+ [pid, code] if code
265
+ end
266
+
267
+ def dont_inherit(file)
268
+ unless file.respond_to?(:fileno)
269
+ raise ArgumentError, "expected #{file.inspect} to respond to :fileno"
270
+ end
271
+
272
+ set_handle_inheritance(handle_for(file.fileno), false)
273
+ end
274
+
275
+ def last_error_message
276
+ errnum = FFI.errno
277
+
278
+ buf = FFI::MemoryPointer.new :char, 512
279
+
280
+ size = format_message(
281
+ FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ARGUMENT_ARRAY,
282
+ nil, errnum, 0, buf, buf.size, nil
283
+ )
284
+
285
+ str = buf.read_string(size).strip
286
+ if errnum == 0
287
+ "Unknown error (Windows says #{str.inspect}, but it did not.)"
288
+ else
289
+ "#{str} (#{errnum})"
290
+ end
291
+ end
292
+
293
+ def each_child_of(pid, &blk)
294
+ raise NotImplementedError
295
+
296
+ # http://stackoverflow.com/questions/1173342/terminate-a-process-tree-c-for-windows?rq=1
297
+
298
+ # for each process entry
299
+ # if pe.th32ParentProcessID == pid
300
+ # Handle.open(pe.pe.th32ProcessId, &blk)
301
+ # end
302
+ #
303
+ end
304
+
305
+ def handle_for(fd_or_io)
306
+ if fd_or_io.kind_of?(IO) || fd_or_io.respond_to?(:fileno)
307
+ if ChildProcess.jruby?
308
+ handle = ChildProcess::JRuby.windows_handle_for(fd_or_io)
309
+ else
310
+ handle = get_osfhandle(fd_or_io.fileno)
311
+ end
312
+ elsif fd_or_io.kind_of?(Integer)
313
+ handle = get_osfhandle(fd_or_io)
314
+ elsif fd_or_io.respond_to?(:to_io)
315
+ io = fd_or_io.to_io
316
+
317
+ unless io.kind_of?(IO)
318
+ raise TypeError, "expected #to_io to return an instance of IO"
319
+ end
320
+
321
+ handle = get_osfhandle(io.fileno)
322
+ else
323
+ raise TypeError, "invalid type: #{fd_or_io.inspect}"
324
+ end
325
+
326
+ if handle == INVALID_HANDLE_VALUE
327
+ raise Error, last_error_message
328
+ end
329
+
330
+ FFI::Pointer.new handle
331
+ end
332
+
333
+ def io_for(handle, flags = File::RDONLY)
334
+ fd = open_osfhandle(handle, flags)
335
+ if fd == -1
336
+ raise Error, last_error_message
337
+ end
338
+
339
+ FFI::IO.for_fd fd, flags
340
+ end
341
+
342
+ def duplicate_handle(handle)
343
+ dup = FFI::MemoryPointer.new(:pointer)
344
+ proc = current_process
345
+
346
+ ok = Lib._duplicate_handle(
347
+ proc,
348
+ handle,
349
+ proc,
350
+ dup,
351
+ 0,
352
+ false,
353
+ DUPLICATE_SAME_ACCESS
354
+ )
355
+
356
+ check_error ok
357
+
358
+ dup.read_pointer
359
+ ensure
360
+ close_handle proc
361
+ end
362
+
363
+ def set_handle_inheritance(handle, bool)
364
+ status = set_handle_information(
365
+ handle,
366
+ HANDLE_FLAG_INHERIT,
367
+ bool ? HANDLE_FLAG_INHERIT : 0
368
+ )
369
+
370
+ check_error status
371
+ end
372
+
373
+ def get_handle_inheritance(handle)
374
+ flags = FFI::MemoryPointer.new(:uint)
375
+
376
+ status = get_handle_information(
377
+ handle,
378
+ flags
379
+ )
380
+
381
+ check_error status
382
+
383
+ flags.read_uint
384
+ end
385
+
386
+ def check_error(bool)
387
+ bool or raise Error, last_error_message
388
+ end
389
+
390
+ def alive?(pid)
391
+ handle = Lib.open_process(PROCESS_ALL_ACCESS, false, pid)
392
+ if handle.null?
393
+ false
394
+ else
395
+ ptr = FFI::MemoryPointer.new :ulong
396
+ Lib.check_error Lib.get_exit_code(handle, ptr)
397
+ ptr.read_ulong == PROCESS_STILL_ACTIVE
398
+ end
399
+ end
400
+
401
+ def no_hang?(flags)
402
+ (flags & Process::WNOHANG) == Process::WNOHANG
403
+ end
404
+
405
+ def wait_for_pid(pid, no_hang)
406
+ code = Handle.open(pid) { |handle|
407
+ handle.wait unless no_hang
408
+ handle.exit_code
409
+ }
410
+
411
+ code if code != PROCESS_STILL_ACTIVE
412
+ end
413
+ end
414
+
415
+ end # Lib
416
+ end # Windows
417
+ end # ChildProcess
418
+ end
@@ -0,0 +1,132 @@
1
+ module Aggkit
2
+ module ChildProcess
3
+ module Windows
4
+ class Process < AbstractProcess
5
+
6
+ attr_reader :pid
7
+
8
+ def io
9
+ @io ||= Windows::IO.new
10
+ end
11
+
12
+ def stop(timeout = 3)
13
+ assert_started
14
+
15
+ log "sending KILL"
16
+ @handle.send(WIN_SIGKILL)
17
+
18
+ poll_for_exit(timeout)
19
+ ensure
20
+ close_handle
21
+ close_job_if_necessary
22
+ end
23
+
24
+ def wait
25
+ if exited?
26
+ exit_code
27
+ else
28
+ @handle.wait
29
+ @exit_code = @handle.exit_code
30
+
31
+ close_handle
32
+ close_job_if_necessary
33
+
34
+ @exit_code
35
+ end
36
+ end
37
+
38
+ def exited?
39
+ return true if @exit_code
40
+ assert_started
41
+
42
+ code = @handle.exit_code
43
+ exited = code != PROCESS_STILL_ACTIVE
44
+
45
+ log(:exited? => exited, :code => code)
46
+
47
+ if exited
48
+ @exit_code = code
49
+ close_handle
50
+ close_job_if_necessary
51
+ end
52
+
53
+ exited
54
+ end
55
+
56
+ private
57
+
58
+ def launch_process
59
+ builder = ProcessBuilder.new(@args)
60
+ builder.leader = leader?
61
+ builder.detach = detach?
62
+ builder.duplex = duplex?
63
+ builder.environment = @environment unless @environment.empty?
64
+ builder.cwd = @cwd
65
+
66
+ if @io
67
+ builder.stdout = @io.stdout
68
+ builder.stderr = @io.stderr
69
+ end
70
+
71
+ @pid = builder.start
72
+ @handle = Handle.open @pid
73
+
74
+ if leader?
75
+ @job = Job.new
76
+ @job << @handle
77
+ end
78
+
79
+ if duplex?
80
+ raise Error, "no stdin stream" unless builder.stdin
81
+ io._stdin = builder.stdin
82
+ end
83
+
84
+ self
85
+ end
86
+
87
+ def close_handle
88
+ @handle.close
89
+ end
90
+
91
+ def close_job_if_necessary
92
+ @job.close if leader?
93
+ end
94
+
95
+
96
+ class Job
97
+ def initialize
98
+ @pointer = Lib.create_job_object(nil, nil)
99
+
100
+ if @pointer.nil? || @pointer.null?
101
+ raise Error, "unable to create job object"
102
+ end
103
+
104
+ basic = JobObjectBasicLimitInformation.new
105
+ basic[:LimitFlags] = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | JOB_OBJECT_LIMIT_BREAKAWAY_OK
106
+
107
+ extended = JobObjectExtendedLimitInformation.new
108
+ extended[:BasicLimitInformation] = basic
109
+
110
+ ret = Lib.set_information_job_object(
111
+ @pointer,
112
+ JOB_OBJECT_EXTENDED_LIMIT_INFORMATION,
113
+ extended,
114
+ extended.size
115
+ )
116
+
117
+ Lib.check_error ret
118
+ end
119
+
120
+ def <<(handle)
121
+ Lib.check_error Lib.assign_process_to_job_object(@pointer, handle.pointer)
122
+ end
123
+
124
+ def close
125
+ Lib.close_handle @pointer
126
+ end
127
+ end
128
+
129
+ end # Process
130
+ end # Windows
131
+ end # ChildProcess
132
+ end
@@ -0,0 +1,177 @@
1
+ module Aggkit
2
+ module ChildProcess
3
+ module Windows
4
+ class ProcessBuilder
5
+ attr_accessor :leader, :detach, :duplex, :environment, :stdout, :stderr, :cwd
6
+ attr_reader :stdin
7
+
8
+ def initialize(args)
9
+ @args = args
10
+
11
+ @detach = false
12
+ @duplex = false
13
+ @environment = nil
14
+ @cwd = nil
15
+
16
+ @stdout = nil
17
+ @stderr = nil
18
+ @stdin = nil
19
+
20
+ @flags = 0
21
+ @job_ptr = nil
22
+ @cmd_ptr = nil
23
+ @env_ptr = nil
24
+ @cwd_ptr = nil
25
+ end
26
+
27
+ def start
28
+ create_command_pointer
29
+ create_environment_pointer
30
+ create_cwd_pointer
31
+
32
+ setup_flags
33
+ setup_io
34
+
35
+ pid = create_process
36
+ close_handles
37
+
38
+ pid
39
+ end
40
+
41
+ private
42
+
43
+ def create_command_pointer
44
+ string = @args.map { |arg| quote_if_necessary(arg.to_s) }.join ' '
45
+ @cmd_ptr = FFI::MemoryPointer.from_string string
46
+ end
47
+
48
+ def create_environment_pointer
49
+ return unless @environment.kind_of?(Hash) && @environment.any?
50
+
51
+ strings = []
52
+
53
+ ENV.to_hash.merge(@environment).each do |key, val|
54
+ next if val.nil?
55
+
56
+ if key.to_s =~ /=|\0/ || val.to_s.include?("\0")
57
+ raise InvalidEnvironmentVariable, "#{key.inspect} => #{val.inspect}"
58
+ end
59
+
60
+ strings << "#{key}=#{val}\0"
61
+ end
62
+
63
+ strings << "\0" # terminate the env block
64
+ env_str = strings.join
65
+
66
+ @env_ptr = FFI::MemoryPointer.new(:long, env_str.bytesize)
67
+ @env_ptr.put_bytes 0, env_str, 0, env_str.bytesize
68
+ end
69
+
70
+ def create_cwd_pointer
71
+ @cwd_ptr = FFI::MemoryPointer.from_string(@cwd || Dir.pwd)
72
+ end
73
+
74
+ def create_process
75
+ ok = Lib.create_process(
76
+ nil, # application name
77
+ @cmd_ptr, # command line
78
+ nil, # process attributes
79
+ nil, # thread attributes
80
+ true, # inherit handles
81
+ @flags, # creation flags
82
+ @env_ptr, # environment
83
+ @cwd_ptr, # current directory
84
+ startup_info, # startup info
85
+ process_info # process info
86
+ )
87
+
88
+ ok or raise LaunchError, Lib.last_error_message
89
+
90
+ process_info[:dwProcessId]
91
+ end
92
+
93
+ def startup_info
94
+ @startup_info ||= StartupInfo.new
95
+ end
96
+
97
+ def process_info
98
+ @process_info ||= ProcessInfo.new
99
+ end
100
+
101
+ def setup_flags
102
+ @flags |= DETACHED_PROCESS if @detach
103
+ @flags |= CREATE_BREAKAWAY_FROM_JOB if @leader
104
+ end
105
+
106
+ def setup_io
107
+ startup_info[:dwFlags] ||= 0
108
+ startup_info[:dwFlags] |= STARTF_USESTDHANDLES
109
+
110
+ if @stdout
111
+ startup_info[:hStdOutput] = std_stream_handle_for(@stdout)
112
+ end
113
+
114
+ if @stderr
115
+ startup_info[:hStdError] = std_stream_handle_for(@stderr)
116
+ end
117
+
118
+ if @duplex
119
+ read_pipe_ptr = FFI::MemoryPointer.new(:pointer)
120
+ write_pipe_ptr = FFI::MemoryPointer.new(:pointer)
121
+ sa = SecurityAttributes.new(:inherit => true)
122
+
123
+ ok = Lib.create_pipe(read_pipe_ptr, write_pipe_ptr, sa, 0)
124
+ Lib.check_error ok
125
+
126
+ @read_pipe = read_pipe_ptr.read_pointer
127
+ @write_pipe = write_pipe_ptr.read_pointer
128
+
129
+ Lib.set_handle_inheritance @read_pipe, true
130
+ Lib.set_handle_inheritance @write_pipe, false
131
+
132
+ startup_info[:hStdInput] = @read_pipe
133
+ else
134
+ startup_info[:hStdInput] = std_stream_handle_for(STDIN)
135
+ end
136
+ end
137
+
138
+ def std_stream_handle_for(io)
139
+ handle = Lib.handle_for(io)
140
+
141
+ begin
142
+ Lib.set_handle_inheritance handle, true
143
+ rescue ChildProcess::Error
144
+ # If the IO was set to close on exec previously, this call will fail.
145
+ # That's probably OK, since the user explicitly asked for it to be
146
+ # closed (at least I have yet to find other cases where this will
147
+ # happen...)
148
+ end
149
+
150
+ handle
151
+ end
152
+
153
+ def close_handles
154
+ Lib.close_handle process_info[:hProcess]
155
+ Lib.close_handle process_info[:hThread]
156
+
157
+ if @duplex
158
+ @stdin = Lib.io_for(Lib.duplicate_handle(@write_pipe), File::WRONLY)
159
+ Lib.close_handle @read_pipe
160
+ Lib.close_handle @write_pipe
161
+ end
162
+ end
163
+
164
+ def quote_if_necessary(str)
165
+ quote = str.start_with?('"') ? "'" : '"'
166
+
167
+ case str
168
+ when /[\s\\'"]/
169
+ [quote, str, quote].join
170
+ else
171
+ str
172
+ end
173
+ end
174
+ end # ProcessBuilder
175
+ end # Windows
176
+ end # ChildProcess
177
+ end