[DRE-commits] [ruby-mixlib-shellout] 02/10: Imported Upstream version 2.1.0
Antonio Terceiro
terceiro at moszumanska.debian.org
Sat Jun 13 16:30:52 UTC 2015
This is an automated email from the git hooks/post-receive script.
terceiro pushed a commit to branch master
in repository ruby-mixlib-shellout.
commit ea5c1274bb54199e1b5878c91fdd9bde82b37d53
Author: Antonio Terceiro <terceiro at debian.org>
Date: Fri Jun 12 22:55:05 2015 -0300
Imported Upstream version 2.1.0
---
README.md | 6 +-
lib/mixlib/shellout.rb | 104 +++++++++++++------
lib/mixlib/shellout/unix.rb | 176 ++++++++++++++++++++++----------
lib/mixlib/shellout/version.rb | 2 +-
lib/mixlib/shellout/windows.rb | 17 ++-
lib/mixlib/shellout/windows/core_ext.rb | 41 +++++---
metadata.yml | 32 +++---
7 files changed, 250 insertions(+), 128 deletions(-)
diff --git a/README.md b/README.md
index 1f0e46f..2cfe5b0 100644
--- a/README.md
+++ b/README.md
@@ -37,9 +37,9 @@ Invoke crontab to edit user cron:
crontab.run_command
## Windows Impersonation Example
-Invoke crontab to edit user cron:
+Invoke "whoami.exe" to demonstrate running a command as another user:
- whomai = Mixlib::ShellOut.new("whoami.exe", :user => "username", :domain => "DOMAIN", :password => "password")
+ whoami = Mixlib::ShellOut.new("whoami.exe", :user => "username", :domain => "DOMAIN", :password => "password")
whoami.run_command
## Platform Support
@@ -51,4 +51,4 @@ Apache 2 Licensed. See LICENSE for full details.
## See Also
* `Process.spawn` in Ruby 1.9
-* [https://github.com/rtomayko/posix-spawn](posix-spawn)
+* [https://github.com/rtomayko/posix-spawn](https://github.com/rtomayko/posix-spawn)
diff --git a/lib/mixlib/shellout.rb b/lib/mixlib/shellout.rb
index 66faa40..c77fdcf 100644
--- a/lib/mixlib/shellout.rb
+++ b/lib/mixlib/shellout.rb
@@ -27,7 +27,6 @@ module Mixlib
READ_WAIT_TIME = 0.01
READ_SIZE = 4096
DEFAULT_READ_TIMEOUT = 600
- DEFAULT_ENVIRONMENT = {'LC_ALL' => 'C'}
if RUBY_PLATFORM =~ /mswin|mingw32|windows/
require 'mixlib/shellout/windows'
@@ -41,22 +40,30 @@ module Mixlib
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! uses this list to determine if
- # the command was successful. Normally set via options to new
+ # 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_stream is set, stdout of the subprocess will be copied to it as
- # the subprocess is running. For example, if live_stream is set to STDOUT,
- # the command's output will be echoed to STDOUT.
- attr_accessor :live_stream
+ # 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.
@@ -81,7 +88,7 @@ module Mixlib
# Environment variables that will be set for the subcommand. Refer to the
# documentation of new to understand how ShellOut interprets this.
- attr_reader :environment
+ attr_accessor :environment
# The maximum time this command is allowed to run. Usually set via options
# to new
@@ -122,16 +129,25 @@ module Mixlib
# 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. By default, the environment will *always* be set to 'LC_ALL' => 'C'
- # to prevent issues with multibyte characters in Ruby 1.8. To avoid this,
- # use :environment => nil for *no* extra environment settings, or
- # :environment => {'LC_ALL'=>nil, ...} to set other environment settings
- # without changing the locale.
+ # 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'")
@@ -146,15 +162,16 @@ module Mixlib
# cmd = Mixlib::ShellOut.new("apachectl", "start", :user => 'www', :env => nil, :cwd => '/tmp')
# cmd.run_command # etc.
def initialize(*command_args)
- @stdout, @stderr = '', ''
- @live_stream = nil
+ @stdout, @stderr, @process_status = '', '', ''
+ @live_stdout = @live_stderr = nil
@input = nil
@log_level = :debug
@log_tag = nil
- @environment = DEFAULT_ENVIRONMENT
+ @environment = {}
@cwd = nil
@valid_exit_codes = [0]
@terminate_reason = nil
+ @timeout = nil
if command_args.last.is_a?(Hash)
parse_options(command_args.pop)
@@ -163,6 +180,18 @@ module Mixlib
@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)
@@ -171,6 +200,7 @@ module Mixlib
# 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
@@ -178,9 +208,11 @@ module Mixlib
# 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 nil unless group
- group.kind_of?(Integer) ? group : Etc.getgrnam(group.to_s).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
@@ -227,17 +259,21 @@ module Mixlib
super
end
- # Checks the +exitstatus+ against the set of +valid_exit_codes+. If
- # +exitstatus+ is not in the list of +valid_exit_codes+, calls +invalid!+,
- # which raises an Exception.
+ # 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!
- unless Array(valid_exit_codes).include?(exitstatus)
- invalid!("Expected process to exit with #{valid_exit_codes.inspect}, but received '#{exitstatus}'")
- end
+ invalid!("Expected process to exit with #{valid_exit_codes.inspect}, but received '#{exitstatus}'") if error?
end
# Raises a ShellCommandFailed exception, appending the
@@ -282,7 +318,11 @@ module Mixlib
when 'returns'
self.valid_exit_codes = Array(setting)
when 'live_stream'
- self.live_stream = setting
+ 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'
@@ -292,14 +332,9 @@ module Mixlib
when 'log_tag'
self.log_tag = setting
when 'environment', 'env'
- # Set the LC_ALL from the parent process if the user wanted
- # to use the default.
- if setting && setting.has_key?("LC_ALL") && setting['LC_ALL'].nil?
- setting['LC_ALL'] = ENV['LC_ALL']
- end
- # passing :environment => nil means don't set any new ENV vars
- @environment = setting.nil? ? {} : @environment.dup.merge!(setting)
-
+ self.environment = setting || {}
+ when 'login'
+ self.login = setting
else
raise InvalidCommandOption, "option '#{option.inspect}' is not a valid option for #{self.class.name}"
end
@@ -309,6 +344,9 @@ module Mixlib
end
def validate_options(opts)
+ if login && !user
+ raise InvalidCommandOption, "cannot set login without specifying a user"
+ end
super
end
end
diff --git a/lib/mixlib/shellout/unix.rb b/lib/mixlib/shellout/unix.rb
index 15ead60..dd22cbe 100644
--- a/lib/mixlib/shellout/unix.rb
+++ b/lib/mixlib/shellout/unix.rb
@@ -20,22 +20,75 @@ 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
+ # 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)
+ # 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
@@ -43,15 +96,16 @@ module Mixlib
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. Some OS Vendors are not interested
- # in updating their ruby packages (Apple, *cough*) and we *have to*
- # make it work. So I give you this epic hack:
- GC.disable
+ # 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
@@ -96,8 +150,8 @@ module Mixlib
def set_user
if user
- Process.euid = uid
Process.uid = uid
+ Process.euid = uid
end
end
@@ -108,8 +162,15 @@ module Mixlib
end
end
+ def set_secondarygroups
+ if sgids
+ Process.groups = sgids
+ end
+ end
+
def set_environment
- environment.each do |env_var,value|
+ # user-set variables should override the login ones
+ process_environment.each do |env_var,value|
ENV[env_var] = value
end
end
@@ -122,6 +183,12 @@ module Mixlib
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
+ - at 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)
@@ -176,31 +243,6 @@ module Mixlib
STDIN.sync = true if input
end
- # When a new process is started with chef, it shares the file
- # descriptors of the parent. We clean the file descriptors
- # coming from the parent to prevent unintended locking if parent
- # is killed.
- # NOTE: After some discussions we've decided to iterate on file
- # descriptors upto 256. We believe this is a reasonable upper
- # limit in a chef environment. If we have issues in the future this
- # number could be made to be configurable or updated based on
- # the ulimit based on platform.
- def clean_parent_file_descriptors
- # Don't clean $stdin, $stdout, $stderr, process_status_pipe.
- 3.upto(256) do |n|
- # We are checking the fd for error pipe before attempting to
- # create a file because error pipe will auto close when we
- # try to create a file since it's set to CLOEXEC.
- if n != @process_status_pipe.last.to_i
- begin
- fd = File.for_fd(n)
- fd.close if fd
- rescue
- end
- end
- end
- end
-
def configure_parent_process_file_descriptors
# Close the sides of the pipes we don't care about
stdin_pipe.first.close
@@ -219,7 +261,7 @@ module Mixlib
# 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]
+ @open_pipes ||= [child_stdout, child_stderr, child_process_status]
end
# Keep this unbuffered for now
@@ -231,11 +273,10 @@ module Mixlib
def attempt_buffer_read
ready = IO.select(open_pipes, nil, nil, READ_WAIT_TIME)
- if ready && ready.first.include?(child_stdout)
- read_stdout_to_buffer
- end
- if ready && ready.first.include?(child_stderr)
- read_stderr_to_buffer
+ 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
@@ -243,7 +284,7 @@ module Mixlib
def read_stdout_to_buffer
while chunk = child_stdout.read_nonblock(READ_SIZE)
@stdout << chunk
- @live_stream << chunk if @live_stream
+ @live_stdout << chunk if @live_stdout
end
rescue Errno::EAGAIN
rescue EOFError
@@ -253,20 +294,40 @@ module Mixlib
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
- configure_subprocess_file_descriptors
+ # 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
- clean_parent_file_descriptors
+ configure_subprocess_file_descriptors
+ set_secondarygroups
set_group
set_user
set_environment
@@ -274,7 +335,7 @@ module Mixlib
set_cwd
begin
- command.kind_of?(Array) ? exec(*command) : exec(command)
+ command.kind_of?(Array) ? exec(*command, :close_others=>true) : exec(command, :close_others=>true)
raise 'forty-two' # Should never get here
rescue Exception => e
@@ -291,25 +352,26 @@ module Mixlib
# assume everything went well.
def propagate_pre_exec_failure
begin
- e = Marshal.load child_process_status
+ attempt_buffer_read until child_process_status.eof?
+ e = Marshal.load(@process_status)
raise(Exception === e ? e : "unknown failure: #{e.inspect}")
- rescue EOFError # If we get an EOF error, then the exec was successful
+ 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 execeded allowed execution time, killed by TERM signal."
- logger.error("Command execeded allowed execution time, sending TERM") if logger
- Process.kill(:TERM, @child_pid)
+ @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
- return if attempt_reap
- @terminate_reason = "Command execeded allowed execution time, did not respond to TERM. Killed by KILL signal."
- logger.error("Command did not exit from TERM, sending KILL") if logger
- Process.kill(:KILL, @child_pid)
+ 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
@@ -323,12 +385,22 @@ module Mixlib
@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
diff --git a/lib/mixlib/shellout/version.rb b/lib/mixlib/shellout/version.rb
index aef17a3..1092f06 100644
--- a/lib/mixlib/shellout/version.rb
+++ b/lib/mixlib/shellout/version.rb
@@ -1,5 +1,5 @@
module Mixlib
class ShellOut
- VERSION = "1.3.0"
+ VERSION = "2.1.0"
end
end
diff --git a/lib/mixlib/shellout/windows.rb b/lib/mixlib/shellout/windows.rb
index ea809b5..1612b09 100644
--- a/lib/mixlib/shellout/windows.rb
+++ b/lib/mixlib/shellout/windows.rb
@@ -111,6 +111,12 @@ module Mixlib
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
@@ -122,8 +128,8 @@ module Mixlib
end
ensure
- CloseHandle(process.thread_handle)
- CloseHandle(process.process_handle)
+ CloseHandle(process.thread_handle) if process.thread_handle
+ CloseHandle(process.process_handle) if process.process_handle
end
ensure
@@ -156,7 +162,7 @@ module Mixlib
begin
next_chunk = stdout_read.readpartial(READ_SIZE)
@stdout << next_chunk
- @live_stream << next_chunk if @live_stream
+ @live_stdout << next_chunk if @live_stdout
rescue EOFError
stdout_read.close
open_streams.delete(stdout_read)
@@ -165,7 +171,9 @@ module Mixlib
if ready.first.include?(stderr_read)
begin
- @stderr << stderr_read.readpartial(READ_SIZE)
+ 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)
@@ -198,7 +206,6 @@ module Mixlib
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
diff --git a/lib/mixlib/shellout/windows/core_ext.rb b/lib/mixlib/shellout/windows/core_ext.rb
index e692c7d..73b848f 100644
--- a/lib/mixlib/shellout/windows/core_ext.rb
+++ b/lib/mixlib/shellout/windows/core_ext.rb
@@ -288,19 +288,23 @@ module Process
token = token.read_ulong
- 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
- )
+ 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)
@@ -346,9 +350,14 @@ module Process
# Automatically close the process and thread handles in the
# PROCESS_INFORMATION struct unless explicitly told not to.
if hash['close_handles']
- CloseHandle(procinfo[:hProcess])
- CloseHandle(procinfo[:hThread])
- CloseHandle(token)
+ 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(
diff --git a/metadata.yml b/metadata.yml
index be43197..fe0b892 100644
--- a/metadata.yml
+++ b/metadata.yml
@@ -1,32 +1,29 @@
--- !ruby/object:Gem::Specification
name: mixlib-shellout
version: !ruby/object:Gem::Version
- version: 1.3.0
- prerelease:
+ version: 2.1.0
platform: ruby
authors:
- Opscode
autorequire:
bindir: bin
cert_chain: []
-date: 2013-12-03 00:00:00.000000000 Z
+date: 2015-05-18 00:00:00.000000000 Z
dependencies:
- !ruby/object:Gem::Dependency
name: rspec
requirement: !ruby/object:Gem::Requirement
- none: false
requirements:
- - - ~>
+ - - "~>"
- !ruby/object:Gem::Version
- version: '2.0'
+ version: '3.0'
type: :development
prerelease: false
version_requirements: !ruby/object:Gem::Requirement
- none: false
requirements:
- - - ~>
+ - - "~>"
- !ruby/object:Gem::Version
- version: '2.0'
+ version: '3.0'
description: Run external commands on Unix or Windows
email: info at opscode.com
executables: []
@@ -37,34 +34,33 @@ extra_rdoc_files:
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/core_ext.rb
- lib/mixlib/shellout/windows.rb
-- lib/mixlib/shellout.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
- none: false
requirements:
- - - ! '>='
+ - - ">="
- !ruby/object:Gem::Version
- version: '0'
+ version: 1.9.3
required_rubygems_version: !ruby/object:Gem::Requirement
- none: false
requirements:
- - - ! '>='
+ - - ">="
- !ruby/object:Gem::Version
version: '0'
requirements: []
rubyforge_project:
-rubygems_version: 1.8.23
+rubygems_version: 2.4.6
signing_key:
-specification_version: 3
+specification_version: 4
summary: Run external commands on Unix or Windows
test_files: []
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-ruby-extras/ruby-mixlib-shellout.git
More information about the Pkg-ruby-extras-commits
mailing list