blob: 2c48e6228523a63c8b1f2f95f523f9efed40144f [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():
Gavin Makea2e3302023-03-11 06:46:20 +000023 """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"
Renaud Paquay2e702912016-11-01 11:23:38 -070029
30
Renaud Paquayd5cec5e2016-11-01 11:24:03 -070031def symlink(source, link_name):
Gavin Makea2e3302023-03-11 06:46:20 +000032 """Creates a symbolic link pointing to source named link_name.
33
34 Note: On Windows, source must exist on disk, as the implementation needs
35 to know whether to create a "File" or a "Directory" symbolic link.
36 """
37 if isWindows():
38 import platform_utils_win32
39
40 source = _validate_winpath(source)
41 link_name = _validate_winpath(link_name)
42 target = os.path.join(os.path.dirname(link_name), source)
43 if isdir(target):
44 platform_utils_win32.create_dirsymlink(
45 _makelongpath(source), link_name
46 )
47 else:
48 platform_utils_win32.create_filesymlink(
49 _makelongpath(source), link_name
50 )
Renaud Paquayd5cec5e2016-11-01 11:24:03 -070051 else:
Gavin Makea2e3302023-03-11 06:46:20 +000052 return os.symlink(source, link_name)
Renaud Paquayd5cec5e2016-11-01 11:24:03 -070053
54
55def _validate_winpath(path):
Gavin Makea2e3302023-03-11 06:46:20 +000056 path = os.path.normpath(path)
57 if _winpath_is_valid(path):
58 return path
59 raise ValueError(
60 'Path "{}" must be a relative path or an absolute '
61 "path starting with a drive letter".format(path)
62 )
Renaud Paquayd5cec5e2016-11-01 11:24:03 -070063
64
65def _winpath_is_valid(path):
Gavin Makea2e3302023-03-11 06:46:20 +000066 """Windows only: returns True if path is relative (e.g. ".\\foo") or is
67 absolute including a drive letter (e.g. "c:\\foo"). Returns False if path
68 is ambiguous (e.g. "x:foo" or "\\foo").
69 """
70 assert isWindows()
71 path = os.path.normpath(path)
72 drive, tail = os.path.splitdrive(path)
73 if tail:
74 if not drive:
75 return tail[0] != os.sep # "\\foo" is invalid
76 else:
77 return tail[0] == os.sep # "x:foo" is invalid
Renaud Paquayd5cec5e2016-11-01 11:24:03 -070078 else:
Gavin Makea2e3302023-03-11 06:46:20 +000079 return not drive # "x:" is invalid
Renaud Paquaya65adf72016-11-03 10:37:53 -070080
81
Renaud Paquaybed8b622018-09-27 10:46:58 -070082def _makelongpath(path):
Gavin Makea2e3302023-03-11 06:46:20 +000083 """Return the input path normalized to support the Windows long path syntax
84 ("\\\\?\\" prefix) if needed, i.e. if the input path is longer than the
85 MAX_PATH limit.
86 """
87 if isWindows():
88 # Note: MAX_PATH is 260, but, for directories, the maximum value is
89 # actually 246.
90 if len(path) < 246:
91 return path
92 if path.startswith("\\\\?\\"):
93 return path
94 if not os.path.isabs(path):
95 return path
96 # Append prefix and ensure unicode so that the special longpath syntax
97 # is supported by underlying Win32 API calls
98 return "\\\\?\\" + os.path.normpath(path)
99 else:
100 return path
Renaud Paquaybed8b622018-09-27 10:46:58 -0700101
102
Mike Frysingerf4545122019-11-11 04:34:16 -0500103def rmtree(path, ignore_errors=False):
Gavin Makea2e3302023-03-11 06:46:20 +0000104 """shutil.rmtree(path) wrapper with support for long paths on Windows.
Renaud Paquaybed8b622018-09-27 10:46:58 -0700105
Gavin Makea2e3302023-03-11 06:46:20 +0000106 Availability: Unix, Windows.
107 """
108 onerror = None
109 if isWindows():
110 path = _makelongpath(path)
111 onerror = handle_rmtree_error
112 shutil.rmtree(path, ignore_errors=ignore_errors, onerror=onerror)
Renaud Paquaya65adf72016-11-03 10:37:53 -0700113
114
115def handle_rmtree_error(function, path, excinfo):
Gavin Makea2e3302023-03-11 06:46:20 +0000116 # Allow deleting read-only files.
117 os.chmod(path, stat.S_IWRITE)
118 function(path)
Renaud Paquayad1abcb2016-11-01 11:34:55 -0700119
120
121def rename(src, dst):
Gavin Makea2e3302023-03-11 06:46:20 +0000122 """os.rename(src, dst) wrapper with support for long paths on Windows.
Renaud Paquaybed8b622018-09-27 10:46:58 -0700123
Gavin Makea2e3302023-03-11 06:46:20 +0000124 Availability: Unix, Windows.
125 """
126 if isWindows():
127 # On Windows, rename fails if destination exists, see
128 # https://docs.python.org/2/library/os.html#os.rename
129 try:
130 os.rename(_makelongpath(src), _makelongpath(dst))
131 except OSError as e:
132 if e.errno == errno.EEXIST:
133 os.remove(_makelongpath(dst))
134 os.rename(_makelongpath(src), _makelongpath(dst))
135 else:
136 raise
137 else:
138 shutil.move(src, dst)
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700139
140
Mike Frysinger9d96f582021-09-28 11:27:24 -0400141def remove(path, missing_ok=False):
Gavin Makea2e3302023-03-11 06:46:20 +0000142 """Remove (delete) the file path. This is a replacement for os.remove that
143 allows deleting read-only files on Windows, with support for long paths and
144 for deleting directory symbolic links.
Renaud Paquaybed8b622018-09-27 10:46:58 -0700145
Gavin Makea2e3302023-03-11 06:46:20 +0000146 Availability: Unix, Windows.
147 """
148 longpath = _makelongpath(path) if isWindows() else path
149 try:
Mike Frysinger9d96f582021-09-28 11:27:24 -0400150 os.remove(longpath)
Gavin Makea2e3302023-03-11 06:46:20 +0000151 except OSError as e:
152 if e.errno == errno.EACCES:
153 os.chmod(longpath, stat.S_IWRITE)
154 # Directory symbolic links must be deleted with 'rmdir'.
155 if islink(longpath) and isdir(longpath):
156 os.rmdir(longpath)
157 else:
158 os.remove(longpath)
159 elif missing_ok and e.errno == errno.ENOENT:
160 pass
161 else:
162 raise
Renaud Paquay010fed72016-11-11 14:25:29 -0800163
164
Renaud Paquaybed8b622018-09-27 10:46:58 -0700165def walk(top, topdown=True, onerror=None, followlinks=False):
Gavin Makea2e3302023-03-11 06:46:20 +0000166 """os.walk(path) wrapper with support for long paths on Windows.
Renaud Paquaybed8b622018-09-27 10:46:58 -0700167
Gavin Makea2e3302023-03-11 06:46:20 +0000168 Availability: Windows, Unix.
169 """
170 if isWindows():
171 return _walk_windows_impl(top, topdown, onerror, followlinks)
172 else:
173 return os.walk(top, topdown, onerror, followlinks)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700174
175
176def _walk_windows_impl(top, topdown, onerror, followlinks):
Gavin Makea2e3302023-03-11 06:46:20 +0000177 try:
178 names = listdir(top)
179 except Exception as err:
180 if onerror is not None:
181 onerror(err)
182 return
Renaud Paquaybed8b622018-09-27 10:46:58 -0700183
Gavin Makea2e3302023-03-11 06:46:20 +0000184 dirs, nondirs = [], []
185 for name in names:
186 if isdir(os.path.join(top, name)):
187 dirs.append(name)
188 else:
189 nondirs.append(name)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700190
Gavin Makea2e3302023-03-11 06:46:20 +0000191 if topdown:
192 yield top, dirs, nondirs
193 for name in dirs:
194 new_path = os.path.join(top, name)
195 if followlinks or not islink(new_path):
196 for x in _walk_windows_impl(
197 new_path, topdown, onerror, followlinks
198 ):
199 yield x
200 if not topdown:
201 yield top, dirs, nondirs
Renaud Paquaybed8b622018-09-27 10:46:58 -0700202
203
204def listdir(path):
Gavin Makea2e3302023-03-11 06:46:20 +0000205 """os.listdir(path) wrapper with support for long paths on Windows.
Renaud Paquaybed8b622018-09-27 10:46:58 -0700206
Gavin Makea2e3302023-03-11 06:46:20 +0000207 Availability: Windows, Unix.
208 """
209 return os.listdir(_makelongpath(path))
Renaud Paquaybed8b622018-09-27 10:46:58 -0700210
211
212def rmdir(path):
Gavin Makea2e3302023-03-11 06:46:20 +0000213 """os.rmdir(path) wrapper with support for long paths on Windows.
Renaud Paquaybed8b622018-09-27 10:46:58 -0700214
Gavin Makea2e3302023-03-11 06:46:20 +0000215 Availability: Windows, Unix.
216 """
217 os.rmdir(_makelongpath(path))
Renaud Paquaybed8b622018-09-27 10:46:58 -0700218
219
220def isdir(path):
Gavin Makea2e3302023-03-11 06:46:20 +0000221 """os.path.isdir(path) wrapper with support for long paths on Windows.
Renaud Paquaybed8b622018-09-27 10:46:58 -0700222
Gavin Makea2e3302023-03-11 06:46:20 +0000223 Availability: Windows, Unix.
224 """
225 return os.path.isdir(_makelongpath(path))
Renaud Paquaybed8b622018-09-27 10:46:58 -0700226
227
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700228def islink(path):
Gavin Makea2e3302023-03-11 06:46:20 +0000229 """os.path.islink(path) wrapper with support for long paths on Windows.
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700230
Gavin Makea2e3302023-03-11 06:46:20 +0000231 Availability: Windows, Unix.
232 """
233 if isWindows():
234 import platform_utils_win32
235
236 return platform_utils_win32.islink(_makelongpath(path))
237 else:
238 return os.path.islink(path)
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700239
240
241def readlink(path):
Gavin Makea2e3302023-03-11 06:46:20 +0000242 """Return a string representing the path to which the symbolic link
243 points. The result may be either an absolute or relative pathname;
244 if it is relative, it may be converted to an absolute pathname using
245 os.path.join(os.path.dirname(path), result).
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700246
Gavin Makea2e3302023-03-11 06:46:20 +0000247 Availability: Windows, Unix.
248 """
249 if isWindows():
250 import platform_utils_win32
251
252 return platform_utils_win32.readlink(_makelongpath(path))
253 else:
254 return os.readlink(path)
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700255
256
257def realpath(path):
Gavin Makea2e3302023-03-11 06:46:20 +0000258 """Return the canonical path of the specified filename, eliminating
259 any symbolic links encountered in the path.
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700260
Gavin Makea2e3302023-03-11 06:46:20 +0000261 Availability: Windows, Unix.
262 """
263 if isWindows():
264 current_path = os.path.abspath(path)
265 path_tail = []
266 for c in range(0, 100): # Avoid cycles
267 if islink(current_path):
268 target = readlink(current_path)
269 current_path = os.path.join(
270 os.path.dirname(current_path), target
271 )
272 else:
273 basename = os.path.basename(current_path)
274 if basename == "":
275 path_tail.append(current_path)
276 break
277 path_tail.append(basename)
278 current_path = os.path.dirname(current_path)
279 path_tail.reverse()
280 result = os.path.normpath(os.path.join(*path_tail))
281 return result
282 else:
283 return os.path.realpath(path)