platform_utils_win32.py 8.3 KB

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