mixlib-shellout 3.2.7-x64-mingw-ucrt

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