mixlib-shellout 2.2.6 → 2.2.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,7 +1,7 @@
1
- module Mixlib
2
- class ShellOut
3
- class ShellCommandFailed < RuntimeError; end
4
- class CommandTimeout < RuntimeError; end
5
- class InvalidCommandOption < RuntimeError; end
6
- end
7
- end
1
+ module Mixlib
2
+ class ShellOut
3
+ class ShellCommandFailed < RuntimeError; end
4
+ class CommandTimeout < RuntimeError; end
5
+ class InvalidCommandOption < RuntimeError; end
6
+ end
7
+ end
@@ -1,415 +1,415 @@
1
- #--
2
- # Author:: Daniel DeLeo (<dan@opscode.com>)
3
- # Copyright:: Copyright (c) 2010, 2011 Opscode, Inc.
4
- # License:: Apache License, Version 2.0
5
- #
6
- # Licensed under the Apache License, Version 2.0 (the "License");
7
- # you may not use this file except in compliance with the License.
8
- # You may obtain a copy of the License at
9
- #
10
- # http://www.apache.org/licenses/LICENSE-2.0
11
- #
12
- # Unless required by applicable law or agreed to in writing, software
13
- # distributed under the License is distributed on an "AS IS" BASIS,
14
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
- # See the License for the specific language governing permissions and
16
- # limitations under the License.
17
- #
18
-
19
- module Mixlib
20
- class ShellOut
21
- module Unix
22
-
23
- # "1.8.7" as a frozen string. We use this with a hack that disables GC to
24
- # avoid segfaults on Ruby 1.8.7, so we need to allocate the fewest
25
- # objects we possibly can.
26
- ONE_DOT_EIGHT_DOT_SEVEN = "1.8.7".freeze
27
-
28
- # Option validation that is unix specific
29
- def validate_options(opts)
30
- # No options to validate, raise exceptions here if needed
31
- end
32
-
33
- # Whether we're simulating a login shell
34
- def using_login?
35
- return login && user
36
- end
37
-
38
- # Helper method for sgids
39
- def all_seconderies
40
- ret = []
41
- Etc.endgrent
42
- while ( g = Etc.getgrent ) do
43
- ret << g
44
- end
45
- Etc.endgrent
46
- return ret
47
- end
48
-
49
- # The secondary groups that the subprocess will switch to.
50
- # Currently valid only if login is used, and is set
51
- # to the user's secondary groups
52
- def sgids
53
- return nil unless using_login?
54
- user_name = Etc.getpwuid(uid).name
55
- all_seconderies.select{|g| g.mem.include?(user_name)}.map{|g|g.gid}
56
- end
57
-
58
- # The environment variables that are deduced from simulating logon
59
- # Only valid if login is used
60
- def logon_environment
61
- return {} unless using_login?
62
- entry = Etc.getpwuid(uid)
63
- # According to `man su`, the set fields are:
64
- # $HOME, $SHELL, $USER, $LOGNAME, $PATH, and $IFS
65
- # Values are copied from "shadow" package in Ubuntu 14.10
66
- {'HOME'=>entry.dir, 'SHELL'=>entry.shell, 'USER'=>entry.name, 'LOGNAME'=>entry.name, 'PATH'=>'/sbin:/bin:/usr/sbin:/usr/bin', 'IFS'=>"\t\n"}
67
- end
68
-
69
- # Merges the two environments for the process
70
- def process_environment
71
- logon_environment.merge(self.environment)
72
- end
73
-
74
- # Run the command, writing the command's standard out and standard error
75
- # to +stdout+ and +stderr+, and saving its exit status object to +status+
76
- # === Returns
77
- # returns +self+; +stdout+, +stderr+, +status+, and +exitstatus+ will be
78
- # populated with results of the command.
79
- # === Raises
80
- # * Errno::EACCES when you are not privileged to execute the command
81
- # * Errno::ENOENT when the command is not available on the system (or not
82
- # in the current $PATH)
83
- # * Chef::Exceptions::CommandTimeout when the command does not complete
84
- # within +timeout+ seconds (default: 600s). When this happens, ShellOut
85
- # will send a TERM and then KILL to the entire process group to ensure
86
- # that any grandchild processes are terminated. If the invocation of
87
- # the child process spawned multiple child processes (which commonly
88
- # happens if the command is passed as a single string to be interpreted
89
- # by bin/sh, and bin/sh is not bash), the exit status object may not
90
- # contain the correct exit code of the process (of course there is no
91
- # exit code if the command is killed by SIGKILL, also).
92
- def run_command
93
- @child_pid = fork_subprocess
94
- @reaped = false
95
-
96
- configure_parent_process_file_descriptors
97
-
98
- # Ruby 1.8.7 and 1.8.6 from mid 2009 try to allocate objects during GC
99
- # when calling IO.select and IO#read. Disabling GC works around the
100
- # segfault, but obviously it's a bad workaround. We no longer support
101
- # 1.8.6 so we only need this hack for 1.8.7.
102
- GC.disable if RUBY_VERSION == ONE_DOT_EIGHT_DOT_SEVEN
103
-
104
- # CHEF-3390: Marshall.load on Ruby < 1.8.7p369 also has a GC bug related
105
- # to Marshall.load, so try disabling GC first.
106
- propagate_pre_exec_failure
107
-
108
- @status = nil
109
- @result = nil
110
- @execution_time = 0
111
-
112
- write_to_child_stdin
113
-
114
- until @status
115
- ready_buffers = attempt_buffer_read
116
- unless ready_buffers
117
- @execution_time += READ_WAIT_TIME
118
- if @execution_time >= timeout && !@result
119
- # kill the bad proccess
120
- reap_errant_child
121
- # read anything it wrote when we killed it
122
- attempt_buffer_read
123
- # raise
124
- raise CommandTimeout, "Command timed out after #{@execution_time.to_i}s:\n#{format_for_exception}"
125
- end
126
- end
127
-
128
- attempt_reap
129
- end
130
-
131
- self
132
- rescue Errno::ENOENT
133
- # When ENOENT happens, we can be reasonably sure that the child process
134
- # is going to exit quickly, so we use the blocking variant of waitpid2
135
- reap
136
- raise
137
- ensure
138
- reap_errant_child if should_reap?
139
- # make one more pass to get the last of the output after the
140
- # child process dies
141
- attempt_buffer_read
142
- # no matter what happens, turn the GC back on, and hope whatever busted
143
- # version of ruby we're on doesn't allocate some objects during the next
144
- # GC run.
145
- GC.enable
146
- close_all_pipes
147
- end
148
-
149
- private
150
-
151
- def set_user
152
- if user
153
- Process.uid = uid
154
- Process.euid = uid
155
- end
156
- end
157
-
158
- def set_group
159
- if group
160
- Process.egid = gid
161
- Process.gid = gid
162
- end
163
- end
164
-
165
- def set_secondarygroups
166
- if sgids
167
- Process.groups = sgids
168
- end
169
- end
170
-
171
- def set_environment
172
- # user-set variables should override the login ones
173
- process_environment.each do |env_var,value|
174
- ENV[env_var] = value
175
- end
176
- end
177
-
178
- def set_umask
179
- File.umask(umask) if umask
180
- end
181
-
182
- def set_cwd
183
- Dir.chdir(cwd) if cwd
184
- end
185
-
186
- # Since we call setsid the child_pgid will be the child_pid, set to negative here
187
- # so it can be directly used in arguments to kill, wait, etc.
188
- def child_pgid
189
- -@child_pid
190
- end
191
-
192
- def initialize_ipc
193
- @stdin_pipe, @stdout_pipe, @stderr_pipe, @process_status_pipe = IO.pipe, IO.pipe, IO.pipe, IO.pipe
194
- @process_status_pipe.last.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
195
- end
196
-
197
- def child_stdin
198
- @stdin_pipe[1]
199
- end
200
-
201
- def child_stdout
202
- @stdout_pipe[0]
203
- end
204
-
205
- def child_stderr
206
- @stderr_pipe[0]
207
- end
208
-
209
- def child_process_status
210
- @process_status_pipe[0]
211
- end
212
-
213
- def close_all_pipes
214
- child_stdin.close unless child_stdin.closed?
215
- child_stdout.close unless child_stdout.closed?
216
- child_stderr.close unless child_stderr.closed?
217
- child_process_status.close unless child_process_status.closed?
218
- end
219
-
220
- # Replace stdout, and stderr with pipes to the parent, and close the
221
- # reader side of the error marshaling side channel.
222
- #
223
- # If there is no input, close STDIN so when we exec,
224
- # the new program will know it's never getting input ever.
225
- def configure_subprocess_file_descriptors
226
- process_status_pipe.first.close
227
-
228
- # HACK: for some reason, just STDIN.close isn't good enough when running
229
- # under ruby 1.9.2, so make it good enough:
230
- stdin_pipe.last.close
231
- STDIN.reopen stdin_pipe.first
232
- stdin_pipe.first.close unless input
233
-
234
- stdout_pipe.first.close
235
- STDOUT.reopen stdout_pipe.last
236
- stdout_pipe.last.close
237
-
238
- stderr_pipe.first.close
239
- STDERR.reopen stderr_pipe.last
240
- stderr_pipe.last.close
241
-
242
- STDOUT.sync = STDERR.sync = true
243
- STDIN.sync = true if input
244
- end
245
-
246
- def configure_parent_process_file_descriptors
247
- # Close the sides of the pipes we don't care about
248
- stdin_pipe.first.close
249
- stdin_pipe.last.close unless input
250
- stdout_pipe.last.close
251
- stderr_pipe.last.close
252
- process_status_pipe.last.close
253
- # Get output as it happens rather than buffered
254
- child_stdin.sync = true if input
255
- child_stdout.sync = true
256
- child_stderr.sync = true
257
-
258
- true
259
- end
260
-
261
- # Some patch levels of ruby in wide use (in particular the ruby 1.8.6 on OSX)
262
- # segfault when you IO.select a pipe that's reached eof. Weak sauce.
263
- def open_pipes
264
- @open_pipes ||= [child_stdout, child_stderr, child_process_status]
265
- end
266
-
267
- # Keep this unbuffered for now
268
- def write_to_child_stdin
269
- return unless input
270
- child_stdin << input
271
- child_stdin.close # Kick things off
272
- end
273
-
274
- def attempt_buffer_read
275
- ready = IO.select(open_pipes, nil, nil, READ_WAIT_TIME)
276
- if ready
277
- read_stdout_to_buffer if ready.first.include?(child_stdout)
278
- read_stderr_to_buffer if ready.first.include?(child_stderr)
279
- read_process_status_to_buffer if ready.first.include?(child_process_status)
280
- end
281
- ready
282
- end
283
-
284
- def read_stdout_to_buffer
285
- while chunk = child_stdout.read_nonblock(READ_SIZE)
286
- @stdout << chunk
287
- @live_stdout << chunk if @live_stdout
288
- end
289
- rescue Errno::EAGAIN
290
- rescue EOFError
291
- open_pipes.delete(child_stdout)
292
- end
293
-
294
- def read_stderr_to_buffer
295
- while chunk = child_stderr.read_nonblock(READ_SIZE)
296
- @stderr << chunk
297
- @live_stderr << chunk if @live_stderr
298
- end
299
- rescue Errno::EAGAIN
300
- rescue EOFError
301
- open_pipes.delete(child_stderr)
302
- end
303
-
304
- def read_process_status_to_buffer
305
- while chunk = child_process_status.read_nonblock(READ_SIZE)
306
- @process_status << chunk
307
- end
308
- rescue Errno::EAGAIN
309
- rescue EOFError
310
- open_pipes.delete(child_process_status)
311
- end
312
-
313
- def fork_subprocess
314
- initialize_ipc
315
-
316
- fork do
317
- # Child processes may themselves fork off children. A common case
318
- # is when the command is given as a single string (instead of
319
- # command name plus Array of arguments) and /bin/sh does not
320
- # support the "ONESHOT" optimization (where sh -c does exec without
321
- # forking). To support cleaning up all the children, we need to
322
- # ensure they're in a unique process group.
323
- #
324
- # We use setsid here to abandon our controlling tty and get a new session
325
- # and process group that are set to the pid of the child process.
326
- Process.setsid
327
-
328
- configure_subprocess_file_descriptors
329
-
330
- set_secondarygroups
331
- set_group
332
- set_user
333
- set_environment
334
- set_umask
335
- set_cwd
336
-
337
- begin
338
- command.kind_of?(Array) ? exec(*command, :close_others=>true) : exec(command, :close_others=>true)
339
-
340
- raise 'forty-two' # Should never get here
341
- rescue Exception => e
342
- Marshal.dump(e, process_status_pipe.last)
343
- process_status_pipe.last.flush
344
- end
345
- process_status_pipe.last.close unless (process_status_pipe.last.closed?)
346
- exit!
347
- end
348
- end
349
-
350
- # Attempt to get a Marshaled error from the side-channel.
351
- # If it's there, un-marshal it and raise. If it's not there,
352
- # assume everything went well.
353
- def propagate_pre_exec_failure
354
- begin
355
- attempt_buffer_read until child_process_status.eof?
356
- e = Marshal.load(@process_status)
357
- raise(Exception === e ? e : "unknown failure: #{e.inspect}")
358
- rescue ArgumentError # If we get an ArgumentError error, then the exec was successful
359
- true
360
- ensure
361
- child_process_status.close
362
- open_pipes.delete(child_process_status)
363
- end
364
- end
365
-
366
- def reap_errant_child
367
- return if attempt_reap
368
- @terminate_reason = "Command exceeded allowed execution time, process terminated"
369
- logger.error("Command exceeded allowed execution time, sending TERM") if logger
370
- Process.kill(:TERM, child_pgid)
371
- sleep 3
372
- attempt_reap
373
- logger.error("Command exceeded allowed execution time, sending KILL") if logger
374
- Process.kill(:KILL, child_pgid)
375
- reap
376
-
377
- # Should not hit this but it's possible if something is calling waitall
378
- # in a separate thread.
379
- rescue Errno::ESRCH
380
- nil
381
- end
382
-
383
- def should_reap?
384
- # if we fail to fork, no child pid so nothing to reap
385
- @child_pid && !@reaped
386
- end
387
-
388
- # Unconditionally reap the child process. This is used in scenarios where
389
- # we can be confident the child will exit quickly, and has not spawned
390
- # and grandchild processes.
391
- def reap
392
- results = Process.waitpid2(@child_pid)
393
- @reaped = true
394
- @status = results.last
395
- rescue Errno::ECHILD
396
- # When cleaning up timed-out processes, we might send SIGKILL to the
397
- # whole process group after we've cleaned up the direct child. In that
398
- # case the grandchildren will have been adopted by init so we can't
399
- # reap them even if we wanted to (we don't).
400
- nil
401
- end
402
-
403
- # Try to reap the child process but don't block if it isn't dead yet.
404
- def attempt_reap
405
- if results = Process.waitpid2(@child_pid, Process::WNOHANG)
406
- @reaped = true
407
- @status = results.last
408
- else
409
- nil
410
- end
411
- end
412
-
413
- end
414
- end
415
- end
1
+ #--
2
+ # Author:: Daniel DeLeo (<dan@chef.io>)
3
+ # Copyright:: Copyright (c) 2010-2016 Chef Software, Inc.
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+
19
+ module Mixlib
20
+ class ShellOut
21
+ module Unix
22
+
23
+ # "1.8.7" as a frozen string. We use this with a hack that disables GC to
24
+ # avoid segfaults on Ruby 1.8.7, so we need to allocate the fewest
25
+ # objects we possibly can.
26
+ ONE_DOT_EIGHT_DOT_SEVEN = "1.8.7".freeze
27
+
28
+ # Option validation that is unix specific
29
+ def validate_options(opts)
30
+ # No options to validate, raise exceptions here if needed
31
+ end
32
+
33
+ # Whether we're simulating a login shell
34
+ def using_login?
35
+ return login && user
36
+ end
37
+
38
+ # Helper method for sgids
39
+ def all_seconderies
40
+ ret = []
41
+ Etc.endgrent
42
+ while ( g = Etc.getgrent ) do
43
+ ret << g
44
+ end
45
+ Etc.endgrent
46
+ return ret
47
+ end
48
+
49
+ # The secondary groups that the subprocess will switch to.
50
+ # Currently valid only if login is used, and is set
51
+ # to the user's secondary groups
52
+ def sgids
53
+ return nil unless using_login?
54
+ user_name = Etc.getpwuid(uid).name
55
+ all_seconderies.select{|g| g.mem.include?(user_name)}.map{|g|g.gid}
56
+ end
57
+
58
+ # The environment variables that are deduced from simulating logon
59
+ # Only valid if login is used
60
+ def logon_environment
61
+ return {} unless using_login?
62
+ entry = Etc.getpwuid(uid)
63
+ # According to `man su`, the set fields are:
64
+ # $HOME, $SHELL, $USER, $LOGNAME, $PATH, and $IFS
65
+ # Values are copied from "shadow" package in Ubuntu 14.10
66
+ {'HOME'=>entry.dir, 'SHELL'=>entry.shell, 'USER'=>entry.name, 'LOGNAME'=>entry.name, 'PATH'=>'/sbin:/bin:/usr/sbin:/usr/bin', 'IFS'=>"\t\n"}
67
+ end
68
+
69
+ # Merges the two environments for the process
70
+ def process_environment
71
+ logon_environment.merge(self.environment)
72
+ end
73
+
74
+ # Run the command, writing the command's standard out and standard error
75
+ # to +stdout+ and +stderr+, and saving its exit status object to +status+
76
+ # === Returns
77
+ # returns +self+; +stdout+, +stderr+, +status+, and +exitstatus+ will be
78
+ # populated with results of the command.
79
+ # === Raises
80
+ # * Errno::EACCES when you are not privileged to execute the command
81
+ # * Errno::ENOENT when the command is not available on the system (or not
82
+ # in the current $PATH)
83
+ # * Chef::Exceptions::CommandTimeout when the command does not complete
84
+ # within +timeout+ seconds (default: 600s). When this happens, ShellOut
85
+ # will send a TERM and then KILL to the entire process group to ensure
86
+ # that any grandchild processes are terminated. If the invocation of
87
+ # the child process spawned multiple child processes (which commonly
88
+ # happens if the command is passed as a single string to be interpreted
89
+ # by bin/sh, and bin/sh is not bash), the exit status object may not
90
+ # contain the correct exit code of the process (of course there is no
91
+ # exit code if the command is killed by SIGKILL, also).
92
+ def run_command
93
+ @child_pid = fork_subprocess
94
+ @reaped = false
95
+
96
+ configure_parent_process_file_descriptors
97
+
98
+ # Ruby 1.8.7 and 1.8.6 from mid 2009 try to allocate objects during GC
99
+ # when calling IO.select and IO#read. Disabling GC works around the
100
+ # segfault, but obviously it's a bad workaround. We no longer support
101
+ # 1.8.6 so we only need this hack for 1.8.7.
102
+ GC.disable if RUBY_VERSION == ONE_DOT_EIGHT_DOT_SEVEN
103
+
104
+ # CHEF-3390: Marshall.load on Ruby < 1.8.7p369 also has a GC bug related
105
+ # to Marshall.load, so try disabling GC first.
106
+ propagate_pre_exec_failure
107
+
108
+ @status = nil
109
+ @result = nil
110
+ @execution_time = 0
111
+
112
+ write_to_child_stdin
113
+
114
+ until @status
115
+ ready_buffers = attempt_buffer_read
116
+ unless ready_buffers
117
+ @execution_time += READ_WAIT_TIME
118
+ if @execution_time >= timeout && !@result
119
+ # kill the bad proccess
120
+ reap_errant_child
121
+ # read anything it wrote when we killed it
122
+ attempt_buffer_read
123
+ # raise
124
+ raise CommandTimeout, "Command timed out after #{@execution_time.to_i}s:\n#{format_for_exception}"
125
+ end
126
+ end
127
+
128
+ attempt_reap
129
+ end
130
+
131
+ self
132
+ rescue Errno::ENOENT
133
+ # When ENOENT happens, we can be reasonably sure that the child process
134
+ # is going to exit quickly, so we use the blocking variant of waitpid2
135
+ reap
136
+ raise
137
+ ensure
138
+ reap_errant_child if should_reap?
139
+ # make one more pass to get the last of the output after the
140
+ # child process dies
141
+ attempt_buffer_read
142
+ # no matter what happens, turn the GC back on, and hope whatever busted
143
+ # version of ruby we're on doesn't allocate some objects during the next
144
+ # GC run.
145
+ GC.enable
146
+ close_all_pipes
147
+ end
148
+
149
+ private
150
+
151
+ def set_user
152
+ if user
153
+ Process.uid = uid
154
+ Process.euid = uid
155
+ end
156
+ end
157
+
158
+ def set_group
159
+ if group
160
+ Process.egid = gid
161
+ Process.gid = gid
162
+ end
163
+ end
164
+
165
+ def set_secondarygroups
166
+ if sgids
167
+ Process.groups = sgids
168
+ end
169
+ end
170
+
171
+ def set_environment
172
+ # user-set variables should override the login ones
173
+ process_environment.each do |env_var,value|
174
+ ENV[env_var] = value
175
+ end
176
+ end
177
+
178
+ def set_umask
179
+ File.umask(umask) if umask
180
+ end
181
+
182
+ def set_cwd
183
+ Dir.chdir(cwd) if cwd
184
+ end
185
+
186
+ # Since we call setsid the child_pgid will be the child_pid, set to negative here
187
+ # so it can be directly used in arguments to kill, wait, etc.
188
+ def child_pgid
189
+ -@child_pid
190
+ end
191
+
192
+ def initialize_ipc
193
+ @stdin_pipe, @stdout_pipe, @stderr_pipe, @process_status_pipe = IO.pipe, IO.pipe, IO.pipe, IO.pipe
194
+ @process_status_pipe.last.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
195
+ end
196
+
197
+ def child_stdin
198
+ @stdin_pipe[1]
199
+ end
200
+
201
+ def child_stdout
202
+ @stdout_pipe[0]
203
+ end
204
+
205
+ def child_stderr
206
+ @stderr_pipe[0]
207
+ end
208
+
209
+ def child_process_status
210
+ @process_status_pipe[0]
211
+ end
212
+
213
+ def close_all_pipes
214
+ child_stdin.close unless child_stdin.closed?
215
+ child_stdout.close unless child_stdout.closed?
216
+ child_stderr.close unless child_stderr.closed?
217
+ child_process_status.close unless child_process_status.closed?
218
+ end
219
+
220
+ # Replace stdout, and stderr with pipes to the parent, and close the
221
+ # reader side of the error marshaling side channel.
222
+ #
223
+ # If there is no input, close STDIN so when we exec,
224
+ # the new program will know it's never getting input ever.
225
+ def configure_subprocess_file_descriptors
226
+ process_status_pipe.first.close
227
+
228
+ # HACK: for some reason, just STDIN.close isn't good enough when running
229
+ # under ruby 1.9.2, so make it good enough:
230
+ stdin_pipe.last.close
231
+ STDIN.reopen stdin_pipe.first
232
+ stdin_pipe.first.close unless input
233
+
234
+ stdout_pipe.first.close
235
+ STDOUT.reopen stdout_pipe.last
236
+ stdout_pipe.last.close
237
+
238
+ stderr_pipe.first.close
239
+ STDERR.reopen stderr_pipe.last
240
+ stderr_pipe.last.close
241
+
242
+ STDOUT.sync = STDERR.sync = true
243
+ STDIN.sync = true if input
244
+ end
245
+
246
+ def configure_parent_process_file_descriptors
247
+ # Close the sides of the pipes we don't care about
248
+ stdin_pipe.first.close
249
+ stdin_pipe.last.close unless input
250
+ stdout_pipe.last.close
251
+ stderr_pipe.last.close
252
+ process_status_pipe.last.close
253
+ # Get output as it happens rather than buffered
254
+ child_stdin.sync = true if input
255
+ child_stdout.sync = true
256
+ child_stderr.sync = true
257
+
258
+ true
259
+ end
260
+
261
+ # Some patch levels of ruby in wide use (in particular the ruby 1.8.6 on OSX)
262
+ # segfault when you IO.select a pipe that's reached eof. Weak sauce.
263
+ def open_pipes
264
+ @open_pipes ||= [child_stdout, child_stderr, child_process_status]
265
+ end
266
+
267
+ # Keep this unbuffered for now
268
+ def write_to_child_stdin
269
+ return unless input
270
+ child_stdin << input
271
+ child_stdin.close # Kick things off
272
+ end
273
+
274
+ def attempt_buffer_read
275
+ ready = IO.select(open_pipes, nil, nil, READ_WAIT_TIME)
276
+ if ready
277
+ read_stdout_to_buffer if ready.first.include?(child_stdout)
278
+ read_stderr_to_buffer if ready.first.include?(child_stderr)
279
+ read_process_status_to_buffer if ready.first.include?(child_process_status)
280
+ end
281
+ ready
282
+ end
283
+
284
+ def read_stdout_to_buffer
285
+ while chunk = child_stdout.read_nonblock(READ_SIZE)
286
+ @stdout << chunk
287
+ @live_stdout << chunk if @live_stdout
288
+ end
289
+ rescue Errno::EAGAIN
290
+ rescue EOFError
291
+ open_pipes.delete(child_stdout)
292
+ end
293
+
294
+ def read_stderr_to_buffer
295
+ while chunk = child_stderr.read_nonblock(READ_SIZE)
296
+ @stderr << chunk
297
+ @live_stderr << chunk if @live_stderr
298
+ end
299
+ rescue Errno::EAGAIN
300
+ rescue EOFError
301
+ open_pipes.delete(child_stderr)
302
+ end
303
+
304
+ def read_process_status_to_buffer
305
+ while chunk = child_process_status.read_nonblock(READ_SIZE)
306
+ @process_status << chunk
307
+ end
308
+ rescue Errno::EAGAIN
309
+ rescue EOFError
310
+ open_pipes.delete(child_process_status)
311
+ end
312
+
313
+ def fork_subprocess
314
+ initialize_ipc
315
+
316
+ fork do
317
+ # Child processes may themselves fork off children. A common case
318
+ # is when the command is given as a single string (instead of
319
+ # command name plus Array of arguments) and /bin/sh does not
320
+ # support the "ONESHOT" optimization (where sh -c does exec without
321
+ # forking). To support cleaning up all the children, we need to
322
+ # ensure they're in a unique process group.
323
+ #
324
+ # We use setsid here to abandon our controlling tty and get a new session
325
+ # and process group that are set to the pid of the child process.
326
+ Process.setsid
327
+
328
+ configure_subprocess_file_descriptors
329
+
330
+ set_secondarygroups
331
+ set_group
332
+ set_user
333
+ set_environment
334
+ set_umask
335
+ set_cwd
336
+
337
+ begin
338
+ command.kind_of?(Array) ? exec(*command, :close_others=>true) : exec(command, :close_others=>true)
339
+
340
+ raise 'forty-two' # Should never get here
341
+ rescue Exception => e
342
+ Marshal.dump(e, process_status_pipe.last)
343
+ process_status_pipe.last.flush
344
+ end
345
+ process_status_pipe.last.close unless (process_status_pipe.last.closed?)
346
+ exit!
347
+ end
348
+ end
349
+
350
+ # Attempt to get a Marshaled error from the side-channel.
351
+ # If it's there, un-marshal it and raise. If it's not there,
352
+ # assume everything went well.
353
+ def propagate_pre_exec_failure
354
+ begin
355
+ attempt_buffer_read until child_process_status.eof?
356
+ e = Marshal.load(@process_status)
357
+ raise(Exception === e ? e : "unknown failure: #{e.inspect}")
358
+ rescue ArgumentError # If we get an ArgumentError error, then the exec was successful
359
+ true
360
+ ensure
361
+ child_process_status.close
362
+ open_pipes.delete(child_process_status)
363
+ end
364
+ end
365
+
366
+ def reap_errant_child
367
+ return if attempt_reap
368
+ @terminate_reason = "Command exceeded allowed execution time, process terminated"
369
+ logger.error("Command exceeded allowed execution time, sending TERM") if logger
370
+ Process.kill(:TERM, child_pgid)
371
+ sleep 3
372
+ attempt_reap
373
+ logger.error("Command exceeded allowed execution time, sending KILL") if logger
374
+ Process.kill(:KILL, child_pgid)
375
+ reap
376
+
377
+ # Should not hit this but it's possible if something is calling waitall
378
+ # in a separate thread.
379
+ rescue Errno::ESRCH
380
+ nil
381
+ end
382
+
383
+ def should_reap?
384
+ # if we fail to fork, no child pid so nothing to reap
385
+ @child_pid && !@reaped
386
+ end
387
+
388
+ # Unconditionally reap the child process. This is used in scenarios where
389
+ # we can be confident the child will exit quickly, and has not spawned
390
+ # and grandchild processes.
391
+ def reap
392
+ results = Process.waitpid2(@child_pid)
393
+ @reaped = true
394
+ @status = results.last
395
+ rescue Errno::ECHILD
396
+ # When cleaning up timed-out processes, we might send SIGKILL to the
397
+ # whole process group after we've cleaned up the direct child. In that
398
+ # case the grandchildren will have been adopted by init so we can't
399
+ # reap them even if we wanted to (we don't).
400
+ nil
401
+ end
402
+
403
+ # Try to reap the child process but don't block if it isn't dead yet.
404
+ def attempt_reap
405
+ if results = Process.waitpid2(@child_pid, Process::WNOHANG)
406
+ @reaped = true
407
+ @status = results.last
408
+ else
409
+ nil
410
+ end
411
+ end
412
+
413
+ end
414
+ end
415
+ end