| #!/usr/bin/python |
| # Copyright 2019 The Cobalt Authors. All Rights Reserved. |
| # |
| # 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. |
| """Provides functions for symlinking on Windows.""" |
| |
| import ctypes |
| from ctypes import wintypes |
| import os |
| |
| DWORD = wintypes.DWORD |
| LPCWSTR = wintypes.LPCWSTR |
| HANDLE = wintypes.HANDLE |
| LPVOID = wintypes.LPVOID |
| BOOL = wintypes.BOOL |
| USHORT = wintypes.USHORT |
| ULONG = wintypes.ULONG |
| WCHAR = wintypes.WCHAR |
| |
| kernel32 = ctypes.windll.kernel32 |
| LPDWORD = ctypes.POINTER(DWORD) |
| UCHAR = ctypes.c_ubyte |
| |
| GetFileAttributesW = kernel32.GetFileAttributesW |
| GetFileAttributesW.restype = DWORD |
| GetFileAttributesW.argtypes = (LPCWSTR,) # lpFileName In |
| |
| INVALID_FILE_ATTRIBUTES = 0xFFFFFFFF |
| FILE_ATTRIBUTE_REPARSE_POINT = 0x00400 |
| |
| CreateFileW = kernel32.CreateFileW |
| CreateFileW.restype = HANDLE |
| CreateFileW.argtypes = ( |
| LPCWSTR, # lpFileName In |
| DWORD, # dwDesiredAccess In |
| DWORD, # dwShareMode In |
| LPVOID, # lpSecurityAttributes In_opt |
| DWORD, # dwCreationDisposition In |
| DWORD, # dwFlagsAndAttributes In |
| HANDLE) # hTemplateFile In_opt |
| |
| CloseHandle = kernel32.CloseHandle |
| CloseHandle.restype = BOOL |
| CloseHandle.argtypes = (HANDLE,) # hObject In |
| |
| INVALID_HANDLE_VALUE = HANDLE(-1).value # pylint:disable=invalid-name |
| OPEN_EXISTING = 3 |
| FILE_FLAG_BACKUP_SEMANTICS = 0x02000000 |
| FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000 |
| |
| DeviceIoControl = kernel32.DeviceIoControl |
| DeviceIoControl.restype = BOOL |
| DeviceIoControl.argtypes = ( |
| HANDLE, # hDevice In |
| DWORD, # dwIoControlCode In |
| LPVOID, # lpInBuffer In_opt |
| DWORD, # nInBufferSize In |
| LPVOID, # lpOutBuffer Out_opt |
| DWORD, # nOutBufferSize In |
| LPDWORD, # lpBytesReturned Out_opt |
| LPVOID) # lpOverlapped Inout_opt |
| |
| FSCTL_GET_REPARSE_POINT = 0x000900A8 |
| IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003 |
| IO_REPARSE_TAG_SYMLINK = 0xA000000C |
| MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 0x4000 |
| SYMBOLIC_LINK_FLAG_DIRECTORY = 0x1 |
| # Developer Mode must be enabled in order to use the following flag. |
| SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE = 0x2 |
| SYMLINK_FLAG_RELATIVE = 0x1 |
| |
| |
| class GenericReparseBuffer(ctypes.Structure): |
| """Win32 api data structure.""" |
| _fields_ = (('DataBuffer', UCHAR * 1),) |
| |
| |
| class SymbolicLinkReparseBuffer(ctypes.Structure): |
| """Win32 api data structure.""" |
| |
| _fields_ = (('SubstituteNameOffset', USHORT), |
| ('SubstituteNameLength', USHORT), ('PrintNameOffset', USHORT), |
| ('PrintNameLength', USHORT), ('Flags', ULONG), ('PathBuffer', |
| WCHAR * 1)) |
| |
| @property |
| def print_name(self): |
| arrayt = WCHAR * (self.PrintNameLength // 2) |
| offset = type(self).PathBuffer.offset + self.PrintNameOffset |
| return arrayt.from_address(ctypes.addressof(self) + offset).value |
| |
| @property |
| def substitute_name(self): |
| arrayt = WCHAR * (self.SubstituteNameLength // 2) |
| offset = type(self).PathBuffer.offset + self.SubstituteNameOffset |
| return arrayt.from_address(ctypes.addressof(self) + offset).value |
| |
| @property |
| def is_relative_path(self): |
| return bool(self.Flags & SYMLINK_FLAG_RELATIVE) |
| |
| |
| class MountPointReparseBuffer(ctypes.Structure): |
| """Win32 api data structure.""" |
| _fields_ = (('SubstituteNameOffset', USHORT), |
| ('SubstituteNameLength', USHORT), ('PrintNameOffset', USHORT), |
| ('PrintNameLength', USHORT), ('PathBuffer', WCHAR * 1)) |
| |
| @property |
| def print_name(self): |
| arrayt = WCHAR * (self.PrintNameLength // 2) |
| offset = type(self).PathBuffer.offset + self.PrintNameOffset |
| return arrayt.from_address(ctypes.addressof(self) + offset).value |
| |
| @property |
| def substitute_name(self): |
| arrayt = WCHAR * (self.SubstituteNameLength // 2) |
| offset = type(self).PathBuffer.offset + self.SubstituteNameOffset |
| return arrayt.from_address(ctypes.addressof(self) + offset).value |
| |
| |
| class ReparseDataBuffer(ctypes.Structure): |
| """Win32 api data structure.""" |
| |
| class ReparseBuffer(ctypes.Union): |
| """Win32 api data structure.""" |
| _fields_ = (('SymbolicLinkReparseBuffer', SymbolicLinkReparseBuffer), |
| ('MountPointReparseBuffer', MountPointReparseBuffer), |
| ('GenericReparseBuffer', GenericReparseBuffer)) |
| |
| _fields_ = (('ReparseTag', ULONG), ('ReparseDataLength', USHORT), |
| ('Reserved', USHORT), ('ReparseBuffer', ReparseBuffer)) |
| _anonymous_ = ('ReparseBuffer',) |
| |
| |
| def _ToUnicode(s): |
| try: |
| return s.decode('utf-8') |
| except AttributeError: |
| return s |
| |
| |
| _kdll = None |
| |
| |
| def _GetKernel32Dll(): |
| global _kdll |
| if _kdll: |
| return _kdll |
| _kdll = ctypes.windll.LoadLibrary('kernel32.dll') |
| return _kdll |
| |
| |
| def FastCreateReparseLink(from_folder, link_folder): |
| """Creates a reparse link. |
| |
| Args: |
| from_folder: The folder that the link will point to. |
| link_folder: The path of the link to be created. |
| |
| Returns: |
| None |
| |
| Raises: |
| OSError: if link cannot be created |
| """ |
| from_folder = _ToUnicode(from_folder) |
| link_folder = _ToUnicode(link_folder) |
| par_dir = os.path.dirname(link_folder) |
| if not os.path.isdir(par_dir): |
| os.makedirs(par_dir) |
| kdll = _GetKernel32Dll() |
| # Only supported from Windows 10 Insiders build 14972 |
| flags = SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE | \ |
| SYMBOLIC_LINK_FLAG_DIRECTORY |
| ok = kdll.CreateSymbolicLinkW(link_folder, from_folder, flags) |
| if not ok or not FastIsReparseLink(link_folder): |
| raise OSError('Could not create sym link ' + link_folder + ' to ' + |
| from_folder) |
| |
| |
| def FastIsReparseLink(path): |
| path = _ToUnicode(path) |
| result = GetFileAttributesW(path) |
| if result == INVALID_FILE_ATTRIBUTES: |
| return False |
| return bool(result & FILE_ATTRIBUTE_REPARSE_POINT) |
| |
| |
| def FastReadReparseLink(path): |
| """See api docstring, above.""" |
| path = _ToUnicode(path) |
| reparse_point_handle = CreateFileW( |
| path, 0, 0, None, OPEN_EXISTING, |
| FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, None) |
| if reparse_point_handle == INVALID_HANDLE_VALUE: |
| return None |
| # Remove false positive below. |
| # pylint: disable=deprecated-method |
| target_buffer = ctypes.c_buffer(MAXIMUM_REPARSE_DATA_BUFFER_SIZE) |
| n_bytes_returned = DWORD() |
| io_result = DeviceIoControl(reparse_point_handle, FSCTL_GET_REPARSE_POINT, |
| None, 0, target_buffer, len(target_buffer), |
| ctypes.byref(n_bytes_returned), None) |
| CloseHandle(reparse_point_handle) |
| if not io_result: |
| return None |
| rdb = ReparseDataBuffer.from_buffer(target_buffer) |
| if rdb.ReparseTag == IO_REPARSE_TAG_SYMLINK: |
| return rdb.SymbolicLinkReparseBuffer.print_name |
| elif rdb.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT: |
| return rdb.MountPointReparseBuffer.print_name |
| return None |