mixlib-shellout 2.2.5-universal-mingw32 → 2.2.6-universal-mingw32

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.
@@ -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@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