summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRenaud Paquay <rpaquay@google.com>2016-11-01 14:37:13 -0700
committerRenaud Paquay <rpaquay@google.com>2017-08-31 13:49:01 -0700
commit227ad2ef42f47798d24814dfc2cef8119c313ab7 (patch)
tree0702d4dac67f59072edc6587122c27f4753021a5
parent2a4be948788dfe5ae9437b048fba229a96bbff2d (diff)
downloadgit-repo-227ad2ef42f47798d24814dfc2cef8119c313ab7.tar.gz
Implement islink, readlink and realpath using Win32 api
Change-Id: I18452cbb32d24db73601ad10485dbe6bb278731c
-rw-r--r--platform_utils.py54
-rw-r--r--platform_utils_win32.py164
-rw-r--r--project.py26
-rw-r--r--subcmds/sync.py2
4 files changed, 227 insertions, 19 deletions
diff --git a/platform_utils.py b/platform_utils.py
index e0fa9dcc..2ad56490 100644
--- a/platform_utils.py
+++ b/platform_utils.py
@@ -242,3 +242,57 @@ def rename(src, dst):
242 raise 242 raise
243 else: 243 else:
244 os.rename(src, dst) 244 os.rename(src, dst)
245
246
247def islink(path):
248 """Test whether a path is a symbolic link.
249
250 Availability: Windows, Unix.
251 """
252 if isWindows():
253 import platform_utils_win32
254 return platform_utils_win32.islink(path)
255 else:
256 return os.path.islink(path)
257
258
259def readlink(path):
260 """Return a string representing the path to which the symbolic link
261 points. The result may be either an absolute or relative pathname;
262 if it is relative, it may be converted to an absolute pathname using
263 os.path.join(os.path.dirname(path), result).
264
265 Availability: Windows, Unix.
266 """
267 if isWindows():
268 import platform_utils_win32
269 return platform_utils_win32.readlink(path)
270 else:
271 return os.readlink(path)
272
273
274def realpath(path):
275 """Return the canonical path of the specified filename, eliminating
276 any symbolic links encountered in the path.
277
278 Availability: Windows, Unix.
279 """
280 if isWindows():
281 current_path = os.path.abspath(path)
282 path_tail = []
283 for c in range(0, 100): # Avoid cycles
284 if islink(current_path):
285 target = readlink(current_path)
286 current_path = os.path.join(os.path.dirname(current_path), target)
287 else:
288 basename = os.path.basename(current_path)
289 if basename == '':
290 path_tail.append(current_path)
291 break
292 path_tail.append(basename)
293 current_path = os.path.dirname(current_path)
294 path_tail.reverse()
295 result = os.path.normpath(os.path.join(*path_tail))
296 return result
297 else:
298 return os.path.realpath(path)
diff --git a/platform_utils_win32.py b/platform_utils_win32.py
index 02fb013a..fe76b3d6 100644
--- a/platform_utils_win32.py
+++ b/platform_utils_win32.py
@@ -15,13 +15,20 @@
15 15
16import errno 16import errno
17 17
18from ctypes import WinDLL, get_last_error, FormatError, WinError 18from ctypes import WinDLL, get_last_error, FormatError, WinError, addressof
19from ctypes.wintypes import BOOL, LPCWSTR, DWORD 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
20 23
21kernel32 = WinDLL('kernel32', use_last_error=True) 24kernel32 = WinDLL('kernel32', use_last_error=True)
22 25
26LPDWORD = POINTER(DWORD)
27UCHAR = c_ubyte
28
23# Win32 error codes 29# Win32 error codes
24ERROR_SUCCESS = 0 30ERROR_SUCCESS = 0
31ERROR_NOT_SUPPORTED = 50
25ERROR_PRIVILEGE_NOT_HELD = 1314 32ERROR_PRIVILEGE_NOT_HELD = 1314
26 33
27# Win32 API entry points 34# Win32 API entry points
@@ -35,6 +42,94 @@ CreateSymbolicLinkW.argtypes = (LPCWSTR, # lpSymlinkFileName In
35SYMBOLIC_LINK_FLAG_FILE = 0x00 42SYMBOLIC_LINK_FLAG_FILE = 0x00
36SYMBOLIC_LINK_FLAG_DIRECTORY = 0x01 43SYMBOLIC_LINK_FLAG_DIRECTORY = 0x01
37 44
45GetFileAttributesW = 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
38 133
39def create_filesymlink(source, link_name): 134def create_filesymlink(source, link_name):
40 """Creates a Windows file symbolic link source pointing to link_name.""" 135 """Creates a Windows file symbolic link source pointing to link_name."""
@@ -58,6 +153,65 @@ def _create_symlink(source, link_name, dwFlags):
58 error_desc = FormatError(code).strip() 153 error_desc = FormatError(code).strip()
59 if code == ERROR_PRIVILEGE_NOT_HELD: 154 if code == ERROR_PRIVILEGE_NOT_HELD:
60 raise OSError(errno.EPERM, error_desc, link_name) 155 raise OSError(errno.EPERM, error_desc, link_name)
61 error_desc = 'Error creating symbolic link %s: %s'.format( 156 _raise_winerror(
62 link_name, error_desc) 157 code,
63 raise WinError(code, error_desc) 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)
diff --git a/project.py b/project.py
index d4c5afd5..655b2024 100644
--- a/project.py
+++ b/project.py
@@ -103,7 +103,7 @@ def _ProjectHooks():
103 """ 103 """
104 global _project_hook_list 104 global _project_hook_list
105 if _project_hook_list is None: 105 if _project_hook_list is None:
106 d = os.path.realpath(os.path.abspath(os.path.dirname(__file__))) 106 d = platform_utils.realpath(os.path.abspath(os.path.dirname(__file__)))
107 d = os.path.join(d, 'hooks') 107 d = os.path.join(d, 'hooks')
108 _project_hook_list = [os.path.join(d, x) for x in os.listdir(d)] 108 _project_hook_list = [os.path.join(d, x) for x in os.listdir(d)]
109 return _project_hook_list 109 return _project_hook_list
@@ -275,7 +275,7 @@ class _LinkFile(object):
275 275
276 def __linkIt(self, relSrc, absDest): 276 def __linkIt(self, relSrc, absDest):
277 # link file if it does not exist or is out of date 277 # link file if it does not exist or is out of date
278 if not os.path.islink(absDest) or (os.readlink(absDest) != relSrc): 278 if not platform_utils.islink(absDest) or (platform_utils.readlink(absDest) != relSrc):
279 try: 279 try:
280 # remove existing file first, since it might be read-only 280 # remove existing file first, since it might be read-only
281 if os.path.lexists(absDest): 281 if os.path.lexists(absDest):
@@ -2315,10 +2315,10 @@ class Project(object):
2315 print("Retrying clone after deleting %s" % 2315 print("Retrying clone after deleting %s" %
2316 self.gitdir, file=sys.stderr) 2316 self.gitdir, file=sys.stderr)
2317 try: 2317 try:
2318 platform_utils.rmtree(os.path.realpath(self.gitdir)) 2318 platform_utils.rmtree(platform_utils.realpath(self.gitdir))
2319 if self.worktree and os.path.exists(os.path.realpath 2319 if self.worktree and os.path.exists(platform_utils.realpath
2320 (self.worktree)): 2320 (self.worktree)):
2321 platform_utils.rmtree(os.path.realpath(self.worktree)) 2321 platform_utils.rmtree(platform_utils.realpath(self.worktree))
2322 return self._InitGitDir(mirror_git=mirror_git, force_sync=False) 2322 return self._InitGitDir(mirror_git=mirror_git, force_sync=False)
2323 except: 2323 except:
2324 raise e 2324 raise e
@@ -2370,7 +2370,7 @@ class Project(object):
2370 self._InitHooks() 2370 self._InitHooks()
2371 2371
2372 def _InitHooks(self): 2372 def _InitHooks(self):
2373 hooks = os.path.realpath(self._gitdir_path('hooks')) 2373 hooks = platform_utils.realpath(self._gitdir_path('hooks'))
2374 if not os.path.exists(hooks): 2374 if not os.path.exists(hooks):
2375 os.makedirs(hooks) 2375 os.makedirs(hooks)
2376 for stock_hook in _ProjectHooks(): 2376 for stock_hook in _ProjectHooks():
@@ -2386,7 +2386,7 @@ class Project(object):
2386 continue 2386 continue
2387 2387
2388 dst = os.path.join(hooks, name) 2388 dst = os.path.join(hooks, name)
2389 if os.path.islink(dst): 2389 if platform_utils.islink(dst):
2390 continue 2390 continue
2391 if os.path.exists(dst): 2391 if os.path.exists(dst):
2392 if filecmp.cmp(stock_hook, dst, shallow=False): 2392 if filecmp.cmp(stock_hook, dst, shallow=False):
@@ -2448,9 +2448,9 @@ class Project(object):
2448 symlink_dirs += self.working_tree_dirs 2448 symlink_dirs += self.working_tree_dirs
2449 to_symlink = symlink_files + symlink_dirs 2449 to_symlink = symlink_files + symlink_dirs
2450 for name in set(to_symlink): 2450 for name in set(to_symlink):
2451 dst = os.path.realpath(os.path.join(destdir, name)) 2451 dst = platform_utils.realpath(os.path.join(destdir, name))
2452 if os.path.lexists(dst): 2452 if os.path.lexists(dst):
2453 src = os.path.realpath(os.path.join(srcdir, name)) 2453 src = platform_utils.realpath(os.path.join(srcdir, name))
2454 # Fail if the links are pointing to the wrong place 2454 # Fail if the links are pointing to the wrong place
2455 if src != dst: 2455 if src != dst:
2456 _error('%s is different in %s vs %s', name, destdir, srcdir) 2456 _error('%s is different in %s vs %s', name, destdir, srcdir)
@@ -2482,10 +2482,10 @@ class Project(object):
2482 if copy_all: 2482 if copy_all:
2483 to_copy = os.listdir(gitdir) 2483 to_copy = os.listdir(gitdir)
2484 2484
2485 dotgit = os.path.realpath(dotgit) 2485 dotgit = platform_utils.realpath(dotgit)
2486 for name in set(to_copy).union(to_symlink): 2486 for name in set(to_copy).union(to_symlink):
2487 try: 2487 try:
2488 src = os.path.realpath(os.path.join(gitdir, name)) 2488 src = platform_utils.realpath(os.path.join(gitdir, name))
2489 dst = os.path.join(dotgit, name) 2489 dst = os.path.join(dotgit, name)
2490 2490
2491 if os.path.lexists(dst): 2491 if os.path.lexists(dst):
@@ -2498,7 +2498,7 @@ class Project(object):
2498 if name in to_symlink: 2498 if name in to_symlink:
2499 platform_utils.symlink( 2499 platform_utils.symlink(
2500 os.path.relpath(src, os.path.dirname(dst)), dst) 2500 os.path.relpath(src, os.path.dirname(dst)), dst)
2501 elif copy_all and not os.path.islink(dst): 2501 elif copy_all and not platform_utils.islink(dst):
2502 if os.path.isdir(src): 2502 if os.path.isdir(src):
2503 shutil.copytree(src, dst) 2503 shutil.copytree(src, dst)
2504 elif os.path.isfile(src): 2504 elif os.path.isfile(src):
@@ -2556,7 +2556,7 @@ class Project(object):
2556 raise 2556 raise
2557 2557
2558 def _gitdir_path(self, path): 2558 def _gitdir_path(self, path):
2559 return os.path.realpath(os.path.join(self.gitdir, path)) 2559 return platform_utils.realpath(os.path.join(self.gitdir, path))
2560 2560
2561 def _revlist(self, *args, **kw): 2561 def _revlist(self, *args, **kw):
2562 a = [] 2562 a = []
diff --git a/subcmds/sync.py b/subcmds/sync.py
index b88c596d..93fea23b 100644
--- a/subcmds/sync.py
+++ b/subcmds/sync.py
@@ -498,7 +498,7 @@ later is required to fix a server side protocol bug.
498 dirs_to_remove += [os.path.join(root, d) for d in dirs 498 dirs_to_remove += [os.path.join(root, d) for d in dirs
499 if os.path.join(root, d) not in dirs_to_remove] 499 if os.path.join(root, d) not in dirs_to_remove]
500 for d in reversed(dirs_to_remove): 500 for d in reversed(dirs_to_remove):
501 if os.path.islink(d): 501 if platform_utils.islink(d):
502 try: 502 try:
503 os.remove(d) 503 os.remove(d)
504 except OSError: 504 except OSError: