childprocess 0.5.9 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/.document +6 -6
  3. data/.gitignore +25 -25
  4. data/.rspec +1 -1
  5. data/.travis.yml +20 -18
  6. data/CHANGELOG.md +8 -0
  7. data/Gemfile +11 -4
  8. data/LICENSE +20 -20
  9. data/README.md +178 -178
  10. data/Rakefile +61 -61
  11. data/childprocess.gemspec +30 -29
  12. data/lib/childprocess.rb +184 -177
  13. data/lib/childprocess/abstract_io.rb +36 -36
  14. data/lib/childprocess/abstract_process.rb +187 -187
  15. data/lib/childprocess/errors.rb +26 -26
  16. data/lib/childprocess/jruby.rb +56 -56
  17. data/lib/childprocess/jruby/io.rb +16 -16
  18. data/lib/childprocess/jruby/process.rb +159 -159
  19. data/lib/childprocess/jruby/pump.rb +52 -52
  20. data/lib/childprocess/tools/generator.rb +145 -145
  21. data/lib/childprocess/unix.rb +9 -9
  22. data/lib/childprocess/unix/fork_exec_process.rb +70 -70
  23. data/lib/childprocess/unix/io.rb +21 -21
  24. data/lib/childprocess/unix/lib.rb +186 -186
  25. data/lib/childprocess/unix/platform/i386-linux.rb +12 -12
  26. data/lib/childprocess/unix/platform/i386-solaris.rb +11 -11
  27. data/lib/childprocess/unix/platform/x86_64-linux.rb +12 -12
  28. data/lib/childprocess/unix/platform/x86_64-macosx.rb +11 -11
  29. data/lib/childprocess/unix/posix_spawn_process.rb +134 -134
  30. data/lib/childprocess/unix/process.rb +89 -89
  31. data/lib/childprocess/version.rb +3 -3
  32. data/lib/childprocess/windows.rb +33 -33
  33. data/lib/childprocess/windows/handle.rb +91 -91
  34. data/lib/childprocess/windows/io.rb +25 -25
  35. data/lib/childprocess/windows/lib.rb +415 -415
  36. data/lib/childprocess/windows/process.rb +129 -129
  37. data/lib/childprocess/windows/process_builder.rb +174 -174
  38. data/lib/childprocess/windows/structs.rb +148 -148
  39. data/spec/abstract_io_spec.rb +12 -12
  40. data/spec/childprocess_spec.rb +291 -256
  41. data/spec/io_spec.rb +228 -228
  42. data/spec/jruby_spec.rb +24 -24
  43. data/spec/pid_behavior.rb +12 -12
  44. data/spec/spec_helper.rb +253 -253
  45. data/spec/unix_spec.rb +57 -57
  46. data/spec/windows_spec.rb +23 -23
  47. metadata +47 -45
@@ -1,129 +1,129 @@
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.detach = detach?
60
- builder.duplex = duplex?
61
- builder.environment = @environment unless @environment.empty?
62
- builder.cwd = @cwd
63
-
64
- if @io
65
- builder.stdout = @io.stdout
66
- builder.stderr = @io.stderr
67
- end
68
-
69
- @pid = builder.start
70
- @handle = Handle.open @pid
71
-
72
- if leader?
73
- @job = Job.new
74
- @job << @handle
75
- end
76
-
77
- if duplex?
78
- raise Error, "no stdin stream" unless builder.stdin
79
- io._stdin = builder.stdin
80
- end
81
-
82
- self
83
- end
84
-
85
- def close_handle
86
- @handle.close
87
- end
88
-
89
- def close_job_if_necessary
90
- @job.close if leader?
91
- end
92
-
93
-
94
- class Job
95
- def initialize
96
- @pointer = Lib.create_job_object(nil, nil)
97
-
98
- if @pointer.nil? || @pointer.null?
99
- raise Error, "unable to create job object"
100
- end
101
-
102
- basic = JobObjectBasicLimitInformation.new
103
- basic[:LimitFlags] = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
104
-
105
- extended = JobObjectExtendedLimitInformation.new
106
- extended[:BasicLimitInformation] = basic
107
-
108
- ret = Lib.set_information_job_object(
109
- @pointer,
110
- JOB_OBJECT_EXTENDED_LIMIT_INFORMATION,
111
- extended,
112
- extended.size
113
- )
114
-
115
- Lib.check_error ret
116
- end
117
-
118
- def <<(handle)
119
- Lib.check_error Lib.assign_process_to_job_object(@pointer, handle.pointer)
120
- end
121
-
122
- def close
123
- Lib.close_handle @pointer
124
- end
125
- end
126
-
127
- end # Process
128
- end # Windows
129
- end # ChildProcess
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.detach = detach?
60
+ builder.duplex = duplex?
61
+ builder.environment = @environment unless @environment.empty?
62
+ builder.cwd = @cwd
63
+
64
+ if @io
65
+ builder.stdout = @io.stdout
66
+ builder.stderr = @io.stderr
67
+ end
68
+
69
+ @pid = builder.start
70
+ @handle = Handle.open @pid
71
+
72
+ if leader?
73
+ @job = Job.new
74
+ @job << @handle
75
+ end
76
+
77
+ if duplex?
78
+ raise Error, "no stdin stream" unless builder.stdin
79
+ io._stdin = builder.stdin
80
+ end
81
+
82
+ self
83
+ end
84
+
85
+ def close_handle
86
+ @handle.close
87
+ end
88
+
89
+ def close_job_if_necessary
90
+ @job.close if leader?
91
+ end
92
+
93
+
94
+ class Job
95
+ def initialize
96
+ @pointer = Lib.create_job_object(nil, nil)
97
+
98
+ if @pointer.nil? || @pointer.null?
99
+ raise Error, "unable to create job object"
100
+ end
101
+
102
+ basic = JobObjectBasicLimitInformation.new
103
+ basic[:LimitFlags] = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE
104
+
105
+ extended = JobObjectExtendedLimitInformation.new
106
+ extended[:BasicLimitInformation] = basic
107
+
108
+ ret = Lib.set_information_job_object(
109
+ @pointer,
110
+ JOB_OBJECT_EXTENDED_LIMIT_INFORMATION,
111
+ extended,
112
+ extended.size
113
+ )
114
+
115
+ Lib.check_error ret
116
+ end
117
+
118
+ def <<(handle)
119
+ Lib.check_error Lib.assign_process_to_job_object(@pointer, handle.pointer)
120
+ end
121
+
122
+ def close
123
+ Lib.close_handle @pointer
124
+ end
125
+ end
126
+
127
+ end # Process
128
+ end # Windows
129
+ end # ChildProcess
@@ -1,174 +1,174 @@
1
- module ChildProcess
2
- module Windows
3
- class ProcessBuilder
4
- attr_accessor :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
- end
103
-
104
- def setup_io
105
- startup_info[:dwFlags] ||= 0
106
- startup_info[:dwFlags] |= STARTF_USESTDHANDLES
107
-
108
- if @stdout
109
- startup_info[:hStdOutput] = std_stream_handle_for(@stdout)
110
- end
111
-
112
- if @stderr
113
- startup_info[:hStdError] = std_stream_handle_for(@stderr)
114
- end
115
-
116
- if @duplex
117
- read_pipe_ptr = FFI::MemoryPointer.new(:pointer)
118
- write_pipe_ptr = FFI::MemoryPointer.new(:pointer)
119
- sa = SecurityAttributes.new(:inherit => true)
120
-
121
- ok = Lib.create_pipe(read_pipe_ptr, write_pipe_ptr, sa, 0)
122
- Lib.check_error ok
123
-
124
- @read_pipe = read_pipe_ptr.read_pointer
125
- @write_pipe = write_pipe_ptr.read_pointer
126
-
127
- Lib.set_handle_inheritance @read_pipe, true
128
- Lib.set_handle_inheritance @write_pipe, false
129
-
130
- startup_info[:hStdInput] = @read_pipe
131
- else
132
- startup_info[:hStdInput] = std_stream_handle_for(STDIN)
133
- end
134
- end
135
-
136
- def std_stream_handle_for(io)
137
- handle = Lib.handle_for(io)
138
-
139
- begin
140
- Lib.set_handle_inheritance handle, true
141
- rescue ChildProcess::Error
142
- # If the IO was set to close on exec previously, this call will fail.
143
- # That's probably OK, since the user explicitly asked for it to be
144
- # closed (at least I have yet to find other cases where this will
145
- # happen...)
146
- end
147
-
148
- handle
149
- end
150
-
151
- def close_handles
152
- Lib.close_handle process_info[:hProcess]
153
- Lib.close_handle process_info[:hThread]
154
-
155
- if @duplex
156
- @stdin = Lib.io_for(Lib.duplicate_handle(@write_pipe), File::WRONLY)
157
- Lib.close_handle @read_pipe
158
- Lib.close_handle @write_pipe
159
- end
160
- end
161
-
162
- def quote_if_necessary(str)
163
- quote = str.start_with?('"') ? "'" : '"'
164
-
165
- case str
166
- when /[\s\\'"]/
167
- [quote, str, quote].join
168
- else
169
- str
170
- end
171
- end
172
- end # ProcessBuilder
173
- end # Windows
174
- end # ChildProcess
1
+ module ChildProcess
2
+ module Windows
3
+ class ProcessBuilder
4
+ attr_accessor :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
+ end
103
+
104
+ def setup_io
105
+ startup_info[:dwFlags] ||= 0
106
+ startup_info[:dwFlags] |= STARTF_USESTDHANDLES
107
+
108
+ if @stdout
109
+ startup_info[:hStdOutput] = std_stream_handle_for(@stdout)
110
+ end
111
+
112
+ if @stderr
113
+ startup_info[:hStdError] = std_stream_handle_for(@stderr)
114
+ end
115
+
116
+ if @duplex
117
+ read_pipe_ptr = FFI::MemoryPointer.new(:pointer)
118
+ write_pipe_ptr = FFI::MemoryPointer.new(:pointer)
119
+ sa = SecurityAttributes.new(:inherit => true)
120
+
121
+ ok = Lib.create_pipe(read_pipe_ptr, write_pipe_ptr, sa, 0)
122
+ Lib.check_error ok
123
+
124
+ @read_pipe = read_pipe_ptr.read_pointer
125
+ @write_pipe = write_pipe_ptr.read_pointer
126
+
127
+ Lib.set_handle_inheritance @read_pipe, true
128
+ Lib.set_handle_inheritance @write_pipe, false
129
+
130
+ startup_info[:hStdInput] = @read_pipe
131
+ else
132
+ startup_info[:hStdInput] = std_stream_handle_for(STDIN)
133
+ end
134
+ end
135
+
136
+ def std_stream_handle_for(io)
137
+ handle = Lib.handle_for(io)
138
+
139
+ begin
140
+ Lib.set_handle_inheritance handle, true
141
+ rescue ChildProcess::Error
142
+ # If the IO was set to close on exec previously, this call will fail.
143
+ # That's probably OK, since the user explicitly asked for it to be
144
+ # closed (at least I have yet to find other cases where this will
145
+ # happen...)
146
+ end
147
+
148
+ handle
149
+ end
150
+
151
+ def close_handles
152
+ Lib.close_handle process_info[:hProcess]
153
+ Lib.close_handle process_info[:hThread]
154
+
155
+ if @duplex
156
+ @stdin = Lib.io_for(Lib.duplicate_handle(@write_pipe), File::WRONLY)
157
+ Lib.close_handle @read_pipe
158
+ Lib.close_handle @write_pipe
159
+ end
160
+ end
161
+
162
+ def quote_if_necessary(str)
163
+ quote = str.start_with?('"') ? "'" : '"'
164
+
165
+ case str
166
+ when /[\s\\'"]/
167
+ [quote, str, quote].join
168
+ else
169
+ str
170
+ end
171
+ end
172
+ end # ProcessBuilder
173
+ end # Windows
174
+ end # ChildProcess