childprocess 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/.document +6 -0
  3. data/.gitignore +28 -0
  4. data/.rspec +1 -0
  5. data/.travis.yml +44 -0
  6. data/CHANGELOG.md +49 -0
  7. data/Gemfile +15 -0
  8. data/LICENSE +20 -0
  9. data/README.md +196 -0
  10. data/Rakefile +61 -0
  11. data/appveyor.yml +60 -0
  12. data/childprocess.gemspec +30 -0
  13. data/lib/childprocess.rb +205 -0
  14. data/lib/childprocess/abstract_io.rb +36 -0
  15. data/lib/childprocess/abstract_process.rb +192 -0
  16. data/lib/childprocess/errors.rb +26 -0
  17. data/lib/childprocess/jruby.rb +56 -0
  18. data/lib/childprocess/jruby/io.rb +16 -0
  19. data/lib/childprocess/jruby/process.rb +159 -0
  20. data/lib/childprocess/jruby/pump.rb +53 -0
  21. data/lib/childprocess/tools/generator.rb +146 -0
  22. data/lib/childprocess/unix.rb +9 -0
  23. data/lib/childprocess/unix/fork_exec_process.rb +70 -0
  24. data/lib/childprocess/unix/io.rb +21 -0
  25. data/lib/childprocess/unix/lib.rb +186 -0
  26. data/lib/childprocess/unix/platform/i386-linux.rb +12 -0
  27. data/lib/childprocess/unix/platform/i386-solaris.rb +11 -0
  28. data/lib/childprocess/unix/platform/x86_64-linux.rb +12 -0
  29. data/lib/childprocess/unix/platform/x86_64-macosx.rb +11 -0
  30. data/lib/childprocess/unix/posix_spawn_process.rb +134 -0
  31. data/lib/childprocess/unix/process.rb +89 -0
  32. data/lib/childprocess/version.rb +3 -0
  33. data/lib/childprocess/windows.rb +33 -0
  34. data/lib/childprocess/windows/handle.rb +91 -0
  35. data/lib/childprocess/windows/io.rb +25 -0
  36. data/lib/childprocess/windows/lib.rb +416 -0
  37. data/lib/childprocess/windows/process.rb +130 -0
  38. data/lib/childprocess/windows/process_builder.rb +175 -0
  39. data/lib/childprocess/windows/structs.rb +149 -0
  40. data/spec/abstract_io_spec.rb +12 -0
  41. data/spec/childprocess_spec.rb +422 -0
  42. data/spec/io_spec.rb +228 -0
  43. data/spec/jruby_spec.rb +24 -0
  44. data/spec/pid_behavior.rb +12 -0
  45. data/spec/platform_detection_spec.rb +86 -0
  46. data/spec/spec_helper.rb +261 -0
  47. data/spec/unix_spec.rb +57 -0
  48. data/spec/windows_spec.rb +23 -0
  49. metadata +179 -0
@@ -0,0 +1,130 @@
1
+ module ChildProcess
2
+ module Windows
3
+ class Process < AbstractProcess
4
+
5
+ attr_reader :pid
6
+
7
+ def io
8
+ @io ||= Windows::IO.new
9
+ end
10
+
11
+ def stop(timeout = 3)
12
+ assert_started
13
+
14
+ log "sending KILL"
15
+ @handle.send(WIN_SIGKILL)
16
+
17
+ poll_for_exit(timeout)
18
+ ensure
19
+ close_handle
20
+ close_job_if_necessary
21
+ end
22
+
23
+ def wait
24
+ if exited?
25
+ exit_code
26
+ else
27
+ @handle.wait
28
+ @exit_code = @handle.exit_code
29
+
30
+ close_handle
31
+ close_job_if_necessary
32
+
33
+ @exit_code
34
+ end
35
+ end
36
+
37
+ def exited?
38
+ return true if @exit_code
39
+ assert_started
40
+
41
+ code = @handle.exit_code
42
+ exited = code != PROCESS_STILL_ACTIVE
43
+
44
+ log(:exited? => exited, :code => code)
45
+
46
+ if exited
47
+ @exit_code = code
48
+ close_handle
49
+ close_job_if_necessary
50
+ end
51
+
52
+ exited
53
+ end
54
+
55
+ private
56
+
57
+ def launch_process
58
+ builder = ProcessBuilder.new(@args)
59
+ builder.leader = leader?
60
+ builder.detach = detach?
61
+ builder.duplex = duplex?
62
+ builder.environment = @environment unless @environment.empty?
63
+ builder.cwd = @cwd
64
+
65
+ if @io
66
+ builder.stdout = @io.stdout
67
+ builder.stderr = @io.stderr
68
+ end
69
+
70
+ @pid = builder.start
71
+ @handle = Handle.open @pid
72
+
73
+ if leader?
74
+ @job = Job.new
75
+ @job << @handle
76
+ end
77
+
78
+ if duplex?
79
+ raise Error, "no stdin stream" unless builder.stdin
80
+ io._stdin = builder.stdin
81
+ end
82
+
83
+ self
84
+ end
85
+
86
+ def close_handle
87
+ @handle.close
88
+ end
89
+
90
+ def close_job_if_necessary
91
+ @job.close if leader?
92
+ end
93
+
94
+
95
+ class Job
96
+ def initialize
97
+ @pointer = Lib.create_job_object(nil, nil)
98
+
99
+ if @pointer.nil? || @pointer.null?
100
+ raise Error, "unable to create job object"
101
+ end
102
+
103
+ basic = JobObjectBasicLimitInformation.new
104
+ basic[:LimitFlags] = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | JOB_OBJECT_LIMIT_BREAKAWAY_OK
105
+
106
+ extended = JobObjectExtendedLimitInformation.new
107
+ extended[:BasicLimitInformation] = basic
108
+
109
+ ret = Lib.set_information_job_object(
110
+ @pointer,
111
+ JOB_OBJECT_EXTENDED_LIMIT_INFORMATION,
112
+ extended,
113
+ extended.size
114
+ )
115
+
116
+ Lib.check_error ret
117
+ end
118
+
119
+ def <<(handle)
120
+ Lib.check_error Lib.assign_process_to_job_object(@pointer, handle.pointer)
121
+ end
122
+
123
+ def close
124
+ Lib.close_handle @pointer
125
+ end
126
+ end
127
+
128
+ end # Process
129
+ end # Windows
130
+ end # ChildProcess
@@ -0,0 +1,175 @@
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 create_command_pointer
43
+ string = @args.map { |arg| quote_if_necessary(arg.to_s) }.join ' '
44
+ @cmd_ptr = FFI::MemoryPointer.from_string string
45
+ end
46
+
47
+ def create_environment_pointer
48
+ return unless @environment.kind_of?(Hash) && @environment.any?
49
+
50
+ strings = []
51
+
52
+ ENV.to_hash.merge(@environment).each do |key, val|
53
+ next if val.nil?
54
+
55
+ if key.to_s =~ /=|\0/ || val.to_s.include?("\0")
56
+ raise InvalidEnvironmentVariable, "#{key.inspect} => #{val.inspect}"
57
+ end
58
+
59
+ strings << "#{key}=#{val}\0"
60
+ end
61
+
62
+ strings << "\0" # terminate the env block
63
+ env_str = strings.join
64
+
65
+ @env_ptr = FFI::MemoryPointer.new(:long, env_str.bytesize)
66
+ @env_ptr.put_bytes 0, env_str, 0, env_str.bytesize
67
+ end
68
+
69
+ def create_cwd_pointer
70
+ @cwd_ptr = FFI::MemoryPointer.from_string(@cwd || Dir.pwd)
71
+ end
72
+
73
+ def create_process
74
+ ok = Lib.create_process(
75
+ nil, # application name
76
+ @cmd_ptr, # command line
77
+ nil, # process attributes
78
+ nil, # thread attributes
79
+ true, # inherit handles
80
+ @flags, # creation flags
81
+ @env_ptr, # environment
82
+ @cwd_ptr, # current directory
83
+ startup_info, # startup info
84
+ process_info # process info
85
+ )
86
+
87
+ ok or raise LaunchError, Lib.last_error_message
88
+
89
+ process_info[:dwProcessId]
90
+ end
91
+
92
+ def startup_info
93
+ @startup_info ||= StartupInfo.new
94
+ end
95
+
96
+ def process_info
97
+ @process_info ||= ProcessInfo.new
98
+ end
99
+
100
+ def setup_flags
101
+ @flags |= DETACHED_PROCESS if @detach
102
+ @flags |= CREATE_BREAKAWAY_FROM_JOB if @leader
103
+ end
104
+
105
+ def setup_io
106
+ startup_info[:dwFlags] ||= 0
107
+ startup_info[:dwFlags] |= STARTF_USESTDHANDLES
108
+
109
+ if @stdout
110
+ startup_info[:hStdOutput] = std_stream_handle_for(@stdout)
111
+ end
112
+
113
+ if @stderr
114
+ startup_info[:hStdError] = std_stream_handle_for(@stderr)
115
+ end
116
+
117
+ if @duplex
118
+ read_pipe_ptr = FFI::MemoryPointer.new(:pointer)
119
+ write_pipe_ptr = FFI::MemoryPointer.new(:pointer)
120
+ sa = SecurityAttributes.new(:inherit => true)
121
+
122
+ ok = Lib.create_pipe(read_pipe_ptr, write_pipe_ptr, sa, 0)
123
+ Lib.check_error ok
124
+
125
+ @read_pipe = read_pipe_ptr.read_pointer
126
+ @write_pipe = write_pipe_ptr.read_pointer
127
+
128
+ Lib.set_handle_inheritance @read_pipe, true
129
+ Lib.set_handle_inheritance @write_pipe, false
130
+
131
+ startup_info[:hStdInput] = @read_pipe
132
+ else
133
+ startup_info[:hStdInput] = std_stream_handle_for(STDIN)
134
+ end
135
+ end
136
+
137
+ def std_stream_handle_for(io)
138
+ handle = Lib.handle_for(io)
139
+
140
+ begin
141
+ Lib.set_handle_inheritance handle, true
142
+ rescue ChildProcess::Error
143
+ # If the IO was set to close on exec previously, this call will fail.
144
+ # That's probably OK, since the user explicitly asked for it to be
145
+ # closed (at least I have yet to find other cases where this will
146
+ # happen...)
147
+ end
148
+
149
+ handle
150
+ end
151
+
152
+ def close_handles
153
+ Lib.close_handle process_info[:hProcess]
154
+ Lib.close_handle process_info[:hThread]
155
+
156
+ if @duplex
157
+ @stdin = Lib.io_for(Lib.duplicate_handle(@write_pipe), File::WRONLY)
158
+ Lib.close_handle @read_pipe
159
+ Lib.close_handle @write_pipe
160
+ end
161
+ end
162
+
163
+ def quote_if_necessary(str)
164
+ quote = str.start_with?('"') ? "'" : '"'
165
+
166
+ case str
167
+ when /[\s\\'"]/
168
+ [quote, str, quote].join
169
+ else
170
+ str
171
+ end
172
+ end
173
+ end # ProcessBuilder
174
+ end # Windows
175
+ end # ChildProcess
@@ -0,0 +1,149 @@
1
+ module ChildProcess
2
+ module Windows
3
+ # typedef struct _STARTUPINFO {
4
+ # DWORD cb;
5
+ # LPTSTR lpReserved;
6
+ # LPTSTR lpDesktop;
7
+ # LPTSTR lpTitle;
8
+ # DWORD dwX;
9
+ # DWORD dwY;
10
+ # DWORD dwXSize;
11
+ # DWORD dwYSize;
12
+ # DWORD dwXCountChars;
13
+ # DWORD dwYCountChars;
14
+ # DWORD dwFillAttribute;
15
+ # DWORD dwFlags;
16
+ # WORD wShowWindow;
17
+ # WORD cbReserved2;
18
+ # LPBYTE lpReserved2;
19
+ # HANDLE hStdInput;
20
+ # HANDLE hStdOutput;
21
+ # HANDLE hStdError;
22
+ # } STARTUPINFO, *LPSTARTUPINFO;
23
+
24
+ class StartupInfo < FFI::Struct
25
+ layout :cb, :ulong,
26
+ :lpReserved, :pointer,
27
+ :lpDesktop, :pointer,
28
+ :lpTitle, :pointer,
29
+ :dwX, :ulong,
30
+ :dwY, :ulong,
31
+ :dwXSize, :ulong,
32
+ :dwYSize, :ulong,
33
+ :dwXCountChars, :ulong,
34
+ :dwYCountChars, :ulong,
35
+ :dwFillAttribute, :ulong,
36
+ :dwFlags, :ulong,
37
+ :wShowWindow, :ushort,
38
+ :cbReserved2, :ushort,
39
+ :lpReserved2, :pointer,
40
+ :hStdInput, :pointer, # void ptr
41
+ :hStdOutput, :pointer, # void ptr
42
+ :hStdError, :pointer # void ptr
43
+ end
44
+
45
+ #
46
+ # typedef struct _PROCESS_INFORMATION {
47
+ # HANDLE hProcess;
48
+ # HANDLE hThread;
49
+ # DWORD dwProcessId;
50
+ # DWORD dwThreadId;
51
+ # } PROCESS_INFORMATION, *LPPROCESS_INFORMATION;
52
+ #
53
+
54
+ class ProcessInfo < FFI::Struct
55
+ layout :hProcess, :pointer, # void ptr
56
+ :hThread, :pointer, # void ptr
57
+ :dwProcessId, :ulong,
58
+ :dwThreadId, :ulong
59
+ end
60
+
61
+ #
62
+ # typedef struct _SECURITY_ATTRIBUTES {
63
+ # DWORD nLength;
64
+ # LPVOID lpSecurityDescriptor;
65
+ # BOOL bInheritHandle;
66
+ # } SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES;
67
+ #
68
+
69
+ class SecurityAttributes < FFI::Struct
70
+ layout :nLength, :ulong,
71
+ :lpSecurityDescriptor, :pointer, # void ptr
72
+ :bInheritHandle, :int
73
+
74
+ def initialize(opts = {})
75
+ super()
76
+
77
+ self[:nLength] = self.class.size
78
+ self[:lpSecurityDescriptor] = nil
79
+ self[:bInheritHandle] = opts[:inherit] ? 1 : 0
80
+ end
81
+ end
82
+
83
+ #
84
+ # typedef struct _JOBOBJECT_BASIC_LIMIT_INFORMATION {
85
+ # LARGE_INTEGER PerProcessUserTimeLimit;
86
+ # LARGE_INTEGER PerJobUserTimeLimit;
87
+ # DWORD LimitFlags;
88
+ # SIZE_T MinimumWorkingSetSize;
89
+ # SIZE_T MaximumWorkingSetSize;
90
+ # DWORD ActiveProcessLimit;
91
+ # ULONG_PTR Affinity;
92
+ # DWORD PriorityClass;
93
+ # DWORD SchedulingClass;
94
+ # } JOBOBJECT_BASIC_LIMIT_INFORMATION, *PJOBOBJECT_BASIC_LIMIT_INFORMATION;
95
+ #
96
+ class JobObjectBasicLimitInformation < FFI::Struct
97
+ layout :PerProcessUserTimeLimit, :int64,
98
+ :PerJobUserTimeLimit, :int64,
99
+ :LimitFlags, :ulong,
100
+ :MinimumWorkingSetSize, :size_t,
101
+ :MaximumWorkingSetSize, :size_t,
102
+ :ActiveProcessLimit, :ulong,
103
+ :Affinity, :pointer,
104
+ :PriorityClass, :ulong,
105
+ :SchedulingClass, :ulong
106
+ end
107
+
108
+ #
109
+ # typedef struct _IO_COUNTERS {
110
+ # ULONGLONG ReadOperationCount;
111
+ # ULONGLONG WriteOperationCount;
112
+ # ULONGLONG OtherOperationCount;
113
+ # ULONGLONG ReadTransferCount;
114
+ # ULONGLONG WriteTransferCount;
115
+ # ULONGLONG OtherTransferCount;
116
+ # } IO_COUNTERS, *PIO_COUNTERS;
117
+ #
118
+
119
+ class IoCounters < FFI::Struct
120
+ layout :ReadOperationCount, :ulong_long,
121
+ :WriteOperationCount, :ulong_long,
122
+ :OtherOperationCount, :ulong_long,
123
+ :ReadTransferCount, :ulong_long,
124
+ :WriteTransferCount, :ulong_long,
125
+ :OtherTransferCount, :ulong_long
126
+ end
127
+ #
128
+ # typedef struct _JOBOBJECT_EXTENDED_LIMIT_INFORMATION {
129
+ # JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
130
+ # IO_COUNTERS IoInfo;
131
+ # SIZE_T ProcessMemoryLimit;
132
+ # SIZE_T JobMemoryLimit;
133
+ # SIZE_T PeakProcessMemoryUsed;
134
+ # SIZE_T PeakJobMemoryUsed;
135
+ # } JOBOBJECT_EXTENDED_LIMIT_INFORMATION, *PJOBOBJECT_EXTENDED_LIMIT_INFORMATION;
136
+ #
137
+
138
+ class JobObjectExtendedLimitInformation < FFI::Struct
139
+ layout :BasicLimitInformation, JobObjectBasicLimitInformation,
140
+ :IoInfo, IoCounters,
141
+ :ProcessMemoryLimit, :size_t,
142
+ :JobMemoryLimit, :size_t,
143
+ :PeakProcessMemoryUsed, :size_t,
144
+ :PeakJobMemoryUsed, :size_t
145
+ end
146
+
147
+
148
+ end # Windows
149
+ end # ChildProcess