platform_utils_win32.py 8.0 KB

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