blob: 26c8ad42027b281917dd5441b1e6650fa9c09719 [file] [log] [blame]
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07001# Copyright (C) 2016 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15import errno
16
Rostislav Krasnyb71d61d2020-01-24 22:29:54 +020017from pyversion import is_python3
Renaud Paquay227ad2e2016-11-01 14:37:13 -070018from ctypes import WinDLL, get_last_error, FormatError, WinError, addressof
19from ctypes import c_buffer
Remy Böhmerdbd277c2020-01-07 08:48:55 +010020from ctypes.wintypes import BOOL, BOOLEAN, LPCWSTR, DWORD, HANDLE
21from ctypes.wintypes import WCHAR, USHORT, LPVOID, ULONG
22if is_python3():
23 from ctypes import c_ubyte, Structure, Union, byref
24 from ctypes.wintypes import LPDWORD
25else:
26 # For legacy Python2 different imports are needed.
27 from ctypes.wintypes import POINTER, c_ubyte, Structure, Union, byref
28 LPDWORD = POINTER(DWORD)
Renaud Paquayd5cec5e2016-11-01 11:24:03 -070029
30kernel32 = WinDLL('kernel32', use_last_error=True)
31
Renaud Paquay227ad2e2016-11-01 14:37:13 -070032UCHAR = c_ubyte
33
Renaud Paquayd5cec5e2016-11-01 11:24:03 -070034# Win32 error codes
35ERROR_SUCCESS = 0
Renaud Paquay227ad2e2016-11-01 14:37:13 -070036ERROR_NOT_SUPPORTED = 50
Renaud Paquayd5cec5e2016-11-01 11:24:03 -070037ERROR_PRIVILEGE_NOT_HELD = 1314
38
39# Win32 API entry points
40CreateSymbolicLinkW = kernel32.CreateSymbolicLinkW
Роман Донченкоa84df062019-03-21 23:45:59 +030041CreateSymbolicLinkW.restype = BOOLEAN
Renaud Paquayd5cec5e2016-11-01 11:24:03 -070042CreateSymbolicLinkW.argtypes = (LPCWSTR, # lpSymlinkFileName In
43 LPCWSTR, # lpTargetFileName In
44 DWORD) # dwFlags In
45
46# Symbolic link creation flags
47SYMBOLIC_LINK_FLAG_FILE = 0x00
48SYMBOLIC_LINK_FLAG_DIRECTORY = 0x01
Renaud Paquay2b42d282018-10-01 14:59:48 -070049# symlink support for CreateSymbolicLink() starting with Windows 10 (1703, v10.0.14972)
50SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE = 0x02
Renaud Paquayd5cec5e2016-11-01 11:24:03 -070051
Renaud Paquay227ad2e2016-11-01 14:37:13 -070052GetFileAttributesW = kernel32.GetFileAttributesW
53GetFileAttributesW.restype = DWORD
54GetFileAttributesW.argtypes = (LPCWSTR,) # lpFileName In
55
56INVALID_FILE_ATTRIBUTES = 0xFFFFFFFF
57FILE_ATTRIBUTE_REPARSE_POINT = 0x00400
58
59CreateFileW = kernel32.CreateFileW
60CreateFileW.restype = HANDLE
61CreateFileW.argtypes = (LPCWSTR, # lpFileName In
62 DWORD, # dwDesiredAccess In
63 DWORD, # dwShareMode In
64 LPVOID, # lpSecurityAttributes In_opt
65 DWORD, # dwCreationDisposition In
66 DWORD, # dwFlagsAndAttributes In
67 HANDLE) # hTemplateFile In_opt
68
69CloseHandle = kernel32.CloseHandle
70CloseHandle.restype = BOOL
71CloseHandle.argtypes = (HANDLE,) # hObject In
72
73INVALID_HANDLE_VALUE = HANDLE(-1).value
74OPEN_EXISTING = 3
75FILE_FLAG_BACKUP_SEMANTICS = 0x02000000
76FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000
77
78DeviceIoControl = kernel32.DeviceIoControl
79DeviceIoControl.restype = BOOL
80DeviceIoControl.argtypes = (HANDLE, # hDevice In
81 DWORD, # dwIoControlCode In
82 LPVOID, # lpInBuffer In_opt
83 DWORD, # nInBufferSize In
84 LPVOID, # lpOutBuffer Out_opt
85 DWORD, # nOutBufferSize In
86 LPDWORD, # lpBytesReturned Out_opt
87 LPVOID) # lpOverlapped Inout_opt
88
89# Device I/O control flags and options
90FSCTL_GET_REPARSE_POINT = 0x000900A8
91IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003
92IO_REPARSE_TAG_SYMLINK = 0xA000000C
93MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 0x4000
94
95
96class GENERIC_REPARSE_BUFFER(Structure):
97 _fields_ = (('DataBuffer', UCHAR * 1),)
98
99
100class SYMBOLIC_LINK_REPARSE_BUFFER(Structure):
101 _fields_ = (('SubstituteNameOffset', USHORT),
102 ('SubstituteNameLength', USHORT),
103 ('PrintNameOffset', USHORT),
104 ('PrintNameLength', USHORT),
105 ('Flags', ULONG),
106 ('PathBuffer', WCHAR * 1))
107
108 @property
109 def PrintName(self):
110 arrayt = WCHAR * (self.PrintNameLength // 2)
111 offset = type(self).PathBuffer.offset + self.PrintNameOffset
112 return arrayt.from_address(addressof(self) + offset).value
113
114
115class MOUNT_POINT_REPARSE_BUFFER(Structure):
116 _fields_ = (('SubstituteNameOffset', USHORT),
117 ('SubstituteNameLength', USHORT),
118 ('PrintNameOffset', USHORT),
119 ('PrintNameLength', USHORT),
120 ('PathBuffer', WCHAR * 1))
121
122 @property
123 def PrintName(self):
124 arrayt = WCHAR * (self.PrintNameLength // 2)
125 offset = type(self).PathBuffer.offset + self.PrintNameOffset
126 return arrayt.from_address(addressof(self) + offset).value
127
128
129class REPARSE_DATA_BUFFER(Structure):
130 class REPARSE_BUFFER(Union):
131 _fields_ = (('SymbolicLinkReparseBuffer', SYMBOLIC_LINK_REPARSE_BUFFER),
132 ('MountPointReparseBuffer', MOUNT_POINT_REPARSE_BUFFER),
133 ('GenericReparseBuffer', GENERIC_REPARSE_BUFFER))
134 _fields_ = (('ReparseTag', ULONG),
135 ('ReparseDataLength', USHORT),
136 ('Reserved', USHORT),
137 ('ReparseBuffer', REPARSE_BUFFER))
138 _anonymous_ = ('ReparseBuffer',)
139
Renaud Paquayd5cec5e2016-11-01 11:24:03 -0700140
141def create_filesymlink(source, link_name):
142 """Creates a Windows file symbolic link source pointing to link_name."""
143 _create_symlink(source, link_name, SYMBOLIC_LINK_FLAG_FILE)
144
145
146def create_dirsymlink(source, link_name):
147 """Creates a Windows directory symbolic link source pointing to link_name.
148 """
149 _create_symlink(source, link_name, SYMBOLIC_LINK_FLAG_DIRECTORY)
150
151
152def _create_symlink(source, link_name, dwFlags):
David Pursehouse3cda50a2020-02-13 13:17:03 +0900153 if not CreateSymbolicLinkW(link_name, source,
154 dwFlags | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE):
Renaud Paquay2b42d282018-10-01 14:59:48 -0700155 # See https://github.com/golang/go/pull/24307/files#diff-b87bc12e4da2497308f9ef746086e4f0
156 # "the unprivileged create flag is unsupported below Windows 10 (1703, v10.0.14972).
157 # retry without it."
Роман Донченкоa84df062019-03-21 23:45:59 +0300158 if not CreateSymbolicLinkW(link_name, source, dwFlags):
159 code = get_last_error()
Renaud Paquay2b42d282018-10-01 14:59:48 -0700160 error_desc = FormatError(code).strip()
161 if code == ERROR_PRIVILEGE_NOT_HELD:
162 raise OSError(errno.EPERM, error_desc, link_name)
163 _raise_winerror(
164 code,
165 'Error creating symbolic link \"%s\"'.format(link_name))
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700166
167
168def islink(path):
169 result = GetFileAttributesW(path)
170 if result == INVALID_FILE_ATTRIBUTES:
171 return False
172 return bool(result & FILE_ATTRIBUTE_REPARSE_POINT)
173
174
175def readlink(path):
176 reparse_point_handle = CreateFileW(path,
177 0,
178 0,
179 None,
180 OPEN_EXISTING,
181 FILE_FLAG_OPEN_REPARSE_POINT |
182 FILE_FLAG_BACKUP_SEMANTICS,
183 None)
184 if reparse_point_handle == INVALID_HANDLE_VALUE:
185 _raise_winerror(
186 get_last_error(),
Rostislav Krasny9da67fe2020-01-24 23:15:09 +0200187 'Error opening symbolic link \"%s\"'.format(path))
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700188 target_buffer = c_buffer(MAXIMUM_REPARSE_DATA_BUFFER_SIZE)
189 n_bytes_returned = DWORD()
190 io_result = DeviceIoControl(reparse_point_handle,
191 FSCTL_GET_REPARSE_POINT,
192 None,
193 0,
194 target_buffer,
195 len(target_buffer),
196 byref(n_bytes_returned),
197 None)
198 CloseHandle(reparse_point_handle)
199 if not io_result:
200 _raise_winerror(
201 get_last_error(),
Rostislav Krasny9da67fe2020-01-24 23:15:09 +0200202 'Error reading symbolic link \"%s\"'.format(path))
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700203 rdb = REPARSE_DATA_BUFFER.from_buffer(target_buffer)
204 if rdb.ReparseTag == IO_REPARSE_TAG_SYMLINK:
205 return _preserve_encoding(path, rdb.SymbolicLinkReparseBuffer.PrintName)
206 elif rdb.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT:
207 return _preserve_encoding(path, rdb.MountPointReparseBuffer.PrintName)
208 # Unsupported reparse point type
209 _raise_winerror(
210 ERROR_NOT_SUPPORTED,
Rostislav Krasny9da67fe2020-01-24 23:15:09 +0200211 'Error reading symbolic link \"%s\"'.format(path))
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700212
213
214def _preserve_encoding(source, target):
215 """Ensures target is the same string type (i.e. unicode or str) as source."""
Rostislav Krasnyb71d61d2020-01-24 22:29:54 +0200216
217 if is_python3():
218 return target
219
David Pursehousea46bf7d2020-02-15 12:45:53 +0900220 if isinstance(source, unicode): # noqa: F821
221 return unicode(target) # noqa: F821
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700222 return str(target)
223
224
225def _raise_winerror(code, error_desc):
226 win_error_desc = FormatError(code).strip()
227 error_desc = "%s: %s".format(error_desc, win_error_desc)
228 raise WinError(code, error_desc)