mixlib-shellout 2.2.7 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 011898db29b4ffcb5c8faca2529b47a329157a64
4
- data.tar.gz: f288d3d4b96149359fa173f6eb4be591b9870e76
3
+ metadata.gz: 8d23587c6be044427986ac00a0881aecb540b69a
4
+ data.tar.gz: dea69938e652a6b531eade231bf3c49d4d2d33ed
5
5
  SHA512:
6
- metadata.gz: 75d722b613591915130aa9e8f6f4950195746d9f8cac0963e6b2557a63c3f2a7d289a905c8e64f9437c75b1570c35cc1aca5e1740bcedde369a96f9025a270ac
7
- data.tar.gz: d112373309d2f7ccc90c5077c4b56900abe27891ca767ba7cef4bc67831c14163babdc730b7db8b8acca6e223bf78e8377757a124e4dc5ce762fdcc33c8fb22a
6
+ metadata.gz: 8ccb01c5680a379e58b64d749aa3c444224335395a5ca0f58a46442693fb4b564604eac2357fcda02a19dd4d7e786e3c9a1d1e4ad7720bce00d07ba9aebc171b
7
+ data.tar.gz: b4993a462a6a05a4adaa2e5db96d7ff4171e07bc07f79cdf2e73e8c8e7fb88b668f8350cb1562a96aa6ee797e0dc49a46453b0f1626560f3b625a7d0c83c511d
data/Gemfile CHANGED
@@ -1,15 +1,15 @@
1
- source 'https://rubygems.org'
1
+ source "https://rubygems.org"
2
2
 
3
3
  gemspec :name => "mixlib-shellout"
4
4
 
5
5
  group(:test) do
6
6
  gem "rspec_junit_formatter"
7
- gem 'rake'
7
+ gem "rake"
8
8
  end
9
9
 
10
10
  group(:development) do
11
- gem 'pry'
12
- gem 'pry-byebug'
13
- gem 'pry-stack_explorer'
14
- gem 'rb-readline'
11
+ gem "pry"
12
+ gem "pry-byebug"
13
+ gem "pry-stack_explorer"
14
+ gem "rb-readline"
15
15
  end
data/README.md CHANGED
@@ -65,9 +65,13 @@ Invoke "whoami.exe" to demonstrate running a command as another user:
65
65
  Mixlib::ShellOut does a standard fork/exec on Unix, and uses the Win32 API on Windows. There is not currently support for JRuby.
66
66
 
67
67
  ## See Also
68
- - `Process.spawn` in Ruby 1.9
68
+ - `Process.spawn` in Ruby 1.9+
69
69
  - [https://github.com/rtomayko/posix-spawn](https://github.com/rtomayko/posix-spawn)
70
70
 
71
+ ## Contributing
72
+
73
+ For information on contributing to this project see <https://github.com/chef/chef/blob/master/CONTRIBUTING.md>
74
+
71
75
  ## License
72
76
  - Copyright:: Copyright (c) 2011-2016 Chef Software, Inc.
73
77
  - License:: Apache License, Version 2.0
data/Rakefile CHANGED
@@ -1,24 +1,16 @@
1
- require 'rspec/core/rake_task'
2
- require 'rubygems/package_task'
3
- require 'mixlib/shellout/version'
1
+ require "bundler"
2
+ require "rspec/core/rake_task"
4
3
 
5
- Dir[File.expand_path("../*gemspec", __FILE__)].reverse.each do |gemspec_path|
6
- gemspec = eval(IO.read(gemspec_path))
7
- Gem::PackageTask.new(gemspec).define
8
- end
4
+ Bundler::GemHelper.install_tasks name: "mixlib-shellout"
5
+
6
+ require "chefstyle"
7
+ require "rubocop/rake_task"
8
+ desc "Run Ruby style checks"
9
+ RuboCop::RakeTask.new(:style)
9
10
 
10
11
  desc "Run all specs in spec directory"
11
12
  RSpec::Core::RakeTask.new(:spec) do |t|
12
- t.pattern = FileList['spec/**/*_spec.rb']
13
- end
14
-
15
- desc "Build it and ship it"
16
- task ship: [:clobber_package, :gem] do
17
- # sh("git tag #{Mixlib::ShellOut::VERSION}")
18
- sh("git push opscode --tags")
19
- Dir[File.expand_path("../pkg/*.gem", __FILE__)].reverse.each do |built_gem|
20
- sh("gem push #{built_gem}")
21
- end
13
+ t.pattern = FileList["spec/**/*_spec.rb"]
22
14
  end
23
15
 
24
- task default: :spec
16
+ task default: [:spec, :style]
@@ -16,10 +16,10 @@
16
16
  # limitations under the License.
17
17
  #
18
18
 
19
- require 'etc'
20
- require 'tmpdir'
21
- require 'fcntl'
22
- require 'mixlib/shellout/exceptions'
19
+ require "etc"
20
+ require "tmpdir"
21
+ require "fcntl"
22
+ require "mixlib/shellout/exceptions"
23
23
 
24
24
  module Mixlib
25
25
 
@@ -29,10 +29,10 @@ module Mixlib
29
29
  DEFAULT_READ_TIMEOUT = 600
30
30
 
31
31
  if RUBY_PLATFORM =~ /mswin|mingw32|windows/
32
- require 'mixlib/shellout/windows'
32
+ require "mixlib/shellout/windows"
33
33
  include ShellOut::Windows
34
34
  else
35
- require 'mixlib/shellout/unix'
35
+ require "mixlib/shellout/unix"
36
36
  include ShellOut::Unix
37
37
  end
38
38
 
@@ -109,6 +109,9 @@ module Mixlib
109
109
 
110
110
  attr_reader :stdin_pipe, :stdout_pipe, :stderr_pipe, :process_status_pipe
111
111
 
112
+ # Runs windows process with elevated privileges. Required for Powershell commands which need elevated privileges
113
+ attr_accessor :elevated
114
+
112
115
  # === Arguments:
113
116
  # Takes a single command, or a list of command fragments. These are used
114
117
  # as arguments to Kernel.exec. See the Kernel.exec documentation for more
@@ -133,7 +136,7 @@ module Mixlib
133
136
  # * +timeout+: a Numeric value for the number of seconds to wait on the
134
137
  # child process before raising an Exception. This is calculated as the
135
138
  # total amount of time that ShellOut waited on the child process without
136
- # receiving any output (i.e., IO.select returned nil). Default is 60
139
+ # receiving any output (i.e., IO.select returned nil). Default is 600
137
140
  # seconds. Note: the stdlib Timeout library is not used.
138
141
  # * +input+: A String of data to be passed to the subcommand. This is
139
142
  # written to the child process' stdin stream before the process is
@@ -162,7 +165,7 @@ module Mixlib
162
165
  # cmd = Mixlib::ShellOut.new("apachectl", "start", :user => 'www', :env => nil, :cwd => '/tmp')
163
166
  # cmd.run_command # etc.
164
167
  def initialize(*command_args)
165
- @stdout, @stderr, @process_status = '', '', ''
168
+ @stdout, @stderr, @process_status = "", "", ""
166
169
  @live_stdout = @live_stderr = nil
167
170
  @input = nil
168
171
  @log_level = :debug
@@ -172,6 +175,7 @@ module Mixlib
172
175
  @valid_exit_codes = [0]
173
176
  @terminate_reason = nil
174
177
  @timeout = nil
178
+ @elevated = false
175
179
 
176
180
  if command_args.last.is_a?(Hash)
177
181
  parse_options(command_args.pop)
@@ -212,7 +216,7 @@ module Mixlib
212
216
  def gid
213
217
  return group.kind_of?(Integer) ? group : Etc.getgrnam(group.to_s).gid if group
214
218
  return Etc.getpwuid(uid).gid if using_login?
215
- return nil
219
+ nil
216
220
  end
217
221
 
218
222
  def timeout
@@ -253,7 +257,7 @@ module Mixlib
253
257
  # within +timeout+ seconds (default: 600s)
254
258
  def run_command
255
259
  if logger
256
- log_message = (log_tag.nil? ? "" : "#@log_tag ") << "sh(#@command)"
260
+ log_message = (log_tag.nil? ? "" : "#{@log_tag} ") << "sh(#{@command})"
257
261
  logger.send(log_level, log_message)
258
262
  end
259
263
  super
@@ -284,15 +288,15 @@ module Mixlib
284
288
  # is highly encouraged.
285
289
  # === Raises
286
290
  # ShellCommandFailed always
287
- def invalid!(msg=nil)
291
+ def invalid!(msg = nil)
288
292
  msg ||= "Command produced unexpected results"
289
293
  raise ShellCommandFailed, msg + "\n" + format_for_exception
290
294
  end
291
295
 
292
296
  def inspect
293
- "<#{self.class.name}##{object_id}: command: '#@command' process_status: #{@status.inspect} " +
294
- "stdout: '#{stdout.strip}' stderr: '#{stderr.strip}' child_pid: #{@child_pid.inspect} " +
295
- "environment: #{@environment.inspect} timeout: #{timeout} user: #@user group: #@group working_dir: #@cwd >"
297
+ "<#{self.class.name}##{object_id}: command: '#{@command}' process_status: #{@status.inspect} " +
298
+ "stdout: '#{stdout.strip}' stderr: '#{stderr.strip}' child_pid: #{@child_pid.inspect} " +
299
+ "environment: #{@environment.inspect} timeout: #{timeout} user: #{@user} group: #{@group} working_dir: #{@cwd} >"
296
300
  end
297
301
 
298
302
  private
@@ -300,45 +304,47 @@ module Mixlib
300
304
  def parse_options(opts)
301
305
  opts.each do |option, setting|
302
306
  case option.to_s
303
- when 'cwd'
307
+ when "cwd"
304
308
  self.cwd = setting
305
- when 'domain'
309
+ when "domain"
306
310
  self.domain = setting
307
- when 'password'
311
+ when "password"
308
312
  self.password = setting
309
- when 'user'
313
+ when "user"
310
314
  self.user = setting
311
315
  self.with_logon = setting
312
- when 'group'
316
+ when "group"
313
317
  self.group = setting
314
- when 'umask'
318
+ when "umask"
315
319
  self.umask = setting
316
- when 'timeout'
320
+ when "timeout"
317
321
  self.timeout = setting
318
- when 'returns'
322
+ when "returns"
319
323
  self.valid_exit_codes = Array(setting)
320
- when 'live_stream'
324
+ when "live_stream"
321
325
  self.live_stdout = self.live_stderr = setting
322
- when 'live_stdout'
326
+ when "live_stdout"
323
327
  self.live_stdout = setting
324
- when 'live_stderr'
328
+ when "live_stderr"
325
329
  self.live_stderr = setting
326
- when 'input'
330
+ when "input"
327
331
  self.input = setting
328
- when 'logger'
332
+ when "logger"
329
333
  self.logger = setting
330
- when 'log_level'
334
+ when "log_level"
331
335
  self.log_level = setting
332
- when 'log_tag'
336
+ when "log_tag"
333
337
  self.log_tag = setting
334
- when 'environment', 'env'
338
+ when "environment", "env"
335
339
  if setting
336
- self.environment = Hash[setting.map{|(k,v)| [k.to_s,v]}]
340
+ self.environment = Hash[setting.map { |(k, v)| [k.to_s, v] }]
337
341
  else
338
342
  self.environment = {}
339
343
  end
340
- when 'login'
344
+ when "login"
341
345
  self.login = setting
346
+ when "elevated"
347
+ self.elevated = setting
342
348
  else
343
349
  raise InvalidCommandOption, "option '#{option.inspect}' is not a valid option for #{self.class.name}"
344
350
  end
@@ -1,7 +1,9 @@
1
1
  module Mixlib
2
2
  class ShellOut
3
- class ShellCommandFailed < RuntimeError; end
4
- class CommandTimeout < RuntimeError; end
5
- class InvalidCommandOption < RuntimeError; end
3
+ class Error < RuntimeError; end
4
+ class ShellCommandFailed < Error; end
5
+ class CommandTimeout < Error; end
6
+ class InvalidCommandOption < Error; end
7
+ class EmptyWindowsCommand < Error; end
6
8
  end
7
9
  end
@@ -27,23 +27,25 @@ module Mixlib
27
27
 
28
28
  # Option validation that is unix specific
29
29
  def validate_options(opts)
30
- # No options to validate, raise exceptions here if needed
30
+ if opts[:elevated]
31
+ raise InvalidCommandOption, "Option `elevated` is supported for Powershell commands only"
32
+ end
31
33
  end
32
34
 
33
35
  # Whether we're simulating a login shell
34
36
  def using_login?
35
- return login && user
37
+ login && user
36
38
  end
37
39
 
38
40
  # Helper method for sgids
39
41
  def all_seconderies
40
42
  ret = []
41
43
  Etc.endgrent
42
- while ( g = Etc.getgrent ) do
44
+ while ( g = Etc.getgrent )
43
45
  ret << g
44
46
  end
45
47
  Etc.endgrent
46
- return ret
48
+ ret
47
49
  end
48
50
 
49
51
  # The secondary groups that the subprocess will switch to.
@@ -52,7 +54,7 @@ module Mixlib
52
54
  def sgids
53
55
  return nil unless using_login?
54
56
  user_name = Etc.getpwuid(uid).name
55
- all_seconderies.select{|g| g.mem.include?(user_name)}.map{|g|g.gid}
57
+ all_seconderies.select { |g| g.mem.include?(user_name) }.map { |g| g.gid }
56
58
  end
57
59
 
58
60
  # The environment variables that are deduced from simulating logon
@@ -63,12 +65,12 @@ module Mixlib
63
65
  # According to `man su`, the set fields are:
64
66
  # $HOME, $SHELL, $USER, $LOGNAME, $PATH, and $IFS
65
67
  # 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"}
68
+ { "HOME" => entry.dir, "SHELL" => entry.shell, "USER" => entry.name, "LOGNAME" => entry.name, "PATH" => "/sbin:/bin:/usr/sbin:/usr/bin", "IFS" => "\t\n" }
67
69
  end
68
70
 
69
71
  # Merges the two environments for the process
70
72
  def process_environment
71
- logon_environment.merge(self.environment)
73
+ logon_environment.merge(environment)
72
74
  end
73
75
 
74
76
  # Run the command, writing the command's standard out and standard error
@@ -170,7 +172,7 @@ module Mixlib
170
172
 
171
173
  def set_environment
172
174
  # user-set variables should override the login ones
173
- process_environment.each do |env_var,value|
175
+ process_environment.each do |env_var, value|
174
176
  ENV[env_var] = value
175
177
  end
176
178
  end
@@ -335,14 +337,14 @@ module Mixlib
335
337
  set_cwd
336
338
 
337
339
  begin
338
- command.kind_of?(Array) ? exec(*command, :close_others=>true) : exec(command, :close_others=>true)
340
+ command.kind_of?(Array) ? exec(*command, :close_others => true) : exec(command, :close_others => true)
339
341
 
340
- raise 'forty-two' # Should never get here
342
+ raise "forty-two" # Should never get here
341
343
  rescue Exception => e
342
344
  Marshal.dump(e, process_status_pipe.last)
343
345
  process_status_pipe.last.flush
344
346
  end
345
- process_status_pipe.last.close unless (process_status_pipe.last.closed?)
347
+ process_status_pipe.last.close unless process_status_pipe.last.closed?
346
348
  exit!
347
349
  end
348
350
  end
@@ -351,16 +353,14 @@ module Mixlib
351
353
  # If it's there, un-marshal it and raise. If it's not there,
352
354
  # assume everything went well.
353
355
  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
356
+ attempt_buffer_read until child_process_status.eof?
357
+ e = Marshal.load(@process_status)
358
+ raise(Exception === e ? e : "unknown failure: #{e.inspect}")
359
+ rescue ArgumentError # If we get an ArgumentError error, then the exec was successful
360
+ true
361
+ ensure
362
+ child_process_status.close
363
+ open_pipes.delete(child_process_status)
364
364
  end
365
365
 
366
366
  def reap_errant_child
@@ -1,5 +1,5 @@
1
1
  module Mixlib
2
2
  class ShellOut
3
- VERSION = "2.2.7"
3
+ VERSION = "2.3.0"
4
4
  end
5
5
  end
@@ -18,8 +18,8 @@
18
18
  # limitations under the License.
19
19
  #
20
20
 
21
- require 'win32/process'
22
- require 'mixlib/shellout/windows/core_ext'
21
+ require "win32/process"
22
+ require "mixlib/shellout/windows/core_ext"
23
23
 
24
24
  module Mixlib
25
25
  class ShellOut
@@ -32,10 +32,12 @@ module Mixlib
32
32
 
33
33
  # Option validation that is windows specific
34
34
  def validate_options(opts)
35
- if opts[:user]
36
- unless opts[:password]
37
- raise InvalidCommandOption, "You must supply both a username and password when supplying a user in windows"
38
- end
35
+ if opts[:user] && !opts[:password]
36
+ raise InvalidCommandOption, "You must supply a password when supplying a user in windows"
37
+ end
38
+
39
+ if opts[:elevated] && opts[:elevated] != true && opts[:elevated] != false
40
+ raise InvalidCommandOption, "Invalid value passed for `elevated`. Please provide true/false."
39
41
  end
40
42
  end
41
43
 
@@ -56,29 +58,30 @@ module Mixlib
56
58
  #
57
59
  # Set cwd, environment, appname, etc.
58
60
  #
59
- app_name, command_line = command_to_run(self.command)
61
+ app_name, command_line = command_to_run(command)
60
62
  create_process_args = {
61
63
  :app_name => app_name,
62
64
  :command_line => command_line,
63
65
  :startup_info => {
64
66
  :stdout => stdout_write,
65
67
  :stderr => stderr_write,
66
- :stdin => stdin_read
68
+ :stdin => stdin_read,
67
69
  },
68
- :environment => inherit_environment.map { |k,v| "#{k}=#{v}" },
69
- :close_handles => false
70
+ :environment => inherit_environment.map { |k, v| "#{k}=#{v}" },
71
+ :close_handles => false,
70
72
  }
71
73
  create_process_args[:cwd] = cwd if cwd
72
74
  # default to local account database if domain is not specified
73
75
  create_process_args[:domain] = domain.nil? ? "." : domain
74
76
  create_process_args[:with_logon] = with_logon if with_logon
75
77
  create_process_args[:password] = password if password
78
+ create_process_args[:elevated] = elevated if elevated
76
79
 
77
80
  #
78
81
  # Start the process
79
82
  #
80
83
  process = Process.create(create_process_args)
81
- logger.debug(Utils.format_process(process, app_name, command_line, timeout)) if logger
84
+ logger.debug(format_process(process, app_name, command_line, timeout)) if logger
82
85
  begin
83
86
  # Start pushing data into input
84
87
  stdin_write << input if input
@@ -90,26 +93,26 @@ module Mixlib
90
93
  # Wait for the process to finish, consuming output as we go
91
94
  #
92
95
  start_wait = Time.now
93
- while true
96
+ loop do
94
97
  wait_status = WaitForSingleObject(process.process_handle, 0)
95
98
  case wait_status
96
99
  when WAIT_OBJECT_0
97
100
  # Get process exit code
98
- exit_code = [0].pack('l')
101
+ exit_code = [0].pack("l")
99
102
  unless GetExitCodeProcess(process.process_handle, exit_code)
100
103
  raise get_last_error
101
104
  end
102
105
  @status = ThingThatLooksSortOfLikeAProcessStatus.new
103
- @status.exitstatus = exit_code.unpack('l').first
106
+ @status.exitstatus = exit_code.unpack("l").first
104
107
 
105
108
  return self
106
109
  when WAIT_TIMEOUT
107
110
  # Kill the process
108
111
  if (Time.now - start_wait) > timeout
109
112
  begin
110
- require 'wmi-lite/wmi'
113
+ require "wmi-lite/wmi"
111
114
  wmi = WmiLite::Wmi.new
112
- Utils.kill_process_tree(process.process_id, wmi, logger)
115
+ kill_process_tree(process.process_id, wmi, logger)
113
116
  Process.kill(:KILL, process.process_id)
114
117
  rescue Errno::EIO, SystemCallError
115
118
  logger.warn("Failed to kill timed out process #{process.process_id}") if logger
@@ -118,13 +121,13 @@ module Mixlib
118
121
  raise Mixlib::ShellOut::CommandTimeout, [
119
122
  "command timed out:",
120
123
  format_for_exception,
121
- Utils.format_process(process, app_name, command_line, timeout)
124
+ format_process(process, app_name, command_line, timeout),
122
125
  ].join("\n")
123
126
  end
124
127
 
125
128
  consume_output(open_streams, stdout_read, stderr_read)
126
129
  else
127
- raise "Unknown response from WaitForSingleObject(#{process.process_handle}, #{timeout*1000}): #{wait_status}"
130
+ raise "Unknown response from WaitForSingleObject(#{process.process_handle}, #{timeout * 1000}): #{wait_status}"
128
131
  end
129
132
 
130
133
  end
@@ -146,8 +149,6 @@ module Mixlib
146
149
  end
147
150
  end
148
151
 
149
- private
150
-
151
152
  class ThingThatLooksSortOfLikeAProcessStatus
152
153
  attr_accessor :exitstatus
153
154
  def success?
@@ -155,6 +156,8 @@ module Mixlib
155
156
  end
156
157
  end
157
158
 
159
+ private
160
+
158
161
  def consume_output(open_streams, stdout_read, stderr_read)
159
162
  return false if open_streams.length == 0
160
163
  ready = IO.select(open_streams, nil, nil, READ_WAIT_TIME)
@@ -182,65 +185,59 @@ module Mixlib
182
185
  end
183
186
  end
184
187
 
185
- return true
188
+ true
186
189
  end
187
190
 
188
- IS_BATCH_FILE = /\.bat"?$|\.cmd"?$/i
189
-
190
191
  def command_to_run(command)
191
- return _run_under_cmd(command) if Utils.should_run_under_cmd?(command)
192
+ return run_under_cmd(command) if should_run_under_cmd?(command)
192
193
 
193
194
  candidate = candidate_executable_for_command(command)
194
195
 
195
- # Don't do searching for empty commands. Let it fail when it runs.
196
- return [ nil, command ] if candidate.length == 0
196
+ if candidate.length == 0
197
+ raise Mixlib::ShellOut::EmptyWindowsCommand, "could not parse script/executable out of command: `#{command}`"
198
+ end
197
199
 
198
200
  # Check if the exe exists directly. Otherwise, search PATH.
199
- exe = Utils.find_executable(candidate)
200
- exe = Utils.which(unquoted_executable_path(command)) if exe.nil? && exe !~ /[\\\/]/
201
-
202
- # Batch files MUST use cmd; and if we couldn't find the command we're looking for,
203
- # we assume it must be a cmd builtin.
204
- if exe.nil? || exe =~ IS_BATCH_FILE
205
- _run_under_cmd(command)
201
+ exe = which(candidate)
202
+ if exe_needs_cmd?(exe)
203
+ run_under_cmd(command)
206
204
  else
207
- _run_directly(command, exe)
205
+ [ exe, command ]
208
206
  end
209
207
  end
210
208
 
209
+ # Batch files MUST use cmd; and if we couldn't find the command we're looking for,
210
+ # we assume it must be a cmd builtin.
211
+ def exe_needs_cmd?(exe)
212
+ !exe || exe =~ /\.bat"?$|\.cmd"?$/i
213
+ end
214
+
211
215
  # cmd does not parse multiple quotes well unless the whole thing is wrapped up in quotes.
212
216
  # https://github.com/opscode/mixlib-shellout/pull/2#issuecomment-4837859
213
217
  # http://ss64.com/nt/syntax-esc.html
214
- def _run_under_cmd(command)
215
- [ ENV['COMSPEC'], "cmd /c \"#{command}\"" ]
216
- end
217
-
218
- def _run_directly(command, exe)
219
- [ exe, command ]
220
- end
221
-
222
- def unquoted_executable_path(command)
223
- command[0,command.index(/\s/) || command.length]
218
+ def run_under_cmd(command)
219
+ [ ENV["COMSPEC"], "cmd /c \"#{command}\"" ]
224
220
  end
225
221
 
222
+ # FIXME: this extracts ARGV[0] but is it correct?
226
223
  def candidate_executable_for_command(command)
227
224
  if command =~ /^\s*"(.*?)"/
228
225
  # If we have quotes, do an exact match
229
226
  $1
230
227
  else
231
228
  # Otherwise check everything up to the first space
232
- unquoted_executable_path(command).strip
229
+ command[0, command.index(/\s/) || command.length].strip
233
230
  end
234
231
  end
235
232
 
236
233
  def inherit_environment
237
234
  result = {}
238
- ENV.each_pair do |k,v|
235
+ ENV.each_pair do |k, v|
239
236
  result[k] = v
240
237
  end
241
238
 
242
- environment.each_pair do |k,v|
243
- if v == nil
239
+ environment.each_pair do |k, v|
240
+ if v.nil?
244
241
  result.delete(k)
245
242
  else
246
243
  result[k] = v
@@ -249,134 +246,134 @@ module Mixlib
249
246
  result
250
247
  end
251
248
 
252
- module Utils
253
- # api: semi-private
254
- # If there are special characters parsable by cmd.exe (such as file redirection), then
255
- # this method should return true.
256
- #
257
- # This parser is based on
258
- # https://github.com/ruby/ruby/blob/9073db5cb1d3173aff62be5b48d00f0fb2890991/win32/win32.c#L1437
259
- def self.should_run_under_cmd?(command)
260
- return true if command =~ /^@/
261
-
262
- quote = nil
263
- env = false
264
- env_first_char = false
265
-
266
- command.dup.each_char do |c|
267
- case c
268
- when "'", '"'
269
- if (!quote)
270
- quote = c
271
- elsif quote == c
272
- quote = nil
273
- end
274
- next
275
- when '>', '<', '|', '&', "\n"
276
- return true unless quote
277
- when '%'
278
- return true if env
279
- env = env_first_char = true
280
- next
281
- else
282
- next unless env
283
- if env_first_char
284
- env_first_char = false
285
- env = false and next if c !~ /[A-Za-z_]/
286
- end
287
- env = false if c !~ /[A-Za-z1-9_]/
249
+ # api: semi-private
250
+ # If there are special characters parsable by cmd.exe (such as file redirection), then
251
+ # this method should return true.
252
+ #
253
+ # This parser is based on
254
+ # https://github.com/ruby/ruby/blob/9073db5cb1d3173aff62be5b48d00f0fb2890991/win32/win32.c#L1437
255
+ def should_run_under_cmd?(command)
256
+ return true if command =~ /^@/
257
+
258
+ quote = nil
259
+ env = false
260
+ env_first_char = false
261
+
262
+ command.dup.each_char do |c|
263
+ case c
264
+ when "'", '"'
265
+ if !quote
266
+ quote = c
267
+ elsif quote == c
268
+ quote = nil
288
269
  end
270
+ next
271
+ when ">", "<", "|", "&", "\n"
272
+ return true unless quote
273
+ when "%"
274
+ return true if env
275
+ env = env_first_char = true
276
+ next
277
+ else
278
+ next unless env
279
+ if env_first_char
280
+ env_first_char = false
281
+ (env = false) && next if c !~ /[A-Za-z_]/
282
+ end
283
+ env = false if c !~ /[A-Za-z1-9_]/
289
284
  end
290
- return false
291
- end
292
-
293
- def self.pathext
294
- @pathext ||= ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') + [''] : ['']
295
285
  end
286
+ false
287
+ end
296
288
 
297
- # which() mimicks the Unix which command
298
- # FIXME: it is not working
299
- def self.which(cmd)
300
- ENV['PATH'].split(File::PATH_SEPARATOR).each do |path|
301
- exe = find_executable("#{path}/#{cmd}")
302
- return exe if exe
303
- end
304
- return nil
289
+ # FIXME: reduce code duplication with chef/chef
290
+ def which(cmd)
291
+ exts = ENV["PATHEXT"] ? ENV["PATHEXT"].split(";") + [""] : [""]
292
+ # windows always searches '.' first
293
+ exts.each do |ext|
294
+ filename = "#{cmd}#{ext}"
295
+ return filename if File.executable?(filename) && !File.directory?(filename)
305
296
  end
306
-
307
- # Windows has a different notion of what "executable" means
308
- # The OS will search through valid the extensions and look
309
- # for a binary there.
310
- def self.find_executable(path)
311
- return path if executable? path
312
-
313
- pathext.each do |ext|
314
- exe = "#{path}#{ext}"
315
- return exe if executable? exe
297
+ # only search through the path if the Filename does not contain separators
298
+ if File.basename(cmd) == cmd
299
+ paths = ENV["PATH"].split(File::PATH_SEPARATOR)
300
+ paths.each do |path|
301
+ exts.each do |ext|
302
+ filename = File.join(path, "#{cmd}#{ext}")
303
+ return filename if File.executable?(filename) && !File.directory?(filename)
304
+ end
316
305
  end
317
- return nil
318
- end
319
-
320
- def self.executable?(path)
321
- File.executable?(path) && !File.directory?(path)
322
306
  end
307
+ false
308
+ end
323
309
 
324
- def self.system_required_processes
325
- [
326
- 'System Idle Process',
327
- 'System',
328
- 'spoolsv.exe',
329
- 'lsass.exe',
330
- 'csrss.exe',
331
- 'smss.exe',
332
- 'svchost.exe'
333
- ]
334
- end
310
+ def system_required_processes
311
+ [
312
+ "System Idle Process",
313
+ "System",
314
+ "spoolsv.exe",
315
+ "lsass.exe",
316
+ "csrss.exe",
317
+ "smss.exe",
318
+ "svchost.exe",
319
+ ]
320
+ end
335
321
 
336
- def self.unsafe_process?(name, logger)
337
- return false unless system_required_processes.include? name
338
- logger.debug(
339
- "A request to kill a critical system process - #{name} - was received and skipped."
340
- )
341
- true
342
- end
322
+ def unsafe_process?(name, logger)
323
+ return false unless system_required_processes.include? name
324
+ logger.debug(
325
+ "A request to kill a critical system process - #{name} - was received and skipped."
326
+ )
327
+ true
328
+ end
343
329
 
344
- # recursively kills all child processes of given pid
345
- # calls itself querying for children child procs until
346
- # none remain. Important that a single WmiLite instance
347
- # is passed in since each creates its own WMI rpc process
348
- def self.kill_process_tree(pid, wmi, logger)
349
- wmi.query("select * from Win32_Process where ParentProcessID=#{pid}").each do |instance|
350
- next if unsafe_process?(instance.wmi_ole_object.name, logger)
351
- child_pid = instance.wmi_ole_object.processid
352
- kill_process_tree(child_pid, wmi, logger)
353
- kill_process(instance, logger)
354
- end
330
+ # recursively kills all child processes of given pid
331
+ # calls itself querying for children child procs until
332
+ # none remain. Important that a single WmiLite instance
333
+ # is passed in since each creates its own WMI rpc process
334
+ def kill_process_tree(pid, wmi, logger)
335
+ wmi.query("select * from Win32_Process where ParentProcessID=#{pid}").each do |instance|
336
+ next if unsafe_process?(instance.wmi_ole_object.name, logger)
337
+ child_pid = instance.wmi_ole_object.processid
338
+ kill_process_tree(child_pid, wmi, logger)
339
+ kill_process(instance, logger)
355
340
  end
341
+ end
356
342
 
357
- def self.kill_process(instance, logger)
358
- child_pid = instance.wmi_ole_object.processid
343
+ def kill_process(instance, logger)
344
+ child_pid = instance.wmi_ole_object.processid
345
+ if logger
359
346
  logger.debug([
360
347
  "killing child process #{child_pid}::",
361
- "#{instance.wmi_ole_object.Name} of parent #{pid}"
362
- ].join) if logger
363
- Process.kill(:KILL, instance.wmi_ole_object.processid)
364
- rescue Errno::EIO, SystemCallError
348
+ "#{instance.wmi_ole_object.Name} of parent #{pid}",
349
+ ].join)
350
+ end
351
+ Process.kill(:KILL, instance.wmi_ole_object.processid)
352
+ rescue Errno::EIO, SystemCallError
353
+ if logger
365
354
  logger.debug([
366
355
  "Failed to kill child process #{child_pid}::",
367
- "#{instance.wmi_ole_object.Name} of parent #{pid}"
368
- ].join) if logger
356
+ "#{instance.wmi_ole_object.Name} of parent #{pid}",
357
+ ].join)
369
358
  end
359
+ end
360
+
361
+ def format_process(process, app_name, command_line, timeout)
362
+ msg = []
363
+ msg << "ProcessId: #{process.process_id}"
364
+ msg << "app_name: #{app_name}"
365
+ msg << "command_line: #{command_line}"
366
+ msg << "timeout: #{timeout}"
367
+ msg.join("\n")
368
+ end
370
369
 
371
- def self.format_process(process, app_name, command_line, timeout)
372
- msg = []
373
- msg << "ProcessId: #{process.process_id}"
374
- msg << "app_name: #{app_name}"
375
- msg << "command_line: #{command_line}"
376
- msg << "timeout: #{timeout}"
377
- msg.join("\n")
370
+ # DEPRECATED do not use
371
+ class Utils
372
+ include Mixlib::ShellOut::Windows
373
+ def self.should_run_under_cmd?(cmd)
374
+ Mixlib::ShellOut::Windows::Utils.new.send(:should_run_under_cmd?, cmd)
378
375
  end
379
376
  end
380
- end # class
377
+ end
381
378
  end
382
379
  end