docker_toolkit 0.1.2 → 0.1.3

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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +10 -1
  3. data/Gemfile.lock +1 -1
  4. data/bin/terminator.rb +56 -0
  5. data/docker/Dockerfile +4 -1
  6. data/docker_toolkit.gemspec +1 -0
  7. data/lib/docker_toolkit/childprocess/abstract_io.rb +38 -0
  8. data/lib/docker_toolkit/childprocess/abstract_process.rb +194 -0
  9. data/lib/docker_toolkit/childprocess/errors.rb +28 -0
  10. data/lib/docker_toolkit/childprocess/jruby/io.rb +17 -0
  11. data/lib/docker_toolkit/childprocess/jruby/process.rb +161 -0
  12. data/lib/docker_toolkit/childprocess/jruby/pump.rb +55 -0
  13. data/lib/docker_toolkit/childprocess/jruby.rb +58 -0
  14. data/lib/docker_toolkit/childprocess/tools/generator.rb +148 -0
  15. data/lib/docker_toolkit/childprocess/unix/fork_exec_process.rb +72 -0
  16. data/lib/docker_toolkit/childprocess/unix/io.rb +22 -0
  17. data/lib/docker_toolkit/childprocess/unix/lib.rb +188 -0
  18. data/lib/docker_toolkit/childprocess/unix/platform/i386-linux.rb +14 -0
  19. data/lib/docker_toolkit/childprocess/unix/platform/i386-solaris.rb +13 -0
  20. data/lib/docker_toolkit/childprocess/unix/platform/x86_64-linux.rb +14 -0
  21. data/lib/docker_toolkit/childprocess/unix/platform/x86_64-macosx.rb +13 -0
  22. data/lib/docker_toolkit/childprocess/unix/posix_spawn_process.rb +135 -0
  23. data/lib/docker_toolkit/childprocess/unix/process.rb +91 -0
  24. data/lib/docker_toolkit/childprocess/unix.rb +11 -0
  25. data/lib/docker_toolkit/childprocess/version.rb +5 -0
  26. data/lib/docker_toolkit/childprocess/windows/handle.rb +93 -0
  27. data/lib/docker_toolkit/childprocess/windows/io.rb +25 -0
  28. data/lib/docker_toolkit/childprocess/windows/lib.rb +418 -0
  29. data/lib/docker_toolkit/childprocess/windows/process.rb +132 -0
  30. data/lib/docker_toolkit/childprocess/windows/process_builder.rb +177 -0
  31. data/lib/docker_toolkit/childprocess/windows/structs.rb +151 -0
  32. data/lib/docker_toolkit/childprocess/windows.rb +35 -0
  33. data/lib/docker_toolkit/childprocess.rb +208 -0
  34. data/lib/docker_toolkit/version.rb +1 -1
  35. data/lib/docker_toolkit/watcher.rb +188 -0
  36. data/lib/docker_toolkit.rb +5 -0
  37. metadata +33 -2
@@ -0,0 +1,91 @@
1
+ module DockerToolkit
2
+ module ChildProcess
3
+ module Unix
4
+ class Process < AbstractProcess
5
+ attr_reader :pid
6
+
7
+ def io
8
+ @io ||= Unix::IO.new
9
+ end
10
+
11
+ def stop(timeout = 3)
12
+ assert_started
13
+ send_term
14
+
15
+ begin
16
+ return poll_for_exit(timeout)
17
+ rescue TimeoutError
18
+ # try next
19
+ end
20
+
21
+ send_kill
22
+ wait
23
+ rescue Errno::ECHILD, Errno::ESRCH
24
+ # handle race condition where process dies between timeout
25
+ # and send_kill
26
+ true
27
+ end
28
+
29
+ def exited?
30
+ return true if @exit_code
31
+
32
+ assert_started
33
+ pid, status = ::Process.waitpid2(_pid, ::Process::WNOHANG | ::Process::WUNTRACED)
34
+ pid = nil if pid == 0 # may happen on jruby
35
+
36
+ log(:pid => pid, :status => status)
37
+
38
+ if pid
39
+ set_exit_code(status)
40
+ end
41
+
42
+ !!pid
43
+ rescue Errno::ECHILD
44
+ # may be thrown for detached processes
45
+ true
46
+ end
47
+
48
+ def wait
49
+ assert_started
50
+
51
+ if exited?
52
+ exit_code
53
+ else
54
+ _, status = ::Process.waitpid2 _pid
55
+ set_exit_code(status)
56
+ end
57
+ end
58
+
59
+ private
60
+
61
+ def send_term
62
+ send_signal 'TERM'
63
+ end
64
+
65
+ def send_kill
66
+ send_signal 'KILL'
67
+ end
68
+
69
+ def send_signal(sig)
70
+ assert_started
71
+
72
+ log "sending #{sig}"
73
+ ::Process.kill sig, _pid
74
+ end
75
+
76
+ def set_exit_code(status)
77
+ @exit_code = status.exitstatus || status.termsig
78
+ end
79
+
80
+ def _pid
81
+ if leader?
82
+ -@pid # negative pid == process group
83
+ else
84
+ @pid
85
+ end
86
+ end
87
+
88
+ end # Process
89
+ end # Unix
90
+ end # ChildProcess
91
+ end
@@ -0,0 +1,11 @@
1
+ module DockerToolkit
2
+ module ChildProcess
3
+ module Unix
4
+ end
5
+ end
6
+ end
7
+
8
+ require "docker_toolkit/childprocess/unix/io"
9
+ require "docker_toolkit/childprocess/unix/process"
10
+ require "docker_toolkit/childprocess/unix/fork_exec_process"
11
+ # PosixSpawnProcess + ffi is required on demand.
@@ -0,0 +1,5 @@
1
+ module DockerToolkit
2
+ module ChildProcess
3
+ VERSION = '0.9.0'
4
+ end
5
+ end
@@ -0,0 +1,93 @@
1
+ module DockerToolkit
2
+ module ChildProcess
3
+ module Windows
4
+ class Handle
5
+
6
+ class << self
7
+ private :new
8
+
9
+ def open(pid, access = PROCESS_ALL_ACCESS)
10
+ handle = Lib.open_process(access, false, pid)
11
+
12
+ if handle.null?
13
+ raise Error, Lib.last_error_message
14
+ end
15
+
16
+ h = new(handle, pid)
17
+ return h unless block_given?
18
+
19
+ begin
20
+ yield h
21
+ ensure
22
+ h.close
23
+ end
24
+ end
25
+ end
26
+
27
+ attr_reader :pointer
28
+
29
+ def initialize(pointer, pid)
30
+ unless pointer.kind_of?(FFI::Pointer)
31
+ raise TypeError, "invalid handle: #{pointer.inspect}"
32
+ end
33
+
34
+ if pointer.null?
35
+ raise ArgumentError, "handle is null: #{pointer.inspect}"
36
+ end
37
+
38
+ @pid = pid
39
+ @pointer = pointer
40
+ @closed = false
41
+ end
42
+
43
+ def exit_code
44
+ code_pointer = FFI::MemoryPointer.new :ulong
45
+ ok = Lib.get_exit_code(@pointer, code_pointer)
46
+
47
+ if ok
48
+ code_pointer.get_ulong(0)
49
+ else
50
+ close
51
+ raise Error, Lib.last_error_message
52
+ end
53
+ end
54
+
55
+ def send(signal)
56
+ case signal
57
+ when 0
58
+ exit_code == PROCESS_STILL_ALIVE
59
+ when WIN_SIGINT
60
+ Lib.generate_console_ctrl_event(CTRL_C_EVENT, @pid)
61
+ when WIN_SIGBREAK
62
+ Lib.generate_console_ctrl_event(CTRL_BREAK_EVENT, @pid)
63
+ when WIN_SIGKILL
64
+ ok = Lib.terminate_process(@pointer, @pid)
65
+ Lib.check_error ok
66
+ else
67
+ thread_id = FFI::MemoryPointer.new(:ulong)
68
+ module_handle = Lib.get_module_handle("kernel32")
69
+ proc_address = Lib.get_proc_address(module_handle, "ExitProcess")
70
+
71
+ thread = Lib.create_remote_thread(@pointer, 0, 0, proc_address, 0, 0, thread_id)
72
+ check_error thread
73
+
74
+ Lib.wait_for_single_object(thread, 5)
75
+ true
76
+ end
77
+ end
78
+
79
+ def close
80
+ return if @closed
81
+
82
+ Lib.close_handle(@pointer)
83
+ @closed = true
84
+ end
85
+
86
+ def wait(milliseconds = nil)
87
+ Lib.wait_for_single_object(@pointer, milliseconds || INFINITE)
88
+ end
89
+
90
+ end # Handle
91
+ end # Windows
92
+ end # ChildProcess
93
+ end
@@ -0,0 +1,25 @@
1
+ module DockerToolkit
2
+ module ChildProcess
3
+ module Windows
4
+ class IO < AbstractIO
5
+ private
6
+
7
+ def check_type(io)
8
+ return if has_fileno?(io)
9
+ return if has_to_io?(io)
10
+
11
+ raise ArgumentError, "#{io.inspect}:#{io.class} must have :fileno or :to_io"
12
+ end
13
+
14
+ def has_fileno?(io)
15
+ io.respond_to?(:fileno) && io.fileno
16
+ end
17
+
18
+ def has_to_io?(io)
19
+ io.respond_to?(:to_io) && io.to_io.kind_of?(::IO)
20
+ end
21
+
22
+ end # IO
23
+ end # Windows
24
+ end # ChildProcess
25
+ end
@@ -0,0 +1,418 @@
1
+ module DockerToolkit
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