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