1*4882a593Smuzhiyun# 2*4882a593Smuzhiyun# SPDX-License-Identifier: GPL-2.0-only 3*4882a593Smuzhiyun# 4*4882a593Smuzhiyun 5*4882a593Smuzhiyunimport errno 6*4882a593Smuzhiyunimport glob 7*4882a593Smuzhiyunimport shutil 8*4882a593Smuzhiyunimport subprocess 9*4882a593Smuzhiyunimport os.path 10*4882a593Smuzhiyun 11*4882a593Smuzhiyundef join(*paths): 12*4882a593Smuzhiyun """Like os.path.join but doesn't treat absolute RHS specially""" 13*4882a593Smuzhiyun return os.path.normpath("/".join(paths)) 14*4882a593Smuzhiyun 15*4882a593Smuzhiyundef relative(src, dest): 16*4882a593Smuzhiyun """ Return a relative path from src to dest. 17*4882a593Smuzhiyun 18*4882a593Smuzhiyun >>> relative("/usr/bin", "/tmp/foo/bar") 19*4882a593Smuzhiyun ../../tmp/foo/bar 20*4882a593Smuzhiyun 21*4882a593Smuzhiyun >>> relative("/usr/bin", "/usr/lib") 22*4882a593Smuzhiyun ../lib 23*4882a593Smuzhiyun 24*4882a593Smuzhiyun >>> relative("/tmp", "/tmp/foo/bar") 25*4882a593Smuzhiyun foo/bar 26*4882a593Smuzhiyun """ 27*4882a593Smuzhiyun 28*4882a593Smuzhiyun return os.path.relpath(dest, src) 29*4882a593Smuzhiyun 30*4882a593Smuzhiyundef make_relative_symlink(path): 31*4882a593Smuzhiyun """ Convert an absolute symlink to a relative one """ 32*4882a593Smuzhiyun if not os.path.islink(path): 33*4882a593Smuzhiyun return 34*4882a593Smuzhiyun link = os.readlink(path) 35*4882a593Smuzhiyun if not os.path.isabs(link): 36*4882a593Smuzhiyun return 37*4882a593Smuzhiyun 38*4882a593Smuzhiyun # find the common ancestor directory 39*4882a593Smuzhiyun ancestor = path 40*4882a593Smuzhiyun depth = 0 41*4882a593Smuzhiyun while ancestor and not link.startswith(ancestor): 42*4882a593Smuzhiyun ancestor = ancestor.rpartition('/')[0] 43*4882a593Smuzhiyun depth += 1 44*4882a593Smuzhiyun 45*4882a593Smuzhiyun if not ancestor: 46*4882a593Smuzhiyun print("make_relative_symlink() Error: unable to find the common ancestor of %s and its target" % path) 47*4882a593Smuzhiyun return 48*4882a593Smuzhiyun 49*4882a593Smuzhiyun base = link.partition(ancestor)[2].strip('/') 50*4882a593Smuzhiyun while depth > 1: 51*4882a593Smuzhiyun base = "../" + base 52*4882a593Smuzhiyun depth -= 1 53*4882a593Smuzhiyun 54*4882a593Smuzhiyun os.remove(path) 55*4882a593Smuzhiyun os.symlink(base, path) 56*4882a593Smuzhiyun 57*4882a593Smuzhiyundef replace_absolute_symlinks(basedir, d): 58*4882a593Smuzhiyun """ 59*4882a593Smuzhiyun Walk basedir looking for absolute symlinks and replacing them with relative ones. 60*4882a593Smuzhiyun The absolute links are assumed to be relative to basedir 61*4882a593Smuzhiyun (compared to make_relative_symlink above which tries to compute common ancestors 62*4882a593Smuzhiyun using pattern matching instead) 63*4882a593Smuzhiyun """ 64*4882a593Smuzhiyun for walkroot, dirs, files in os.walk(basedir): 65*4882a593Smuzhiyun for file in files + dirs: 66*4882a593Smuzhiyun path = os.path.join(walkroot, file) 67*4882a593Smuzhiyun if not os.path.islink(path): 68*4882a593Smuzhiyun continue 69*4882a593Smuzhiyun link = os.readlink(path) 70*4882a593Smuzhiyun if not os.path.isabs(link): 71*4882a593Smuzhiyun continue 72*4882a593Smuzhiyun walkdir = os.path.dirname(path.rpartition(basedir)[2]) 73*4882a593Smuzhiyun base = os.path.relpath(link, walkdir) 74*4882a593Smuzhiyun bb.debug(2, "Replacing absolute path %s with relative path %s" % (link, base)) 75*4882a593Smuzhiyun os.remove(path) 76*4882a593Smuzhiyun os.symlink(base, path) 77*4882a593Smuzhiyun 78*4882a593Smuzhiyundef format_display(path, metadata): 79*4882a593Smuzhiyun """ Prepare a path for display to the user. """ 80*4882a593Smuzhiyun rel = relative(metadata.getVar("TOPDIR"), path) 81*4882a593Smuzhiyun if len(rel) > len(path): 82*4882a593Smuzhiyun return path 83*4882a593Smuzhiyun else: 84*4882a593Smuzhiyun return rel 85*4882a593Smuzhiyun 86*4882a593Smuzhiyundef copytree(src, dst): 87*4882a593Smuzhiyun # We could use something like shutil.copytree here but it turns out to 88*4882a593Smuzhiyun # to be slow. It takes twice as long copying to an empty directory. 89*4882a593Smuzhiyun # If dst already has contents performance can be 15 time slower 90*4882a593Smuzhiyun # This way we also preserve hardlinks between files in the tree. 91*4882a593Smuzhiyun 92*4882a593Smuzhiyun bb.utils.mkdirhier(dst) 93*4882a593Smuzhiyun cmd = "tar --xattrs --xattrs-include='*' -cf - -S -C %s -p . | tar --xattrs --xattrs-include='*' -xf - -C %s" % (src, dst) 94*4882a593Smuzhiyun subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT) 95*4882a593Smuzhiyun 96*4882a593Smuzhiyundef copyhardlinktree(src, dst): 97*4882a593Smuzhiyun """Make a tree of hard links when possible, otherwise copy.""" 98*4882a593Smuzhiyun bb.utils.mkdirhier(dst) 99*4882a593Smuzhiyun if os.path.isdir(src) and not len(os.listdir(src)): 100*4882a593Smuzhiyun return 101*4882a593Smuzhiyun 102*4882a593Smuzhiyun canhard = False 103*4882a593Smuzhiyun testfile = None 104*4882a593Smuzhiyun for root, dirs, files in os.walk(src): 105*4882a593Smuzhiyun if len(files): 106*4882a593Smuzhiyun testfile = os.path.join(root, files[0]) 107*4882a593Smuzhiyun break 108*4882a593Smuzhiyun 109*4882a593Smuzhiyun if testfile is not None: 110*4882a593Smuzhiyun try: 111*4882a593Smuzhiyun os.link(testfile, os.path.join(dst, 'testfile')) 112*4882a593Smuzhiyun os.unlink(os.path.join(dst, 'testfile')) 113*4882a593Smuzhiyun canhard = True 114*4882a593Smuzhiyun except Exception as e: 115*4882a593Smuzhiyun bb.debug(2, "Hardlink test failed with " + str(e)) 116*4882a593Smuzhiyun 117*4882a593Smuzhiyun if (canhard): 118*4882a593Smuzhiyun # Need to copy directories only with tar first since cp will error if two 119*4882a593Smuzhiyun # writers try and create a directory at the same time 120*4882a593Smuzhiyun cmd = "cd %s; find . -type d -print | tar --xattrs --xattrs-include='*' -cf - -S -C %s -p --no-recursion --files-from - | tar --xattrs --xattrs-include='*' -xhf - -C %s" % (src, src, dst) 121*4882a593Smuzhiyun subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT) 122*4882a593Smuzhiyun source = '' 123*4882a593Smuzhiyun if os.path.isdir(src): 124*4882a593Smuzhiyun if len(glob.glob('%s/.??*' % src)) > 0: 125*4882a593Smuzhiyun source = './.??* ' 126*4882a593Smuzhiyun source += './*' 127*4882a593Smuzhiyun s_dir = src 128*4882a593Smuzhiyun else: 129*4882a593Smuzhiyun source = src 130*4882a593Smuzhiyun s_dir = os.getcwd() 131*4882a593Smuzhiyun cmd = 'cp -afl --preserve=xattr %s %s' % (source, os.path.realpath(dst)) 132*4882a593Smuzhiyun subprocess.check_output(cmd, shell=True, cwd=s_dir, stderr=subprocess.STDOUT) 133*4882a593Smuzhiyun else: 134*4882a593Smuzhiyun copytree(src, dst) 135*4882a593Smuzhiyun 136*4882a593Smuzhiyundef copyhardlink(src, dst): 137*4882a593Smuzhiyun """Make a hard link when possible, otherwise copy.""" 138*4882a593Smuzhiyun 139*4882a593Smuzhiyun try: 140*4882a593Smuzhiyun os.link(src, dst) 141*4882a593Smuzhiyun except OSError: 142*4882a593Smuzhiyun shutil.copy(src, dst) 143*4882a593Smuzhiyun 144*4882a593Smuzhiyundef remove(path, recurse=True): 145*4882a593Smuzhiyun """ 146*4882a593Smuzhiyun Equivalent to rm -f or rm -rf 147*4882a593Smuzhiyun NOTE: be careful about passing paths that may contain filenames with 148*4882a593Smuzhiyun wildcards in them (as opposed to passing an actual wildcarded path) - 149*4882a593Smuzhiyun since we use glob.glob() to expand the path. Filenames containing 150*4882a593Smuzhiyun square brackets are particularly problematic since the they may not 151*4882a593Smuzhiyun actually expand to match the original filename. 152*4882a593Smuzhiyun """ 153*4882a593Smuzhiyun for name in glob.glob(path): 154*4882a593Smuzhiyun try: 155*4882a593Smuzhiyun os.unlink(name) 156*4882a593Smuzhiyun except OSError as exc: 157*4882a593Smuzhiyun if recurse and exc.errno == errno.EISDIR: 158*4882a593Smuzhiyun shutil.rmtree(name) 159*4882a593Smuzhiyun elif exc.errno != errno.ENOENT: 160*4882a593Smuzhiyun raise 161*4882a593Smuzhiyun 162*4882a593Smuzhiyundef symlink(source, destination, force=False): 163*4882a593Smuzhiyun """Create a symbolic link""" 164*4882a593Smuzhiyun try: 165*4882a593Smuzhiyun if force: 166*4882a593Smuzhiyun remove(destination) 167*4882a593Smuzhiyun os.symlink(source, destination) 168*4882a593Smuzhiyun except OSError as e: 169*4882a593Smuzhiyun if e.errno != errno.EEXIST or os.readlink(destination) != source: 170*4882a593Smuzhiyun raise 171*4882a593Smuzhiyun 172*4882a593Smuzhiyundef find(dir, **walkoptions): 173*4882a593Smuzhiyun """ Given a directory, recurses into that directory, 174*4882a593Smuzhiyun returning all files as absolute paths. """ 175*4882a593Smuzhiyun 176*4882a593Smuzhiyun for root, dirs, files in os.walk(dir, **walkoptions): 177*4882a593Smuzhiyun for file in files: 178*4882a593Smuzhiyun yield os.path.join(root, file) 179*4882a593Smuzhiyun 180*4882a593Smuzhiyun 181*4882a593Smuzhiyun## realpath() related functions 182*4882a593Smuzhiyundef __is_path_below(file, root): 183*4882a593Smuzhiyun return (file + os.path.sep).startswith(root) 184*4882a593Smuzhiyun 185*4882a593Smuzhiyundef __realpath_rel(start, rel_path, root, loop_cnt, assume_dir): 186*4882a593Smuzhiyun """Calculates real path of symlink 'start' + 'rel_path' below 187*4882a593Smuzhiyun 'root'; no part of 'start' below 'root' must contain symlinks. """ 188*4882a593Smuzhiyun have_dir = True 189*4882a593Smuzhiyun 190*4882a593Smuzhiyun for d in rel_path.split(os.path.sep): 191*4882a593Smuzhiyun if not have_dir and not assume_dir: 192*4882a593Smuzhiyun raise OSError(errno.ENOENT, "no such directory %s" % start) 193*4882a593Smuzhiyun 194*4882a593Smuzhiyun if d == os.path.pardir: # '..' 195*4882a593Smuzhiyun if len(start) >= len(root): 196*4882a593Smuzhiyun # do not follow '..' before root 197*4882a593Smuzhiyun start = os.path.dirname(start) 198*4882a593Smuzhiyun else: 199*4882a593Smuzhiyun # emit warning? 200*4882a593Smuzhiyun pass 201*4882a593Smuzhiyun else: 202*4882a593Smuzhiyun (start, have_dir) = __realpath(os.path.join(start, d), 203*4882a593Smuzhiyun root, loop_cnt, assume_dir) 204*4882a593Smuzhiyun 205*4882a593Smuzhiyun assert(__is_path_below(start, root)) 206*4882a593Smuzhiyun 207*4882a593Smuzhiyun return start 208*4882a593Smuzhiyun 209*4882a593Smuzhiyundef __realpath(file, root, loop_cnt, assume_dir): 210*4882a593Smuzhiyun while os.path.islink(file) and len(file) >= len(root): 211*4882a593Smuzhiyun if loop_cnt == 0: 212*4882a593Smuzhiyun raise OSError(errno.ELOOP, file) 213*4882a593Smuzhiyun 214*4882a593Smuzhiyun loop_cnt -= 1 215*4882a593Smuzhiyun target = os.path.normpath(os.readlink(file)) 216*4882a593Smuzhiyun 217*4882a593Smuzhiyun if not os.path.isabs(target): 218*4882a593Smuzhiyun tdir = os.path.dirname(file) 219*4882a593Smuzhiyun assert(__is_path_below(tdir, root)) 220*4882a593Smuzhiyun else: 221*4882a593Smuzhiyun tdir = root 222*4882a593Smuzhiyun 223*4882a593Smuzhiyun file = __realpath_rel(tdir, target, root, loop_cnt, assume_dir) 224*4882a593Smuzhiyun 225*4882a593Smuzhiyun try: 226*4882a593Smuzhiyun is_dir = os.path.isdir(file) 227*4882a593Smuzhiyun except: 228*4882a593Smuzhiyun is_dir = false 229*4882a593Smuzhiyun 230*4882a593Smuzhiyun return (file, is_dir) 231*4882a593Smuzhiyun 232*4882a593Smuzhiyundef realpath(file, root, use_physdir = True, loop_cnt = 100, assume_dir = False): 233*4882a593Smuzhiyun """ Returns the canonical path of 'file' with assuming a 234*4882a593Smuzhiyun toplevel 'root' directory. When 'use_physdir' is set, all 235*4882a593Smuzhiyun preceding path components of 'file' will be resolved first; 236*4882a593Smuzhiyun this flag should be set unless it is guaranteed that there is 237*4882a593Smuzhiyun no symlink in the path. When 'assume_dir' is not set, missing 238*4882a593Smuzhiyun path components will raise an ENOENT error""" 239*4882a593Smuzhiyun 240*4882a593Smuzhiyun root = os.path.normpath(root) 241*4882a593Smuzhiyun file = os.path.normpath(file) 242*4882a593Smuzhiyun 243*4882a593Smuzhiyun if not root.endswith(os.path.sep): 244*4882a593Smuzhiyun # letting root end with '/' makes some things easier 245*4882a593Smuzhiyun root = root + os.path.sep 246*4882a593Smuzhiyun 247*4882a593Smuzhiyun if not __is_path_below(file, root): 248*4882a593Smuzhiyun raise OSError(errno.EINVAL, "file '%s' is not below root" % file) 249*4882a593Smuzhiyun 250*4882a593Smuzhiyun try: 251*4882a593Smuzhiyun if use_physdir: 252*4882a593Smuzhiyun file = __realpath_rel(root, file[(len(root) - 1):], root, loop_cnt, assume_dir) 253*4882a593Smuzhiyun else: 254*4882a593Smuzhiyun file = __realpath(file, root, loop_cnt, assume_dir)[0] 255*4882a593Smuzhiyun except OSError as e: 256*4882a593Smuzhiyun if e.errno == errno.ELOOP: 257*4882a593Smuzhiyun # make ELOOP more readable; without catching it, there will 258*4882a593Smuzhiyun # be printed a backtrace with 100s of OSError exceptions 259*4882a593Smuzhiyun # else 260*4882a593Smuzhiyun raise OSError(errno.ELOOP, 261*4882a593Smuzhiyun "too much recursions while resolving '%s'; loop in '%s'" % 262*4882a593Smuzhiyun (file, e.strerror)) 263*4882a593Smuzhiyun 264*4882a593Smuzhiyun raise 265*4882a593Smuzhiyun 266*4882a593Smuzhiyun return file 267*4882a593Smuzhiyun 268*4882a593Smuzhiyundef is_path_parent(possible_parent, *paths): 269*4882a593Smuzhiyun """ 270*4882a593Smuzhiyun Return True if a path is the parent of another, False otherwise. 271*4882a593Smuzhiyun Multiple paths to test can be specified in which case all 272*4882a593Smuzhiyun specified test paths must be under the parent in order to 273*4882a593Smuzhiyun return True. 274*4882a593Smuzhiyun """ 275*4882a593Smuzhiyun def abs_path_trailing(pth): 276*4882a593Smuzhiyun pth_abs = os.path.abspath(pth) 277*4882a593Smuzhiyun if not pth_abs.endswith(os.sep): 278*4882a593Smuzhiyun pth_abs += os.sep 279*4882a593Smuzhiyun return pth_abs 280*4882a593Smuzhiyun 281*4882a593Smuzhiyun possible_parent_abs = abs_path_trailing(possible_parent) 282*4882a593Smuzhiyun if not paths: 283*4882a593Smuzhiyun return False 284*4882a593Smuzhiyun for path in paths: 285*4882a593Smuzhiyun path_abs = abs_path_trailing(path) 286*4882a593Smuzhiyun if not path_abs.startswith(possible_parent_abs): 287*4882a593Smuzhiyun return False 288*4882a593Smuzhiyun return True 289*4882a593Smuzhiyun 290*4882a593Smuzhiyundef which_wild(pathname, path=None, mode=os.F_OK, *, reverse=False, candidates=False): 291*4882a593Smuzhiyun """Search a search path for pathname, supporting wildcards. 292*4882a593Smuzhiyun 293*4882a593Smuzhiyun Return all paths in the specific search path matching the wildcard pattern 294*4882a593Smuzhiyun in pathname, returning only the first encountered for each file. If 295*4882a593Smuzhiyun candidates is True, information on all potential candidate paths are 296*4882a593Smuzhiyun included. 297*4882a593Smuzhiyun """ 298*4882a593Smuzhiyun paths = (path or os.environ.get('PATH', os.defpath)).split(':') 299*4882a593Smuzhiyun if reverse: 300*4882a593Smuzhiyun paths.reverse() 301*4882a593Smuzhiyun 302*4882a593Smuzhiyun seen, files = set(), [] 303*4882a593Smuzhiyun for index, element in enumerate(paths): 304*4882a593Smuzhiyun if not os.path.isabs(element): 305*4882a593Smuzhiyun element = os.path.abspath(element) 306*4882a593Smuzhiyun 307*4882a593Smuzhiyun candidate = os.path.join(element, pathname) 308*4882a593Smuzhiyun globbed = glob.glob(candidate) 309*4882a593Smuzhiyun if globbed: 310*4882a593Smuzhiyun for found_path in sorted(globbed): 311*4882a593Smuzhiyun if not os.access(found_path, mode): 312*4882a593Smuzhiyun continue 313*4882a593Smuzhiyun rel = os.path.relpath(found_path, element) 314*4882a593Smuzhiyun if rel not in seen: 315*4882a593Smuzhiyun seen.add(rel) 316*4882a593Smuzhiyun if candidates: 317*4882a593Smuzhiyun files.append((found_path, [os.path.join(p, rel) for p in paths[:index+1]])) 318*4882a593Smuzhiyun else: 319*4882a593Smuzhiyun files.append(found_path) 320*4882a593Smuzhiyun 321*4882a593Smuzhiyun return files 322*4882a593Smuzhiyun 323*4882a593Smuzhiyundef canonicalize(paths, sep=','): 324*4882a593Smuzhiyun """Given a string with paths (separated by commas by default), expand 325*4882a593Smuzhiyun each path using os.path.realpath() and return the resulting paths as a 326*4882a593Smuzhiyun string (separated using the same separator a the original string). 327*4882a593Smuzhiyun """ 328*4882a593Smuzhiyun # Ignore paths containing "$" as they are assumed to be unexpanded bitbake 329*4882a593Smuzhiyun # variables. Normally they would be ignored, e.g., when passing the paths 330*4882a593Smuzhiyun # through the shell they would expand to empty strings. However, when they 331*4882a593Smuzhiyun # are passed through os.path.realpath(), it will cause them to be prefixed 332*4882a593Smuzhiyun # with the absolute path to the current directory and thus not be empty 333*4882a593Smuzhiyun # anymore. 334*4882a593Smuzhiyun # 335*4882a593Smuzhiyun # Also maintain trailing slashes, as the paths may actually be used as 336*4882a593Smuzhiyun # prefixes in sting compares later on, where the slashes then are important. 337*4882a593Smuzhiyun canonical_paths = [] 338*4882a593Smuzhiyun for path in (paths or '').split(sep): 339*4882a593Smuzhiyun if '$' not in path: 340*4882a593Smuzhiyun trailing_slash = path.endswith('/') and '/' or '' 341*4882a593Smuzhiyun canonical_paths.append(os.path.realpath(path) + trailing_slash) 342*4882a593Smuzhiyun 343*4882a593Smuzhiyun return sep.join(canonical_paths) 344