mixlib-shellout 1.3.0 → 1.4.0.rc.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: abc0fcb2fac6bbaa656ec5986083c3e6d9f80137
4
+ data.tar.gz: bed2f4cda27c43e7f4a8bd1265089bf7b8016065
5
+ SHA512:
6
+ metadata.gz: c580694c90a79f7ef01b3e4b7930e954a77ece31f3a16e0b6d10ef02ea24d73e26f4de316f5fdd0cb4ca0162fbde9a303e763c992acf6bffef65ca2bb99dddd3
7
+ data.tar.gz: 5c7af3ef8b2bb9f5e7ef9e36dce50f94afc0d3ce56702ac6e6bd45343cc367fe057ea7ad8b04881e6d0aaaebcaffd45ae95f9d69bf3e36ea385127f2f023b61b
@@ -49,8 +49,8 @@ module Mixlib
49
49
  # Working directory for the subprocess. Normally set via options to new
50
50
  attr_accessor :cwd
51
51
 
52
- # An Array of acceptable exit codes. #error! uses this list to determine if
53
- # the command was successful. Normally set via options to new
52
+ # An Array of acceptable exit codes. #error? (and #error!) use this list
53
+ # to determine if the command was successful. Normally set via options to new
54
54
  attr_accessor :valid_exit_codes
55
55
 
56
56
  # When live_stream is set, stdout of the subprocess will be copied to it as
@@ -227,17 +227,21 @@ module Mixlib
227
227
  super
228
228
  end
229
229
 
230
- # Checks the +exitstatus+ against the set of +valid_exit_codes+. If
231
- # +exitstatus+ is not in the list of +valid_exit_codes+, calls +invalid!+,
232
- # which raises an Exception.
230
+ # Checks the +exitstatus+ against the set of +valid_exit_codes+.
231
+ # === Returns
232
+ # +true+ if +exitstatus+ is not in the list of +valid_exit_codes+, false
233
+ # otherwise.
234
+ def error?
235
+ !Array(valid_exit_codes).include?(exitstatus)
236
+ end
237
+
238
+ # If #error? is true, calls +invalid!+, which raises an Exception.
233
239
  # === Returns
234
240
  # nil::: always returns nil when it does not raise
235
241
  # === Raises
236
242
  # ::ShellCommandFailed::: via +invalid!+
237
243
  def error!
238
- unless Array(valid_exit_codes).include?(exitstatus)
239
- invalid!("Expected process to exit with #{valid_exit_codes.inspect}, but received '#{exitstatus}'")
240
- end
244
+ invalid!("Expected process to exit with #{valid_exit_codes.inspect}, but received '#{exitstatus}'") if error?
241
245
  end
242
246
 
243
247
  # Raises a ShellCommandFailed exception, appending the
@@ -20,6 +20,11 @@ module Mixlib
20
20
  class ShellOut
21
21
  module Unix
22
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
+
23
28
  # Option validation that is unix specific
24
29
  def validate_options(opts)
25
30
  # No options to validate, raise exceptions here if needed
@@ -29,13 +34,20 @@ module Mixlib
29
34
  # to +stdout+ and +stderr+, and saving its exit status object to +status+
30
35
  # === Returns
31
36
  # returns +self+; +stdout+, +stderr+, +status+, and +exitstatus+ will be
32
- # populated with results of the command
37
+ # populated with results of the command.
33
38
  # === Raises
34
39
  # * Errno::EACCES when you are not privileged to execute the command
35
40
  # * Errno::ENOENT when the command is not available on the system (or not
36
41
  # in the current $PATH)
37
42
  # * Chef::Exceptions::CommandTimeout when the command does not complete
38
- # within +timeout+ seconds (default: 600s)
43
+ # within +timeout+ seconds (default: 600s). When this happens, ShellOut
44
+ # will send a TERM and then KILL to the entire process group to ensure
45
+ # that any grandchild processes are terminated. If the invocation of
46
+ # the child process spawned multiple child processes (which commonly
47
+ # happens if the command is passed as a single string to be interpreted
48
+ # by bin/sh, and bin/sh is not bash), the exit status object may not
49
+ # contain the correct exit code of the process (of course there is no
50
+ # exit code if the command is killed by SIGKILL, also).
39
51
  def run_command
40
52
  @child_pid = fork_subprocess
41
53
  @reaped = false
@@ -43,14 +55,15 @@ module Mixlib
43
55
  configure_parent_process_file_descriptors
44
56
 
45
57
  # Ruby 1.8.7 and 1.8.6 from mid 2009 try to allocate objects during GC
46
- # when calling IO.select and IO#read. Some OS Vendors are not interested
47
- # in updating their ruby packages (Apple, *cough*) and we *have to*
48
- # make it work. So I give you this epic hack:
49
- GC.disable
58
+ # when calling IO.select and IO#read. Disabling GC works around the
59
+ # segfault, but obviously it's a bad workaround. We no longer support
60
+ # 1.8.6 so we only need this hack for 1.8.7.
61
+ GC.disable if RUBY_VERSION == ONE_DOT_EIGHT_DOT_SEVEN
50
62
 
51
63
  # CHEF-3390: Marshall.load on Ruby < 1.8.7p369 also has a GC bug related
52
64
  # to Marshall.load, so try disabling GC first.
53
65
  propagate_pre_exec_failure
66
+ @child_pgid = -Process.getpgid(@child_pid)
54
67
 
55
68
  @result = nil
56
69
  @execution_time = 0
@@ -122,6 +135,12 @@ module Mixlib
122
135
  Dir.chdir(cwd) if cwd
123
136
  end
124
137
 
138
+ # Process group id of the child. Returned as a negative value so you can
139
+ # put it directly in arguments to kill, wait, etc.
140
+ def child_pgid
141
+ @child_pgid
142
+ end
143
+
125
144
  def initialize_ipc
126
145
  @stdin_pipe, @stdout_pipe, @stderr_pipe, @process_status_pipe = IO.pipe, IO.pipe, IO.pipe, IO.pipe
127
146
  @process_status_pipe.last.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
@@ -263,6 +282,14 @@ module Mixlib
263
282
  initialize_ipc
264
283
 
265
284
  fork do
285
+ # Child processes may themselves fork off children. A common case
286
+ # is when the command is given as a single string (instead of
287
+ # command name plus Array of arguments) and /bin/sh does not
288
+ # support the "ONESHOT" optimization (where sh -c does exec without
289
+ # forking). To support cleaning up all the children, we need to
290
+ # ensure they're in a unique process group.
291
+ Process.setsid
292
+
266
293
  configure_subprocess_file_descriptors
267
294
 
268
295
  clean_parent_file_descriptors
@@ -302,14 +329,13 @@ module Mixlib
302
329
 
303
330
  def reap_errant_child
304
331
  return if attempt_reap
305
- @terminate_reason = "Command execeded allowed execution time, killed by TERM signal."
332
+ @terminate_reason = "Command execeded allowed execution time, process terminated"
306
333
  logger.error("Command execeded allowed execution time, sending TERM") if logger
307
- Process.kill(:TERM, @child_pid)
334
+ Process.kill(:TERM, child_pgid)
308
335
  sleep 3
309
- return if attempt_reap
310
- @terminate_reason = "Command execeded allowed execution time, did not respond to TERM. Killed by KILL signal."
311
- logger.error("Command did not exit from TERM, sending KILL") if logger
312
- Process.kill(:KILL, @child_pid)
336
+ attempt_reap
337
+ logger.error("Command execeded allowed execution time, sending KILL") if logger
338
+ Process.kill(:KILL, child_pgid)
313
339
  reap
314
340
 
315
341
  # Should not hit this but it's possible if something is calling waitall
@@ -323,12 +349,22 @@ module Mixlib
323
349
  @child_pid && !@reaped
324
350
  end
325
351
 
352
+ # Unconditionally reap the child process. This is used in scenarios where
353
+ # we can be confident the child will exit quickly, and has not spawned
354
+ # and grandchild processes.
326
355
  def reap
327
356
  results = Process.waitpid2(@child_pid)
328
357
  @reaped = true
329
358
  @status = results.last
359
+ rescue Errno::ECHILD
360
+ # When cleaning up timed-out processes, we might send SIGKILL to the
361
+ # whole process group after we've cleaned up the direct child. In that
362
+ # case the grandchildren will have been adopted by init so we can't
363
+ # reap them even if we wanted to (we don't).
364
+ nil
330
365
  end
331
366
 
367
+ # Try to reap the child process but don't block if it isn't dead yet.
332
368
  def attempt_reap
333
369
  if results = Process.waitpid2(@child_pid, Process::WNOHANG)
334
370
  @reaped = true
@@ -1,5 +1,5 @@
1
1
  module Mixlib
2
2
  class ShellOut
3
- VERSION = "1.3.0"
3
+ VERSION = "1.4.0.rc.0"
4
4
  end
5
5
  end
@@ -122,8 +122,8 @@ module Mixlib
122
122
  end
123
123
 
124
124
  ensure
125
- CloseHandle(process.thread_handle)
126
- CloseHandle(process.process_handle)
125
+ CloseHandle(process.thread_handle) if process.thread_handle
126
+ CloseHandle(process.process_handle) if process.process_handle
127
127
  end
128
128
 
129
129
  ensure
@@ -288,19 +288,23 @@ module Process
288
288
 
289
289
  token = token.read_ulong
290
290
 
291
- bool = CreateProcessAsUserW(
292
- token, # User token handle
293
- app, # App name
294
- cmd, # Command line
295
- process_security, # Process attributes
296
- thread_security, # Thread attributes
297
- inherit, # Inherit handles
298
- hash['creation_flags'], # Creation Flags
299
- env, # Environment
300
- cwd, # Working directory
301
- startinfo, # Startup Info
302
- procinfo # Process Info
303
- )
291
+ begin
292
+ bool = CreateProcessAsUserW(
293
+ token, # User token handle
294
+ app, # App name
295
+ cmd, # Command line
296
+ process_security, # Process attributes
297
+ thread_security, # Thread attributes
298
+ inherit, # Inherit handles
299
+ hash['creation_flags'], # Creation Flags
300
+ env, # Environment
301
+ cwd, # Working directory
302
+ startinfo, # Startup Info
303
+ procinfo # Process Info
304
+ )
305
+ ensure
306
+ CloseHandle(token)
307
+ end
304
308
 
305
309
  unless bool
306
310
  raise SystemCallError.new("CreateProcessAsUserW (You must hold the 'Replace a process level token' permission)", FFI.errno)
@@ -346,9 +350,14 @@ module Process
346
350
  # Automatically close the process and thread handles in the
347
351
  # PROCESS_INFORMATION struct unless explicitly told not to.
348
352
  if hash['close_handles']
349
- CloseHandle(procinfo[:hProcess])
350
- CloseHandle(procinfo[:hThread])
351
- CloseHandle(token)
353
+ CloseHandle(procinfo[:hProcess]) if procinfo[:hProcess]
354
+ CloseHandle(procinfo[:hThread]) if procinfo[:hThread]
355
+
356
+ # Set fields to nil so callers don't attempt to close the handle
357
+ # which can result in the wrong handle being closed or an
358
+ # exception in some circumstances
359
+ procinfo[:hProcess] = nil
360
+ procinfo[:hThread] = nil
352
361
  end
353
362
 
354
363
  ProcessInfo.new(
metadata CHANGED
@@ -1,30 +1,27 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mixlib-shellout
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0
5
- prerelease:
4
+ version: 1.4.0.rc.0
6
5
  platform: ruby
7
6
  authors:
8
7
  - Opscode
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2013-12-03 00:00:00.000000000 Z
11
+ date: 2014-03-30 00:00:00.000000000 Z
13
12
  dependencies:
14
13
  - !ruby/object:Gem::Dependency
15
14
  name: rspec
16
15
  requirement: !ruby/object:Gem::Requirement
17
- none: false
18
16
  requirements:
19
- - - ~>
17
+ - - "~>"
20
18
  - !ruby/object:Gem::Version
21
19
  version: '2.0'
22
20
  type: :development
23
21
  prerelease: false
24
22
  version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
23
  requirements:
27
- - - ~>
24
+ - - "~>"
28
25
  - !ruby/object:Gem::Version
29
26
  version: '2.0'
30
27
  description: Run external commands on Unix or Windows
@@ -37,34 +34,33 @@ extra_rdoc_files:
37
34
  files:
38
35
  - LICENSE
39
36
  - README.md
37
+ - lib/mixlib/shellout.rb
40
38
  - lib/mixlib/shellout/exceptions.rb
41
39
  - lib/mixlib/shellout/unix.rb
42
40
  - lib/mixlib/shellout/version.rb
43
- - lib/mixlib/shellout/windows/core_ext.rb
44
41
  - lib/mixlib/shellout/windows.rb
45
- - lib/mixlib/shellout.rb
42
+ - lib/mixlib/shellout/windows/core_ext.rb
46
43
  homepage: http://wiki.opscode.com/
47
44
  licenses: []
45
+ metadata: {}
48
46
  post_install_message:
49
47
  rdoc_options: []
50
48
  require_paths:
51
49
  - lib
52
50
  required_ruby_version: !ruby/object:Gem::Requirement
53
- none: false
54
51
  requirements:
55
- - - ! '>='
52
+ - - ">="
56
53
  - !ruby/object:Gem::Version
57
54
  version: '0'
58
55
  required_rubygems_version: !ruby/object:Gem::Requirement
59
- none: false
60
56
  requirements:
61
- - - ! '>='
57
+ - - ">"
62
58
  - !ruby/object:Gem::Version
63
- version: '0'
59
+ version: 1.3.1
64
60
  requirements: []
65
61
  rubyforge_project:
66
- rubygems_version: 1.8.23
62
+ rubygems_version: 2.2.2
67
63
  signing_key:
68
- specification_version: 3
64
+ specification_version: 4
69
65
  summary: Run external commands on Unix or Windows
70
66
  test_files: []