# Copyright (C) 2016 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import errno import os import platform import shutil import stat def isWindows(): """Returns True when running with the native port of Python for Windows, False when running on any other platform (including the Cygwin port of Python). """ # Note: The cygwin port of Python returns "CYGWIN_NT_xxx" return platform.system() == "Windows" def symlink(source, link_name): """Creates a symbolic link pointing to source named link_name. Note: On Windows, source must exist on disk, as the implementation needs to know whether to create a "File" or a "Directory" symbolic link. """ if isWindows(): import platform_utils_win32 source = _validate_winpath(source) link_name = _validate_winpath(link_name) target = os.path.join(os.path.dirname(link_name), source) if isdir(target): platform_utils_win32.create_dirsymlink( _makelongpath(source), link_name ) else: platform_utils_win32.create_filesymlink( _makelongpath(source), link_name ) else: return os.symlink(source, link_name) def _validate_winpath(path): path = os.path.normpath(path) if _winpath_is_valid(path): return path raise ValueError( f'Path "{path}" must be a relative path or an absolute ' "path starting with a drive letter" ) def _winpath_is_valid(path): """Windows only: returns True if path is relative (e.g. ".\\foo") or is absolute including a drive letter (e.g. "c:\\foo"). Returns False if path is ambiguous (e.g. "x:foo" or "\\foo"). """ assert isWindows() path = os.path.normpath(path) drive, tail = os.path.splitdrive(path) if tail: if not drive: return tail[0] != os.sep # "\\foo" is invalid else: return tail[0] == os.sep # "x:foo" is invalid else: return not drive # "x:" is invalid def _makelongpath(path): """Return the input path normalized to support the Windows long path syntax ("\\\\?\\" prefix) if needed, i.e. if the input path is longer than the MAX_PATH limit. """ if isWindows(): # Note: MAX_PATH is 260, but, for directories, the maximum value is # actually 246. if len(path) < 246: return path if path.startswith("\\\\?\\"): return path if not os.path.isabs(path): return path # Append prefix and ensure unicode so that the special longpath syntax # is supported by underlying Win32 API calls return "\\\\?\\" + os.path.normpath(path) else: return path def rmtree(path, ignore_errors=False): """shutil.rmtree(path) wrapper with support for long paths on Windows. Availability: Unix, Windows. """ onerror = None if isWindows(): path = _makelongpath(path) onerror = handle_rmtree_error shutil.rmtree(path, ignore_errors=ignore_errors, onerror=onerror) def handle_rmtree_error(function, path, excinfo): # Allow deleting read-only files. os.chmod(path, stat.S_IWRITE) function(path) def rename(src, dst): """os.rename(src, dst) wrapper with support for long paths on Windows. Availability: Unix, Windows. """ if isWindows(): # On Windows, rename fails if destination exists, see # https://docs.python.org/2/library/os.html#os.rename try: os.rename(_makelongpath(src), _makelongpath(dst)) except OSError as e: if e.errno == errno.EEXIST: os.remove(_makelongpath(dst)) os.rename(_makelongpath(src), _makelongpath(dst)) else: raise else: shutil.move(src, dst) def remove(path, missing_ok=False): """Remove (delete) the file path. This is a replacement for os.remove that allows deleting read-only files on Windows, with support for long paths and for deleting directory symbolic links. Availability: Unix, Windows. """ longpath = _makelongpath(path) if isWindows() else path try: os.remove(longpath) except OSError as e: if e.errno == errno.EACCES: os.chmod(longpath, stat.S_IWRITE) # Directory symbolic links must be deleted with 'rmdir'. if islink(longpath) and isdir(longpath): os.rmdir(longpath) else: os.remove(longpath) elif missing_ok and e.errno == errno.ENOENT: pass else: raise def walk(top, topdown=True, onerror=None, followlinks=False): """os.walk(path) wrapper with support for long paths on Windows. Availability: Windows, Unix. """ if isWindows(): return _walk_windows_impl(top, topdown, onerror, followlinks) else: return os.walk(top, topdown, onerror, followlinks) def _walk_windows_impl(top, topdown, onerror, followlinks): try: names = listdir(top) except Exception as err: if onerror is not None: onerror(err) return dirs, nondirs = [], [] for name in names: if isdir(os.path.join(top, name)): dirs.append(name) else: nondirs.append(name) if topdown: yield top, dirs, nondirs for name in dirs: new_path = os.path.join(top, name) if followlinks or not islink(new_path): yield from _walk_windows_impl( new_path, topdown, onerror, followlinks ) if not topdown: yield top, dirs, nondirs def listdir(path): """os.listdir(path) wrapper with support for long paths on Windows. Availability: Windows, Unix. """ return os.listdir(_makelongpath(path)) def rmdir(path): """os.rmdir(path) wrapper with support for long paths on Windows. Availability: Windows, Unix. """ os.rmdir(_makelongpath(path)) def isdir(path): """os.path.isdir(path) wrapper with support for long paths on Windows. Availability: Windows, Unix. """ return os.path.isdir(_makelongpath(path)) def islink(path): """os.path.islink(path) wrapper with support for long paths on Windows. Availability: Windows, Unix. """ if isWindows(): import platform_utils_win32 return platform_utils_win32.islink(_makelongpath(path)) else: return os.path.islink(path) def readlink(path): """Return a string representing the path to which the symbolic link points. The result may be either an absolute or relative pathname; if it is relative, it may be converted to an absolute pathname using os.path.join(os.path.dirname(path), result). Availability: Windows, Unix. """ if isWindows(): import platform_utils_win32 return platform_utils_win32.readlink(_makelongpath(path)) else: return os.readlink(path) def realpath(path): """Return the canonical path of the specified filename, eliminating any symbolic links encountered in the path. Availability: Windows, Unix. """ if isWindows(): current_path = os.path.abspath(path) path_tail = [] for c in range(0, 100): # Avoid cycles if islink(current_path): target = readlink(current_path) current_path = os.path.join( os.path.dirname(current_path), target ) else: basename = os.path.basename(current_path) if basename == "": path_tail.append(current_path) break path_tail.append(basename) current_path = os.path.dirname(current_path) path_tail.reverse() result = os.path.normpath(os.path.join(*path_tail)) return result else: return os.path.realpath(path)