# # Author:: Seth Chisamore () # Author:: Mark Mzyk () # Copyright:: Copyright 2011-2016, Chef Software 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 "chef/win32/api" require "chef/win32/api/security" require "chef/win32/api/system" require "chef/win32/unicode" class Chef module ReservedNames::Win32 module API module File extend Chef::ReservedNames::Win32::API include Chef::ReservedNames::Win32::API::Security include Chef::ReservedNames::Win32::API::System ############################################### # Win32 API Constants ############################################### FILE_ATTRIBUTE_READONLY = 0x00000001 FILE_ATTRIBUTE_HIDDEN = 0x00000002 FILE_ATTRIBUTE_SYSTEM = 0x00000004 FILE_ATTRIBUTE_DIRECTORY = 0x00000010 FILE_ATTRIBUTE_ARCHIVE = 0x00000020 FILE_ATTRIBUTE_DEVICE = 0x00000040 FILE_ATTRIBUTE_NORMAL = 0x00000080 FILE_ATTRIBUTE_TEMPORARY = 0x00000100 FILE_ATTRIBUTE_SPARSE_FILE = 0x00000200 FILE_ATTRIBUTE_REPARSE_POINT = 0x00000400 FILE_ATTRIBUTE_COMPRESSED = 0x00000800 FILE_ATTRIBUTE_OFFLINE = 0x00001000 FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 0x00002000 FILE_ATTRIBUTE_ENCRYPTED = 0x00004000 FILE_ATTRIBUTE_VIRTUAL = 0x00010000 INVALID_FILE_ATTRIBUTES = 0xFFFFFFFF FILE_FLAG_WRITE_THROUGH = 0x80000000 FILE_FLAG_OVERLAPPED = 0x40000000 FILE_FLAG_NO_BUFFERING = 0x20000000 FILE_FLAG_RANDOM_ACCESS = 0x10000000 FILE_FLAG_SEQUENTIAL_SCAN = 0x08000000 FILE_FLAG_DELETE_ON_CLOSE = 0x04000000 FILE_FLAG_BACKUP_SEMANTICS = 0x02000000 FILE_FLAG_POSIX_SEMANTICS = 0x01000000 FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000 FILE_FLAG_OPEN_NO_RECALL = 0x00100000 FILE_FLAG_FIRST_PIPE_INSTANCE = 0x00080000 INVALID_HANDLE_VALUE = 0xFFFFFFFF MAX_PATH = 260 SYMBOLIC_LINK_FLAG_DIRECTORY = 0x1 FILE_NAME_NORMALIZED = 0x0 FILE_NAME_OPENED = 0x8 # TODO add the rest of these CONSTS FILE_SHARE_READ = 0x00000001 OPEN_EXISTING = 3 # DeviceIoControl control codes # ----------------------------- FILE_DEVICE_BEEP = 0x00000001 FILE_DEVICE_CD_ROM = 0x00000002 FILE_DEVICE_CD_ROM_FILE_SYSTEM = 0x00000003 FILE_DEVICE_CONTROLLER = 0x00000004 FILE_DEVICE_DATALINK = 0x00000005 FILE_DEVICE_DFS = 0x00000006 FILE_DEVICE_DISK = 0x00000007 FILE_DEVICE_DISK_FILE_SYSTEM = 0x00000008 FILE_DEVICE_FILE_SYSTEM = 0x00000009 FILE_DEVICE_INPORT_PORT = 0x0000000a FILE_DEVICE_KEYBOARD = 0x0000000b FILE_DEVICE_MAILSLOT = 0x0000000c FILE_DEVICE_MIDI_IN = 0x0000000d FILE_DEVICE_MIDI_OUT = 0x0000000e FILE_DEVICE_MOUSE = 0x0000000f FILE_DEVICE_MULTI_UNC_PROVIDER = 0x00000010 FILE_DEVICE_NAMED_PIPE = 0x00000011 FILE_DEVICE_NETWORK = 0x00000012 FILE_DEVICE_NETWORK_BROWSER = 0x00000013 FILE_DEVICE_NETWORK_FILE_SYSTEM = 0x00000014 FILE_DEVICE_NULL = 0x00000015 FILE_DEVICE_PARALLEL_PORT = 0x00000016 FILE_DEVICE_PHYSICAL_NETCARD = 0x00000017 FILE_DEVICE_PRINTER = 0x00000018 FILE_DEVICE_SCANNER = 0x00000019 FILE_DEVICE_SERIAL_MOUSE_PORT = 0x0000001a FILE_DEVICE_SERIAL_PORT = 0x0000001b FILE_DEVICE_SCREEN = 0x0000001c FILE_DEVICE_SOUND = 0x0000001d FILE_DEVICE_STREAMS = 0x0000001e FILE_DEVICE_TAPE = 0x0000001f FILE_DEVICE_TAPE_FILE_SYSTEM = 0x00000020 FILE_DEVICE_TRANSPORT = 0x00000021 FILE_DEVICE_UNKNOWN = 0x00000022 FILE_DEVICE_VIDEO = 0x00000023 FILE_DEVICE_VIRTUAL_DISK = 0x00000024 FILE_DEVICE_WAVE_IN = 0x00000025 FILE_DEVICE_WAVE_OUT = 0x00000026 FILE_DEVICE_8042_PORT = 0x00000027 FILE_DEVICE_NETWORK_REDIRECTOR = 0x00000028 FILE_DEVICE_BATTERY = 0x00000029 FILE_DEVICE_BUS_EXTENDER = 0x0000002a FILE_DEVICE_MODEM = 0x0000002b FILE_DEVICE_VDM = 0x0000002c FILE_DEVICE_MASS_STORAGE = 0x0000002d FILE_DEVICE_SMB = 0x0000002e FILE_DEVICE_KS = 0x0000002f FILE_DEVICE_CHANGER = 0x00000030 FILE_DEVICE_SMARTCARD = 0x00000031 FILE_DEVICE_ACPI = 0x00000032 FILE_DEVICE_DVD = 0x00000033 FILE_DEVICE_FULLSCREEN_VIDEO = 0x00000034 FILE_DEVICE_DFS_FILE_SYSTEM = 0x00000035 FILE_DEVICE_DFS_VOLUME = 0x00000036 FILE_DEVICE_SERENUM = 0x00000037 FILE_DEVICE_TERMSRV = 0x00000038 FILE_DEVICE_KSEC = 0x00000039 FILE_DEVICE_FIPS = 0x0000003A FILE_DEVICE_INFINIBAND = 0x0000003B FILE_DEVICE_VMBUS = 0x0000003E FILE_DEVICE_CRYPT_PROVIDER = 0x0000003F FILE_DEVICE_WPD = 0x00000040 FILE_DEVICE_BLUETOOTH = 0x00000041 FILE_DEVICE_MT_COMPOSITE = 0x00000042 FILE_DEVICE_MT_TRANSPORT = 0x00000043 FILE_DEVICE_BIOMETRIC = 0x00000044 FILE_DEVICE_PMI = 0x00000045 # Methods METHOD_BUFFERED = 0 METHOD_IN_DIRECT = 1 METHOD_OUT_DIRECT = 2 METHOD_NEITHER = 3 METHOD_DIRECT_TO_HARDWARE = METHOD_IN_DIRECT METHOD_DIRECT_FROM_HARDWARE = METHOD_OUT_DIRECT # Access FILE_ANY_ACCESS = 0 FILE_SPECIAL_ACCESS = FILE_ANY_ACCESS FILE_READ_ACCESS = 0x0001 FILE_WRITE_ACCESS = 0x0002 def self.CTL_CODE( device_type, function, method, access ) (device_type << 16) | (access << 14) | (function << 2) | method end FSCTL_GET_REPARSE_POINT = CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 42, METHOD_BUFFERED, FILE_ANY_ACCESS) # Reparse point tags IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003 IO_REPARSE_TAG_HSM = 0xC0000004 IO_REPARSE_TAG_HSM2 = 0x80000006 IO_REPARSE_TAG_SIS = 0x80000007 IO_REPARSE_TAG_WIM = 0x80000008 IO_REPARSE_TAG_CSV = 0x80000009 IO_REPARSE_TAG_DFS = 0x8000000A IO_REPARSE_TAG_SYMLINK = 0xA000000C IO_REPARSE_TAG_DFSR = 0x80000012 MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 16 * 1024 ############################################### # Win32 API Bindings ############################################### ffi_lib "kernel32", "version" # Does not map directly to a win32 struct # see https://msdn.microsoft.com/en-us/library/windows/desktop/ms647464(v=vs.85).aspx class Translation < FFI::Struct layout :w_lang, :WORD, :w_code_page, :WORD end =begin typedef struct _FILETIME { DWORD dwLowDateTime; DWORD dwHighDateTime; } FILETIME, *PFILETIME; =end class FILETIME < FFI::Struct layout :dw_low_date_time, :DWORD, :dw_high_date_time, :DWORD end =begin typedef struct _SECURITY_ATTRIBUTES { DWORD nLength; LPVOID lpSecurityDescriptor; BOOL bInheritHandle; } SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES; =end class SECURITY_ATTRIBUTES < FFI::Struct layout :n_length, :DWORD, :lp_security_descriptor, :LPVOID, :b_inherit_handle, :DWORD end =begin typedef struct _WIN32_FIND_DATA { DWORD dwFileAttributes; FILETIME ftCreationTime; FILETIME ftLastAccessTime; FILETIME ftLastWriteTime; DWORD nFileSizeHigh; DWORD nFileSizeLow; DWORD dwReserved0; DWORD dwReserved1; TCHAR cFileName[MAX_PATH]; TCHAR cAlternateFileName[14]; } WIN32_FIND_DATA, *PWIN32_FIND_DATA, *LPWIN32_FIND_DATA; =end class WIN32_FIND_DATA < FFI::Struct layout :dw_file_attributes, :DWORD, :ft_creation_time, FILETIME, :ft_last_access_time, FILETIME, :ft_last_write_time, FILETIME, :n_file_size_high, :DWORD, :n_file_size_low, :DWORD, :dw_reserved_0, :DWORD, :dw_reserved_1, :DWORD, :c_file_name, [:BYTE, MAX_PATH * 2], :c_alternate_file_name, [:BYTE, 14] end =begin typedef struct _BY_HANDLE_FILE_INFORMATION { DWORD dwFileAttributes; FILETIME ftCreationTime; FILETIME ftLastAccessTime; FILETIME ftLastWriteTime; DWORD dwVolumeSerialNumber; DWORD nFileSizeHigh; DWORD nFileSizeLow; DWORD nNumberOfLinks; DWORD nFileIndexHigh; DWORD nFileIndexLow; } BY_HANDLE_FILE_INFORMATION, *PBY_HANDLE_FILE_INFORMATION; =end class BY_HANDLE_FILE_INFORMATION < FFI::Struct layout :dw_file_attributes, :DWORD, :ft_creation_time, FILETIME, :ft_last_access_time, FILETIME, :ft_last_write_time, FILETIME, :dw_volume_serial_number, :DWORD, :n_file_size_high, :DWORD, :n_file_size_low, :DWORD, :n_number_of_links, :DWORD, :n_file_index_high, :DWORD, :n_file_index_low, :DWORD end =begin typedef struct _REPARSE_DATA_BUFFER { ULONG ReparseTag; USHORT ReparseDataLength; USHORT Reserved; union { struct { USHORT SubstituteNameOffset; USHORT SubstituteNameLength; USHORT PrintNameOffset; USHORT PrintNameLength; ULONG Flags; WCHAR PathBuffer[1]; } SymbolicLinkReparseBuffer; struct { USHORT SubstituteNameOffset; USHORT SubstituteNameLength; USHORT PrintNameOffset; USHORT PrintNameLength; WCHAR PathBuffer[1]; } MountPointReparseBuffer; struct { UCHAR DataBuffer[1]; } GenericReparseBuffer; }; } REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER; =end class REPARSE_DATA_BUFFER_SYMBOLIC_LINK < FFI::Struct layout :SubstituteNameOffset, :ushort, :SubstituteNameLength, :ushort, :PrintNameOffset, :ushort, :PrintNameLength, :ushort, :Flags, :uint32, :PathBuffer, :ushort def substitute_name string_pointer = FFI::Pointer.new(pointer.address) + offset_of(:PathBuffer) + self[:SubstituteNameOffset] string_pointer.read_wstring(self[:SubstituteNameLength] / 2) end def print_name string_pointer = FFI::Pointer.new(pointer.address) + offset_of(:PathBuffer) + self[:PrintNameOffset] string_pointer.read_wstring(self[:PrintNameLength] / 2) end end class REPARSE_DATA_BUFFER_MOUNT_POINT < FFI::Struct layout :SubstituteNameOffset, :ushort, :SubstituteNameLength, :ushort, :PrintNameOffset, :ushort, :PrintNameLength, :ushort, :PathBuffer, :ushort def substitute_name string_pointer = FFI::Pointer.new(pointer.address) + offset_of(:PathBuffer) + self[:SubstituteNameOffset] string_pointer.read_wstring(self[:SubstituteNameLength] / 2) end def print_name string_pointer = FFI::Pointer.new(pointer.address) + offset_of(:PathBuffer) + self[:PrintNameOffset] string_pointer.read_wstring(self[:PrintNameLength] / 2) end end class REPARSE_DATA_BUFFER_GENERIC < FFI::Struct layout :DataBuffer, :uchar end class REPARSE_DATA_BUFFER_UNION < FFI::Union layout :SymbolicLinkReparseBuffer, REPARSE_DATA_BUFFER_SYMBOLIC_LINK, :MountPointReparseBuffer, REPARSE_DATA_BUFFER_MOUNT_POINT, :GenericReparseBuffer, REPARSE_DATA_BUFFER_GENERIC end class REPARSE_DATA_BUFFER < FFI::Struct layout :ReparseTag, :uint32, :ReparseDataLength, :ushort, :Reserved, :ushort, :ReparseBuffer, REPARSE_DATA_BUFFER_UNION def reparse_buffer if self[:ReparseTag] == IO_REPARSE_TAG_SYMLINK self[:ReparseBuffer][:SymbolicLinkReparseBuffer] elsif self[:ReparseTag] == IO_REPARSE_TAG_MOUNT_POINT self[:ReparseBuffer][:MountPointReparseBuffer] else self[:ReparseBuffer][:GenericReparseBuffer] end end end =begin 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 ); =end safe_attach_function :CreateFileW, [:LPCTSTR, :DWORD, :DWORD, :LPSECURITY_ATTRIBUTES, :DWORD, :DWORD, :pointer], :HANDLE =begin BOOL WINAPI FindClose( __inout HANDLE hFindFile ); =end safe_attach_function :FindClose, [:HANDLE], :BOOL =begin DWORD WINAPI GetFileAttributes( __in LPCTSTR lpFileName ); =end safe_attach_function :GetFileAttributesW, [:LPCWSTR], :DWORD =begin DWORD WINAPI GetFinalPathNameByHandle( __in HANDLE hFile, __out LPTSTR lpszFilePath, __in DWORD cchFilePath, __in DWORD dwFlags ); =end safe_attach_function :GetFinalPathNameByHandleW, [:HANDLE, :LPTSTR, :DWORD, :DWORD], :DWORD =begin BOOL WINAPI GetFileInformationByHandle( __in HANDLE hFile, __out LPBY_HANDLE_FILE_INFORMATION lpFileInformation ); =end safe_attach_function :GetFileInformationByHandle, [:HANDLE, :LPBY_HANDLE_FILE_INFORMATION], :BOOL =begin HANDLE WINAPI FindFirstFile( __in LPCTSTR lpFileName, __out LPWIN32_FIND_DATA lpFindFileData ); =end safe_attach_function :FindFirstFileW, [:LPCTSTR, :LPWIN32_FIND_DATA], :HANDLE =begin BOOL WINAPI CreateHardLink( __in LPCTSTR lpFileName, __in LPCTSTR lpExistingFileName, __reserved LPSECURITY_ATTRIBUTES lpSecurityAttributes ); =end safe_attach_function :CreateHardLinkW, [:LPCTSTR, :LPCTSTR, :LPSECURITY_ATTRIBUTES], :BOOLEAN =begin BOOLEAN WINAPI CreateSymbolicLink( __in LPTSTR lpSymlinkFileName, __in LPTSTR lpTargetFileName, __in DWORD dwFlags ); =end safe_attach_function :CreateSymbolicLinkW, [:LPTSTR, :LPTSTR, :DWORD], :BOOLEAN =begin DWORD WINAPI GetLongPathName( __in LPCTSTR lpszShortPath, __out LPTSTR lpszLongPath, __in DWORD cchBuffer ); =end safe_attach_function :GetLongPathNameW, [:LPCTSTR, :LPTSTR, :DWORD], :DWORD =begin DWORD WINAPI GetShortPathName( __in LPCTSTR lpszLongPath, __out LPTSTR lpszShortPath, __in DWORD cchBuffer ); =end safe_attach_function :GetShortPathNameW, [:LPCTSTR, :LPTSTR, :DWORD], :DWORD =begin 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 ); =end safe_attach_function :DeviceIoControl, [:HANDLE, :DWORD, :LPVOID, :DWORD, :LPVOID, :DWORD, :LPDWORD, :pointer], :BOOL #BOOL WINAPI DeleteVolumeMountPoint( #_In_ LPCTSTR lpszVolumeMountPoint #); safe_attach_function :DeleteVolumeMountPointW, [:LPCTSTR], :BOOL #BOOL WINAPI SetVolumeMountPoint( #_In_ LPCTSTR lpszVolumeMountPoint, #_In_ LPCTSTR lpszVolumeName #); safe_attach_function :SetVolumeMountPointW, [:LPCTSTR, :LPCTSTR], :BOOL #BOOL WINAPI GetVolumeNameForVolumeMountPoint( #_In_ LPCTSTR lpszVolumeMountPoint, #_Out_ LPTSTR lpszVolumeName, #_In_ DWORD cchBufferLength #); safe_attach_function :GetVolumeNameForVolumeMountPointW, [:LPCTSTR, :LPTSTR, :DWORD], :BOOL =begin BOOL WINAPI GetFileVersionInfo( _In_ LPCTSTR lptstrFilename, _Reserved_ DWORD dwHandle, _In_ DWORD dwLen, _Out_ LPVOID lpData ); =end safe_attach_function :GetFileVersionInfoW, [:LPCTSTR, :DWORD, :DWORD, :LPVOID], :BOOL =begin DWORD WINAPI GetFileVersionInfoSize( _In_ LPCTSTR lptstrFilename, _Out_opt_ LPDWORD lpdwHandle ); =end safe_attach_function :GetFileVersionInfoSizeW, [:LPCTSTR, :LPDWORD], :DWORD =begin BOOL WINAPI VerQueryValue( _In_ LPCVOID pBlock, _In_ LPCTSTR lpSubBlock, _Out_ LPVOID *lplpBuffer, _Out_ PUINT puLen ); =end safe_attach_function :VerQueryValueW, [:LPCVOID, :LPCTSTR, :LPVOID, :PUINT], :BOOL ############################################### # Helpers ############################################### # takes the given path pre-pends "\\?\" and # UTF-16LE encodes it. Used to prepare paths # to be passed to the *W vesion of WinAPI File # functions. # This function is used by the "Link" resources where we need # preserve relative paths because symbolic links can actually # point to a relative path (relative to the link itself). def encode_path(path) (path_prepender << path.gsub(::File::SEPARATOR, ::File::ALT_SEPARATOR)).to_wstring end # Expands the path, prepends "\\?\" and UTF-16LE encodes it. # This function is used by the "File" resources where we need # convert relative paths to fully qualified paths. def canonical_encode_path(path) Chef::Util::PathHelper.canonical_path(path).to_wstring end def path_prepender "\\\\?\\" end # retrieves a file search handle and passes it # to +&block+ along with the find_data. also # ensures the handle is closed on exit of the block # FIXME: yard with @yield def file_search_handle(path) # Workaround for CHEF-4419: # Make sure paths starting with "/" has a drive letter # assigned from the current working diretory. # Note: With CHEF-4427 this issue will be fixed with a # broader fix to map all the paths starting with "/" to # SYSTEM_DRIVE on windows. path = ::File.expand_path(path) if path.start_with? "/" path = canonical_encode_path(path) find_data = WIN32_FIND_DATA.new handle = FindFirstFileW(path, find_data) if handle == INVALID_HANDLE_VALUE Chef::ReservedNames::Win32::Error.raise! end yield(handle, find_data) ensure FindClose(handle) if handle && handle != INVALID_HANDLE_VALUE end # retrieves a file handle and passes it # to +&block+ along with the find_data. also # ensures the handle is closed on exit of the block # FIXME: yard with @yield def file_handle(path) path = canonical_encode_path(path) handle = CreateFileW(path, GENERIC_READ, FILE_SHARE_READ, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, nil) if handle == INVALID_HANDLE_VALUE Chef::ReservedNames::Win32::Error.raise! end yield(handle) ensure CloseHandle(handle) if handle && handle != INVALID_HANDLE_VALUE end # FIXME: yard with @yield def symlink_file_handle(path) path = encode_path(path) handle = CreateFileW(path, FILE_READ_EA, FILE_SHARE_READ, nil, OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, nil) if handle == INVALID_HANDLE_VALUE Chef::ReservedNames::Win32::Error.raise! end yield(handle) ensure CloseHandle(handle) if handle && handle != INVALID_HANDLE_VALUE end def retrieve_file_info(file_name) file_information = nil file_handle(file_name) do |handle| file_information = BY_HANDLE_FILE_INFORMATION.new success = GetFileInformationByHandle(handle, file_information) if success == 0 Chef::ReservedNames::Win32::Error.raise! end end file_information end def retrieve_file_version_info(file_name) file_name = encode_path(file_name) file_size = GetFileVersionInfoSizeW(file_name, nil) if file_size == 0 Chef::ReservedNames::Win32::Error.raise! end version_info = FFI::MemoryPointer.new(file_size) unless GetFileVersionInfoW(file_name, 0, file_size, version_info) Chef::ReservedNames::Win32::Error.raise! end version_info end end end end end