blob: bf916d47826836d0fb4066916e8a1eccb96ab962 [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
22kernel32 = WinDLL('kernel32', use_last_error=True)
23
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
Renaud Paquayd5cec5e2016-11-01 11:24:03 -070034CreateSymbolicLinkW.argtypes = (LPCWSTR, # lpSymlinkFileName In
35 LPCWSTR, # lpTargetFileName In
36 DWORD) # dwFlags In
37
38# Symbolic link creation flags
39SYMBOLIC_LINK_FLAG_FILE = 0x00
40SYMBOLIC_LINK_FLAG_DIRECTORY = 0x01
Renaud Paquay2b42d282018-10-01 14:59:48 -070041# symlink support for CreateSymbolicLink() starting with Windows 10 (1703, v10.0.14972)
42SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE = 0x02
Renaud Paquayd5cec5e2016-11-01 11:24:03 -070043
Renaud Paquay227ad2e2016-11-01 14:37:13 -070044GetFileAttributesW = kernel32.GetFileAttributesW
45GetFileAttributesW.restype = DWORD
46GetFileAttributesW.argtypes = (LPCWSTR,) # lpFileName In
47
48INVALID_FILE_ATTRIBUTES = 0xFFFFFFFF
49FILE_ATTRIBUTE_REPARSE_POINT = 0x00400
50
51CreateFileW = kernel32.CreateFileW
52CreateFileW.restype = HANDLE
53CreateFileW.argtypes = (LPCWSTR, # lpFileName In
54 DWORD, # dwDesiredAccess In
55 DWORD, # dwShareMode In
56 LPVOID, # lpSecurityAttributes In_opt
57 DWORD, # dwCreationDisposition In
58 DWORD, # dwFlagsAndAttributes In
59 HANDLE) # hTemplateFile In_opt
60
61CloseHandle = kernel32.CloseHandle
62CloseHandle.restype = BOOL
63CloseHandle.argtypes = (HANDLE,) # hObject In
64
65INVALID_HANDLE_VALUE = HANDLE(-1).value
66OPEN_EXISTING = 3
67FILE_FLAG_BACKUP_SEMANTICS = 0x02000000
68FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000
69
70DeviceIoControl = kernel32.DeviceIoControl
71DeviceIoControl.restype = BOOL
72DeviceIoControl.argtypes = (HANDLE, # hDevice In
73 DWORD, # dwIoControlCode In
74 LPVOID, # lpInBuffer In_opt
75 DWORD, # nInBufferSize In
76 LPVOID, # lpOutBuffer Out_opt
77 DWORD, # nOutBufferSize In
78 LPDWORD, # lpBytesReturned Out_opt
79 LPVOID) # lpOverlapped Inout_opt
80
81# Device I/O control flags and options
82FSCTL_GET_REPARSE_POINT = 0x000900A8
83IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003
84IO_REPARSE_TAG_SYMLINK = 0xA000000C
85MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 0x4000
86
87
88class GENERIC_REPARSE_BUFFER(Structure):
89 _fields_ = (('DataBuffer', UCHAR * 1),)
90
91
92class SYMBOLIC_LINK_REPARSE_BUFFER(Structure):
93 _fields_ = (('SubstituteNameOffset', USHORT),
94 ('SubstituteNameLength', USHORT),
95 ('PrintNameOffset', USHORT),
96 ('PrintNameLength', USHORT),
97 ('Flags', ULONG),
98 ('PathBuffer', WCHAR * 1))
99
100 @property
101 def PrintName(self):
102 arrayt = WCHAR * (self.PrintNameLength // 2)
103 offset = type(self).PathBuffer.offset + self.PrintNameOffset
104 return arrayt.from_address(addressof(self) + offset).value
105
106
107class MOUNT_POINT_REPARSE_BUFFER(Structure):
108 _fields_ = (('SubstituteNameOffset', USHORT),
109 ('SubstituteNameLength', USHORT),
110 ('PrintNameOffset', USHORT),
111 ('PrintNameLength', USHORT),
112 ('PathBuffer', WCHAR * 1))
113
114 @property
115 def PrintName(self):
116 arrayt = WCHAR * (self.PrintNameLength // 2)
117 offset = type(self).PathBuffer.offset + self.PrintNameOffset
118 return arrayt.from_address(addressof(self) + offset).value
119
120
121class REPARSE_DATA_BUFFER(Structure):
122 class REPARSE_BUFFER(Union):
123 _fields_ = (('SymbolicLinkReparseBuffer', SYMBOLIC_LINK_REPARSE_BUFFER),
124 ('MountPointReparseBuffer', MOUNT_POINT_REPARSE_BUFFER),
125 ('GenericReparseBuffer', GENERIC_REPARSE_BUFFER))
126 _fields_ = (('ReparseTag', ULONG),
127 ('ReparseDataLength', USHORT),
128 ('Reserved', USHORT),
129 ('ReparseBuffer', REPARSE_BUFFER))
130 _anonymous_ = ('ReparseBuffer',)
131
Renaud Paquayd5cec5e2016-11-01 11:24:03 -0700132
133def create_filesymlink(source, link_name):
134 """Creates a Windows file symbolic link source pointing to link_name."""
135 _create_symlink(source, link_name, SYMBOLIC_LINK_FLAG_FILE)
136
137
138def create_dirsymlink(source, link_name):
139 """Creates a Windows directory symbolic link source pointing to link_name.
140 """
141 _create_symlink(source, link_name, SYMBOLIC_LINK_FLAG_DIRECTORY)
142
143
144def _create_symlink(source, link_name, dwFlags):
David Pursehouse3cda50a2020-02-13 13:17:03 +0900145 if not CreateSymbolicLinkW(link_name, source,
146 dwFlags | SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE):
Renaud Paquay2b42d282018-10-01 14:59:48 -0700147 # See https://github.com/golang/go/pull/24307/files#diff-b87bc12e4da2497308f9ef746086e4f0
148 # "the unprivileged create flag is unsupported below Windows 10 (1703, v10.0.14972).
149 # retry without it."
Роман Донченкоa84df062019-03-21 23:45:59 +0300150 if not CreateSymbolicLinkW(link_name, source, dwFlags):
151 code = get_last_error()
Renaud Paquay2b42d282018-10-01 14:59:48 -0700152 error_desc = FormatError(code).strip()
153 if code == ERROR_PRIVILEGE_NOT_HELD:
154 raise OSError(errno.EPERM, error_desc, link_name)
155 _raise_winerror(
156 code,
157 'Error creating symbolic link \"%s\"'.format(link_name))
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700158
159
160def islink(path):
161 result = GetFileAttributesW(path)
162 if result == INVALID_FILE_ATTRIBUTES:
163 return False
164 return bool(result & FILE_ATTRIBUTE_REPARSE_POINT)
165
166
167def readlink(path):
168 reparse_point_handle = CreateFileW(path,
169 0,
170 0,
171 None,
172 OPEN_EXISTING,
173 FILE_FLAG_OPEN_REPARSE_POINT |
174 FILE_FLAG_BACKUP_SEMANTICS,
175 None)
176 if reparse_point_handle == INVALID_HANDLE_VALUE:
177 _raise_winerror(
178 get_last_error(),
Rostislav Krasny9da67fe2020-01-24 23:15:09 +0200179 'Error opening symbolic link \"%s\"'.format(path))
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700180 target_buffer = c_buffer(MAXIMUM_REPARSE_DATA_BUFFER_SIZE)
181 n_bytes_returned = DWORD()
182 io_result = DeviceIoControl(reparse_point_handle,
183 FSCTL_GET_REPARSE_POINT,
184 None,
185 0,
186 target_buffer,
187 len(target_buffer),
188 byref(n_bytes_returned),
189 None)
190 CloseHandle(reparse_point_handle)
191 if not io_result:
192 _raise_winerror(
193 get_last_error(),
Rostislav Krasny9da67fe2020-01-24 23:15:09 +0200194 'Error reading symbolic link \"%s\"'.format(path))
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700195 rdb = REPARSE_DATA_BUFFER.from_buffer(target_buffer)
196 if rdb.ReparseTag == IO_REPARSE_TAG_SYMLINK:
Mike Frysingeracf63b22019-06-13 02:24:21 -0400197 return rdb.SymbolicLinkReparseBuffer.PrintName
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700198 elif rdb.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT:
Mike Frysingeracf63b22019-06-13 02:24:21 -0400199 return rdb.MountPointReparseBuffer.PrintName
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700200 # Unsupported reparse point type
201 _raise_winerror(
202 ERROR_NOT_SUPPORTED,
Rostislav Krasny9da67fe2020-01-24 23:15:09 +0200203 'Error reading symbolic link \"%s\"'.format(path))
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700204
205
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700206def _raise_winerror(code, error_desc):
207 win_error_desc = FormatError(code).strip()
208 error_desc = "%s: %s".format(error_desc, win_error_desc)
209 raise WinError(code, error_desc)