diff --git a/lib/puppet/util/windows/file.rb b/lib/puppet/util/windows/file.rb index 141873071..3dbc12ac1 100644 --- a/lib/puppet/util/windows/file.rb +++ b/lib/puppet/util/windows/file.rb @@ -1,240 +1,253 @@ require 'puppet/util/windows' module Puppet::Util::Windows::File require 'ffi' require 'windows/api' - require 'windows/wide_string' - ReplaceFileWithoutBackupW = Windows::API.new('ReplaceFileW', 'PPVLVV', 'B') + def wide_string(str) + wstr = str.encode('UTF-16LE') + + ptr = FFI::MemoryPointer.new(:uint16, wstr.length + 1) + ptr.put_string(0, wstr) + ptr.put_uint8(ptr.size - 1, 0) + ptr.put_uint8(ptr.size - 2, 0) + + ffi_str = ptr.get_bytes(0, ptr.size) + ffi_str.force_encoding('UTF-16LE') + ffi_str + end + module_function :wide_string + + ReplaceFileWithoutBackupW = Windows::API.new('ReplaceFileW', 'PPPLPP', 'B') def replace_file(target, source) - result = ReplaceFileWithoutBackupW.call(WideString.new(target.to_s), - WideString.new(source.to_s), + result = ReplaceFileWithoutBackupW.call(wide_string(target.to_s), + wide_string(source.to_s), 0, 0x1, 0, 0) return true unless result == 0 raise Puppet::Util::Windows::Error.new("ReplaceFile(#{target}, #{source})") end module_function :replace_file MoveFileEx = Windows::API.new('MoveFileExW', 'PPL', 'B') def move_file_ex(source, target, flags = 0) - result = MoveFileEx.call(WideString.new(source.to_s), - WideString.new(target.to_s), + result = MoveFileEx.call(wide_string(source.to_s), + wide_string(target.to_s), flags) return true unless result == 0 raise Puppet::Util::Windows::Error. new("MoveFileEx(#{source}, #{target}, #{flags.to_s(8)})") end module_function :move_file_ex module API extend FFI::Library ffi_lib 'kernel32' ffi_convention :stdcall # BOOLEAN WINAPI CreateSymbolicLink( # _In_ LPTSTR lpSymlinkFileName, - symbolic link to be created # _In_ LPTSTR lpTargetFileName, - name of target for symbolic link # _In_ DWORD dwFlags - 0x0 target is a file, 0x1 target is a directory # ); # rescue on Windows < 6.0 so that code doesn't explode begin attach_function :create_symbolic_link, :CreateSymbolicLinkW, [:buffer_in, :buffer_in, :uint], :bool rescue LoadError end # DWORD WINAPI GetFileAttributes( # _In_ LPCTSTR lpFileName # ); attach_function :get_file_attributes, :GetFileAttributesW, [:buffer_in], :uint # HANDLE WINAPI CreateFile( # _In_ LPCTSTR lpFileName, # _In_ DWORD dwDesiredAccess, # _In_ DWORD dwShareMode, # _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, # _In_ DWORD dwCreationDisposition, # _In_ DWORD dwFlagsAndAttributes, # _In_opt_ HANDLE hTemplateFile # ); attach_function :create_file, :CreateFileW, [:buffer_in, :uint, :uint, :pointer, :uint, :uint, :uint], :uint # http://msdn.microsoft.com/en-us/library/windows/desktop/aa363216(v=vs.85).aspx # BOOL WINAPI DeviceIoControl( # _In_ HANDLE hDevice, # _In_ DWORD dwIoControlCode, # _In_opt_ LPVOID lpInBuffer, # _In_ DWORD nInBufferSize, # _Out_opt_ LPVOID lpOutBuffer, # _In_ DWORD nOutBufferSize, # _Out_opt_ LPDWORD lpBytesReturned, # _Inout_opt_ LPOVERLAPPED lpOverlapped # ); attach_function :device_io_control, :DeviceIoControl, [:uint, :uint, :pointer, :uint, :pointer, :uint, :pointer, :pointer], :bool MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 16384 # REPARSE_DATA_BUFFER # http://msdn.microsoft.com/en-us/library/cc232006.aspx # http://msdn.microsoft.com/en-us/library/windows/hardware/ff552012(v=vs.85).aspx # struct is always MAXIMUM_REPARSE_DATA_BUFFER_SIZE bytes class ReparseDataBuffer < FFI::Struct layout :reparse_tag, :uint, :reparse_data_length, :ushort, :reserved, :ushort, :substitute_name_offset, :ushort, :substitute_name_length, :ushort, :print_name_offset, :ushort, :print_name_length, :ushort, :flags, :uint, # max less above fields dword / uint 4 bytes, ushort 2 bytes :path_buffer, [:uchar, MAXIMUM_REPARSE_DATA_BUFFER_SIZE - 20] end # BOOL WINAPI CloseHandle( # _In_ HANDLE hObject # ); attach_function :close_handle, :CloseHandle, [:uint], :bool end def symlink(target, symlink) flags = File.directory?(target) ? 0x1 : 0x0 - result = API.create_symbolic_link(WideString.new(symlink.to_s), - WideString.new(target.to_s), flags) + result = API.create_symbolic_link(wide_string(symlink.to_s), + wide_string(target.to_s), flags) return true if result raise Puppet::Util::Windows::Error.new( "CreateSymbolicLink(#{symlink}, #{target}, #{flags.to_s(8)})") end module_function :symlink INVALID_FILE_ATTRIBUTES = 0xFFFFFFFF #define INVALID_FILE_ATTRIBUTES (DWORD (-1)) def self.get_file_attributes(file_name) - result = API.get_file_attributes(WideString.new(file_name.to_s)) + result = API.get_file_attributes(wide_string(file_name.to_s)) return result unless result == INVALID_FILE_ATTRIBUTES raise Puppet::Util::Windows::Error.new("GetFileAttributes(#{file_name})") end INVALID_HANDLE_VALUE = -1 #define INVALID_HANDLE_VALUE ((HANDLE)(LONG_PTR)-1) def self.create_file(file_name, desired_access, share_mode, security_attributes, creation_disposition, flags_and_attributes, template_file_handle) - result = API.create_file(WideString.new(file_name.to_s), + result = API.create_file(wide_string(file_name.to_s), desired_access, share_mode, security_attributes, creation_disposition, flags_and_attributes, template_file_handle) return result unless result == INVALID_HANDLE_VALUE raise Puppet::Util::Windows::Error.new( "CreateFile(#{file_name}, #{desired_access.to_s(8)}, #{share_mode.to_s(8)}, " + "#{security_attributes}, #{creation_disposition.to_s(8)}, " + "#{flags_and_attributes.to_s(8)}, #{template_file_handle})") end def self.device_io_control(handle, io_control_code, in_buffer = nil, out_buffer = nil) if out_buffer.nil? raise Puppet::Util::Windows::Error.new("out_buffer is required") end result = API.device_io_control( handle, io_control_code, in_buffer, in_buffer.nil? ? 0 : in_buffer.size, out_buffer, out_buffer.size, FFI::MemoryPointer.new(:uint, 1), nil ) return out_buffer if result raise Puppet::Util::Windows::Error.new( "DeviceIoControl(#{handle}, #{io_control_code}, #{in_buffer}, #{in_buffer.size}, " + "#{out_buffer}, #{out_buffer.size}") end FILE_ATTRIBUTE_REPARSE_POINT = 0x400 def symlink?(file_name) begin attributes = get_file_attributes(file_name) (attributes & FILE_ATTRIBUTE_REPARSE_POINT) == FILE_ATTRIBUTE_REPARSE_POINT rescue # raised INVALID_FILE_ATTRIBUTES is equivalent to file not found false end end module_function :symlink? GENERIC_READ = 0x80000000 FILE_SHARE_READ = 1 OPEN_EXISTING = 3 FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000 FILE_FLAG_BACKUP_SEMANTICS = 0x02000000 def self.open_symlink(link_name) begin yield handle = create_file( - WideString.new(link_name.to_s), + wide_string(link_name.to_s), GENERIC_READ, FILE_SHARE_READ, nil, # security_attributes OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, 0) # template_file ensure API.close_handle(handle) if handle end end def readlink(link_name) open_symlink(link_name) do |handle| resolve_symlink(handle) end end module_function :readlink def stat(file_name) file_name = file_name.to_s # accomodate PathName or String stat = File.stat(file_name) if symlink?(file_name) link_ftype = File.stat(readlink(file_name)).ftype # sigh, monkey patch instance method for instance, and close over link_ftype singleton_class = class << stat; self; end singleton_class.send(:define_method, :ftype) do link_ftype end end stat end module_function :stat def lstat(file_name) file_name = file_name.to_s # accomodate PathName or String # monkey'ing around! stat = File.lstat(file_name) if symlink?(file_name) def stat.ftype "link" end end stat end module_function :lstat private # http://msdn.microsoft.com/en-us/library/windows/desktop/aa364571(v=vs.85).aspx FSCTL_GET_REPARSE_POINT = 0x900a8 def self.resolve_symlink(handle) # must be multiple of 1024, min 10240 out_buffer = FFI::MemoryPointer.new(API::ReparseDataBuffer.size) device_io_control(handle, FSCTL_GET_REPARSE_POINT, nil, out_buffer) reparse_data = API::ReparseDataBuffer.new(out_buffer) offset = reparse_data[:print_name_offset] length = reparse_data[:print_name_length] result = reparse_data[:path_buffer].to_a[offset, length].pack('C*') result.force_encoding('UTF-16LE').encode(Encoding.default_external) end end