mixlib-shellout 1.3.0-x86-mingw32 → 1.4.0.rc.0-x86-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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e6982a3f776c89a117273e34a9207226ae91d430
4
+ data.tar.gz: bed2f4cda27c43e7f4a8bd1265089bf7b8016065
5
+ SHA512:
6
+ metadata.gz: dd33a078724263f3aec8d63518c0b7c67883e0fca6b8a359b3ca573228ba6a194032305dfa85b2f55b901c4c0b9dcdf877908170d590bcebb30f947e3777cd1f
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,62 +1,55 @@
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: x86-mingw32
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
  - !ruby/object:Gem::Dependency
31
28
  name: win32-process
32
29
  requirement: !ruby/object:Gem::Requirement
33
- none: false
34
30
  requirements:
35
- - - ~>
31
+ - - "~>"
36
32
  - !ruby/object:Gem::Version
37
33
  version: 0.7.1
38
34
  type: :runtime
39
35
  prerelease: false
40
36
  version_requirements: !ruby/object:Gem::Requirement
41
- none: false
42
37
  requirements:
43
- - - ~>
38
+ - - "~>"
44
39
  - !ruby/object:Gem::Version
45
40
  version: 0.7.1
46
41
  - !ruby/object:Gem::Dependency
47
42
  name: windows-pr
48
43
  requirement: !ruby/object:Gem::Requirement
49
- none: false
50
44
  requirements:
51
- - - ~>
45
+ - - "~>"
52
46
  - !ruby/object:Gem::Version
53
47
  version: 1.2.2
54
48
  type: :runtime
55
49
  prerelease: false
56
50
  version_requirements: !ruby/object:Gem::Requirement
57
- none: false
58
51
  requirements:
59
- - - ~>
52
+ - - "~>"
60
53
  - !ruby/object:Gem::Version
61
54
  version: 1.2.2
62
55
  description: Run external commands on Unix or Windows
@@ -69,34 +62,33 @@ extra_rdoc_files:
69
62
  files:
70
63
  - LICENSE
71
64
  - README.md
65
+ - lib/mixlib/shellout.rb
72
66
  - lib/mixlib/shellout/exceptions.rb
73
67
  - lib/mixlib/shellout/unix.rb
74
68
  - lib/mixlib/shellout/version.rb
75
- - lib/mixlib/shellout/windows/core_ext.rb
76
69
  - lib/mixlib/shellout/windows.rb
77
- - lib/mixlib/shellout.rb
70
+ - lib/mixlib/shellout/windows/core_ext.rb
78
71
  homepage: http://wiki.opscode.com/
79
72
  licenses: []
73
+ metadata: {}
80
74
  post_install_message:
81
75
  rdoc_options: []
82
76
  require_paths:
83
77
  - lib
84
78
  required_ruby_version: !ruby/object:Gem::Requirement
85
- none: false
86
79
  requirements:
87
- - - ! '>='
80
+ - - ">="
88
81
  - !ruby/object:Gem::Version
89
82
  version: '0'
90
83
  required_rubygems_version: !ruby/object:Gem::Requirement
91
- none: false
92
84
  requirements:
93
- - - ! '>='
85
+ - - ">"
94
86
  - !ruby/object:Gem::Version
95
- version: '0'
87
+ version: 1.3.1
96
88
  requirements: []
97
89
  rubyforge_project:
98
- rubygems_version: 1.8.23
90
+ rubygems_version: 2.2.2
99
91
  signing_key:
100
- specification_version: 3
92
+ specification_version: 4
101
93
  summary: Run external commands on Unix or Windows
102
94
  test_files: []