blob: 00c51d9ba4efdff32b1a861c30d656852297508c [file] [log] [blame]
Renaud Paquay2e702912016-11-01 11:23:38 -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
Renaud Paquayad1abcb2016-11-01 11:34:55 -070015import errno
Renaud Paquay2e702912016-11-01 11:23:38 -070016import os
17import platform
Renaud Paquaya65adf72016-11-03 10:37:53 -070018import shutil
19import stat
Renaud Paquay2e702912016-11-01 11:23:38 -070020
21
22def isWindows():
23 """ Returns True when running with the native port of Python for Windows,
24 False when running on any other platform (including the Cygwin port of
25 Python).
26 """
27 # Note: The cygwin port of Python returns "CYGWIN_NT_xxx"
28 return platform.system() == "Windows"
29
30
Renaud Paquayd5cec5e2016-11-01 11:24:03 -070031def symlink(source, link_name):
32 """Creates a symbolic link pointing to source named link_name.
33 Note: On Windows, source must exist on disk, as the implementation needs
34 to know whether to create a "File" or a "Directory" symbolic link.
35 """
36 if isWindows():
37 import platform_utils_win32
38 source = _validate_winpath(source)
39 link_name = _validate_winpath(link_name)
40 target = os.path.join(os.path.dirname(link_name), source)
Renaud Paquaybed8b622018-09-27 10:46:58 -070041 if isdir(target):
42 platform_utils_win32.create_dirsymlink(_makelongpath(source), link_name)
Renaud Paquayd5cec5e2016-11-01 11:24:03 -070043 else:
Renaud Paquaybed8b622018-09-27 10:46:58 -070044 platform_utils_win32.create_filesymlink(_makelongpath(source), link_name)
Renaud Paquayd5cec5e2016-11-01 11:24:03 -070045 else:
46 return os.symlink(source, link_name)
47
48
49def _validate_winpath(path):
50 path = os.path.normpath(path)
51 if _winpath_is_valid(path):
52 return path
53 raise ValueError("Path \"%s\" must be a relative path or an absolute "
54 "path starting with a drive letter".format(path))
55
56
57def _winpath_is_valid(path):
58 """Windows only: returns True if path is relative (e.g. ".\\foo") or is
59 absolute including a drive letter (e.g. "c:\\foo"). Returns False if path
60 is ambiguous (e.g. "x:foo" or "\\foo").
61 """
62 assert isWindows()
63 path = os.path.normpath(path)
64 drive, tail = os.path.splitdrive(path)
65 if tail:
66 if not drive:
67 return tail[0] != os.sep # "\\foo" is invalid
68 else:
69 return tail[0] == os.sep # "x:foo" is invalid
70 else:
71 return not drive # "x:" is invalid
Renaud Paquaya65adf72016-11-03 10:37:53 -070072
73
Renaud Paquaybed8b622018-09-27 10:46:58 -070074def _makelongpath(path):
75 """Return the input path normalized to support the Windows long path syntax
76 ("\\\\?\\" prefix) if needed, i.e. if the input path is longer than the
77 MAX_PATH limit.
78 """
Renaud Paquaya65adf72016-11-03 10:37:53 -070079 if isWindows():
Renaud Paquaybed8b622018-09-27 10:46:58 -070080 # Note: MAX_PATH is 260, but, for directories, the maximum value is actually 246.
81 if len(path) < 246:
82 return path
83 if path.startswith(u"\\\\?\\"):
84 return path
85 if not os.path.isabs(path):
86 return path
87 # Append prefix and ensure unicode so that the special longpath syntax
88 # is supported by underlying Win32 API calls
89 return u"\\\\?\\" + os.path.normpath(path)
90 else:
91 return path
92
93
Mike Frysingerf4545122019-11-11 04:34:16 -050094def rmtree(path, ignore_errors=False):
Renaud Paquaybed8b622018-09-27 10:46:58 -070095 """shutil.rmtree(path) wrapper with support for long paths on Windows.
96
97 Availability: Unix, Windows."""
Mike Frysingerf4545122019-11-11 04:34:16 -050098 onerror = None
Renaud Paquaybed8b622018-09-27 10:46:58 -070099 if isWindows():
Mike Frysingerf4545122019-11-11 04:34:16 -0500100 path = _makelongpath(path)
101 onerror = handle_rmtree_error
102 shutil.rmtree(path, ignore_errors=ignore_errors, onerror=onerror)
Renaud Paquaya65adf72016-11-03 10:37:53 -0700103
104
105def handle_rmtree_error(function, path, excinfo):
106 # Allow deleting read-only files
107 os.chmod(path, stat.S_IWRITE)
108 function(path)
Renaud Paquayad1abcb2016-11-01 11:34:55 -0700109
110
111def rename(src, dst):
Renaud Paquaybed8b622018-09-27 10:46:58 -0700112 """os.rename(src, dst) wrapper with support for long paths on Windows.
113
114 Availability: Unix, Windows."""
Renaud Paquayad1abcb2016-11-01 11:34:55 -0700115 if isWindows():
116 # On Windows, rename fails if destination exists, see
117 # https://docs.python.org/2/library/os.html#os.rename
118 try:
Renaud Paquaybed8b622018-09-27 10:46:58 -0700119 os.rename(_makelongpath(src), _makelongpath(dst))
Renaud Paquayad1abcb2016-11-01 11:34:55 -0700120 except OSError as e:
121 if e.errno == errno.EEXIST:
Renaud Paquaybed8b622018-09-27 10:46:58 -0700122 os.remove(_makelongpath(dst))
123 os.rename(_makelongpath(src), _makelongpath(dst))
Renaud Paquayad1abcb2016-11-01 11:34:55 -0700124 else:
125 raise
126 else:
127 os.rename(src, dst)
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700128
129
Renaud Paquay010fed72016-11-11 14:25:29 -0800130def remove(path):
Renaud Paquaybed8b622018-09-27 10:46:58 -0700131 """Remove (delete) the file path. This is a replacement for os.remove that
132 allows deleting read-only files on Windows, with support for long paths and
133 for deleting directory symbolic links.
134
135 Availability: Unix, Windows."""
Renaud Paquay010fed72016-11-11 14:25:29 -0800136 if isWindows():
Renaud Paquaybed8b622018-09-27 10:46:58 -0700137 longpath = _makelongpath(path)
Renaud Paquay010fed72016-11-11 14:25:29 -0800138 try:
Renaud Paquaybed8b622018-09-27 10:46:58 -0700139 os.remove(longpath)
Renaud Paquay010fed72016-11-11 14:25:29 -0800140 except OSError as e:
141 if e.errno == errno.EACCES:
Renaud Paquaybed8b622018-09-27 10:46:58 -0700142 os.chmod(longpath, stat.S_IWRITE)
143 # Directory symbolic links must be deleted with 'rmdir'.
144 if islink(longpath) and isdir(longpath):
145 os.rmdir(longpath)
146 else:
147 os.remove(longpath)
Renaud Paquay010fed72016-11-11 14:25:29 -0800148 else:
149 raise
150 else:
151 os.remove(path)
152
153
Renaud Paquaybed8b622018-09-27 10:46:58 -0700154def walk(top, topdown=True, onerror=None, followlinks=False):
155 """os.walk(path) wrapper with support for long paths on Windows.
156
157 Availability: Windows, Unix.
158 """
159 if isWindows():
160 return _walk_windows_impl(top, topdown, onerror, followlinks)
161 else:
162 return os.walk(top, topdown, onerror, followlinks)
163
164
165def _walk_windows_impl(top, topdown, onerror, followlinks):
166 try:
167 names = listdir(top)
David Pursehoused26146d2018-11-01 11:54:10 +0900168 except Exception as err:
Renaud Paquaybed8b622018-09-27 10:46:58 -0700169 if onerror is not None:
170 onerror(err)
171 return
172
173 dirs, nondirs = [], []
174 for name in names:
175 if isdir(os.path.join(top, name)):
176 dirs.append(name)
177 else:
178 nondirs.append(name)
179
180 if topdown:
181 yield top, dirs, nondirs
182 for name in dirs:
183 new_path = os.path.join(top, name)
184 if followlinks or not islink(new_path):
185 for x in _walk_windows_impl(new_path, topdown, onerror, followlinks):
186 yield x
187 if not topdown:
188 yield top, dirs, nondirs
189
190
191def listdir(path):
192 """os.listdir(path) wrapper with support for long paths on Windows.
193
194 Availability: Windows, Unix.
195 """
196 return os.listdir(_makelongpath(path))
197
198
199def rmdir(path):
200 """os.rmdir(path) wrapper with support for long paths on Windows.
201
202 Availability: Windows, Unix.
203 """
204 os.rmdir(_makelongpath(path))
205
206
207def isdir(path):
208 """os.path.isdir(path) wrapper with support for long paths on Windows.
209
210 Availability: Windows, Unix.
211 """
212 return os.path.isdir(_makelongpath(path))
213
214
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700215def islink(path):
Renaud Paquaybed8b622018-09-27 10:46:58 -0700216 """os.path.islink(path) wrapper with support for long paths on Windows.
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700217
218 Availability: Windows, Unix.
219 """
220 if isWindows():
221 import platform_utils_win32
Renaud Paquaybed8b622018-09-27 10:46:58 -0700222 return platform_utils_win32.islink(_makelongpath(path))
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700223 else:
224 return os.path.islink(path)
225
226
227def readlink(path):
228 """Return a string representing the path to which the symbolic link
229 points. The result may be either an absolute or relative pathname;
230 if it is relative, it may be converted to an absolute pathname using
231 os.path.join(os.path.dirname(path), result).
232
233 Availability: Windows, Unix.
234 """
235 if isWindows():
236 import platform_utils_win32
Renaud Paquaybed8b622018-09-27 10:46:58 -0700237 return platform_utils_win32.readlink(_makelongpath(path))
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700238 else:
239 return os.readlink(path)
240
241
242def realpath(path):
243 """Return the canonical path of the specified filename, eliminating
244 any symbolic links encountered in the path.
245
246 Availability: Windows, Unix.
247 """
248 if isWindows():
249 current_path = os.path.abspath(path)
250 path_tail = []
251 for c in range(0, 100): # Avoid cycles
252 if islink(current_path):
253 target = readlink(current_path)
254 current_path = os.path.join(os.path.dirname(current_path), target)
255 else:
256 basename = os.path.basename(current_path)
257 if basename == '':
258 path_tail.append(current_path)
259 break
260 path_tail.append(basename)
261 current_path = os.path.dirname(current_path)
262 path_tail.reverse()
263 result = os.path.normpath(os.path.join(*path_tail))
264 return result
265 else:
266 return os.path.realpath(path)