blob: 45ffec78c43eb8b96fcc8d4126ddbdf6824ebbb9 [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)
Egor Dudaf0703312025-03-06 10:14:44 +0300159 elif (
160 e.errno == errno.EROFS
161 and missing_ok
162 and not os.path.exists(longpath)
163 ):
164 pass
Gavin Makea2e3302023-03-11 06:46:20 +0000165 elif missing_ok and e.errno == errno.ENOENT:
166 pass
167 else:
168 raise
Renaud Paquay010fed72016-11-11 14:25:29 -0800169
170
Renaud Paquaybed8b622018-09-27 10:46:58 -0700171def walk(top, topdown=True, onerror=None, followlinks=False):
Gavin Makea2e3302023-03-11 06:46:20 +0000172 """os.walk(path) wrapper with support for long paths on Windows.
Renaud Paquaybed8b622018-09-27 10:46:58 -0700173
Gavin Makea2e3302023-03-11 06:46:20 +0000174 Availability: Windows, Unix.
175 """
176 if isWindows():
177 return _walk_windows_impl(top, topdown, onerror, followlinks)
178 else:
179 return os.walk(top, topdown, onerror, followlinks)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700180
181
182def _walk_windows_impl(top, topdown, onerror, followlinks):
Gavin Makea2e3302023-03-11 06:46:20 +0000183 try:
184 names = listdir(top)
185 except Exception as err:
186 if onerror is not None:
187 onerror(err)
188 return
Renaud Paquaybed8b622018-09-27 10:46:58 -0700189
Gavin Makea2e3302023-03-11 06:46:20 +0000190 dirs, nondirs = [], []
191 for name in names:
192 if isdir(os.path.join(top, name)):
193 dirs.append(name)
194 else:
195 nondirs.append(name)
Renaud Paquaybed8b622018-09-27 10:46:58 -0700196
Gavin Makea2e3302023-03-11 06:46:20 +0000197 if topdown:
198 yield top, dirs, nondirs
199 for name in dirs:
200 new_path = os.path.join(top, name)
201 if followlinks or not islink(new_path):
Jason R. Coombs8dd85212023-10-20 06:48:20 -0400202 yield from _walk_windows_impl(
Gavin Makea2e3302023-03-11 06:46:20 +0000203 new_path, topdown, onerror, followlinks
Jason R. Coombs8dd85212023-10-20 06:48:20 -0400204 )
Gavin Makea2e3302023-03-11 06:46:20 +0000205 if not topdown:
206 yield top, dirs, nondirs
Renaud Paquaybed8b622018-09-27 10:46:58 -0700207
208
209def listdir(path):
Gavin Makea2e3302023-03-11 06:46:20 +0000210 """os.listdir(path) wrapper with support for long paths on Windows.
Renaud Paquaybed8b622018-09-27 10:46:58 -0700211
Gavin Makea2e3302023-03-11 06:46:20 +0000212 Availability: Windows, Unix.
213 """
214 return os.listdir(_makelongpath(path))
Renaud Paquaybed8b622018-09-27 10:46:58 -0700215
216
217def rmdir(path):
Gavin Makea2e3302023-03-11 06:46:20 +0000218 """os.rmdir(path) wrapper with support for long paths on Windows.
Renaud Paquaybed8b622018-09-27 10:46:58 -0700219
Gavin Makea2e3302023-03-11 06:46:20 +0000220 Availability: Windows, Unix.
221 """
222 os.rmdir(_makelongpath(path))
Renaud Paquaybed8b622018-09-27 10:46:58 -0700223
224
225def isdir(path):
Gavin Makea2e3302023-03-11 06:46:20 +0000226 """os.path.isdir(path) wrapper with support for long paths on Windows.
Renaud Paquaybed8b622018-09-27 10:46:58 -0700227
Gavin Makea2e3302023-03-11 06:46:20 +0000228 Availability: Windows, Unix.
229 """
230 return os.path.isdir(_makelongpath(path))
Renaud Paquaybed8b622018-09-27 10:46:58 -0700231
232
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700233def islink(path):
Gavin Makea2e3302023-03-11 06:46:20 +0000234 """os.path.islink(path) wrapper with support for long paths on Windows.
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700235
Gavin Makea2e3302023-03-11 06:46:20 +0000236 Availability: Windows, Unix.
237 """
238 if isWindows():
239 import platform_utils_win32
240
241 return platform_utils_win32.islink(_makelongpath(path))
242 else:
243 return os.path.islink(path)
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700244
245
246def readlink(path):
Gavin Makea2e3302023-03-11 06:46:20 +0000247 """Return a string representing the path to which the symbolic link
248 points. The result may be either an absolute or relative pathname;
249 if it is relative, it may be converted to an absolute pathname using
250 os.path.join(os.path.dirname(path), result).
Renaud Paquay227ad2e2016-11-01 14:37:13 -0700251
Gavin Makea2e3302023-03-11 06:46:20 +0000252 Availability: Windows, Unix.
253 """
254 if isWindows():
255 import platform_utils_win32
256
257 return platform_utils_win32.readlink(_makelongpath(path))
258 else:
259 return os.readlink(path)