| Patrick Williams | c124f4f | 2015-09-15 14:41:29 -0500 | [diff] [blame] | 1 | import errno | 
|  | 2 | import glob | 
|  | 3 | import shutil | 
|  | 4 | import subprocess | 
|  | 5 | import os.path | 
|  | 6 |  | 
|  | 7 | def join(*paths): | 
|  | 8 | """Like os.path.join but doesn't treat absolute RHS specially""" | 
|  | 9 | return os.path.normpath("/".join(paths)) | 
|  | 10 |  | 
|  | 11 | def relative(src, dest): | 
|  | 12 | """ Return a relative path from src to dest. | 
|  | 13 |  | 
|  | 14 | >>> relative("/usr/bin", "/tmp/foo/bar") | 
|  | 15 | ../../tmp/foo/bar | 
|  | 16 |  | 
|  | 17 | >>> relative("/usr/bin", "/usr/lib") | 
|  | 18 | ../lib | 
|  | 19 |  | 
|  | 20 | >>> relative("/tmp", "/tmp/foo/bar") | 
|  | 21 | foo/bar | 
|  | 22 | """ | 
|  | 23 |  | 
|  | 24 | return os.path.relpath(dest, src) | 
|  | 25 |  | 
|  | 26 | def make_relative_symlink(path): | 
|  | 27 | """ Convert an absolute symlink to a relative one """ | 
|  | 28 | if not os.path.islink(path): | 
|  | 29 | return | 
|  | 30 | link = os.readlink(path) | 
|  | 31 | if not os.path.isabs(link): | 
|  | 32 | return | 
|  | 33 |  | 
|  | 34 | # find the common ancestor directory | 
|  | 35 | ancestor = path | 
|  | 36 | depth = 0 | 
|  | 37 | while ancestor and not link.startswith(ancestor): | 
|  | 38 | ancestor = ancestor.rpartition('/')[0] | 
|  | 39 | depth += 1 | 
|  | 40 |  | 
|  | 41 | if not ancestor: | 
|  | 42 | print("make_relative_symlink() Error: unable to find the common ancestor of %s and its target" % path) | 
|  | 43 | return | 
|  | 44 |  | 
|  | 45 | base = link.partition(ancestor)[2].strip('/') | 
|  | 46 | while depth > 1: | 
|  | 47 | base = "../" + base | 
|  | 48 | depth -= 1 | 
|  | 49 |  | 
|  | 50 | os.remove(path) | 
|  | 51 | os.symlink(base, path) | 
|  | 52 |  | 
|  | 53 | def format_display(path, metadata): | 
|  | 54 | """ Prepare a path for display to the user. """ | 
|  | 55 | rel = relative(metadata.getVar("TOPDIR", True), path) | 
|  | 56 | if len(rel) > len(path): | 
|  | 57 | return path | 
|  | 58 | else: | 
|  | 59 | return rel | 
|  | 60 |  | 
|  | 61 | def copytree(src, dst): | 
|  | 62 | # We could use something like shutil.copytree here but it turns out to | 
|  | 63 | # to be slow. It takes twice as long copying to an empty directory. | 
|  | 64 | # If dst already has contents performance can be 15 time slower | 
|  | 65 | # This way we also preserve hardlinks between files in the tree. | 
|  | 66 |  | 
|  | 67 | bb.utils.mkdirhier(dst) | 
|  | 68 | cmd = 'tar -cf - -C %s -p . | tar -xf - -C %s' % (src, dst) | 
|  | 69 | check_output(cmd, shell=True, stderr=subprocess.STDOUT) | 
|  | 70 |  | 
|  | 71 | def copyhardlinktree(src, dst): | 
|  | 72 | """ Make the hard link when possible, otherwise copy. """ | 
|  | 73 | bb.utils.mkdirhier(dst) | 
|  | 74 | if os.path.isdir(src) and not len(os.listdir(src)): | 
|  | 75 | return | 
|  | 76 |  | 
|  | 77 | if (os.stat(src).st_dev ==  os.stat(dst).st_dev): | 
|  | 78 | # Need to copy directories only with tar first since cp will error if two | 
|  | 79 | # writers try and create a directory at the same time | 
|  | 80 | cmd = 'cd %s; find . -type d -print | tar -cf - -C %s -p --files-from - --no-recursion | tar -xf - -C %s' % (src, src, dst) | 
|  | 81 | check_output(cmd, shell=True, stderr=subprocess.STDOUT) | 
|  | 82 | cmd = 'cd %s; find . -print0 | cpio --null -pdlu %s' % (src, dst) | 
|  | 83 | check_output(cmd, shell=True, stderr=subprocess.STDOUT) | 
|  | 84 | else: | 
|  | 85 | copytree(src, dst) | 
|  | 86 |  | 
|  | 87 | def remove(path, recurse=True): | 
|  | 88 | """Equivalent to rm -f or rm -rf""" | 
|  | 89 | for name in glob.glob(path): | 
|  | 90 | try: | 
|  | 91 | os.unlink(name) | 
|  | 92 | except OSError as exc: | 
|  | 93 | if recurse and exc.errno == errno.EISDIR: | 
|  | 94 | shutil.rmtree(name) | 
|  | 95 | elif exc.errno != errno.ENOENT: | 
|  | 96 | raise | 
|  | 97 |  | 
|  | 98 | def symlink(source, destination, force=False): | 
|  | 99 | """Create a symbolic link""" | 
|  | 100 | try: | 
|  | 101 | if force: | 
|  | 102 | remove(destination) | 
|  | 103 | os.symlink(source, destination) | 
|  | 104 | except OSError as e: | 
|  | 105 | if e.errno != errno.EEXIST or os.readlink(destination) != source: | 
|  | 106 | raise | 
|  | 107 |  | 
|  | 108 | class CalledProcessError(Exception): | 
|  | 109 | def __init__(self, retcode, cmd, output = None): | 
|  | 110 | self.retcode = retcode | 
|  | 111 | self.cmd = cmd | 
|  | 112 | self.output = output | 
|  | 113 | def __str__(self): | 
|  | 114 | return "Command '%s' returned non-zero exit status %d with output %s" % (self.cmd, self.retcode, self.output) | 
|  | 115 |  | 
|  | 116 | # Not needed when we move to python 2.7 | 
|  | 117 | def check_output(*popenargs, **kwargs): | 
|  | 118 | r"""Run command with arguments and return its output as a byte string. | 
|  | 119 |  | 
|  | 120 | If the exit code was non-zero it raises a CalledProcessError.  The | 
|  | 121 | CalledProcessError object will have the return code in the returncode | 
|  | 122 | attribute and output in the output attribute. | 
|  | 123 |  | 
|  | 124 | The arguments are the same as for the Popen constructor.  Example: | 
|  | 125 |  | 
|  | 126 | >>> check_output(["ls", "-l", "/dev/null"]) | 
|  | 127 | 'crw-rw-rw- 1 root root 1, 3 Oct 18  2007 /dev/null\n' | 
|  | 128 |  | 
|  | 129 | The stdout argument is not allowed as it is used internally. | 
|  | 130 | To capture standard error in the result, use stderr=STDOUT. | 
|  | 131 |  | 
|  | 132 | >>> check_output(["/bin/sh", "-c", | 
|  | 133 | ...               "ls -l non_existent_file ; exit 0"], | 
|  | 134 | ...              stderr=STDOUT) | 
|  | 135 | 'ls: non_existent_file: No such file or directory\n' | 
|  | 136 | """ | 
|  | 137 | if 'stdout' in kwargs: | 
|  | 138 | raise ValueError('stdout argument not allowed, it will be overridden.') | 
|  | 139 | process = subprocess.Popen(stdout=subprocess.PIPE, *popenargs, **kwargs) | 
|  | 140 | output, unused_err = process.communicate() | 
|  | 141 | retcode = process.poll() | 
|  | 142 | if retcode: | 
|  | 143 | cmd = kwargs.get("args") | 
|  | 144 | if cmd is None: | 
|  | 145 | cmd = popenargs[0] | 
|  | 146 | raise CalledProcessError(retcode, cmd, output=output) | 
|  | 147 | return output | 
|  | 148 |  | 
|  | 149 | def find(dir, **walkoptions): | 
|  | 150 | """ Given a directory, recurses into that directory, | 
|  | 151 | returning all files as absolute paths. """ | 
|  | 152 |  | 
|  | 153 | for root, dirs, files in os.walk(dir, **walkoptions): | 
|  | 154 | for file in files: | 
|  | 155 | yield os.path.join(root, file) | 
|  | 156 |  | 
|  | 157 |  | 
|  | 158 | ## realpath() related functions | 
|  | 159 | def __is_path_below(file, root): | 
|  | 160 | return (file + os.path.sep).startswith(root) | 
|  | 161 |  | 
|  | 162 | def __realpath_rel(start, rel_path, root, loop_cnt, assume_dir): | 
|  | 163 | """Calculates real path of symlink 'start' + 'rel_path' below | 
|  | 164 | 'root'; no part of 'start' below 'root' must contain symlinks. """ | 
|  | 165 | have_dir = True | 
|  | 166 |  | 
|  | 167 | for d in rel_path.split(os.path.sep): | 
|  | 168 | if not have_dir and not assume_dir: | 
|  | 169 | raise OSError(errno.ENOENT, "no such directory %s" % start) | 
|  | 170 |  | 
|  | 171 | if d == os.path.pardir: # '..' | 
|  | 172 | if len(start) >= len(root): | 
|  | 173 | # do not follow '..' before root | 
|  | 174 | start = os.path.dirname(start) | 
|  | 175 | else: | 
|  | 176 | # emit warning? | 
|  | 177 | pass | 
|  | 178 | else: | 
|  | 179 | (start, have_dir) = __realpath(os.path.join(start, d), | 
|  | 180 | root, loop_cnt, assume_dir) | 
|  | 181 |  | 
|  | 182 | assert(__is_path_below(start, root)) | 
|  | 183 |  | 
|  | 184 | return start | 
|  | 185 |  | 
|  | 186 | def __realpath(file, root, loop_cnt, assume_dir): | 
|  | 187 | while os.path.islink(file) and len(file) >= len(root): | 
|  | 188 | if loop_cnt == 0: | 
|  | 189 | raise OSError(errno.ELOOP, file) | 
|  | 190 |  | 
|  | 191 | loop_cnt -= 1 | 
|  | 192 | target = os.path.normpath(os.readlink(file)) | 
|  | 193 |  | 
|  | 194 | if not os.path.isabs(target): | 
|  | 195 | tdir = os.path.dirname(file) | 
|  | 196 | assert(__is_path_below(tdir, root)) | 
|  | 197 | else: | 
|  | 198 | tdir = root | 
|  | 199 |  | 
|  | 200 | file = __realpath_rel(tdir, target, root, loop_cnt, assume_dir) | 
|  | 201 |  | 
|  | 202 | try: | 
|  | 203 | is_dir = os.path.isdir(file) | 
|  | 204 | except: | 
|  | 205 | is_dir = false | 
|  | 206 |  | 
|  | 207 | return (file, is_dir) | 
|  | 208 |  | 
|  | 209 | def realpath(file, root, use_physdir = True, loop_cnt = 100, assume_dir = False): | 
|  | 210 | """ Returns the canonical path of 'file' with assuming a | 
|  | 211 | toplevel 'root' directory. When 'use_physdir' is set, all | 
|  | 212 | preceding path components of 'file' will be resolved first; | 
|  | 213 | this flag should be set unless it is guaranteed that there is | 
|  | 214 | no symlink in the path. When 'assume_dir' is not set, missing | 
|  | 215 | path components will raise an ENOENT error""" | 
|  | 216 |  | 
|  | 217 | root = os.path.normpath(root) | 
|  | 218 | file = os.path.normpath(file) | 
|  | 219 |  | 
|  | 220 | if not root.endswith(os.path.sep): | 
|  | 221 | # letting root end with '/' makes some things easier | 
|  | 222 | root = root + os.path.sep | 
|  | 223 |  | 
|  | 224 | if not __is_path_below(file, root): | 
|  | 225 | raise OSError(errno.EINVAL, "file '%s' is not below root" % file) | 
|  | 226 |  | 
|  | 227 | try: | 
|  | 228 | if use_physdir: | 
|  | 229 | file = __realpath_rel(root, file[(len(root) - 1):], root, loop_cnt, assume_dir) | 
|  | 230 | else: | 
|  | 231 | file = __realpath(file, root, loop_cnt, assume_dir)[0] | 
|  | 232 | except OSError as e: | 
|  | 233 | if e.errno == errno.ELOOP: | 
|  | 234 | # make ELOOP more readable; without catching it, there will | 
|  | 235 | # be printed a backtrace with 100s of OSError exceptions | 
|  | 236 | # else | 
|  | 237 | raise OSError(errno.ELOOP, | 
|  | 238 | "too much recursions while resolving '%s'; loop in '%s'" % | 
|  | 239 | (file, e.strerror)) | 
|  | 240 |  | 
|  | 241 | raise | 
|  | 242 |  | 
|  | 243 | return file |