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