platform_utils_win32.py 8.0 KB

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