blob: 06a5af26594d9aa5fc9c51a656992f1cb4880c4b [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)
Patrick Williamsc0f7c042017-02-23 20:41:17 -060068 cmd = "tar --xattrs --xattrs-include='*' -cf - -C %s -p . | tar --xattrs --xattrs-include='*' -xf - -C %s" % (src, dst)
69 subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT)
Patrick Williamsc124f4f2015-09-15 14:41:29 -050070
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)):
Patrick Williamsc0f7c042017-02-23 20:41:17 -060075 return
Patrick Williamsc124f4f2015-09-15 14:41:29 -050076
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
Patrick Williamsc0f7c042017-02-23 20:41:17 -060080 cmd = "cd %s; find . -type d -print | tar --xattrs --xattrs-include='*' -cf - -C %s -p --no-recursion --files-from - | tar --xattrs --xattrs-include='*' -xf - -C %s" % (src, src, dst)
81 subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT)
82 source = ''
83 if os.path.isdir(src):
84 import glob
85 if len(glob.glob('%s/.??*' % src)) > 0:
86 source = '%s/.??* ' % src
87 source = source + '%s/*' % src
88 else:
89 source = src
90 cmd = 'cp -afl --preserve=xattr %s %s' % (source, dst)
91 subprocess.check_output(cmd, shell=True, stderr=subprocess.STDOUT)
Patrick Williamsc124f4f2015-09-15 14:41:29 -050092 else:
93 copytree(src, dst)
94
95def remove(path, recurse=True):
96 """Equivalent to rm -f or rm -rf"""
97 for name in glob.glob(path):
98 try:
99 os.unlink(name)
100 except OSError as exc:
101 if recurse and exc.errno == errno.EISDIR:
102 shutil.rmtree(name)
103 elif exc.errno != errno.ENOENT:
104 raise
105
106def symlink(source, destination, force=False):
107 """Create a symbolic link"""
108 try:
109 if force:
110 remove(destination)
111 os.symlink(source, destination)
112 except OSError as e:
113 if e.errno != errno.EEXIST or os.readlink(destination) != source:
114 raise
115
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500116def find(dir, **walkoptions):
117 """ Given a directory, recurses into that directory,
118 returning all files as absolute paths. """
119
120 for root, dirs, files in os.walk(dir, **walkoptions):
121 for file in files:
122 yield os.path.join(root, file)
123
124
125## realpath() related functions
126def __is_path_below(file, root):
127 return (file + os.path.sep).startswith(root)
128
129def __realpath_rel(start, rel_path, root, loop_cnt, assume_dir):
130 """Calculates real path of symlink 'start' + 'rel_path' below
131 'root'; no part of 'start' below 'root' must contain symlinks. """
132 have_dir = True
133
134 for d in rel_path.split(os.path.sep):
135 if not have_dir and not assume_dir:
136 raise OSError(errno.ENOENT, "no such directory %s" % start)
137
138 if d == os.path.pardir: # '..'
139 if len(start) >= len(root):
140 # do not follow '..' before root
141 start = os.path.dirname(start)
142 else:
143 # emit warning?
144 pass
145 else:
146 (start, have_dir) = __realpath(os.path.join(start, d),
147 root, loop_cnt, assume_dir)
148
149 assert(__is_path_below(start, root))
150
151 return start
152
153def __realpath(file, root, loop_cnt, assume_dir):
154 while os.path.islink(file) and len(file) >= len(root):
155 if loop_cnt == 0:
156 raise OSError(errno.ELOOP, file)
157
158 loop_cnt -= 1
159 target = os.path.normpath(os.readlink(file))
160
161 if not os.path.isabs(target):
162 tdir = os.path.dirname(file)
163 assert(__is_path_below(tdir, root))
164 else:
165 tdir = root
166
167 file = __realpath_rel(tdir, target, root, loop_cnt, assume_dir)
168
169 try:
170 is_dir = os.path.isdir(file)
171 except:
172 is_dir = false
173
174 return (file, is_dir)
175
176def realpath(file, root, use_physdir = True, loop_cnt = 100, assume_dir = False):
177 """ Returns the canonical path of 'file' with assuming a
178 toplevel 'root' directory. When 'use_physdir' is set, all
179 preceding path components of 'file' will be resolved first;
180 this flag should be set unless it is guaranteed that there is
181 no symlink in the path. When 'assume_dir' is not set, missing
182 path components will raise an ENOENT error"""
183
184 root = os.path.normpath(root)
185 file = os.path.normpath(file)
186
187 if not root.endswith(os.path.sep):
188 # letting root end with '/' makes some things easier
189 root = root + os.path.sep
190
191 if not __is_path_below(file, root):
192 raise OSError(errno.EINVAL, "file '%s' is not below root" % file)
193
194 try:
195 if use_physdir:
196 file = __realpath_rel(root, file[(len(root) - 1):], root, loop_cnt, assume_dir)
197 else:
198 file = __realpath(file, root, loop_cnt, assume_dir)[0]
199 except OSError as e:
200 if e.errno == errno.ELOOP:
201 # make ELOOP more readable; without catching it, there will
202 # be printed a backtrace with 100s of OSError exceptions
203 # else
204 raise OSError(errno.ELOOP,
205 "too much recursions while resolving '%s'; loop in '%s'" %
206 (file, e.strerror))
207
208 raise
209
210 return file