mixlib-shellout 3.2.7-x64-mingw-ucrt

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