aggkit 0.2.5

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