platform_utils_win32.py 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. # -*- coding:utf-8 -*-
  2. #
  3. # Copyright (C) 2016 The Android Open Source Project
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License");
  6. # you may not use this file except in compliance with the License.
  7. # You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS,
  13. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. # See the License for the specific language governing permissions and
  15. # limitations under the License.
  16. import errno
  17. from pyversion import is_python3
  18. from ctypes import WinDLL, get_last_error, FormatError, WinError, addressof
  19. from ctypes import c_buffer
  20. from ctypes.wintypes import BOOL, BOOLEAN, LPCWSTR, DWORD, HANDLE, POINTER, c_ubyte
  21. from ctypes.wintypes import WCHAR, USHORT, LPVOID, Structure, Union, ULONG
  22. from ctypes.wintypes import byref
  23. kernel32 = WinDLL('kernel32', use_last_error=True)
  24. LPDWORD = POINTER(DWORD)
  25. UCHAR = c_ubyte
  26. # Win32 error codes
  27. ERROR_SUCCESS = 0
  28. ERROR_NOT_SUPPORTED = 50
  29. ERROR_PRIVILEGE_NOT_HELD = 1314
  30. # Win32 API entry points
  31. CreateSymbolicLinkW = kernel32.CreateSymbolicLinkW
  32. CreateSymbolicLinkW.restype = BOOLEAN
  33. CreateSymbolicLinkW.argtypes = (LPCWSTR, # lpSymlinkFileName In
  34. LPCWSTR, # lpTargetFileName In
  35. DWORD) # dwFlags In
  36. # Symbolic link creation flags
  37. SYMBOLIC_LINK_FLAG_FILE = 0x00
  38. SYMBOLIC_LINK_FLAG_DIRECTORY = 0x01
  39. # symlink support for CreateSymbolicLink() starting with Windows 10 (1703, v10.0.14972)
  40. SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE = 0x02
  41. GetFileAttributesW = kernel32.GetFileAttributesW
  42. GetFileAttributesW.restype = DWORD
  43. GetFileAttributesW.argtypes = (LPCWSTR,) # lpFileName In
  44. INVALID_FILE_ATTRIBUTES = 0xFFFFFFFF
  45. FILE_ATTRIBUTE_REPARSE_POINT = 0x00400
  46. CreateFileW = kernel32.CreateFileW
  47. CreateFileW.restype = HANDLE
  48. CreateFileW.argtypes = (LPCWSTR, # lpFileName In
  49. DWORD, # dwDesiredAccess In
  50. DWORD, # dwShareMode In
  51. LPVOID, # lpSecurityAttributes In_opt
  52. DWORD, # dwCreationDisposition In
  53. DWORD, # dwFlagsAndAttributes In
  54. HANDLE) # hTemplateFile In_opt
  55. CloseHandle = kernel32.CloseHandle
  56. CloseHandle.restype = BOOL
  57. CloseHandle.argtypes = (HANDLE,) # hObject In
  58. INVALID_HANDLE_VALUE = HANDLE(-1).value
  59. OPEN_EXISTING = 3
  60. FILE_FLAG_BACKUP_SEMANTICS = 0x02000000
  61. FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000
  62. DeviceIoControl = kernel32.DeviceIoControl
  63. DeviceIoControl.restype = BOOL
  64. DeviceIoControl.argtypes = (HANDLE, # hDevice In
  65. DWORD, # dwIoControlCode In
  66. LPVOID, # lpInBuffer In_opt
  67. DWORD, # nInBufferSize In
  68. LPVOID, # lpOutBuffer Out_opt
  69. DWORD, # nOutBufferSize In
  70. LPDWORD, # lpBytesReturned Out_opt
  71. LPVOID) # lpOverlapped Inout_opt
  72. # Device I/O control flags and options
  73. FSCTL_GET_REPARSE_POINT = 0x000900A8
  74. IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003
  75. IO_REPARSE_TAG_SYMLINK = 0xA000000C
  76. MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 0x4000
  77. class GENERIC_REPARSE_BUFFER(Structure):
  78. _fields_ = (('DataBuffer', UCHAR * 1),)
  79. class SYMBOLIC_LINK_REPARSE_BUFFER(Structure):
  80. _fields_ = (('SubstituteNameOffset', USHORT),
  81. ('SubstituteNameLength', USHORT),
  82. ('PrintNameOffset', USHORT),
  83. ('PrintNameLength', USHORT),
  84. ('Flags', ULONG),
  85. ('PathBuffer', WCHAR * 1))
  86. @property
  87. def PrintName(self):
  88. arrayt = WCHAR * (self.PrintNameLength // 2)
  89. offset = type(self).PathBuffer.offset + self.PrintNameOffset
  90. return arrayt.from_address(addressof(self) + offset).value
  91. class MOUNT_POINT_REPARSE_BUFFER(Structure):
  92. _fields_ = (('SubstituteNameOffset', USHORT),
  93. ('SubstituteNameLength', USHORT),
  94. ('PrintNameOffset', USHORT),
  95. ('PrintNameLength', USHORT),
  96. ('PathBuffer', WCHAR * 1))
  97. @property
  98. def PrintName(self):
  99. arrayt = WCHAR * (self.PrintNameLength // 2)
  100. offset = type(self).PathBuffer.offset + self.PrintNameOffset
  101. return arrayt.from_address(addressof(self) + offset).value
  102. class REPARSE_DATA_BUFFER(Structure):
  103. class REPARSE_BUFFER(Union):
  104. _fields_ = (('SymbolicLinkReparseBuffer', SYMBOLIC_LINK_REPARSE_BUFFER),
  105. ('MountPointReparseBuffer', MOUNT_POINT_REPARSE_BUFFER),
  106. ('GenericReparseBuffer', GENERIC_REPARSE_BUFFER))
  107. _fields_ = (('ReparseTag', ULONG),
  108. ('ReparseDataLength', USHORT),
  109. ('Reserved', USHORT),
  110. ('ReparseBuffer', REPARSE_BUFFER))
  111. _anonymous_ = ('ReparseBuffer',)
  112. def create_filesymlink(source, link_name):
  113. """Creates a Windows file symbolic link source pointing to link_name."""
  114. _create_symlink(source, link_name, SYMBOLIC_LINK_FLAG_FILE)
  115. def create_dirsymlink(source, link_name):
  116. """Creates a Windows directory symbolic link source pointing to link_name.
  117. """
  118. _create_symlink(source, link_name, SYMBOLIC_LINK_FLAG_DIRECTORY)
  119. def _create_symlink(source, link_name, dwFlags):
  120. if not CreateSymbolicLinkW(link_name, source, dwFlags | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE):
  121. # See https://github.com/golang/go/pull/24307/files#diff-b87bc12e4da2497308f9ef746086e4f0
  122. # "the unprivileged create flag is unsupported below Windows 10 (1703, v10.0.14972).
  123. # retry without it."
  124. if not CreateSymbolicLinkW(link_name, source, dwFlags):
  125. code = get_last_error()
  126. error_desc = FormatError(code).strip()
  127. if code == ERROR_PRIVILEGE_NOT_HELD:
  128. raise OSError(errno.EPERM, error_desc, link_name)
  129. _raise_winerror(
  130. code,
  131. 'Error creating symbolic link \"%s\"'.format(link_name))
  132. def islink(path):
  133. result = GetFileAttributesW(path)
  134. if result == INVALID_FILE_ATTRIBUTES:
  135. return False
  136. return bool(result & FILE_ATTRIBUTE_REPARSE_POINT)
  137. def readlink(path):
  138. reparse_point_handle = CreateFileW(path,
  139. 0,
  140. 0,
  141. None,
  142. OPEN_EXISTING,
  143. FILE_FLAG_OPEN_REPARSE_POINT |
  144. FILE_FLAG_BACKUP_SEMANTICS,
  145. None)
  146. if reparse_point_handle == INVALID_HANDLE_VALUE:
  147. _raise_winerror(
  148. get_last_error(),
  149. 'Error opening symblic link \"%s\"'.format(path))
  150. target_buffer = c_buffer(MAXIMUM_REPARSE_DATA_BUFFER_SIZE)
  151. n_bytes_returned = DWORD()
  152. io_result = DeviceIoControl(reparse_point_handle,
  153. FSCTL_GET_REPARSE_POINT,
  154. None,
  155. 0,
  156. target_buffer,
  157. len(target_buffer),
  158. byref(n_bytes_returned),
  159. None)
  160. CloseHandle(reparse_point_handle)
  161. if not io_result:
  162. _raise_winerror(
  163. get_last_error(),
  164. 'Error reading symblic link \"%s\"'.format(path))
  165. rdb = REPARSE_DATA_BUFFER.from_buffer(target_buffer)
  166. if rdb.ReparseTag == IO_REPARSE_TAG_SYMLINK:
  167. return _preserve_encoding(path, rdb.SymbolicLinkReparseBuffer.PrintName)
  168. elif rdb.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT:
  169. return _preserve_encoding(path, rdb.MountPointReparseBuffer.PrintName)
  170. # Unsupported reparse point type
  171. _raise_winerror(
  172. ERROR_NOT_SUPPORTED,
  173. 'Error reading symblic link \"%s\"'.format(path))
  174. def _preserve_encoding(source, target):
  175. """Ensures target is the same string type (i.e. unicode or str) as source."""
  176. if is_python3():
  177. return target
  178. if isinstance(source, unicode):
  179. return unicode(target)
  180. return str(target)
  181. def _raise_winerror(code, error_desc):
  182. win_error_desc = FormatError(code).strip()
  183. error_desc = "%s: %s".format(error_desc, win_error_desc)
  184. raise WinError(code, error_desc)