blob: e9b15f46291501eaf88d35d2a39aa315533c4e81 [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
Renaud Paquay227ad2e2016-11-01 14:37:13 -070017from ctypes import WinDLL, get_last_error, FormatError, WinError, addressof
Mike Frysingeracf63b22019-06-13 02:24:21 -040018from ctypes import c_buffer, c_ubyte, Structure, Union, byref
Remy Böhmerdbd277c2020-01-07 08:48:55 +010019from ctypes.wintypes import BOOL, BOOLEAN, LPCWSTR, DWORD, HANDLE
Mike Frysingeracf63b22019-06-13 02:24:21 -040020from ctypes.wintypes import WCHAR, USHORT, LPVOID, ULONG, LPDWORD
Renaud Paquayd5cec5e2016-11-01 11:24:03 -070021
Gavin Makea2e3302023-03-11 06:46:20 +000022kernel32 = WinDLL("kernel32", use_last_error=True)
Renaud Paquayd5cec5e2016-11-01 11:24:03 -070023
Renaud Paquay227ad2e2016-11-01 14:37:13 -070024UCHAR = c_ubyte
25
Renaud Paquayd5cec5e2016-11-01 11:24:03 -070026# Win32 error codes
27ERROR_SUCCESS = 0
Renaud Paquay227ad2e2016-11-01 14:37:13 -070028ERROR_NOT_SUPPORTED = 50
Renaud Paquayd5cec5e2016-11-01 11:24:03 -070029ERROR_PRIVILEGE_NOT_HELD = 1314
30
31# Win32 API entry points
32CreateSymbolicLinkW = kernel32.CreateSymbolicLinkW
Роман Донченкоa84df062019-03-21 23:45:59 +030033CreateSymbolicLinkW.restype = BOOLEAN
Gavin Makea2e3302023-03-11 06:46:20 +000034CreateSymbolicLinkW.argtypes = (
35 LPCWSTR, # lpSymlinkFileName In
36 LPCWSTR, # lpTargetFileName In
37 DWORD, # dwFlags In
38)
Renaud Paquayd5cec5e2016-11-01 11:24:03 -070039
40# Symbolic link creation flags
41SYMBOLIC_LINK_FLAG_FILE = 0x00
42SYMBOLIC_LINK_FLAG_DIRECTORY = 0x01
Gavin Makea2e3302023-03-11 06:46:20 +000043# symlink support for CreateSymbolicLink() starting with Windows 10 (1703,
44# v10.0.14972)
Renaud Paquay2b42d282018-10-01 14:59:48 -070045SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE = 0x02
Renaud Paquayd5cec5e2016-11-01 11:24:03 -070046
Renaud Paquay227ad2e2016-11-01 14:37:13 -070047GetFileAttributesW = kernel32.GetFileAttributesW
48GetFileAttributesW.restype = DWORD
49GetFileAttributesW.argtypes = (LPCWSTR,) # lpFileName In
50
51INVALID_FILE_ATTRIBUTES = 0xFFFFFFFF
52FILE_ATTRIBUTE_REPARSE_POINT = 0x00400
53
54CreateFileW = kernel32.CreateFileW
55CreateFileW.restype = HANDLE
Gavin Makea2e3302023-03-11 06:46:20 +000056CreateFileW.argtypes = (
57 LPCWSTR, # lpFileName In
58 DWORD, # dwDesiredAccess In
59 DWORD, # dwShareMode In
60 LPVOID, # lpSecurityAttributes In_opt
61 DWORD, # dwCreationDisposition In
62 DWORD, # dwFlagsAndAttributes In
63 HANDLE, # hTemplateFile In_opt
64)
Renaud Paquay227ad2e2016-11-01 14:37:13 -070065
66CloseHandle = kernel32.CloseHandle
67CloseHandle.restype = BOOL
68CloseHandle.argtypes = (HANDLE,) # hObject In
69
70INVALID_HANDLE_VALUE = HANDLE(-1).value
71OPEN_EXISTING = 3
72FILE_FLAG_BACKUP_SEMANTICS = 0x02000000
73FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000
74
75DeviceIoControl = kernel32.DeviceIoControl
76DeviceIoControl.restype = BOOL
Gavin Makea2e3302023-03-11 06:46:20 +000077DeviceIoControl.argtypes = (
78 HANDLE, # hDevice In
79 DWORD, # dwIoControlCode In
80 LPVOID, # lpInBuffer In_opt
81 DWORD, # nInBufferSize In
82 LPVOID, # lpOutBuffer Out_opt
83 DWORD, # nOutBufferSize In
84 LPDWORD, # lpBytesReturned Out_opt
85 LPVOID, # lpOverlapped Inout_opt
86)
Renaud Paquay227ad2e2016-11-01 14:37:13 -070087
88# Device I/O control flags and options
89FSCTL_GET_REPARSE_POINT = 0x000900A8
90IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003
91IO_REPARSE_TAG_SYMLINK = 0xA000000C
92MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 0x4000
93
94
95class GENERIC_REPARSE_BUFFER(Structure):
Gavin Makea2e3302023-03-11 06:46:20 +000096 _fields_ = (("DataBuffer", UCHAR * 1),)
Renaud Paquay227ad2e2016-11-01 14:37:13 -070097
98
99class SYMBOLIC_LINK_REPARSE_BUFFER(Structure):
Gavin Makea2e3302023-03-11 06:46:20 +0000100 _fields_ = (
101 ("SubstituteNameOffset", USHORT),
102 ("SubstituteNameLength", USHORT),
103 ("PrintNameOffset", USHORT),
104 ("PrintNameLength", USHORT),
105 ("Flags", ULONG),
106 ("PathBuffer", WCHAR * 1),
107 )
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700108
Gavin Makea2e3302023-03-11 06:46:20 +0000109 @property
110 def PrintName(self):
111 arrayt = WCHAR * (self.PrintNameLength // 2)
112 offset = type(self).PathBuffer.offset + self.PrintNameOffset
113 return arrayt.from_address(addressof(self) + offset).value
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700114
115
116class MOUNT_POINT_REPARSE_BUFFER(Structure):
Gavin Makea2e3302023-03-11 06:46:20 +0000117 _fields_ = (
118 ("SubstituteNameOffset", USHORT),
119 ("SubstituteNameLength", USHORT),
120 ("PrintNameOffset", USHORT),
121 ("PrintNameLength", USHORT),
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 REPARSE_DATA_BUFFER(Structure):
Gavin Makea2e3302023-03-11 06:46:20 +0000133 class REPARSE_BUFFER(Union):
134 _fields_ = (
135 ("SymbolicLinkReparseBuffer", SYMBOLIC_LINK_REPARSE_BUFFER),
136 ("MountPointReparseBuffer", MOUNT_POINT_REPARSE_BUFFER),
137 ("GenericReparseBuffer", GENERIC_REPARSE_BUFFER),
138 )
139
140 _fields_ = (
141 ("ReparseTag", ULONG),
142 ("ReparseDataLength", USHORT),
143 ("Reserved", USHORT),
144 ("ReparseBuffer", REPARSE_BUFFER),
145 )
146 _anonymous_ = ("ReparseBuffer",)
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700147
Renaud Paquayd5cec5e2016-11-01 11:24:03 -0700148
149def create_filesymlink(source, link_name):
Gavin Makea2e3302023-03-11 06:46:20 +0000150 """Creates a Windows file symbolic link source pointing to link_name."""
151 _create_symlink(source, link_name, SYMBOLIC_LINK_FLAG_FILE)
Renaud Paquayd5cec5e2016-11-01 11:24:03 -0700152
153
154def create_dirsymlink(source, link_name):
Gavin Makea2e3302023-03-11 06:46:20 +0000155 """Creates a Windows directory symbolic link source pointing to link_name.""" # noqa: E501
156 _create_symlink(source, link_name, SYMBOLIC_LINK_FLAG_DIRECTORY)
Renaud Paquayd5cec5e2016-11-01 11:24:03 -0700157
158
159def _create_symlink(source, link_name, dwFlags):
Gavin Makea2e3302023-03-11 06:46:20 +0000160 if not CreateSymbolicLinkW(
161 link_name,
162 source,
163 dwFlags | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE,
164 ):
165 # See https://github.com/golang/go/pull/24307/files#diff-b87bc12e4da2497308f9ef746086e4f0 # noqa: E501
166 # "the unprivileged create flag is unsupported below Windows 10 (1703,
167 # v10.0.14972). retry without it."
168 if not CreateSymbolicLinkW(link_name, source, dwFlags):
169 code = get_last_error()
170 error_desc = FormatError(code).strip()
171 if code == ERROR_PRIVILEGE_NOT_HELD:
172 raise OSError(errno.EPERM, error_desc, link_name)
173 _raise_winerror(
174 code, 'Error creating symbolic link "{}"'.format(link_name)
175 )
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700176
177
178def islink(path):
Gavin Makea2e3302023-03-11 06:46:20 +0000179 result = GetFileAttributesW(path)
180 if result == INVALID_FILE_ATTRIBUTES:
181 return False
182 return bool(result & FILE_ATTRIBUTE_REPARSE_POINT)
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700183
184
185def readlink(path):
Gavin Makea2e3302023-03-11 06:46:20 +0000186 reparse_point_handle = CreateFileW(
187 path,
188 0,
189 0,
190 None,
191 OPEN_EXISTING,
192 FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS,
193 None,
194 )
195 if reparse_point_handle == INVALID_HANDLE_VALUE:
196 _raise_winerror(
197 get_last_error(), 'Error opening symbolic link "{}"'.format(path)
198 )
199 target_buffer = c_buffer(MAXIMUM_REPARSE_DATA_BUFFER_SIZE)
200 n_bytes_returned = DWORD()
201 io_result = DeviceIoControl(
202 reparse_point_handle,
203 FSCTL_GET_REPARSE_POINT,
204 None,
205 0,
206 target_buffer,
207 len(target_buffer),
208 byref(n_bytes_returned),
209 None,
210 )
211 CloseHandle(reparse_point_handle)
212 if not io_result:
213 _raise_winerror(
214 get_last_error(), 'Error reading symbolic link "{}"'.format(path)
215 )
216 rdb = REPARSE_DATA_BUFFER.from_buffer(target_buffer)
217 if rdb.ReparseTag == IO_REPARSE_TAG_SYMLINK:
218 return rdb.SymbolicLinkReparseBuffer.PrintName
219 elif rdb.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT:
220 return rdb.MountPointReparseBuffer.PrintName
221 # Unsupported reparse point type.
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700222 _raise_winerror(
Gavin Makea2e3302023-03-11 06:46:20 +0000223 ERROR_NOT_SUPPORTED, 'Error reading symbolic link "{}"'.format(path)
224 )
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700225
226
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700227def _raise_winerror(code, error_desc):
Gavin Makea2e3302023-03-11 06:46:20 +0000228 win_error_desc = FormatError(code).strip()
229 error_desc = "{0}: {1}".format(error_desc, win_error_desc)
230 raise WinError(code, error_desc)