diff options
author | Matt Wrock <matt@mattwrock.com> | 2015-10-19 16:30:59 -0700 |
---|---|---|
committer | Matt Wrock <matt@mattwrock.com> | 2015-10-19 16:30:59 -0700 |
commit | 46d565c170eea893c95b5cb7d64378557938d309 (patch) | |
tree | caf6f53d226a272edc17f238ead5157eb6fe1370 /lib | |
parent | d9c4da3170cb6561d2148674ed9cce10baf76600 (diff) | |
parent | 24e7ea98594f5bf72ca3d9741c2bb600f53d8b84 (diff) | |
download | mixlib-shellout-46d565c170eea893c95b5cb7d64378557938d309.tar.gz |
merging latest master
Diffstat (limited to 'lib')
-rw-r--r-- | lib/mixlib/shellout/version.rb | 2 | ||||
-rw-r--r-- | lib/mixlib/shellout/windows/core_ext.rb | 493 |
2 files changed, 244 insertions, 251 deletions
diff --git a/lib/mixlib/shellout/version.rb b/lib/mixlib/shellout/version.rb index 2301857..4e968e8 100644 --- a/lib/mixlib/shellout/version.rb +++ b/lib/mixlib/shellout/version.rb @@ -1,5 +1,5 @@ module Mixlib class ShellOut - VERSION = "2.0.1" + VERSION = "2.2.2" end end diff --git a/lib/mixlib/shellout/windows/core_ext.rb b/lib/mixlib/shellout/windows/core_ext.rb index 6dfed2f..d889778 100644 --- a/lib/mixlib/shellout/windows/core_ext.rb +++ b/lib/mixlib/shellout/windows/core_ext.rb @@ -21,6 +21,8 @@ require 'win32/process' # Add new constants for Logon module Process::Constants + private + LOGON32_LOGON_INTERACTIVE = 0x00000002 LOGON32_PROVIDER_DEFAULT = 0x00000000 UOI_NAME = 0x00000002 @@ -34,16 +36,6 @@ 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, @@ -64,315 +56,316 @@ end # Override Process.create to check for running in the Service window station and doing # a full logon with LogonUser, instead of a CreateProcessWithLogon +# Cloned from https://github.com/djberg96/win32-process/blob/ffi/lib/win32/process.rb +# as of 2015-10-15 from commit cc066e5df25048f9806a610f54bf5f7f253e86f7 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}'" + # Explicitly reopen singleton class so that class/constant declarations from + # extensions are visible in Modules.nesting. + class << self + def create(args) + unless args.kind_of?(Hash) + raise TypeError, 'hash keyword arguments expected' end - hash[key] = val - } - si_hash = {} + 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 + } - # If the startup_info key is present, validate its subkeys - if hash['startup_info'] - hash['startup_info'].each{ |key, val| + # Validate the keys, and convert symbols and case to lowercase strings. + args.each{ |key, val| key = key.to_s.downcase - unless valid_si_keys.include?(key) - raise ArgumentError, "invalid startup_info key '#{key}'" + unless valid_keys.include?(key) + raise ArgumentError, "invalid key '#{key}'" end - si_hash[key] = val + 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 + si_hash = {} - # The env string should be passed as a string of ';' separated paths. - if hash['environment'] - env = hash['environment'] + # 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 - unless env.respond_to?(:join) - env = hash['environment'].split(File::PATH_SEPARATOR) + # 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 = env.map{ |e| e + 0.chr }.join('') + 0.chr - env.to_wide_string! if hash['with_logon'] - end + env = nil - # Process SECURITY_ATTRIBUTE structure - process_security = nil + # The env string should be passed as a string of ';' separated paths. + if hash['environment'] + env = hash['environment'] - if hash['process_inherit'] - process_security = SECURITY_ATTRIBUTES.new - process_security[:nLength] = 12 - process_security[:bInheritHandle] = true - end + unless env.respond_to?(:join) + env = hash['environment'].split(File::PATH_SEPARATOR) + end - # Thread SECURITY_ATTRIBUTE structure - thread_security = nil + env = env.map{ |e| e + 0.chr }.join('') + 0.chr + env.to_wide_string! if hash['with_logon'] + end - if hash['thread_inherit'] - thread_security = SECURITY_ATTRIBUTES.new - thread_security[:nLength] = 12 - thread_security[:bInheritHandle] = true - end + # Process SECURITY_ATTRIBUTE structure + process_security = nil - # 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 hash['process_inherit'] + process_security = SECURITY_ATTRIBUTES.new + process_security[:nLength] = 12 + process_security[:bInheritHandle] = 1 + end - if handle == INVALID_HANDLE_VALUE - ptr = FFI::MemoryPointer.new(:int) + # Thread SECURITY_ATTRIBUTE structure + thread_security = nil - if windows_version >= 6 && get_errno(ptr) == 0 - errno = ptr.read_int + if hash['thread_inherit'] + thread_security = SECURITY_ATTRIBUTES.new + thread_security[:nLength] = 12 + thread_security[:bInheritHandle] = 1 + 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 - errno = FFI.errno + handle = get_osfhandle(si_hash[io]) end - raise SystemCallError.new("get_osfhandle", errno) - end + if handle == INVALID_HANDLE_VALUE + ptr = FFI::MemoryPointer.new(:int) - # 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 - ) + if windows_version >= 6 && get_errno(ptr) == 0 + errno = ptr.read_int + else + errno = FFI.errno + end - raise SystemCallError.new("SetHandleInformation", FFI.errno) unless bool + raise SystemCallError.new("get_osfhandle", errno) + end - 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 + # 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 + ) - app = nil - cmd = nil + raise SystemCallError.new("SetHandleInformation", FFI.errno) unless bool - # 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 + si_hash[io] = handle + si_hash['startf_flags'] ||= 0 + si_hash['startf_flags'] |= STARTF_USESTDHANDLES + hash['inherit'] = true + end + } - if hash['cwd'] - cwd = hash['cwd'].to_wide_string - 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 - inherit = hash['inherit'] || false + app = nil + cmd = nil - if hash['with_logon'] - logon = hash['with_logon'].to_wide_string + # Convert strings to wide character strings if present + if hash['app_name'] + app = hash['app_name'].to_wide_string + end - if hash['password'] - passwd = hash['password'].to_wide_string - else - raise ArgumentError, 'password must be specified if with_logon is used' + if hash['command_line'] + cmd = hash['command_line'].to_wide_string end - if hash['domain'] - domain = hash['domain'].to_wide_string + if hash['cwd'] + cwd = hash['cwd'].to_wide_string end - hash['creation_flags'] |= CREATE_UNICODE_ENVIRONMENT + inherit = hash['inherit'] ? 1 : 0 - winsta_name = FFI::MemoryPointer.new(:char, 256) - return_size = FFI::MemoryPointer.new(:ulong) + if hash['with_logon'] + logon = hash['with_logon'].to_wide_string - 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 - ) + if hash['password'] + passwd = hash['password'].to_wide_string + else + raise ArgumentError, 'password must be specified if with_logon is used' + end - unless bool - raise SystemCallError.new("GetUserObjectInformationA", FFI.errno) - end + if hash['domain'] + domain = hash['domain'].to_wide_string + end + + hash['creation_flags'] |= CREATE_UNICODE_ENVIRONMENT - 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 + 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("LogonUserW", FFI.errno) + raise SystemCallError.new("GetUserObjectInformationA", FFI.errno) end - token = token.read_ulong + 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 - begin - bool = CreateProcessAsUserW( - token, # User token handle + 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 - process_security, # Process attributes - thread_security, # Thread attributes - inherit, # Inherit handles - hash['creation_flags'], # Creation Flags + 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) + unless bool + raise SystemCallError.new("CreateProcessWithLogonW", FFI.errno) + end end else - bool = CreateProcessWithLogonW( - logon, # User - domain, # Domain - passwd, # Password - LOGON_WITH_PROFILE, # Logon flags + 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 ) - end - unless bool - raise SystemCallError.new("CreateProcessWithLogonW", FFI.errno) + unless bool + raise SystemCallError.new("CreateProcessW", FFI.errno) + end 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) + # 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]) + # Clear these fields 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] = 0 + procinfo[:hThread] = 0 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 + ProcessInfo.new( + procinfo[:hProcess], + procinfo[:hThread], + procinfo[:dwProcessId], + procinfo[:dwThreadId] + ) end - - ProcessInfo.new( - procinfo[:hProcess], - procinfo[:hThread], - procinfo[:dwProcessId], - procinfo[:dwThreadId] - ) end - - module_function :create end |