blob: 4cf994bc45083d1abb5979f9226c4f212e034e7b [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(
Jason R. Coombsb32ccbb2023-09-29 11:04:49 -040060 f'Path "{path}" must be a relative path or an absolute '
61 "path starting with a drive letter"
Gavin Makea2e3302023-03-11 06:46:20 +000062 )
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):
Jason R. Coombs8dd85212023-10-20 06:48:20 -0400196 yield from _walk_windows_impl(
Gavin Makea2e3302023-03-11 06:46:20 +0000197 new_path, topdown, onerror, followlinks
Jason R. Coombs8dd85212023-10-20 06:48:20 -0400198 )
Gavin Makea2e3302023-03-11 06:46:20 +0000199 if not topdown:
200 yield top, dirs, nondirs
Renaud Paquaybed8b622018-09-27 10:46:58 -0700201
202
203def listdir(path):
Gavin Makea2e3302023-03-11 06:46:20 +0000204 """os.listdir(path) wrapper with support for long paths on Windows.
Renaud Paquaybed8b622018-09-27 10:46:58 -0700205
Gavin Makea2e3302023-03-11 06:46:20 +0000206 Availability: Windows, Unix.
207 """
208 return os.listdir(_makelongpath(path))
Renaud Paquaybed8b622018-09-27 10:46:58 -0700209
210
211def rmdir(path):
Gavin Makea2e3302023-03-11 06:46:20 +0000212 """os.rmdir(path) wrapper with support for long paths on Windows.
Renaud Paquaybed8b622018-09-27 10:46:58 -0700213
Gavin Makea2e3302023-03-11 06:46:20 +0000214 Availability: Windows, Unix.
215 """
216 os.rmdir(_makelongpath(path))
Renaud Paquaybed8b622018-09-27 10:46:58 -0700217
218
219def isdir(path):
Gavin Makea2e3302023-03-11 06:46:20 +0000220 """os.path.isdir(path) wrapper with support for long paths on Windows.
Renaud Paquaybed8b622018-09-27 10:46:58 -0700221
Gavin Makea2e3302023-03-11 06:46:20 +0000222 Availability: Windows, Unix.
223 """
224 return os.path.isdir(_makelongpath(path))
Renaud Paquaybed8b622018-09-27 10:46:58 -0700225
226
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700227def islink(path):
Gavin Makea2e3302023-03-11 06:46:20 +0000228 """os.path.islink(path) wrapper with support for long paths on Windows.
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700229
Gavin Makea2e3302023-03-11 06:46:20 +0000230 Availability: Windows, Unix.
231 """
232 if isWindows():
233 import platform_utils_win32
234
235 return platform_utils_win32.islink(_makelongpath(path))
236 else:
237 return os.path.islink(path)
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700238
239
240def readlink(path):
Gavin Makea2e3302023-03-11 06:46:20 +0000241 """Return a string representing the path to which the symbolic link
242 points. The result may be either an absolute or relative pathname;
243 if it is relative, it may be converted to an absolute pathname using
244 os.path.join(os.path.dirname(path), result).
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700245
Gavin Makea2e3302023-03-11 06:46:20 +0000246 Availability: Windows, Unix.
247 """
248 if isWindows():
249 import platform_utils_win32
250
251 return platform_utils_win32.readlink(_makelongpath(path))
252 else:
253 return os.readlink(path)
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700254
255
256def realpath(path):
Gavin Makea2e3302023-03-11 06:46:20 +0000257 """Return the canonical path of the specified filename, eliminating
258 any symbolic links encountered in the path.
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700259
Gavin Makea2e3302023-03-11 06:46:20 +0000260 Availability: Windows, Unix.
261 """
262 if isWindows():
263 current_path = os.path.abspath(path)
264 path_tail = []
265 for c in range(0, 100): # Avoid cycles
266 if islink(current_path):
267 target = readlink(current_path)
268 current_path = os.path.join(
269 os.path.dirname(current_path), target
270 )
271 else:
272 basename = os.path.basename(current_path)
273 if basename == "":
274 path_tail.append(current_path)
275 break
276 path_tail.append(basename)
277 current_path = os.path.dirname(current_path)
278 path_tail.reverse()
279 result = os.path.normpath(os.path.join(*path_tail))
280 return result
281 else:
282 return os.path.realpath(path)