blob: 80a52639e5c9a1df3d931ccbf67137483c1cabf5 [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
Mike Frysinger64477332023-08-21 21:20:32 -040015from ctypes import addressof
16from ctypes import byref
17from ctypes import c_buffer
18from ctypes import c_ubyte
19from ctypes import FormatError
20from ctypes import get_last_error
21from ctypes import Structure
22from ctypes import Union
23from ctypes import WinDLL
24from ctypes import WinError
25from ctypes.wintypes import BOOL
26from ctypes.wintypes import BOOLEAN
27from ctypes.wintypes import DWORD
28from ctypes.wintypes import HANDLE
29from ctypes.wintypes import LPCWSTR
30from ctypes.wintypes import LPDWORD
31from ctypes.wintypes import LPVOID
32from ctypes.wintypes import ULONG
33from ctypes.wintypes import USHORT
34from ctypes.wintypes import WCHAR
Renaud Paquayd5cec5e2016-11-01 11:24:03 -070035import errno
36
Renaud Paquayd5cec5e2016-11-01 11:24:03 -070037
Gavin Makea2e3302023-03-11 06:46:20 +000038kernel32 = WinDLL("kernel32", use_last_error=True)
Renaud Paquayd5cec5e2016-11-01 11:24:03 -070039
Renaud Paquay227ad2e2016-11-01 14:37:13 -070040UCHAR = c_ubyte
41
Renaud Paquayd5cec5e2016-11-01 11:24:03 -070042# Win32 error codes
43ERROR_SUCCESS = 0
Renaud Paquay227ad2e2016-11-01 14:37:13 -070044ERROR_NOT_SUPPORTED = 50
Renaud Paquayd5cec5e2016-11-01 11:24:03 -070045ERROR_PRIVILEGE_NOT_HELD = 1314
46
47# Win32 API entry points
48CreateSymbolicLinkW = kernel32.CreateSymbolicLinkW
Роман Донченкоa84df062019-03-21 23:45:59 +030049CreateSymbolicLinkW.restype = BOOLEAN
Gavin Makea2e3302023-03-11 06:46:20 +000050CreateSymbolicLinkW.argtypes = (
51 LPCWSTR, # lpSymlinkFileName In
52 LPCWSTR, # lpTargetFileName In
53 DWORD, # dwFlags In
54)
Renaud Paquayd5cec5e2016-11-01 11:24:03 -070055
56# Symbolic link creation flags
57SYMBOLIC_LINK_FLAG_FILE = 0x00
58SYMBOLIC_LINK_FLAG_DIRECTORY = 0x01
Gavin Makea2e3302023-03-11 06:46:20 +000059# symlink support for CreateSymbolicLink() starting with Windows 10 (1703,
60# v10.0.14972)
Renaud Paquay2b42d282018-10-01 14:59:48 -070061SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE = 0x02
Renaud Paquayd5cec5e2016-11-01 11:24:03 -070062
Renaud Paquay227ad2e2016-11-01 14:37:13 -070063GetFileAttributesW = kernel32.GetFileAttributesW
64GetFileAttributesW.restype = DWORD
65GetFileAttributesW.argtypes = (LPCWSTR,) # lpFileName In
66
67INVALID_FILE_ATTRIBUTES = 0xFFFFFFFF
68FILE_ATTRIBUTE_REPARSE_POINT = 0x00400
69
70CreateFileW = kernel32.CreateFileW
71CreateFileW.restype = HANDLE
Gavin Makea2e3302023-03-11 06:46:20 +000072CreateFileW.argtypes = (
73 LPCWSTR, # lpFileName In
74 DWORD, # dwDesiredAccess In
75 DWORD, # dwShareMode In
76 LPVOID, # lpSecurityAttributes In_opt
77 DWORD, # dwCreationDisposition In
78 DWORD, # dwFlagsAndAttributes In
79 HANDLE, # hTemplateFile In_opt
80)
Renaud Paquay227ad2e2016-11-01 14:37:13 -070081
82CloseHandle = kernel32.CloseHandle
83CloseHandle.restype = BOOL
84CloseHandle.argtypes = (HANDLE,) # hObject In
85
86INVALID_HANDLE_VALUE = HANDLE(-1).value
87OPEN_EXISTING = 3
88FILE_FLAG_BACKUP_SEMANTICS = 0x02000000
89FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000
90
91DeviceIoControl = kernel32.DeviceIoControl
92DeviceIoControl.restype = BOOL
Gavin Makea2e3302023-03-11 06:46:20 +000093DeviceIoControl.argtypes = (
94 HANDLE, # hDevice In
95 DWORD, # dwIoControlCode In
96 LPVOID, # lpInBuffer In_opt
97 DWORD, # nInBufferSize In
98 LPVOID, # lpOutBuffer Out_opt
99 DWORD, # nOutBufferSize In
100 LPDWORD, # lpBytesReturned Out_opt
101 LPVOID, # lpOverlapped Inout_opt
102)
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700103
104# Device I/O control flags and options
105FSCTL_GET_REPARSE_POINT = 0x000900A8
106IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003
107IO_REPARSE_TAG_SYMLINK = 0xA000000C
108MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 0x4000
109
110
111class GENERIC_REPARSE_BUFFER(Structure):
Gavin Makea2e3302023-03-11 06:46:20 +0000112 _fields_ = (("DataBuffer", UCHAR * 1),)
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700113
114
115class SYMBOLIC_LINK_REPARSE_BUFFER(Structure):
Gavin Makea2e3302023-03-11 06:46:20 +0000116 _fields_ = (
117 ("SubstituteNameOffset", USHORT),
118 ("SubstituteNameLength", USHORT),
119 ("PrintNameOffset", USHORT),
120 ("PrintNameLength", USHORT),
121 ("Flags", ULONG),
122 ("PathBuffer", WCHAR * 1),
123 )
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700124
Gavin Makea2e3302023-03-11 06:46:20 +0000125 @property
126 def PrintName(self):
127 arrayt = WCHAR * (self.PrintNameLength // 2)
128 offset = type(self).PathBuffer.offset + self.PrintNameOffset
129 return arrayt.from_address(addressof(self) + offset).value
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700130
131
132class MOUNT_POINT_REPARSE_BUFFER(Structure):
Gavin Makea2e3302023-03-11 06:46:20 +0000133 _fields_ = (
134 ("SubstituteNameOffset", USHORT),
135 ("SubstituteNameLength", USHORT),
136 ("PrintNameOffset", USHORT),
137 ("PrintNameLength", USHORT),
138 ("PathBuffer", WCHAR * 1),
139 )
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700140
Gavin Makea2e3302023-03-11 06:46:20 +0000141 @property
142 def PrintName(self):
143 arrayt = WCHAR * (self.PrintNameLength // 2)
144 offset = type(self).PathBuffer.offset + self.PrintNameOffset
145 return arrayt.from_address(addressof(self) + offset).value
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700146
147
148class REPARSE_DATA_BUFFER(Structure):
Gavin Makea2e3302023-03-11 06:46:20 +0000149 class REPARSE_BUFFER(Union):
150 _fields_ = (
151 ("SymbolicLinkReparseBuffer", SYMBOLIC_LINK_REPARSE_BUFFER),
152 ("MountPointReparseBuffer", MOUNT_POINT_REPARSE_BUFFER),
153 ("GenericReparseBuffer", GENERIC_REPARSE_BUFFER),
154 )
155
156 _fields_ = (
157 ("ReparseTag", ULONG),
158 ("ReparseDataLength", USHORT),
159 ("Reserved", USHORT),
160 ("ReparseBuffer", REPARSE_BUFFER),
161 )
162 _anonymous_ = ("ReparseBuffer",)
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700163
Renaud Paquayd5cec5e2016-11-01 11:24:03 -0700164
165def create_filesymlink(source, link_name):
Gavin Makea2e3302023-03-11 06:46:20 +0000166 """Creates a Windows file symbolic link source pointing to link_name."""
167 _create_symlink(source, link_name, SYMBOLIC_LINK_FLAG_FILE)
Renaud Paquayd5cec5e2016-11-01 11:24:03 -0700168
169
170def create_dirsymlink(source, link_name):
Gavin Makea2e3302023-03-11 06:46:20 +0000171 """Creates a Windows directory symbolic link source pointing to link_name.""" # noqa: E501
172 _create_symlink(source, link_name, SYMBOLIC_LINK_FLAG_DIRECTORY)
Renaud Paquayd5cec5e2016-11-01 11:24:03 -0700173
174
175def _create_symlink(source, link_name, dwFlags):
Gavin Makea2e3302023-03-11 06:46:20 +0000176 if not CreateSymbolicLinkW(
177 link_name,
178 source,
179 dwFlags | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE,
180 ):
181 # See https://github.com/golang/go/pull/24307/files#diff-b87bc12e4da2497308f9ef746086e4f0 # noqa: E501
182 # "the unprivileged create flag is unsupported below Windows 10 (1703,
183 # v10.0.14972). retry without it."
184 if not CreateSymbolicLinkW(link_name, source, dwFlags):
185 code = get_last_error()
186 error_desc = FormatError(code).strip()
187 if code == ERROR_PRIVILEGE_NOT_HELD:
188 raise OSError(errno.EPERM, error_desc, link_name)
189 _raise_winerror(
190 code, 'Error creating symbolic link "{}"'.format(link_name)
191 )
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700192
193
194def islink(path):
Gavin Makea2e3302023-03-11 06:46:20 +0000195 result = GetFileAttributesW(path)
196 if result == INVALID_FILE_ATTRIBUTES:
197 return False
198 return bool(result & FILE_ATTRIBUTE_REPARSE_POINT)
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700199
200
201def readlink(path):
Gavin Makea2e3302023-03-11 06:46:20 +0000202 reparse_point_handle = CreateFileW(
203 path,
204 0,
205 0,
206 None,
207 OPEN_EXISTING,
208 FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS,
209 None,
210 )
211 if reparse_point_handle == INVALID_HANDLE_VALUE:
212 _raise_winerror(
213 get_last_error(), 'Error opening symbolic link "{}"'.format(path)
214 )
215 target_buffer = c_buffer(MAXIMUM_REPARSE_DATA_BUFFER_SIZE)
216 n_bytes_returned = DWORD()
217 io_result = DeviceIoControl(
218 reparse_point_handle,
219 FSCTL_GET_REPARSE_POINT,
220 None,
221 0,
222 target_buffer,
223 len(target_buffer),
224 byref(n_bytes_returned),
225 None,
226 )
227 CloseHandle(reparse_point_handle)
228 if not io_result:
229 _raise_winerror(
230 get_last_error(), 'Error reading symbolic link "{}"'.format(path)
231 )
232 rdb = REPARSE_DATA_BUFFER.from_buffer(target_buffer)
233 if rdb.ReparseTag == IO_REPARSE_TAG_SYMLINK:
234 return rdb.SymbolicLinkReparseBuffer.PrintName
235 elif rdb.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT:
236 return rdb.MountPointReparseBuffer.PrintName
237 # Unsupported reparse point type.
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700238 _raise_winerror(
Gavin Makea2e3302023-03-11 06:46:20 +0000239 ERROR_NOT_SUPPORTED, 'Error reading symbolic link "{}"'.format(path)
240 )
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700241
242
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700243def _raise_winerror(code, error_desc):
Gavin Makea2e3302023-03-11 06:46:20 +0000244 win_error_desc = FormatError(code).strip()
245 error_desc = "{0}: {1}".format(error_desc, win_error_desc)
246 raise WinError(code, error_desc)