diff options
| -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 | |||
