blob: f10d9d0a5a8e9be220b6a804d3ae934d667042ab [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)
Jason R. Coombsb32ccbb2023-09-29 11:04:49 -0400189 _raise_winerror(code, f'Error creating symbolic link "{link_name}"')
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700190
191
192def islink(path):
Gavin Makea2e3302023-03-11 06:46:20 +0000193 result = GetFileAttributesW(path)
194 if result == INVALID_FILE_ATTRIBUTES:
195 return False
196 return bool(result & FILE_ATTRIBUTE_REPARSE_POINT)
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700197
198
199def readlink(path):
Gavin Makea2e3302023-03-11 06:46:20 +0000200 reparse_point_handle = CreateFileW(
201 path,
202 0,
203 0,
204 None,
205 OPEN_EXISTING,
206 FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS,
207 None,
208 )
209 if reparse_point_handle == INVALID_HANDLE_VALUE:
210 _raise_winerror(
Jason R. Coombsb32ccbb2023-09-29 11:04:49 -0400211 get_last_error(), f'Error opening symbolic link "{path}"'
Gavin Makea2e3302023-03-11 06:46:20 +0000212 )
213 target_buffer = c_buffer(MAXIMUM_REPARSE_DATA_BUFFER_SIZE)
214 n_bytes_returned = DWORD()
215 io_result = DeviceIoControl(
216 reparse_point_handle,
217 FSCTL_GET_REPARSE_POINT,
218 None,
219 0,
220 target_buffer,
221 len(target_buffer),
222 byref(n_bytes_returned),
223 None,
224 )
225 CloseHandle(reparse_point_handle)
226 if not io_result:
227 _raise_winerror(
Jason R. Coombsb32ccbb2023-09-29 11:04:49 -0400228 get_last_error(), f'Error reading symbolic link "{path}"'
Gavin Makea2e3302023-03-11 06:46:20 +0000229 )
230 rdb = REPARSE_DATA_BUFFER.from_buffer(target_buffer)
231 if rdb.ReparseTag == IO_REPARSE_TAG_SYMLINK:
232 return rdb.SymbolicLinkReparseBuffer.PrintName
233 elif rdb.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT:
234 return rdb.MountPointReparseBuffer.PrintName
235 # Unsupported reparse point type.
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700236 _raise_winerror(
Jason R. Coombsb32ccbb2023-09-29 11:04:49 -0400237 ERROR_NOT_SUPPORTED, f'Error reading symbolic link "{path}"'
Gavin Makea2e3302023-03-11 06:46:20 +0000238 )
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700239
240
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700241def _raise_winerror(code, error_desc):
Gavin Makea2e3302023-03-11 06:46:20 +0000242 win_error_desc = FormatError(code).strip()
Jason R. Coombsb32ccbb2023-09-29 11:04:49 -0400243 error_desc = f"{error_desc}: {win_error_desc}"
Gavin Makea2e3302023-03-11 06:46:20 +0000244 raise WinError(code, error_desc)