blob: 413ebfb395c24cf6d0ab91a0ec9567d75a881173 [file] [log] [blame]
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001import errno
2import glob
3import shutil
4import subprocess
5import os.path
6
7def join(*paths):
8 """Like os.path.join but doesn't treat absolute RHS specially"""
9 return os.path.normpath("/".join(paths))
10
11def 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
26def 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
53def 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
61def 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
71def 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
87def 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
98def 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
108class 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
117def 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
149def 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
159def __is_path_below(file, root):
160 return (file + os.path.sep).startswith(root)
161
162def __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
186def __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
209def 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