diff options
Diffstat (limited to 'meta/lib/oe')
-rw-r--r-- | meta/lib/oe/path.py | 87 | ||||
-rw-r--r-- | meta/lib/oe/tests/test_path.py | 89 |
2 files changed, 176 insertions, 0 deletions
diff --git a/meta/lib/oe/path.py b/meta/lib/oe/path.py index ea58bedc8b..0b34cebdab 100644 --- a/meta/lib/oe/path.py +++ b/meta/lib/oe/path.py | |||
@@ -2,6 +2,7 @@ import errno | |||
2 | import glob | 2 | import glob |
3 | import shutil | 3 | import shutil |
4 | import subprocess | 4 | import subprocess |
5 | import os.path | ||
5 | 6 | ||
6 | def join(*paths): | 7 | def join(*paths): |
7 | """Like os.path.join but doesn't treat absolute RHS specially""" | 8 | """Like os.path.join but doesn't treat absolute RHS specially""" |
@@ -161,3 +162,89 @@ def find(dir, **walkoptions): | |||
161 | for root, dirs, files in os.walk(dir, **walkoptions): | 162 | for root, dirs, files in os.walk(dir, **walkoptions): |
162 | for file in files: | 163 | for file in files: |
163 | yield os.path.join(root, file) | 164 | yield os.path.join(root, file) |
165 | |||
166 | |||
167 | ## realpath() related functions | ||
168 | def __is_path_below(file, root): | ||
169 | return (file + os.path.sep).startswith(root) | ||
170 | |||
171 | def __realpath_rel(start, rel_path, root, loop_cnt): | ||
172 | """Calculates real path of symlink 'start' + 'rel_path' below | ||
173 | 'root'; no part of 'start' below 'root' must contain symlinks. """ | ||
174 | have_dir = True | ||
175 | |||
176 | for d in rel_path.split(os.path.sep): | ||
177 | if not have_dir: | ||
178 | raise OSError(errno.ENOENT, "no such directory %s" % start) | ||
179 | |||
180 | if d == os.path.pardir: # '..' | ||
181 | if len(start) >= len(root): | ||
182 | # do not follow '..' before root | ||
183 | start = os.path.dirname(start) | ||
184 | else: | ||
185 | # emit warning? | ||
186 | pass | ||
187 | else: | ||
188 | (start, have_dir) = __realpath(os.path.join(start, d), | ||
189 | root, loop_cnt) | ||
190 | |||
191 | assert(__is_path_below(start, root)) | ||
192 | |||
193 | return start | ||
194 | |||
195 | def __realpath(file, root, loop_cnt): | ||
196 | while os.path.islink(file) and len(file) >= len(root): | ||
197 | if loop_cnt == 0: | ||
198 | raise OSError(errno.ELOOP, file) | ||
199 | |||
200 | loop_cnt -= 1 | ||
201 | target = os.path.normpath(os.readlink(file)) | ||
202 | |||
203 | if not os.path.isabs(target): | ||
204 | tdir = os.path.dirname(file) | ||
205 | assert(__is_path_below(tdir, root)) | ||
206 | else: | ||
207 | tdir = root | ||
208 | |||
209 | file = __realpath_rel(tdir, target, root, loop_cnt) | ||
210 | |||
211 | try: | ||
212 | is_dir = os.path.isdir(file) | ||
213 | except: | ||
214 | is_dir = false | ||
215 | |||
216 | return (file, is_dir) | ||
217 | |||
218 | def realpath(file, root, use_physdir = True, loop_cnt = 100): | ||
219 | """ Returns the canonical path of 'file' with assuming a toplevel | ||
220 | 'root' directory. When 'use_physdir' is set, all preceding path | ||
221 | components of 'file' will be resolved first; this flag should be | ||
222 | set unless it is guaranteed that there is no symlink in the path.""" | ||
223 | |||
224 | root = os.path.normpath(root) | ||
225 | file = os.path.normpath(file) | ||
226 | |||
227 | if not root.endswith(os.path.sep): | ||
228 | # letting root end with '/' makes some things easier | ||
229 | root = root + os.path.sep | ||
230 | |||
231 | if not __is_path_below(file, root): | ||
232 | raise OSError(errno.EINVAL, "file '%s' is not below root" % file) | ||
233 | |||
234 | try: | ||
235 | if use_physdir: | ||
236 | file = __realpath_rel(root, file[(len(root) - 1):], root, loop_cnt) | ||
237 | else: | ||
238 | file = __realpath(file, root, loop_cnt)[0] | ||
239 | except OSError, e: | ||
240 | if e.errno == errno.ELOOP: | ||
241 | # make ELOOP more readable; without catching it, there will | ||
242 | # be printed a backtrace with 100s of OSError exceptions | ||
243 | # else | ||
244 | raise OSError(errno.ELOOP, | ||
245 | "too much recursions while resolving '%s'; loop in '%s'" % | ||
246 | (file, e.strerror)) | ||
247 | |||
248 | raise | ||
249 | |||
250 | return file | ||
diff --git a/meta/lib/oe/tests/test_path.py b/meta/lib/oe/tests/test_path.py new file mode 100644 index 0000000000..e6aa601618 --- /dev/null +++ b/meta/lib/oe/tests/test_path.py | |||
@@ -0,0 +1,89 @@ | |||
1 | import unittest | ||
2 | import oe, oe.path | ||
3 | import tempfile | ||
4 | import os | ||
5 | import errno | ||
6 | import shutil | ||
7 | |||
8 | class TestRealPath(unittest.TestCase): | ||
9 | DIRS = [ "a", "b", "etc", "sbin", "usr", "usr/bin", "usr/binX", "usr/sbin", "usr/include", "usr/include/gdbm" ] | ||
10 | FILES = [ "etc/passwd", "b/file" ] | ||
11 | LINKS = [ | ||
12 | ( "bin", "/usr/bin", "/usr/bin" ), | ||
13 | ( "binX", "usr/binX", "/usr/binX" ), | ||
14 | ( "c", "broken", "/broken" ), | ||
15 | ( "etc/passwd-1", "passwd", "/etc/passwd" ), | ||
16 | ( "etc/passwd-2", "passwd-1", "/etc/passwd" ), | ||
17 | ( "etc/passwd-3", "/etc/passwd-1", "/etc/passwd" ), | ||
18 | ( "etc/shadow-1", "/etc/shadow", "/etc/shadow" ), | ||
19 | ( "etc/shadow-2", "/etc/shadow-1", "/etc/shadow" ), | ||
20 | ( "prog-A", "bin/prog-A", "/usr/bin/prog-A" ), | ||
21 | ( "prog-B", "/bin/prog-B", "/usr/bin/prog-B" ), | ||
22 | ( "usr/bin/prog-C", "../../sbin/prog-C", "/sbin/prog-C" ), | ||
23 | ( "usr/bin/prog-D", "/sbin/prog-D", "/sbin/prog-D" ), | ||
24 | ( "usr/binX/prog-E", "../sbin/prog-E", None ), | ||
25 | ( "usr/bin/prog-F", "../../../sbin/prog-F", "/sbin/prog-F" ), | ||
26 | ( "loop", "a/loop", None ), | ||
27 | ( "a/loop", "../loop", None ), | ||
28 | ( "b/test", "file/foo", None ), | ||
29 | ] | ||
30 | |||
31 | LINKS_PHYS = [ | ||
32 | ( "./", "/", "" ), | ||
33 | ( "binX/prog-E", "/usr/sbin/prog-E", "/sbin/prog-E" ), | ||
34 | ] | ||
35 | |||
36 | EXCEPTIONS = [ | ||
37 | ( "loop", errno.ELOOP ), | ||
38 | ( "b/test", errno.ENOENT ), | ||
39 | ] | ||
40 | |||
41 | def __del__(self): | ||
42 | try: | ||
43 | #os.system("tree -F %s" % self.tmpdir) | ||
44 | shutil.rmtree(self.tmpdir) | ||
45 | except: | ||
46 | pass | ||
47 | |||
48 | def setUp(self): | ||
49 | self.tmpdir = tempfile.mkdtemp(prefix = "oe-test_path") | ||
50 | self.root = os.path.join(self.tmpdir, "R") | ||
51 | |||
52 | os.mkdir(os.path.join(self.tmpdir, "_real")) | ||
53 | os.symlink("_real", self.root) | ||
54 | |||
55 | for d in self.DIRS: | ||
56 | os.mkdir(os.path.join(self.root, d)) | ||
57 | for f in self.FILES: | ||
58 | file(os.path.join(self.root, f), "w") | ||
59 | for l in self.LINKS: | ||
60 | os.symlink(l[1], os.path.join(self.root, l[0])) | ||
61 | |||
62 | def __realpath(self, file, use_physdir): | ||
63 | return oe.path.realpath(os.path.join(self.root, file), self.root, use_physdir) | ||
64 | |||
65 | def test_norm(self): | ||
66 | for l in self.LINKS: | ||
67 | if l[2] == None: | ||
68 | continue | ||
69 | |||
70 | target_p = self.__realpath(l[0], True) | ||
71 | target_l = self.__realpath(l[0], False) | ||
72 | |||
73 | if l[2] != False: | ||
74 | self.assertEqual(target_p, target_l) | ||
75 | self.assertEqual(l[2], target_p[len(self.root):]) | ||
76 | |||
77 | def test_phys(self): | ||
78 | for l in self.LINKS_PHYS: | ||
79 | target_p = self.__realpath(l[0], True) | ||
80 | target_l = self.__realpath(l[0], False) | ||
81 | |||
82 | self.assertEqual(l[1], target_p[len(self.root):]) | ||
83 | self.assertEqual(l[2], target_l[len(self.root):]) | ||
84 | |||
85 | def test_loop(self): | ||
86 | for e in self.EXCEPTIONS: | ||
87 | self.assertRaisesRegexp(OSError, r'\[Errno %u\]' % e[1], | ||
88 | self.__realpath, e[0], False) | ||
89 | |||