diff --git a/lib/puppet/util/windows/api_types.rb b/lib/puppet/util/windows/api_types.rb index afa986e0c..b1ac804e1 100644 --- a/lib/puppet/util/windows/api_types.rb +++ b/lib/puppet/util/windows/api_types.rb @@ -1,103 +1,102 @@ require 'ffi' require 'puppet/util/windows/string' module Puppet::Util::Windows::APITypes module ::FFI WIN32_FALSE = 0 end module ::FFI::Library # Wrapper method for attach_function + private def attach_function_private(*args) attach_function(*args) private args[0] end end class ::FFI::Pointer NULL_HANDLE = 0 - end - class ::FFI::MemoryPointer def self.from_string_to_wide_string(str) str = Puppet::Util::Windows::String.wide_string(str) ptr = FFI::MemoryPointer.new(:byte, str.bytesize) # uchar here is synonymous with byte ptr.put_array_of_uchar(0, str.bytes.to_a) ptr end def read_win32_bool # BOOL is always a 32-bit integer in Win32 # some Win32 APIs return 1 for true, while others are non-0 read_int32 != FFI::WIN32_FALSE end alias_method :read_dword, :read_uint32 def read_handle type_size == 4 ? read_uint32 : read_uint64 end def read_wide_string(char_length) # char_length is number of wide chars (typically excluding NULLs), *not* bytes str = get_bytes(0, char_length * 2).force_encoding('UTF-16LE') str.encode(Encoding.default_external) end alias_method :write_dword, :write_uint32 end # FFI Types # https://github.com/ffi/ffi/wiki/Types # Windows - Common Data Types # http://msdn.microsoft.com/en-us/library/cc230309.aspx # Windows Data Types # http://msdn.microsoft.com/en-us/library/windows/desktop/aa383751(v=vs.85).aspx FFI.typedef :uint16, :word FFI.typedef :uint32, :dword # uintptr_t is defined in an FFI conf as platform specific, either # ulong_long on x64 or just ulong on x86 FFI.typedef :uintptr_t, :handle # buffer_inout is similar to pointer (platform specific), but optimized for buffers FFI.typedef :buffer_inout, :lpwstr # buffer_in is similar to pointer (platform specific), but optimized for CONST read only buffers FFI.typedef :buffer_in, :lpcwstr # string is also similar to pointer, but should be used for const char * # NOTE that this is not wide, useful only for A suffixed functions FFI.typedef :string, :lpcstr # pointer in FFI is platform specific # NOTE: for API calls with reserved lpvoid parameters, pass a FFI::Pointer::NULL + FFI.typedef :pointer, :lpcvoid FFI.typedef :pointer, :lpvoid FFI.typedef :pointer, :lpword FFI.typedef :pointer, :lpdword FFI.typedef :pointer, :pdword FFI.typedef :pointer, :phandle FFI.typedef :pointer, :ulong_ptr FFI.typedef :pointer, :pbool # any time LONG / ULONG is in a win32 API definition DO NOT USE platform specific width # which is what FFI uses by default # instead create new aliases for these very special cases # NOTE: not a good idea to redefine FFI :ulong since other typedefs may rely on it FFI.typedef :uint32, :win32_ulong FFI.typedef :int32, :win32_long # FFI bool can be only 1 byte at times, # Win32 BOOL is a signed int, and is always 4 bytes, even on x64 # http://blogs.msdn.com/b/oldnewthing/archive/2011/03/28/10146459.aspx FFI.typedef :int32, :win32_bool # NOTE: FFI already defines (u)short as a 16-bit (un)signed like this: # FFI.typedef :uint16, :ushort # FFI.typedef :int16, :short # 8 bits per byte FFI.typedef :uchar, :byte end diff --git a/lib/puppet/util/windows/error.rb b/lib/puppet/util/windows/error.rb index c6088fa7a..7d14307e4 100644 --- a/lib/puppet/util/windows/error.rb +++ b/lib/puppet/util/windows/error.rb @@ -1,16 +1,98 @@ require 'puppet/util/windows' # represents an error resulting from a Win32 error code class Puppet::Util::Windows::Error < Puppet::Error - require 'windows/error' - include ::Windows::Error + require 'ffi' + extend FFI::Library attr_reader :code - def initialize(message, code = GetLastError.call, original = nil) - super(message + ": #{get_last_error(code)}", original) + def initialize(message, code = @@GetLastError.call(), original = nil) + super(message + ": #{self.class.format_error_code(code)}", original) @code = code end -end + # Helper method that wraps FormatMessage that returns a human readable string. + def self.format_error_code(code) + # specifying 0 will look for LANGID in the following order + # 1.Language neutral + # 2.Thread LANGID, based on the thread's locale value + # 3.User default LANGID, based on the user's default locale value + # 4.System default LANGID, based on the system default locale value + # 5.US English + dwLanguageId = 0 + flags = FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_ARGUMENT_ARRAY | + FORMAT_MESSAGE_IGNORE_INSERTS | + FORMAT_MESSAGE_MAX_WIDTH_MASK + # this pointer actually points to a :lpwstr (pointer) since we're letting Windows allocate for us + buffer_ptr = FFI::MemoryPointer.new(:pointer, 1) + + begin + length = FormatMessageW(flags, FFI::Pointer::NULL, code, dwLanguageId, + buffer_ptr, 0, FFI::Pointer::NULL) + + if length == FFI::WIN32_FALSE + # can't raise same error type here or potentially recurse infinitely + raise Puppet::Error.new("FormatMessageW could not format code #{code}") + end + + # returns an FFI::Pointer with autorelease set to false, which is what we want + wide_string_ptr = buffer_ptr.read_pointer + + if wide_string_ptr.null? + raise Puppet::Error.new("FormatMessageW failed to allocate buffer for code #{code}") + end + + return wide_string_ptr.read_wide_string(length) + ensure + if ! wide_string_ptr.nil? && ! wide_string_ptr.null? + if LocalFree(wide_string_ptr.address) != FFI::Pointer::NULL_HANDLE + Puppet.debug "LocalFree memory leak" + end + end + end + end + + FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000100 + FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200 + FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000 + FORMAT_MESSAGE_ARGUMENT_ARRAY = 0x00002000 + FORMAT_MESSAGE_MAX_WIDTH_MASK = 0x000000FF + + ffi_convention :stdcall + + # NOTE: It seems like FFI.errno is already implemented as GetLastError... or is it? + # http://msdn.microsoft.com/en-us/library/windows/desktop/ms679360(v=vs.85).aspx + # DWORD WINAPI GetLastError(void); + # HACK: unfortunately using FFI.errno or attach_function to hook GetLastError in + # FFI like the following will not work. Something internal to FFI appears to + # be stomping out the value of GetLastError when calling via FFI. + # attach_function_private :GetLastError, [], :dword + require 'Win32API' + @@GetLastError = Win32API.new('kernel32', 'GetLastError', [], 'L') + + # http://msdn.microsoft.com/en-us/library/windows/desktop/ms679351(v=vs.85).aspx + # DWORD WINAPI FormatMessage( + # _In_ DWORD dwFlags, + # _In_opt_ LPCVOID lpSource, + # _In_ DWORD dwMessageId, + # _In_ DWORD dwLanguageId, + # _Out_ LPTSTR lpBuffer, + # _In_ DWORD nSize, + # _In_opt_ va_list *Arguments + # ); + # NOTE: since we're not preallocating the buffer, use a :pointer for lpBuffer + ffi_lib :kernel32 + attach_function_private :FormatMessageW, + [:dword, :lpcvoid, :dword, :dword, :pointer, :dword, :pointer], :dword + + # http://msdn.microsoft.com/en-us/library/windows/desktop/aa366730(v=vs.85).aspx + # HLOCAL WINAPI LocalFree( + # _In_ HLOCAL hMem + # ); + ffi_lib :kernel32 + attach_function_private :LocalFree, [:handle], :handle +end