blob: fa209b9795b711592910296768370a2d66921ae7 [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
102 if (os.stat(src).st_dev == os.stat(dst).st_dev):
103 # Need to copy directories only with tar first since cp will error if two
104 # writers try and create a directory at the same time
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800105 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 -0600106 subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT)
107 source = ''
108 if os.path.isdir(src):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600109 if len(glob.glob('%s/.??*' % src)) > 0:
Brad Bishop37a0e4d2017-12-04 01:01:44 -0500110 source = './.??* '
111 source += './*'
112 s_dir = src
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600113 else:
114 source = src
Brad Bishop37a0e4d2017-12-04 01:01:44 -0500115 s_dir = os.getcwd()
116 cmd = 'cp -afl --preserve=xattr %s %s' % (source, os.path.realpath(dst))
117 subprocess.check_output(cmd, shell=True, cwd=s_dir, stderr=subprocess.STDOUT)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500118 else:
119 copytree(src, dst)
120
Brad Bishopc342db32019-05-15 21:57:59 -0400121def copyhardlink(src, dst):
122 """Make a hard link when possible, otherwise copy."""
123
124 # We need to stat the destination directory as the destination file probably
125 # doesn't exist yet.
126 dstdir = os.path.dirname(dst)
127 if os.stat(src).st_dev == os.stat(dstdir).st_dev:
128 os.link(src, dst)
129 else:
130 shutil.copy(src, dst)
131
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500132def remove(path, recurse=True):
Brad Bishop6e60e8b2018-02-01 10:27:11 -0500133 """
134 Equivalent to rm -f or rm -rf
135 NOTE: be careful about passing paths that may contain filenames with
136 wildcards in them (as opposed to passing an actual wildcarded path) -
137 since we use glob.glob() to expand the path. Filenames containing
138 square brackets are particularly problematic since the they may not
139 actually expand to match the original filename.
140 """
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500141 for name in glob.glob(path):
142 try:
143 os.unlink(name)
144 except OSError as exc:
145 if recurse and exc.errno == errno.EISDIR:
146 shutil.rmtree(name)
147 elif exc.errno != errno.ENOENT:
148 raise
149
150def symlink(source, destination, force=False):
151 """Create a symbolic link"""
152 try:
153 if force:
154 remove(destination)
155 os.symlink(source, destination)
156 except OSError as e:
157 if e.errno != errno.EEXIST or os.readlink(destination) != source:
158 raise
159
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500160def find(dir, **walkoptions):
161 """ Given a directory, recurses into that directory,
162 returning all files as absolute paths. """
163
164 for root, dirs, files in os.walk(dir, **walkoptions):
165 for file in files:
166 yield os.path.join(root, file)
167
168
169## realpath() related functions
170def __is_path_below(file, root):
171 return (file + os.path.sep).startswith(root)
172
173def __realpath_rel(start, rel_path, root, loop_cnt, assume_dir):
174 """Calculates real path of symlink 'start' + 'rel_path' below
175 'root'; no part of 'start' below 'root' must contain symlinks. """
176 have_dir = True
177
178 for d in rel_path.split(os.path.sep):
179 if not have_dir and not assume_dir:
180 raise OSError(errno.ENOENT, "no such directory %s" % start)
181
182 if d == os.path.pardir: # '..'
183 if len(start) >= len(root):
184 # do not follow '..' before root
185 start = os.path.dirname(start)
186 else:
187 # emit warning?
188 pass
189 else:
190 (start, have_dir) = __realpath(os.path.join(start, d),
191 root, loop_cnt, assume_dir)
192
193 assert(__is_path_below(start, root))
194
195 return start
196
197def __realpath(file, root, loop_cnt, assume_dir):
198 while os.path.islink(file) and len(file) >= len(root):
199 if loop_cnt == 0:
200 raise OSError(errno.ELOOP, file)
201
202 loop_cnt -= 1
203 target = os.path.normpath(os.readlink(file))
204
205 if not os.path.isabs(target):
206 tdir = os.path.dirname(file)
207 assert(__is_path_below(tdir, root))
208 else:
209 tdir = root
210
211 file = __realpath_rel(tdir, target, root, loop_cnt, assume_dir)
212
213 try:
214 is_dir = os.path.isdir(file)
215 except:
216 is_dir = false
217
218 return (file, is_dir)
219
220def realpath(file, root, use_physdir = True, loop_cnt = 100, assume_dir = False):
221 """ Returns the canonical path of 'file' with assuming a
222 toplevel 'root' directory. When 'use_physdir' is set, all
223 preceding path components of 'file' will be resolved first;
224 this flag should be set unless it is guaranteed that there is
225 no symlink in the path. When 'assume_dir' is not set, missing
226 path components will raise an ENOENT error"""
227
228 root = os.path.normpath(root)
229 file = os.path.normpath(file)
230
231 if not root.endswith(os.path.sep):
232 # letting root end with '/' makes some things easier
233 root = root + os.path.sep
234
235 if not __is_path_below(file, root):
236 raise OSError(errno.EINVAL, "file '%s' is not below root" % file)
237
238 try:
239 if use_physdir:
240 file = __realpath_rel(root, file[(len(root) - 1):], root, loop_cnt, assume_dir)
241 else:
242 file = __realpath(file, root, loop_cnt, assume_dir)[0]
243 except OSError as e:
244 if e.errno == errno.ELOOP:
245 # make ELOOP more readable; without catching it, there will
246 # be printed a backtrace with 100s of OSError exceptions
247 # else
248 raise OSError(errno.ELOOP,
249 "too much recursions while resolving '%s'; loop in '%s'" %
250 (file, e.strerror))
251
252 raise
253
254 return file
Brad Bishop316dfdd2018-06-25 12:45:53 -0400255
256def is_path_parent(possible_parent, *paths):
257 """
258 Return True if a path is the parent of another, False otherwise.
259 Multiple paths to test can be specified in which case all
260 specified test paths must be under the parent in order to
261 return True.
262 """
263 def abs_path_trailing(pth):
264 pth_abs = os.path.abspath(pth)
265 if not pth_abs.endswith(os.sep):
266 pth_abs += os.sep
267 return pth_abs
268
269 possible_parent_abs = abs_path_trailing(possible_parent)
270 if not paths:
271 return False
272 for path in paths:
273 path_abs = abs_path_trailing(path)
274 if not path_abs.startswith(possible_parent_abs):
275 return False
276 return True
Brad Bishop1a4b7ee2018-12-16 17:11:34 -0800277
278def which_wild(pathname, path=None, mode=os.F_OK, *, reverse=False, candidates=False):
279 """Search a search path for pathname, supporting wildcards.
280
281 Return all paths in the specific search path matching the wildcard pattern
282 in pathname, returning only the first encountered for each file. If
283 candidates is True, information on all potential candidate paths are
284 included.
285 """
286 paths = (path or os.environ.get('PATH', os.defpath)).split(':')
287 if reverse:
288 paths.reverse()
289
290 seen, files = set(), []
291 for index, element in enumerate(paths):
292 if not os.path.isabs(element):
293 element = os.path.abspath(element)
294
295 candidate = os.path.join(element, pathname)
296 globbed = glob.glob(candidate)
297 if globbed:
298 for found_path in sorted(globbed):
299 if not os.access(found_path, mode):
300 continue
301 rel = os.path.relpath(found_path, element)
302 if rel not in seen:
303 seen.add(rel)
304 if candidates:
305 files.append((found_path, [os.path.join(p, rel) for p in paths[:index+1]]))
306 else:
307 files.append(found_path)
308
309 return files
310