blob: 1e24d0586b6f4f125bb7242ed3d965691f3b86dd [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
Brad Bishop6e60e8b2018-02-01 10:27:11 -050053def replace_absolute_symlinks(basedir, d):
54 """
55 Walk basedir looking for absolute symlinks and replacing them with relative ones.
56 The absolute links are assumed to be relative to basedir
57 (compared to make_relative_symlink above which tries to compute common ancestors
58 using pattern matching instead)
59 """
60 for walkroot, dirs, files in os.walk(basedir):
61 for file in files + dirs:
62 path = os.path.join(walkroot, file)
63 if not os.path.islink(path):
64 continue
65 link = os.readlink(path)
66 if not os.path.isabs(link):
67 continue
68 walkdir = os.path.dirname(path.rpartition(basedir)[2])
69 base = os.path.relpath(link, walkdir)
70 bb.debug(2, "Replacing absolute path %s with relative path %s" % (link, base))
71 os.remove(path)
72 os.symlink(base, path)
73
Patrick Williamsc124f4f2015-09-15 14:41:29 -050074def format_display(path, metadata):
75 """ Prepare a path for display to the user. """
Brad Bishop6e60e8b2018-02-01 10:27:11 -050076 rel = relative(metadata.getVar("TOPDIR"), path)
Patrick Williamsc124f4f2015-09-15 14:41:29 -050077 if len(rel) > len(path):
78 return path
79 else:
80 return rel
81
82def copytree(src, dst):
83 # We could use something like shutil.copytree here but it turns out to
84 # to be slow. It takes twice as long copying to an empty directory.
85 # If dst already has contents performance can be 15 time slower
86 # This way we also preserve hardlinks between files in the tree.
87
88 bb.utils.mkdirhier(dst)
Brad Bishop1a4b7ee2018-12-16 17:11:34 -080089 cmd = "tar --xattrs --xattrs-include='*' -cf - -S -C %s -p . | tar --xattrs --xattrs-include='*' -xf - -C %s" % (src, dst)
Patrick Williamsc0f7c042017-02-23 20:41:17 -060090 subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT)
Patrick Williamsc124f4f2015-09-15 14:41:29 -050091
92def copyhardlinktree(src, dst):
93 """ Make the hard link when possible, otherwise copy. """
94 bb.utils.mkdirhier(dst)
95 if os.path.isdir(src) and not len(os.listdir(src)):
Patrick Williamsc0f7c042017-02-23 20:41:17 -060096 return
Patrick Williamsc124f4f2015-09-15 14:41:29 -050097
98 if (os.stat(src).st_dev == os.stat(dst).st_dev):
99 # Need to copy directories only with tar first since cp will error if two
100 # writers try and create a directory at the same time
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800101 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)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600102 subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT)
103 source = ''
104 if os.path.isdir(src):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600105 if len(glob.glob('%s/.??*' % src)) > 0:
Brad Bishop37a0e4d2017-12-04 01:01:44 -0500106 source = './.??* '
107 source += './*'
108 s_dir = src
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600109 else:
110 source = src
Brad Bishop37a0e4d2017-12-04 01:01:44 -0500111 s_dir = os.getcwd()
112 cmd = 'cp -afl --preserve=xattr %s %s' % (source, os.path.realpath(dst))
113 subprocess.check_output(cmd, shell=True, cwd=s_dir, stderr=subprocess.STDOUT)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500114 else:
115 copytree(src, dst)
116
117def remove(path, recurse=True):
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500118 """
119 Equivalent to rm -f or rm -rf
120 NOTE: be careful about passing paths that may contain filenames with
121 wildcards in them (as opposed to passing an actual wildcarded path) -
122 since we use glob.glob() to expand the path. Filenames containing
123 square brackets are particularly problematic since the they may not
124 actually expand to match the original filename.
125 """
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500126 for name in glob.glob(path):
127 try:
128 os.unlink(name)
129 except OSError as exc:
130 if recurse and exc.errno == errno.EISDIR:
131 shutil.rmtree(name)
132 elif exc.errno != errno.ENOENT:
133 raise
134
135def symlink(source, destination, force=False):
136 """Create a symbolic link"""
137 try:
138 if force:
139 remove(destination)
140 os.symlink(source, destination)
141 except OSError as e:
142 if e.errno != errno.EEXIST or os.readlink(destination) != source:
143 raise
144
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500145def find(dir, **walkoptions):
146 """ Given a directory, recurses into that directory,
147 returning all files as absolute paths. """
148
149 for root, dirs, files in os.walk(dir, **walkoptions):
150 for file in files:
151 yield os.path.join(root, file)
152
153
154## realpath() related functions
155def __is_path_below(file, root):
156 return (file + os.path.sep).startswith(root)
157
158def __realpath_rel(start, rel_path, root, loop_cnt, assume_dir):
159 """Calculates real path of symlink 'start' + 'rel_path' below
160 'root'; no part of 'start' below 'root' must contain symlinks. """
161 have_dir = True
162
163 for d in rel_path.split(os.path.sep):
164 if not have_dir and not assume_dir:
165 raise OSError(errno.ENOENT, "no such directory %s" % start)
166
167 if d == os.path.pardir: # '..'
168 if len(start) >= len(root):
169 # do not follow '..' before root
170 start = os.path.dirname(start)
171 else:
172 # emit warning?
173 pass
174 else:
175 (start, have_dir) = __realpath(os.path.join(start, d),
176 root, loop_cnt, assume_dir)
177
178 assert(__is_path_below(start, root))
179
180 return start
181
182def __realpath(file, root, loop_cnt, assume_dir):
183 while os.path.islink(file) and len(file) >= len(root):
184 if loop_cnt == 0:
185 raise OSError(errno.ELOOP, file)
186
187 loop_cnt -= 1
188 target = os.path.normpath(os.readlink(file))
189
190 if not os.path.isabs(target):
191 tdir = os.path.dirname(file)
192 assert(__is_path_below(tdir, root))
193 else:
194 tdir = root
195
196 file = __realpath_rel(tdir, target, root, loop_cnt, assume_dir)
197
198 try:
199 is_dir = os.path.isdir(file)
200 except:
201 is_dir = false
202
203 return (file, is_dir)
204
205def realpath(file, root, use_physdir = True, loop_cnt = 100, assume_dir = False):
206 """ Returns the canonical path of 'file' with assuming a
207 toplevel 'root' directory. When 'use_physdir' is set, all
208 preceding path components of 'file' will be resolved first;
209 this flag should be set unless it is guaranteed that there is
210 no symlink in the path. When 'assume_dir' is not set, missing
211 path components will raise an ENOENT error"""
212
213 root = os.path.normpath(root)
214 file = os.path.normpath(file)
215
216 if not root.endswith(os.path.sep):
217 # letting root end with '/' makes some things easier
218 root = root + os.path.sep
219
220 if not __is_path_below(file, root):
221 raise OSError(errno.EINVAL, "file '%s' is not below root" % file)
222
223 try:
224 if use_physdir:
225 file = __realpath_rel(root, file[(len(root) - 1):], root, loop_cnt, assume_dir)
226 else:
227 file = __realpath(file, root, loop_cnt, assume_dir)[0]
228 except OSError as e:
229 if e.errno == errno.ELOOP:
230 # make ELOOP more readable; without catching it, there will
231 # be printed a backtrace with 100s of OSError exceptions
232 # else
233 raise OSError(errno.ELOOP,
234 "too much recursions while resolving '%s'; loop in '%s'" %
235 (file, e.strerror))
236
237 raise
238
239 return file
Brad Bishop316dfdd2018-06-25 12:45:53 -0400240
241def is_path_parent(possible_parent, *paths):
242 """
243 Return True if a path is the parent of another, False otherwise.
244 Multiple paths to test can be specified in which case all
245 specified test paths must be under the parent in order to
246 return True.
247 """
248 def abs_path_trailing(pth):
249 pth_abs = os.path.abspath(pth)
250 if not pth_abs.endswith(os.sep):
251 pth_abs += os.sep
252 return pth_abs
253
254 possible_parent_abs = abs_path_trailing(possible_parent)
255 if not paths:
256 return False
257 for path in paths:
258 path_abs = abs_path_trailing(path)
259 if not path_abs.startswith(possible_parent_abs):
260 return False
261 return True
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800262
263def which_wild(pathname, path=None, mode=os.F_OK, *, reverse=False, candidates=False):
264 """Search a search path for pathname, supporting wildcards.
265
266 Return all paths in the specific search path matching the wildcard pattern
267 in pathname, returning only the first encountered for each file. If
268 candidates is True, information on all potential candidate paths are
269 included.
270 """
271 paths = (path or os.environ.get('PATH', os.defpath)).split(':')
272 if reverse:
273 paths.reverse()
274
275 seen, files = set(), []
276 for index, element in enumerate(paths):
277 if not os.path.isabs(element):
278 element = os.path.abspath(element)
279
280 candidate = os.path.join(element, pathname)
281 globbed = glob.glob(candidate)
282 if globbed:
283 for found_path in sorted(globbed):
284 if not os.access(found_path, mode):
285 continue
286 rel = os.path.relpath(found_path, element)
287 if rel not in seen:
288 seen.add(rel)
289 if candidates:
290 files.append((found_path, [os.path.join(p, rel) for p in paths[:index+1]]))
291 else:
292 files.append(found_path)
293
294 return files
295