# Copyright (C) 2016 The Android Open Source Project # # 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. from ctypes import addressof from ctypes import byref from ctypes import c_buffer from ctypes import c_ubyte from ctypes import FormatError from ctypes import get_last_error from ctypes import Structure from ctypes import Union from ctypes import WinDLL from ctypes import WinError from ctypes.wintypes import BOOL from ctypes.wintypes import BOOLEAN from ctypes.wintypes import DWORD from ctypes.wintypes import HANDLE from ctypes.wintypes import LPCWSTR from ctypes.wintypes import LPDWORD from ctypes.wintypes import LPVOID from ctypes.wintypes import ULONG from ctypes.wintypes import USHORT from ctypes.wintypes import WCHAR import errno kernel32 = WinDLL("kernel32", use_last_error=True) UCHAR = c_ubyte # Win32 error codes ERROR_SUCCESS = 0 ERROR_NOT_SUPPORTED = 50 ERROR_PRIVILEGE_NOT_HELD = 1314 # Win32 API entry points CreateSymbolicLinkW = kernel32.CreateSymbolicLinkW CreateSymbolicLinkW.restype = BOOLEAN CreateSymbolicLinkW.argtypes = ( LPCWSTR, # lpSymlinkFileName In LPCWSTR, # lpTargetFileName In DWORD, # dwFlags In ) # Symbolic link creation flags SYMBOLIC_LINK_FLAG_FILE = 0x00 SYMBOLIC_LINK_FLAG_DIRECTORY = 0x01 # symlink support for CreateSymbolicLink() starting with Windows 10 (1703, # v10.0.14972) SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE = 0x02 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 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 ) # Device I/O control flags and options FSCTL_GET_REPARSE_POINT = 0x000900A8 IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003 IO_REPARSE_TAG_SYMLINK = 0xA000000C MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 0x4000 class GENERIC_REPARSE_BUFFER(Structure): _fields_ = (("DataBuffer", UCHAR * 1),) class SYMBOLIC_LINK_REPARSE_BUFFER(Structure): _fields_ = ( ("SubstituteNameOffset", USHORT), ("SubstituteNameLength", USHORT), ("PrintNameOffset", USHORT), ("PrintNameLength", USHORT), ("Flags", ULONG), ("PathBuffer", WCHAR * 1), ) @property def PrintName(self): arrayt = WCHAR * (self.PrintNameLength // 2) offset = type(self).PathBuffer.offset + self.PrintNameOffset return arrayt.from_address(addressof(self) + offset).value class MOUNT_POINT_REPARSE_BUFFER(Structure): _fields_ = ( ("SubstituteNameOffset", USHORT), ("SubstituteNameLength", USHORT), ("PrintNameOffset", USHORT), ("PrintNameLength", USHORT), ("PathBuffer", WCHAR * 1), ) @property def PrintName(self): arrayt = WCHAR * (self.PrintNameLength // 2) offset = type(self).PathBuffer.offset + self.PrintNameOffset return arrayt.from_address(addressof(self) + offset).value class REPARSE_DATA_BUFFER(Structure): class REPARSE_BUFFER(Union): _fields_ = ( ("SymbolicLinkReparseBuffer", SYMBOLIC_LINK_REPARSE_BUFFER), ("MountPointReparseBuffer", MOUNT_POINT_REPARSE_BUFFER), ("GenericReparseBuffer", GENERIC_REPARSE_BUFFER), ) _fields_ = ( ("ReparseTag", ULONG), ("ReparseDataLength", USHORT), ("Reserved", USHORT), ("ReparseBuffer", REPARSE_BUFFER), ) _anonymous_ = ("ReparseBuffer",) def create_filesymlink(source, link_name): """Creates a Windows file symbolic link source pointing to link_name.""" _create_symlink(source, link_name, SYMBOLIC_LINK_FLAG_FILE) def create_dirsymlink(source, link_name): """Creates a Windows directory symbolic link source pointing to link_name.""" # noqa: E501 _create_symlink(source, link_name, SYMBOLIC_LINK_FLAG_DIRECTORY) def _create_symlink(source, link_name, dwFlags): if not CreateSymbolicLinkW( link_name, source, dwFlags | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE, ): # See https://github.com/golang/go/pull/24307/files#diff-b87bc12e4da2497308f9ef746086e4f0 # noqa: E501 # "the unprivileged create flag is unsupported below Windows 10 (1703, # v10.0.14972). retry without it." if not CreateSymbolicLinkW(link_name, source, dwFlags): code = get_last_error() error_desc = FormatError(code).strip() if code == ERROR_PRIVILEGE_NOT_HELD: raise OSError(errno.EPERM, error_desc, link_name) _raise_winerror(code, f'Error creating symbolic link "{link_name}"') def islink(path): result = GetFileAttributesW(path) if result == INVALID_FILE_ATTRIBUTES: return False return bool(result & FILE_ATTRIBUTE_REPARSE_POINT) def readlink(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: _raise_winerror( get_last_error(), f'Error opening symbolic link "{path}"' ) target_buffer = 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), byref(n_bytes_returned), None, ) CloseHandle(reparse_point_handle) if not io_result: _raise_winerror( get_last_error(), f'Error reading symbolic link "{path}"' ) rdb = REPARSE_DATA_BUFFER.from_buffer(target_buffer) if rdb.ReparseTag == IO_REPARSE_TAG_SYMLINK: return rdb.SymbolicLinkReparseBuffer.PrintName elif rdb.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT: return rdb.MountPointReparseBuffer.PrintName # Unsupported reparse point type. _raise_winerror( ERROR_NOT_SUPPORTED, f'Error reading symbolic link "{path}"' ) def _raise_winerror(code, error_desc): win_error_desc = FormatError(code).strip() error_desc = f"{error_desc}: {win_error_desc}" raise WinError(code, error_desc)