mixlib-shellout-2.1.0/0000755000004100000410000000000012536706365014700 5ustar www-datawww-datamixlib-shellout-2.1.0/lib/0000755000004100000410000000000012536706365015446 5ustar www-datawww-datamixlib-shellout-2.1.0/lib/mixlib/0000755000004100000410000000000012536706365016732 5ustar www-datawww-datamixlib-shellout-2.1.0/lib/mixlib/shellout.rb0000644000004100000410000003145412536706365021125 0ustar www-datawww-data#-- # Author:: Daniel DeLeo () # Copyright:: Copyright (c) 2010, 2011 Opscode, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require 'etc' require 'tmpdir' require 'fcntl' require 'mixlib/shellout/exceptions' module Mixlib class ShellOut READ_WAIT_TIME = 0.01 READ_SIZE = 4096 DEFAULT_READ_TIMEOUT = 600 if RUBY_PLATFORM =~ /mswin|mingw32|windows/ require 'mixlib/shellout/windows' include ShellOut::Windows else require 'mixlib/shellout/unix' include ShellOut::Unix end # User the command will run as. Normally set via options passed to new attr_accessor :user attr_accessor :domain attr_accessor :password # TODO remove attr_accessor :with_logon # Whether to simulate logon as the user. Normally set via options passed to new # Always enabled on windows attr_accessor :login # Group the command will run as. Normally set via options passed to new attr_accessor :group # Working directory for the subprocess. Normally set via options to new attr_accessor :cwd # An Array of acceptable exit codes. #error? (and #error!) use this list # to determine if the command was successful. Normally set via options to new attr_accessor :valid_exit_codes # When live_stdout is set, the stdout of the subprocess will be copied to it # as the subprocess is running. attr_accessor :live_stdout # When live_stderr is set, the stderr of the subprocess will be copied to it # as the subprocess is running. attr_accessor :live_stderr # ShellOut will push data from :input down the stdin of the subprocss. # Normally set via options passed to new. # Default: nil attr_accessor :input # If a logger is set, ShellOut will log a message before it executes the # command. attr_accessor :logger # The log level at which ShellOut should log. attr_accessor :log_level # A string which will be prepended to the log message. attr_accessor :log_tag # The command to be executed. attr_reader :command # The umask that will be set for the subcommand. attr_reader :umask # Environment variables that will be set for the subcommand. Refer to the # documentation of new to understand how ShellOut interprets this. attr_accessor :environment # The maximum time this command is allowed to run. Usually set via options # to new attr_writer :timeout # The amount of time the subcommand took to execute attr_reader :execution_time # Data written to stdout by the subprocess attr_reader :stdout # Data written to stderr by the subprocess attr_reader :stderr # A Process::Status (or ducktype) object collected when the subprocess is # reaped. attr_reader :status attr_reader :stdin_pipe, :stdout_pipe, :stderr_pipe, :process_status_pipe # === Arguments: # Takes a single command, or a list of command fragments. These are used # as arguments to Kernel.exec. See the Kernel.exec documentation for more # explanation of how arguments are evaluated. The last argument can be an # options Hash. # === Options: # If the last argument is a Hash, it is removed from the list of args passed # to exec and used as an options hash. The following options are available: # * +user+: the user the commmand should run as. if an integer is given, it is # used as a uid. A string is treated as a username and resolved to a uid # with Etc.getpwnam # * +group+: the group the command should run as. works similarly to +user+ # * +cwd+: the directory to chdir to before running the command # * +umask+: a umask to set before running the command. If given as an Integer, # be sure to use two leading zeros so it's parsed as Octal. A string will # be treated as an octal integer # * +returns+: one or more Integer values to use as valid exit codes for the # subprocess. This only has an effect if you call +error!+ after # +run_command+. # * +environment+: a Hash of environment variables to set before the command # is run. # * +timeout+: a Numeric value for the number of seconds to wait on the # child process before raising an Exception. This is calculated as the # total amount of time that ShellOut waited on the child process without # receiving any output (i.e., IO.select returned nil). Default is 60 # seconds. Note: the stdlib Timeout library is not used. # * +input+: A String of data to be passed to the subcommand. This is # written to the child process' stdin stream before the process is # launched. The child's stdin stream will be a pipe, so the size of input # data should not exceed the system's default pipe capacity (4096 bytes # is a safe value, though on newer Linux systems the capacity is 64k by # default). # * +live_stream+: An IO or Logger-like object (must respond to the append # operator +<<+) that will receive data as ShellOut reads it from the # child process. Generally this is used to copy data from the child to # the parent's stdout so that users may observe the progress of # long-running commands. # * +login+: Whether to simulate a login (set secondary groups, primary group, environment # variables etc) as done by the OS in an actual login # === Examples: # Invoke find(1) to search for .rb files: # find = Mixlib::ShellOut.new("find . -name '*.rb'") # find.run_command # # If all went well, the results are on +stdout+ # puts find.stdout # # find(1) prints diagnostic info to STDERR: # puts "error messages" + find.stderr # # Raise an exception if it didn't exit with 0 # find.error! # Run a command as the +www+ user with no extra ENV settings from +/tmp+ # cmd = Mixlib::ShellOut.new("apachectl", "start", :user => 'www', :env => nil, :cwd => '/tmp') # cmd.run_command # etc. def initialize(*command_args) @stdout, @stderr, @process_status = '', '', '' @live_stdout = @live_stderr = nil @input = nil @log_level = :debug @log_tag = nil @environment = {} @cwd = nil @valid_exit_codes = [0] @terminate_reason = nil @timeout = nil if command_args.last.is_a?(Hash) parse_options(command_args.pop) end @command = command_args.size == 1 ? command_args.first : command_args end # Returns the stream that both is being used by both live_stdout and live_stderr, or nil def live_stream live_stdout == live_stderr ? live_stdout : nil end # A shortcut for setting both live_stdout and live_stderr, so that both the # stdout and stderr from the subprocess will be copied to the same stream as # the subprocess is running. def live_stream=(stream) @live_stdout = @live_stderr = stream end # Set the umask that the subprocess will have. If given as a string, it # will be converted to an integer by String#oct. def umask=(new_umask) @umask = (new_umask.respond_to?(:oct) ? new_umask.oct : new_umask.to_i) & 007777 end # The uid that the subprocess will switch to. If the user attribute was # given as a username, it is converted to a uid by Etc.getpwnam # TODO migrate to shellout/unix.rb def uid return nil unless user user.kind_of?(Integer) ? user : Etc.getpwnam(user.to_s).uid end # The gid that the subprocess will switch to. If the group attribute is # given as a group name, it is converted to a gid by Etc.getgrnam # TODO migrate to shellout/unix.rb def gid return group.kind_of?(Integer) ? group : Etc.getgrnam(group.to_s).gid if group return Etc.getpwuid(uid).gid if using_login? return nil end def timeout @timeout || DEFAULT_READ_TIMEOUT end # Creates a String showing the output of the command, including a banner # showing the exact command executed. Used by +invalid!+ to show command # results when the command exited with an unexpected status. def format_for_exception msg = "" msg << "#{@terminate_reason}\n" if @terminate_reason msg << "---- Begin output of #{command} ----\n" msg << "STDOUT: #{stdout.strip}\n" msg << "STDERR: #{stderr.strip}\n" msg << "---- End output of #{command} ----\n" msg << "Ran #{command} returned #{status.exitstatus}" if status msg end # The exit status of the subprocess. Will be nil if the command is still # running or died without setting an exit status (e.g., terminated by # `kill -9`). def exitstatus @status && @status.exitstatus end # Run the command, writing the command's standard out and standard error # to +stdout+ and +stderr+, and saving its exit status object to +status+ # === Returns # returns +self+; +stdout+, +stderr+, +status+, and +exitstatus+ will be # populated with results of the command # === Raises # * Errno::EACCES when you are not privileged to execute the command # * Errno::ENOENT when the command is not available on the system (or not # in the current $PATH) # * CommandTimeout when the command does not complete # within +timeout+ seconds (default: 600s) def run_command if logger log_message = (log_tag.nil? ? "" : "#@log_tag ") << "sh(#@command)" logger.send(log_level, log_message) end super end # Checks the +exitstatus+ against the set of +valid_exit_codes+. # === Returns # +true+ if +exitstatus+ is not in the list of +valid_exit_codes+, false # otherwise. def error? !Array(valid_exit_codes).include?(exitstatus) end # If #error? is true, calls +invalid!+, which raises an Exception. # === Returns # nil::: always returns nil when it does not raise # === Raises # ::ShellCommandFailed::: via +invalid!+ def error! invalid!("Expected process to exit with #{valid_exit_codes.inspect}, but received '#{exitstatus}'") if error? end # Raises a ShellCommandFailed exception, appending the # command's stdout, stderr, and exitstatus to the exception message. # === Arguments # +msg+: A String to use as the basis of the exception message. The # default explanation is very generic, providing a more informative message # is highly encouraged. # === Raises # ShellCommandFailed always def invalid!(msg=nil) msg ||= "Command produced unexpected results" raise ShellCommandFailed, msg + "\n" + format_for_exception end def inspect "<#{self.class.name}##{object_id}: command: '#@command' process_status: #{@status.inspect} " + "stdout: '#{stdout.strip}' stderr: '#{stderr.strip}' child_pid: #{@child_pid.inspect} " + "environment: #{@environment.inspect} timeout: #{timeout} user: #@user group: #@group working_dir: #@cwd >" end private def parse_options(opts) opts.each do |option, setting| case option.to_s when 'cwd' self.cwd = setting when 'domain' self.domain = setting when 'password' self.password = setting when 'user' self.user = setting self.with_logon = setting when 'group' self.group = setting when 'umask' self.umask = setting when 'timeout' self.timeout = setting when 'returns' self.valid_exit_codes = Array(setting) when 'live_stream' self.live_stdout = self.live_stderr = setting when 'live_stdout' self.live_stdout = setting when 'live_stderr' self.live_stderr = setting when 'input' self.input = setting when 'logger' self.logger = setting when 'log_level' self.log_level = setting when 'log_tag' self.log_tag = setting when 'environment', 'env' self.environment = setting || {} when 'login' self.login = setting else raise InvalidCommandOption, "option '#{option.inspect}' is not a valid option for #{self.class.name}" end end validate_options(opts) end def validate_options(opts) if login && !user raise InvalidCommandOption, "cannot set login without specifying a user" end super end end end mixlib-shellout-2.1.0/lib/mixlib/shellout/0000755000004100000410000000000012536706365020571 5ustar www-datawww-datamixlib-shellout-2.1.0/lib/mixlib/shellout/windows/0000755000004100000410000000000012536706365022263 5ustar www-datawww-datamixlib-shellout-2.1.0/lib/mixlib/shellout/windows/core_ext.rb0000644000004100000410000002763412536706365024434 0ustar www-datawww-data#-- # Author:: Daniel DeLeo () # Author:: John Keiser () # Copyright:: Copyright (c) 2011, 2012 Opscode, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require 'win32/process' # Add new constants for Logon module Process::Constants LOGON32_LOGON_INTERACTIVE = 0x00000002 LOGON32_PROVIDER_DEFAULT = 0x00000000 UOI_NAME = 0x00000002 end # Define the functions needed to check with Service windows station module Process::Functions module FFI::Library # Wrapper method for attach_function + private def attach_pfunc(*args) attach_function(*args) private args[0] end end extend FFI::Library ffi_lib :advapi32 attach_pfunc :LogonUserW, [:buffer_in, :buffer_in, :buffer_in, :ulong, :ulong, :pointer], :bool attach_pfunc :CreateProcessAsUserW, [:ulong, :buffer_in, :buffer_in, :pointer, :pointer, :bool, :ulong, :buffer_in, :buffer_in, :pointer, :pointer], :bool ffi_lib :user32 attach_pfunc :GetProcessWindowStation, [], :ulong attach_pfunc :GetUserObjectInformationA, [:ulong, :uint, :buffer_out, :ulong, :pointer], :bool end # Override Process.create to check for running in the Service window station and doing # a full logon with LogonUser, instead of a CreateProcessWithLogon module Process include Process::Constants include Process::Structs def create(args) unless args.kind_of?(Hash) raise TypeError, 'hash keyword arguments expected' end valid_keys = %w[ app_name command_line inherit creation_flags cwd environment startup_info thread_inherit process_inherit close_handles with_logon domain password ] valid_si_keys = %w[ startf_flags desktop title x y x_size y_size x_count_chars y_count_chars fill_attribute sw_flags stdin stdout stderr ] # Set default values hash = { 'app_name' => nil, 'creation_flags' => 0, 'close_handles' => true } # Validate the keys, and convert symbols and case to lowercase strings. args.each{ |key, val| key = key.to_s.downcase unless valid_keys.include?(key) raise ArgumentError, "invalid key '#{key}'" end hash[key] = val } si_hash = {} # If the startup_info key is present, validate its subkeys if hash['startup_info'] hash['startup_info'].each{ |key, val| key = key.to_s.downcase unless valid_si_keys.include?(key) raise ArgumentError, "invalid startup_info key '#{key}'" end si_hash[key] = val } end # The +command_line+ key is mandatory unless the +app_name+ key # is specified. unless hash['command_line'] if hash['app_name'] hash['command_line'] = hash['app_name'] hash['app_name'] = nil else raise ArgumentError, 'command_line or app_name must be specified' end end env = nil # The env string should be passed as a string of ';' separated paths. if hash['environment'] env = hash['environment'] unless env.respond_to?(:join) env = hash['environment'].split(File::PATH_SEPARATOR) end env = env.map{ |e| e + 0.chr }.join('') + 0.chr env.to_wide_string! if hash['with_logon'] end # Process SECURITY_ATTRIBUTE structure process_security = nil if hash['process_inherit'] process_security = SECURITY_ATTRIBUTES.new process_security[:nLength] = 12 process_security[:bInheritHandle] = true end # Thread SECURITY_ATTRIBUTE structure thread_security = nil if hash['thread_inherit'] thread_security = SECURITY_ATTRIBUTES.new thread_security[:nLength] = 12 thread_security[:bInheritHandle] = true end # Automatically handle stdin, stdout and stderr as either IO objects # or file descriptors. This won't work for StringIO, however. It also # will not work on JRuby because of the way it handles internal file # descriptors. # ['stdin', 'stdout', 'stderr'].each{ |io| if si_hash[io] if si_hash[io].respond_to?(:fileno) handle = get_osfhandle(si_hash[io].fileno) else handle = get_osfhandle(si_hash[io]) end if handle == INVALID_HANDLE_VALUE ptr = FFI::MemoryPointer.new(:int) if windows_version >= 6 && get_errno(ptr) == 0 errno = ptr.read_int else errno = FFI.errno end raise SystemCallError.new("get_osfhandle", errno) end # Most implementations of Ruby on Windows create inheritable # handles by default, but some do not. RF bug #26988. bool = SetHandleInformation( handle, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT ) raise SystemCallError.new("SetHandleInformation", FFI.errno) unless bool si_hash[io] = handle si_hash['startf_flags'] ||= 0 si_hash['startf_flags'] |= STARTF_USESTDHANDLES hash['inherit'] = true end } procinfo = PROCESS_INFORMATION.new startinfo = STARTUPINFO.new unless si_hash.empty? startinfo[:cb] = startinfo.size startinfo[:lpDesktop] = si_hash['desktop'] if si_hash['desktop'] startinfo[:lpTitle] = si_hash['title'] if si_hash['title'] startinfo[:dwX] = si_hash['x'] if si_hash['x'] startinfo[:dwY] = si_hash['y'] if si_hash['y'] startinfo[:dwXSize] = si_hash['x_size'] if si_hash['x_size'] startinfo[:dwYSize] = si_hash['y_size'] if si_hash['y_size'] startinfo[:dwXCountChars] = si_hash['x_count_chars'] if si_hash['x_count_chars'] startinfo[:dwYCountChars] = si_hash['y_count_chars'] if si_hash['y_count_chars'] startinfo[:dwFillAttribute] = si_hash['fill_attribute'] if si_hash['fill_attribute'] startinfo[:dwFlags] = si_hash['startf_flags'] if si_hash['startf_flags'] startinfo[:wShowWindow] = si_hash['sw_flags'] if si_hash['sw_flags'] startinfo[:cbReserved2] = 0 startinfo[:hStdInput] = si_hash['stdin'] if si_hash['stdin'] startinfo[:hStdOutput] = si_hash['stdout'] if si_hash['stdout'] startinfo[:hStdError] = si_hash['stderr'] if si_hash['stderr'] end app = nil cmd = nil # Convert strings to wide character strings if present if hash['app_name'] app = hash['app_name'].to_wide_string end if hash['command_line'] cmd = hash['command_line'].to_wide_string end if hash['cwd'] cwd = hash['cwd'].to_wide_string end inherit = hash['inherit'] || false if hash['with_logon'] logon = hash['with_logon'].to_wide_string if hash['password'] passwd = hash['password'].to_wide_string else raise ArgumentError, 'password must be specified if with_logon is used' end if hash['domain'] domain = hash['domain'].to_wide_string end hash['creation_flags'] |= CREATE_UNICODE_ENVIRONMENT winsta_name = FFI::MemoryPointer.new(:char, 256) return_size = FFI::MemoryPointer.new(:ulong) bool = GetUserObjectInformationA( GetProcessWindowStation(), # Window station handle UOI_NAME, # Information to get winsta_name, # Buffer to receive information winsta_name.size, # Size of buffer return_size # Size filled into buffer ) unless bool raise SystemCallError.new("GetUserObjectInformationA", FFI.errno) end winsta_name = winsta_name.read_string(return_size.read_ulong) # If running in the service windows station must do a log on to get # to the interactive desktop. Running process user account must have # the 'Replace a process level token' permission. This is necessary as # the logon (which happens with CreateProcessWithLogon) must have an # interactive windows station to attach to, which is created with the # LogonUser cann with the LOGON32_LOGON_INTERACTIVE flag. if winsta_name =~ /^Service-0x0-.*$/i token = FFI::MemoryPointer.new(:ulong) bool = LogonUserW( logon, # User domain, # Domain passwd, # Password LOGON32_LOGON_INTERACTIVE, # Logon Type LOGON32_PROVIDER_DEFAULT, # Logon Provider token # User token handle ) unless bool raise SystemCallError.new("LogonUserW", FFI.errno) end token = token.read_ulong begin bool = CreateProcessAsUserW( token, # User token handle app, # App name cmd, # Command line process_security, # Process attributes thread_security, # Thread attributes inherit, # Inherit handles hash['creation_flags'], # Creation Flags env, # Environment cwd, # Working directory startinfo, # Startup Info procinfo # Process Info ) ensure CloseHandle(token) end unless bool raise SystemCallError.new("CreateProcessAsUserW (You must hold the 'Replace a process level token' permission)", FFI.errno) end else bool = CreateProcessWithLogonW( logon, # User domain, # Domain passwd, # Password LOGON_WITH_PROFILE, # Logon flags app, # App name cmd, # Command line hash['creation_flags'], # Creation flags env, # Environment cwd, # Working directory startinfo, # Startup Info procinfo # Process Info ) end unless bool raise SystemCallError.new("CreateProcessWithLogonW", FFI.errno) end else bool = CreateProcessW( app, # App name cmd, # Command line process_security, # Process attributes thread_security, # Thread attributes inherit, # Inherit handles? hash['creation_flags'], # Creation flags env, # Environment cwd, # Working directory startinfo, # Startup Info procinfo # Process Info ) unless bool raise SystemCallError.new("CreateProcessW", FFI.errno) end end # Automatically close the process and thread handles in the # PROCESS_INFORMATION struct unless explicitly told not to. if hash['close_handles'] CloseHandle(procinfo[:hProcess]) if procinfo[:hProcess] CloseHandle(procinfo[:hThread]) if procinfo[:hThread] # Set fields to nil so callers don't attempt to close the handle # which can result in the wrong handle being closed or an # exception in some circumstances procinfo[:hProcess] = nil procinfo[:hThread] = nil end ProcessInfo.new( procinfo[:hProcess], procinfo[:hThread], procinfo[:dwProcessId], procinfo[:dwThreadId] ) end module_function :create end mixlib-shellout-2.1.0/lib/mixlib/shellout/version.rb0000644000004100000410000000007712536706365022607 0ustar www-datawww-datamodule Mixlib class ShellOut VERSION = "2.1.0" end end mixlib-shellout-2.1.0/lib/mixlib/shellout/windows.rb0000644000004100000410000002335712536706365022622 0ustar www-datawww-data#-- # Author:: Daniel DeLeo () # Author:: John Keiser () # Author:: Ho-Sheng Hsiao () # Copyright:: Copyright (c) 2011, 2012 Opscode, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require 'win32/process' require 'windows/handle' require 'windows/process' require 'windows/synchronize' require 'mixlib/shellout/windows/core_ext' module Mixlib class ShellOut module Windows include ::Windows::Handle include ::Windows::Process include ::Windows::Synchronize TIME_SLICE = 0.05 # Option validation that is windows specific def validate_options(opts) if opts[:user] unless opts[:password] raise InvalidCommandOption, "You must supply both a username and password when supplying a user in windows" end end end #-- # Missing lots of features from the UNIX version, such as # uid, etc. def run_command # # Create pipes to capture stdout and stderr, # stdout_read, stdout_write = IO.pipe stderr_read, stderr_write = IO.pipe stdin_read, stdin_write = IO.pipe open_streams = [ stdout_read, stderr_read ] begin # # Set cwd, environment, appname, etc. # app_name, command_line = command_to_run(self.command) create_process_args = { :app_name => app_name, :command_line => command_line, :startup_info => { :stdout => stdout_write, :stderr => stderr_write, :stdin => stdin_read }, :environment => inherit_environment.map { |k,v| "#{k}=#{v}" }, :close_handles => false } create_process_args[:cwd] = cwd if cwd # default to local account database if domain is not specified create_process_args[:domain] = domain.nil? ? "." : domain create_process_args[:with_logon] = with_logon if with_logon create_process_args[:password] = password if password # # Start the process # process = Process.create(create_process_args) begin # Start pushing data into input stdin_write << input if input # Close pipe to kick things off stdin_write.close # # Wait for the process to finish, consuming output as we go # start_wait = Time.now while true wait_status = WaitForSingleObject(process.process_handle, 0) case wait_status when WAIT_OBJECT_0 # Get process exit code exit_code = [0].pack('l') unless GetExitCodeProcess(process.process_handle, exit_code) raise get_last_error end @status = ThingThatLooksSortOfLikeAProcessStatus.new @status.exitstatus = exit_code.unpack('l').first return self when WAIT_TIMEOUT # Kill the process if (Time.now - start_wait) > timeout begin Process.kill(:KILL, process.process_id) rescue Errno::EIO logger.warn("Failed to kill timed out process #{process.process_id}") if logger end raise Mixlib::ShellOut::CommandTimeout, "command timed out:\n#{format_for_exception}" end consume_output(open_streams, stdout_read, stderr_read) else raise "Unknown response from WaitForSingleObject(#{process.process_handle}, #{timeout*1000}): #{wait_status}" end end ensure CloseHandle(process.thread_handle) if process.thread_handle CloseHandle(process.process_handle) if process.process_handle end ensure # # Consume all remaining data from the pipes until they are closed # stdout_write.close stderr_write.close while consume_output(open_streams, stdout_read, stderr_read) end end end private class ThingThatLooksSortOfLikeAProcessStatus attr_accessor :exitstatus def success? exitstatus == 0 end end def consume_output(open_streams, stdout_read, stderr_read) return false if open_streams.length == 0 ready = IO.select(open_streams, nil, nil, READ_WAIT_TIME) return true if ! ready if ready.first.include?(stdout_read) begin next_chunk = stdout_read.readpartial(READ_SIZE) @stdout << next_chunk @live_stdout << next_chunk if @live_stdout rescue EOFError stdout_read.close open_streams.delete(stdout_read) end end if ready.first.include?(stderr_read) begin next_chunk = stderr_read.readpartial(READ_SIZE) @stderr << next_chunk @live_stderr << next_chunk if @live_stderr rescue EOFError stderr_read.close open_streams.delete(stderr_read) end end return true end IS_BATCH_FILE = /\.bat"?$|\.cmd"?$/i def command_to_run(command) return _run_under_cmd(command) if Utils.should_run_under_cmd?(command) candidate = candidate_executable_for_command(command) # Don't do searching for empty commands. Let it fail when it runs. return [ nil, command ] if candidate.length == 0 # Check if the exe exists directly. Otherwise, search PATH. exe = Utils.find_executable(candidate) exe = Utils.which(unquoted_executable_path(command)) if exe.nil? && exe !~ /[\\\/]/ # Batch files MUST use cmd; and if we couldn't find the command we're looking for, # we assume it must be a cmd builtin. if exe.nil? || exe =~ IS_BATCH_FILE _run_under_cmd(command) else _run_directly(command, exe) end end # cmd does not parse multiple quotes well unless the whole thing is wrapped up in quotes. # https://github.com/opscode/mixlib-shellout/pull/2#issuecomment-4837859 # http://ss64.com/nt/syntax-esc.html def _run_under_cmd(command) [ ENV['COMSPEC'], "cmd /c \"#{command}\"" ] end def _run_directly(command, exe) [ exe, command ] end def unquoted_executable_path(command) command[0,command.index(/\s/) || command.length] end def candidate_executable_for_command(command) if command =~ /^\s*"(.*?)"/ # If we have quotes, do an exact match $1 else # Otherwise check everything up to the first space unquoted_executable_path(command).strip end end def inherit_environment result = {} ENV.each_pair do |k,v| result[k] = v end environment.each_pair do |k,v| if v == nil result.delete(k) else result[k] = v end end result end module Utils # api: semi-private # If there are special characters parsable by cmd.exe (such as file redirection), then # this method should return true. # # This parser is based on # https://github.com/ruby/ruby/blob/9073db5cb1d3173aff62be5b48d00f0fb2890991/win32/win32.c#L1437 def self.should_run_under_cmd?(command) return true if command =~ /^@/ quote = nil env = false env_first_char = false command.dup.each_char do |c| case c when "'", '"' if (!quote) quote = c elsif quote == c quote = nil end next when '>', '<', '|', '&', "\n" return true unless quote when '%' return true if env env = env_first_char = true next else next unless env if env_first_char env_first_char = false env = false and next if c !~ /[A-Za-z_]/ end env = false if c !~ /[A-Za-z1-9_]/ end end return false end def self.pathext @pathext ||= ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') + [''] : [''] end # which() mimicks the Unix which command # FIXME: it is not working def self.which(cmd) ENV['PATH'].split(File::PATH_SEPARATOR).each do |path| exe = find_executable("#{path}/#{cmd}") return exe if exe end return nil end # Windows has a different notion of what "executable" means # The OS will search through valid the extensions and look # for a binary there. def self.find_executable(path) return path if File.executable? path pathext.each do |ext| exe = "#{path}#{ext}" return exe if File.executable? exe end return nil end end end # class end end mixlib-shellout-2.1.0/lib/mixlib/shellout/exceptions.rb0000644000004100000410000000027212536706365023300 0ustar www-datawww-datamodule Mixlib class ShellOut class ShellCommandFailed < RuntimeError; end class CommandTimeout < RuntimeError; end class InvalidCommandOption < RuntimeError; end end end mixlib-shellout-2.1.0/lib/mixlib/shellout/unix.rb0000644000004100000410000003312512536706365022105 0ustar www-datawww-data#-- # Author:: Daniel DeLeo () # Copyright:: Copyright (c) 2010, 2011 Opscode, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # module Mixlib class ShellOut module Unix # "1.8.7" as a frozen string. We use this with a hack that disables GC to # avoid segfaults on Ruby 1.8.7, so we need to allocate the fewest # objects we possibly can. ONE_DOT_EIGHT_DOT_SEVEN = "1.8.7".freeze # Option validation that is unix specific def validate_options(opts) # No options to validate, raise exceptions here if needed end # Whether we're simulating a login shell def using_login? return login && user end # Helper method for sgids def all_seconderies ret = [] Etc.endgrent while ( g = Etc.getgrent ) do ret << g end Etc.endgrent return ret end # The secondary groups that the subprocess will switch to. # Currently valid only if login is used, and is set # to the user's secondary groups def sgids return nil unless using_login? user_name = Etc.getpwuid(uid).name all_seconderies.select{|g| g.mem.include?(user_name)}.map{|g|g.gid} end # The environment variables that are deduced from simulating logon # Only valid if login is used def logon_environment return {} unless using_login? entry = Etc.getpwuid(uid) # According to `man su`, the set fields are: # $HOME, $SHELL, $USER, $LOGNAME, $PATH, and $IFS # Values are copied from "shadow" package in Ubuntu 14.10 {'HOME'=>entry.dir, 'SHELL'=>entry.shell, 'USER'=>entry.name, 'LOGNAME'=>entry.name, 'PATH'=>'/sbin:/bin:/usr/sbin:/usr/bin', 'IFS'=>"\t\n"} end # Merges the two environments for the process def process_environment logon_environment.merge(self.environment) end # Run the command, writing the command's standard out and standard error # to +stdout+ and +stderr+, and saving its exit status object to +status+ # === Returns # returns +self+; +stdout+, +stderr+, +status+, and +exitstatus+ will be # populated with results of the command. # === Raises # * Errno::EACCES when you are not privileged to execute the command # * Errno::ENOENT when the command is not available on the system (or not # in the current $PATH) # * Chef::Exceptions::CommandTimeout when the command does not complete # within +timeout+ seconds (default: 600s). When this happens, ShellOut # will send a TERM and then KILL to the entire process group to ensure # that any grandchild processes are terminated. If the invocation of # the child process spawned multiple child processes (which commonly # happens if the command is passed as a single string to be interpreted # by bin/sh, and bin/sh is not bash), the exit status object may not # contain the correct exit code of the process (of course there is no # exit code if the command is killed by SIGKILL, also). def run_command @child_pid = fork_subprocess @reaped = false configure_parent_process_file_descriptors # Ruby 1.8.7 and 1.8.6 from mid 2009 try to allocate objects during GC # when calling IO.select and IO#read. Disabling GC works around the # segfault, but obviously it's a bad workaround. We no longer support # 1.8.6 so we only need this hack for 1.8.7. GC.disable if RUBY_VERSION == ONE_DOT_EIGHT_DOT_SEVEN # CHEF-3390: Marshall.load on Ruby < 1.8.7p369 also has a GC bug related # to Marshall.load, so try disabling GC first. propagate_pre_exec_failure @status = nil @result = nil @execution_time = 0 write_to_child_stdin until @status ready_buffers = attempt_buffer_read unless ready_buffers @execution_time += READ_WAIT_TIME if @execution_time >= timeout && !@result # kill the bad proccess reap_errant_child # read anything it wrote when we killed it attempt_buffer_read # raise raise CommandTimeout, "Command timed out after #{@execution_time.to_i}s:\n#{format_for_exception}" end end attempt_reap end self rescue Errno::ENOENT # When ENOENT happens, we can be reasonably sure that the child process # is going to exit quickly, so we use the blocking variant of waitpid2 reap raise ensure reap_errant_child if should_reap? # make one more pass to get the last of the output after the # child process dies attempt_buffer_read # no matter what happens, turn the GC back on, and hope whatever busted # version of ruby we're on doesn't allocate some objects during the next # GC run. GC.enable close_all_pipes end private def set_user if user Process.uid = uid Process.euid = uid end end def set_group if group Process.egid = gid Process.gid = gid end end def set_secondarygroups if sgids Process.groups = sgids end end def set_environment # user-set variables should override the login ones process_environment.each do |env_var,value| ENV[env_var] = value end end def set_umask File.umask(umask) if umask end def set_cwd Dir.chdir(cwd) if cwd end # Since we call setsid the child_pgid will be the child_pid, set to negative here # so it can be directly used in arguments to kill, wait, etc. def child_pgid -@child_pid end def initialize_ipc @stdin_pipe, @stdout_pipe, @stderr_pipe, @process_status_pipe = IO.pipe, IO.pipe, IO.pipe, IO.pipe @process_status_pipe.last.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC) end def child_stdin @stdin_pipe[1] end def child_stdout @stdout_pipe[0] end def child_stderr @stderr_pipe[0] end def child_process_status @process_status_pipe[0] end def close_all_pipes child_stdin.close unless child_stdin.closed? child_stdout.close unless child_stdout.closed? child_stderr.close unless child_stderr.closed? child_process_status.close unless child_process_status.closed? end # Replace stdout, and stderr with pipes to the parent, and close the # reader side of the error marshaling side channel. # # If there is no input, close STDIN so when we exec, # the new program will know it's never getting input ever. def configure_subprocess_file_descriptors process_status_pipe.first.close # HACK: for some reason, just STDIN.close isn't good enough when running # under ruby 1.9.2, so make it good enough: stdin_pipe.last.close STDIN.reopen stdin_pipe.first stdin_pipe.first.close unless input stdout_pipe.first.close STDOUT.reopen stdout_pipe.last stdout_pipe.last.close stderr_pipe.first.close STDERR.reopen stderr_pipe.last stderr_pipe.last.close STDOUT.sync = STDERR.sync = true STDIN.sync = true if input end def configure_parent_process_file_descriptors # Close the sides of the pipes we don't care about stdin_pipe.first.close stdin_pipe.last.close unless input stdout_pipe.last.close stderr_pipe.last.close process_status_pipe.last.close # Get output as it happens rather than buffered child_stdin.sync = true if input child_stdout.sync = true child_stderr.sync = true true end # Some patch levels of ruby in wide use (in particular the ruby 1.8.6 on OSX) # segfault when you IO.select a pipe that's reached eof. Weak sauce. def open_pipes @open_pipes ||= [child_stdout, child_stderr, child_process_status] end # Keep this unbuffered for now def write_to_child_stdin return unless input child_stdin << input child_stdin.close # Kick things off end def attempt_buffer_read ready = IO.select(open_pipes, nil, nil, READ_WAIT_TIME) if ready read_stdout_to_buffer if ready.first.include?(child_stdout) read_stderr_to_buffer if ready.first.include?(child_stderr) read_process_status_to_buffer if ready.first.include?(child_process_status) end ready end def read_stdout_to_buffer while chunk = child_stdout.read_nonblock(READ_SIZE) @stdout << chunk @live_stdout << chunk if @live_stdout end rescue Errno::EAGAIN rescue EOFError open_pipes.delete(child_stdout) end def read_stderr_to_buffer while chunk = child_stderr.read_nonblock(READ_SIZE) @stderr << chunk @live_stderr << chunk if @live_stderr end rescue Errno::EAGAIN rescue EOFError open_pipes.delete(child_stderr) end def read_process_status_to_buffer while chunk = child_process_status.read_nonblock(READ_SIZE) @process_status << chunk end rescue Errno::EAGAIN rescue EOFError open_pipes.delete(child_process_status) end def fork_subprocess initialize_ipc fork do # Child processes may themselves fork off children. A common case # is when the command is given as a single string (instead of # command name plus Array of arguments) and /bin/sh does not # support the "ONESHOT" optimization (where sh -c does exec without # forking). To support cleaning up all the children, we need to # ensure they're in a unique process group. # # We use setsid here to abandon our controlling tty and get a new session # and process group that are set to the pid of the child process. Process.setsid configure_subprocess_file_descriptors set_secondarygroups set_group set_user set_environment set_umask set_cwd begin command.kind_of?(Array) ? exec(*command, :close_others=>true) : exec(command, :close_others=>true) raise 'forty-two' # Should never get here rescue Exception => e Marshal.dump(e, process_status_pipe.last) process_status_pipe.last.flush end process_status_pipe.last.close unless (process_status_pipe.last.closed?) exit! end end # Attempt to get a Marshaled error from the side-channel. # If it's there, un-marshal it and raise. If it's not there, # assume everything went well. def propagate_pre_exec_failure begin attempt_buffer_read until child_process_status.eof? e = Marshal.load(@process_status) raise(Exception === e ? e : "unknown failure: #{e.inspect}") rescue ArgumentError # If we get an ArgumentError error, then the exec was successful true ensure child_process_status.close open_pipes.delete(child_process_status) end end def reap_errant_child return if attempt_reap @terminate_reason = "Command exceeded allowed execution time, process terminated" logger.error("Command exceeded allowed execution time, sending TERM") if logger Process.kill(:TERM, child_pgid) sleep 3 attempt_reap logger.error("Command exceeded allowed execution time, sending KILL") if logger Process.kill(:KILL, child_pgid) reap # Should not hit this but it's possible if something is calling waitall # in a separate thread. rescue Errno::ESRCH nil end def should_reap? # if we fail to fork, no child pid so nothing to reap @child_pid && !@reaped end # Unconditionally reap the child process. This is used in scenarios where # we can be confident the child will exit quickly, and has not spawned # and grandchild processes. def reap results = Process.waitpid2(@child_pid) @reaped = true @status = results.last rescue Errno::ECHILD # When cleaning up timed-out processes, we might send SIGKILL to the # whole process group after we've cleaned up the direct child. In that # case the grandchildren will have been adopted by init so we can't # reap them even if we wanted to (we don't). nil end # Try to reap the child process but don't block if it isn't dead yet. def attempt_reap if results = Process.waitpid2(@child_pid, Process::WNOHANG) @reaped = true @status = results.last else nil end end end end end mixlib-shellout-2.1.0/metadata.yml0000644000004100000410000000277212536706365017213 0ustar www-datawww-data--- !ruby/object:Gem::Specification name: mixlib-shellout version: !ruby/object:Gem::Version version: 2.1.0 platform: ruby authors: - Opscode autorequire: bindir: bin cert_chain: [] date: 2015-05-18 00:00:00.000000000 Z dependencies: - !ruby/object:Gem::Dependency name: rspec requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '3.0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '3.0' description: Run external commands on Unix or Windows email: info@opscode.com executables: [] extensions: [] extra_rdoc_files: - README.md - LICENSE files: - LICENSE - README.md - lib/mixlib/shellout.rb - lib/mixlib/shellout/exceptions.rb - lib/mixlib/shellout/unix.rb - lib/mixlib/shellout/version.rb - lib/mixlib/shellout/windows.rb - lib/mixlib/shellout/windows/core_ext.rb homepage: http://wiki.opscode.com/ licenses: [] metadata: {} post_install_message: rdoc_options: [] require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: 1.9.3 required_rubygems_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' requirements: [] rubyforge_project: rubygems_version: 2.4.6 signing_key: specification_version: 4 summary: Run external commands on Unix or Windows test_files: [] mixlib-shellout-2.1.0/LICENSE0000644000004100000410000002514212536706365015711 0ustar www-datawww-data Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. mixlib-shellout-2.1.0/README.md0000644000004100000410000000327512536706365016166 0ustar www-datawww-data# Mixlib::ShellOut Provides a simplified interface to shelling out yet still collecting both standard out and standard error and providing full control over environment, working directory, uid, gid, etc. No means for passing input to the subprocess is provided. ## Example Invoke find(1) to search for .rb files: find = Mixlib::ShellOut.new("find . -name '*.rb'") find.run_command If all went well, the results are on `stdout` puts find.stdout `find(1)` prints diagnostic info to STDERR: puts "error messages" + find.stderr Raise an exception if it didn't exit with 0 find.error! Run a command as the `www` user with no extra ENV settings from `/tmp` cmd = Mixlib::ShellOut.new("apachectl", "start", :user => 'www', :env => nil, :cwd => '/tmp') cmd.run_command # etc. ## STDIN Example Invoke crontab to edit user cron: # :input only supports simple strings crontab_lines = [ "* * * * * /bin/true", "* * * * * touch /tmp/here" ] crontab = Mixlib::ShellOut.new("crontab -l -u #{@new_resource.user}", :input => crontab_lines.join("\n")) crontab.run_command ## Windows Impersonation Example Invoke "whoami.exe" to demonstrate running a command as another user: whoami = Mixlib::ShellOut.new("whoami.exe", :user => "username", :domain => "DOMAIN", :password => "password") whoami.run_command ## Platform Support Mixlib::ShellOut does a standard fork/exec on Unix, and uses the Win32 API on Windows. There is not currently support for JRuby. ## License Apache 2 Licensed. See LICENSE for full details. ## See Also * `Process.spawn` in Ruby 1.9 * [https://github.com/rtomayko/posix-spawn](https://github.com/rtomayko/posix-spawn)