blob: fe76b3d642cebe5c6bc71d93aee18ae96e279762 [file] [log] [blame]
Renaud Paquayd5cec5e2016-11-01 11:24:03 -07001#
2# Copyright (C) 2016 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16import errno
17
Renaud Paquay227ad2e2016-11-01 14:37:13 -070018from ctypes import WinDLL, get_last_error, FormatError, WinError, addressof
19from ctypes import c_buffer
20from ctypes.wintypes import BOOL, LPCWSTR, DWORD, HANDLE, POINTER, c_ubyte
21from ctypes.wintypes import WCHAR, USHORT, LPVOID, Structure, Union, ULONG
22from ctypes.wintypes import byref
Renaud Paquayd5cec5e2016-11-01 11:24:03 -070023
24kernel32 = WinDLL('kernel32', use_last_error=True)
25
Renaud Paquay227ad2e2016-11-01 14:37:13 -070026LPDWORD = POINTER(DWORD)
27UCHAR = c_ubyte
28
Renaud Paquayd5cec5e2016-11-01 11:24:03 -070029# Win32 error codes
30ERROR_SUCCESS = 0
Renaud Paquay227ad2e2016-11-01 14:37:13 -070031ERROR_NOT_SUPPORTED = 50
Renaud Paquayd5cec5e2016-11-01 11:24:03 -070032ERROR_PRIVILEGE_NOT_HELD = 1314
33
34# Win32 API entry points
35CreateSymbolicLinkW = kernel32.CreateSymbolicLinkW
36CreateSymbolicLinkW.restype = BOOL
37CreateSymbolicLinkW.argtypes = (LPCWSTR, # lpSymlinkFileName In
38 LPCWSTR, # lpTargetFileName In
39 DWORD) # dwFlags In
40
41# Symbolic link creation flags
42SYMBOLIC_LINK_FLAG_FILE = 0x00
43SYMBOLIC_LINK_FLAG_DIRECTORY = 0x01
44
Renaud Paquay227ad2e2016-11-01 14:37:13 -070045GetFileAttributesW = kernel32.GetFileAttributesW
46GetFileAttributesW.restype = DWORD
47GetFileAttributesW.argtypes = (LPCWSTR,) # lpFileName In
48
49INVALID_FILE_ATTRIBUTES = 0xFFFFFFFF
50FILE_ATTRIBUTE_REPARSE_POINT = 0x00400
51
52CreateFileW = kernel32.CreateFileW
53CreateFileW.restype = HANDLE
54CreateFileW.argtypes = (LPCWSTR, # lpFileName In
55 DWORD, # dwDesiredAccess In
56 DWORD, # dwShareMode In
57 LPVOID, # lpSecurityAttributes In_opt
58 DWORD, # dwCreationDisposition In
59 DWORD, # dwFlagsAndAttributes In
60 HANDLE) # hTemplateFile In_opt
61
62CloseHandle = kernel32.CloseHandle
63CloseHandle.restype = BOOL
64CloseHandle.argtypes = (HANDLE,) # hObject In
65
66INVALID_HANDLE_VALUE = HANDLE(-1).value
67OPEN_EXISTING = 3
68FILE_FLAG_BACKUP_SEMANTICS = 0x02000000
69FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000
70
71DeviceIoControl = kernel32.DeviceIoControl
72DeviceIoControl.restype = BOOL
73DeviceIoControl.argtypes = (HANDLE, # hDevice In
74 DWORD, # dwIoControlCode In
75 LPVOID, # lpInBuffer In_opt
76 DWORD, # nInBufferSize In
77 LPVOID, # lpOutBuffer Out_opt
78 DWORD, # nOutBufferSize In
79 LPDWORD, # lpBytesReturned Out_opt
80 LPVOID) # lpOverlapped Inout_opt
81
82# Device I/O control flags and options
83FSCTL_GET_REPARSE_POINT = 0x000900A8
84IO_REPARSE_TAG_MOUNT_POINT = 0xA0000003
85IO_REPARSE_TAG_SYMLINK = 0xA000000C
86MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 0x4000
87
88
89class GENERIC_REPARSE_BUFFER(Structure):
90 _fields_ = (('DataBuffer', UCHAR * 1),)
91
92
93class SYMBOLIC_LINK_REPARSE_BUFFER(Structure):
94 _fields_ = (('SubstituteNameOffset', USHORT),
95 ('SubstituteNameLength', USHORT),
96 ('PrintNameOffset', USHORT),
97 ('PrintNameLength', USHORT),
98 ('Flags', ULONG),
99 ('PathBuffer', WCHAR * 1))
100
101 @property
102 def PrintName(self):
103 arrayt = WCHAR * (self.PrintNameLength // 2)
104 offset = type(self).PathBuffer.offset + self.PrintNameOffset
105 return arrayt.from_address(addressof(self) + offset).value
106
107
108class MOUNT_POINT_REPARSE_BUFFER(Structure):
109 _fields_ = (('SubstituteNameOffset', USHORT),
110 ('SubstituteNameLength', USHORT),
111 ('PrintNameOffset', USHORT),
112 ('PrintNameLength', USHORT),
113 ('PathBuffer', WCHAR * 1))
114
115 @property
116 def PrintName(self):
117 arrayt = WCHAR * (self.PrintNameLength // 2)
118 offset = type(self).PathBuffer.offset + self.PrintNameOffset
119 return arrayt.from_address(addressof(self) + offset).value
120
121
122class REPARSE_DATA_BUFFER(Structure):
123 class REPARSE_BUFFER(Union):
124 _fields_ = (('SymbolicLinkReparseBuffer', SYMBOLIC_LINK_REPARSE_BUFFER),
125 ('MountPointReparseBuffer', MOUNT_POINT_REPARSE_BUFFER),
126 ('GenericReparseBuffer', GENERIC_REPARSE_BUFFER))
127 _fields_ = (('ReparseTag', ULONG),
128 ('ReparseDataLength', USHORT),
129 ('Reserved', USHORT),
130 ('ReparseBuffer', REPARSE_BUFFER))
131 _anonymous_ = ('ReparseBuffer',)
132
Renaud Paquayd5cec5e2016-11-01 11:24:03 -0700133
134def create_filesymlink(source, link_name):
135 """Creates a Windows file symbolic link source pointing to link_name."""
136 _create_symlink(source, link_name, SYMBOLIC_LINK_FLAG_FILE)
137
138
139def create_dirsymlink(source, link_name):
140 """Creates a Windows directory symbolic link source pointing to link_name.
141 """
142 _create_symlink(source, link_name, SYMBOLIC_LINK_FLAG_DIRECTORY)
143
144
145def _create_symlink(source, link_name, dwFlags):
146 # Note: Win32 documentation for CreateSymbolicLink is incorrect.
147 # On success, the function returns "1".
148 # On error, the function returns some random value (e.g. 1280).
149 # The best bet seems to use "GetLastError" and check for error/success.
150 CreateSymbolicLinkW(link_name, source, dwFlags)
151 code = get_last_error()
152 if code != ERROR_SUCCESS:
153 error_desc = FormatError(code).strip()
154 if code == ERROR_PRIVILEGE_NOT_HELD:
155 raise OSError(errno.EPERM, error_desc, link_name)
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700156 _raise_winerror(
157 code,
158 'Error creating symbolic link \"%s\"'.format(link_name))
159
160
161def islink(path):
162 result = GetFileAttributesW(path)
163 if result == INVALID_FILE_ATTRIBUTES:
164 return False
165 return bool(result & FILE_ATTRIBUTE_REPARSE_POINT)
166
167
168def readlink(path):
169 reparse_point_handle = CreateFileW(path,
170 0,
171 0,
172 None,
173 OPEN_EXISTING,
174 FILE_FLAG_OPEN_REPARSE_POINT |
175 FILE_FLAG_BACKUP_SEMANTICS,
176 None)
177 if reparse_point_handle == INVALID_HANDLE_VALUE:
178 _raise_winerror(
179 get_last_error(),
180 'Error opening symblic link \"%s\"'.format(path))
181 target_buffer = c_buffer(MAXIMUM_REPARSE_DATA_BUFFER_SIZE)
182 n_bytes_returned = DWORD()
183 io_result = DeviceIoControl(reparse_point_handle,
184 FSCTL_GET_REPARSE_POINT,
185 None,
186 0,
187 target_buffer,
188 len(target_buffer),
189 byref(n_bytes_returned),
190 None)
191 CloseHandle(reparse_point_handle)
192 if not io_result:
193 _raise_winerror(
194 get_last_error(),
195 'Error reading symblic link \"%s\"'.format(path))
196 rdb = REPARSE_DATA_BUFFER.from_buffer(target_buffer)
197 if rdb.ReparseTag == IO_REPARSE_TAG_SYMLINK:
198 return _preserve_encoding(path, rdb.SymbolicLinkReparseBuffer.PrintName)
199 elif rdb.ReparseTag == IO_REPARSE_TAG_MOUNT_POINT:
200 return _preserve_encoding(path, rdb.MountPointReparseBuffer.PrintName)
201 # Unsupported reparse point type
202 _raise_winerror(
203 ERROR_NOT_SUPPORTED,
204 'Error reading symblic link \"%s\"'.format(path))
205
206
207def _preserve_encoding(source, target):
208 """Ensures target is the same string type (i.e. unicode or str) as source."""
209 if isinstance(source, unicode):
210 return unicode(target)
211 return str(target)
212
213
214def _raise_winerror(code, error_desc):
215 win_error_desc = FormatError(code).strip()
216 error_desc = "%s: %s".format(error_desc, win_error_desc)
217 raise WinError(code, error_desc)