cachedpath.py 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. #
  2. # Based on standard python library functions but avoid
  3. # repeated stat calls. Its assumed the files will not change from under us
  4. # so we can cache stat calls.
  5. #
  6. import os
  7. import errno
  8. import stat as statmod
  9. class CachedPath(object):
  10. def __init__(self):
  11. self.statcache = {}
  12. self.lstatcache = {}
  13. self.normpathcache = {}
  14. return
  15. def updatecache(self, x):
  16. x = self.normpath(x)
  17. if x in self.statcache:
  18. del self.statcache[x]
  19. if x in self.lstatcache:
  20. del self.lstatcache[x]
  21. def normpath(self, path):
  22. if path in self.normpathcache:
  23. return self.normpathcache[path]
  24. newpath = os.path.normpath(path)
  25. self.normpathcache[path] = newpath
  26. return newpath
  27. def _callstat(self, path):
  28. if path in self.statcache:
  29. return self.statcache[path]
  30. try:
  31. st = os.stat(path)
  32. self.statcache[path] = st
  33. return st
  34. except os.error:
  35. self.statcache[path] = False
  36. return False
  37. # We might as well call lstat and then only
  38. # call stat as well in the symbolic link case
  39. # since this turns out to be much more optimal
  40. # in real world usage of this cache
  41. def callstat(self, path):
  42. path = self.normpath(path)
  43. self.calllstat(path)
  44. return self.statcache[path]
  45. def calllstat(self, path):
  46. path = self.normpath(path)
  47. if path in self.lstatcache:
  48. return self.lstatcache[path]
  49. #bb.error("LStatpath:" + path)
  50. try:
  51. lst = os.lstat(path)
  52. self.lstatcache[path] = lst
  53. if not statmod.S_ISLNK(lst.st_mode):
  54. self.statcache[path] = lst
  55. else:
  56. self._callstat(path)
  57. return lst
  58. except (os.error, AttributeError):
  59. self.lstatcache[path] = False
  60. self.statcache[path] = False
  61. return False
  62. # This follows symbolic links, so both islink() and isdir() can be true
  63. # for the same path ono systems that support symlinks
  64. def isfile(self, path):
  65. """Test whether a path is a regular file"""
  66. st = self.callstat(path)
  67. if not st:
  68. return False
  69. return statmod.S_ISREG(st.st_mode)
  70. # Is a path a directory?
  71. # This follows symbolic links, so both islink() and isdir()
  72. # can be true for the same path on systems that support symlinks
  73. def isdir(self, s):
  74. """Return true if the pathname refers to an existing directory."""
  75. st = self.callstat(s)
  76. if not st:
  77. return False
  78. return statmod.S_ISDIR(st.st_mode)
  79. def islink(self, path):
  80. """Test whether a path is a symbolic link"""
  81. st = self.calllstat(path)
  82. if not st:
  83. return False
  84. return statmod.S_ISLNK(st.st_mode)
  85. # Does a path exist?
  86. # This is false for dangling symbolic links on systems that support them.
  87. def exists(self, path):
  88. """Test whether a path exists. Returns False for broken symbolic links"""
  89. if self.callstat(path):
  90. return True
  91. return False
  92. def lexists(self, path):
  93. """Test whether a path exists. Returns True for broken symbolic links"""
  94. if self.calllstat(path):
  95. return True
  96. return False
  97. def stat(self, path):
  98. return self.callstat(path)
  99. def lstat(self, path):
  100. return self.calllstat(path)
  101. def walk(self, top, topdown=True, onerror=None, followlinks=False):
  102. # Matches os.walk, not os.path.walk()
  103. # We may not have read permission for top, in which case we can't
  104. # get a list of the files the directory contains. os.path.walk
  105. # always suppressed the exception then, rather than blow up for a
  106. # minor reason when (say) a thousand readable directories are still
  107. # left to visit. That logic is copied here.
  108. try:
  109. names = os.listdir(top)
  110. except os.error as err:
  111. if onerror is not None:
  112. onerror(err)
  113. return
  114. dirs, nondirs = [], []
  115. for name in names:
  116. if self.isdir(os.path.join(top, name)):
  117. dirs.append(name)
  118. else:
  119. nondirs.append(name)
  120. if topdown:
  121. yield top, dirs, nondirs
  122. for name in dirs:
  123. new_path = os.path.join(top, name)
  124. if followlinks or not self.islink(new_path):
  125. for x in self.walk(new_path, topdown, onerror, followlinks):
  126. yield x
  127. if not topdown:
  128. yield top, dirs, nondirs
  129. ## realpath() related functions
  130. def __is_path_below(self, file, root):
  131. return (file + os.path.sep).startswith(root)
  132. def __realpath_rel(self, start, rel_path, root, loop_cnt, assume_dir):
  133. """Calculates real path of symlink 'start' + 'rel_path' below
  134. 'root'; no part of 'start' below 'root' must contain symlinks. """
  135. have_dir = True
  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. if d == os.path.pardir: # '..'
  140. if len(start) >= len(root):
  141. # do not follow '..' before root
  142. start = os.path.dirname(start)
  143. else:
  144. # emit warning?
  145. pass
  146. else:
  147. (start, have_dir) = self.__realpath(os.path.join(start, d),
  148. root, loop_cnt, assume_dir)
  149. assert(self.__is_path_below(start, root))
  150. return start
  151. def __realpath(self, file, root, loop_cnt, assume_dir):
  152. while self.islink(file) and len(file) >= len(root):
  153. if loop_cnt == 0:
  154. raise OSError(errno.ELOOP, file)
  155. loop_cnt -= 1
  156. target = os.path.normpath(os.readlink(file))
  157. if not os.path.isabs(target):
  158. tdir = os.path.dirname(file)
  159. assert(self.__is_path_below(tdir, root))
  160. else:
  161. tdir = root
  162. file = self.__realpath_rel(tdir, target, root, loop_cnt, assume_dir)
  163. try:
  164. is_dir = self.isdir(file)
  165. except:
  166. is_dir = False
  167. return (file, is_dir)
  168. def realpath(self, file, root, use_physdir = True, loop_cnt = 100, assume_dir = False):
  169. """ Returns the canonical path of 'file' with assuming a
  170. toplevel 'root' directory. When 'use_physdir' is set, all
  171. preceding path components of 'file' will be resolved first;
  172. this flag should be set unless it is guaranteed that there is
  173. no symlink in the path. When 'assume_dir' is not set, missing
  174. path components will raise an ENOENT error"""
  175. root = os.path.normpath(root)
  176. file = os.path.normpath(file)
  177. if not root.endswith(os.path.sep):
  178. # letting root end with '/' makes some things easier
  179. root = root + os.path.sep
  180. if not self.__is_path_below(file, root):
  181. raise OSError(errno.EINVAL, "file '%s' is not below root" % file)
  182. try:
  183. if use_physdir:
  184. file = self.__realpath_rel(root, file[(len(root) - 1):], root, loop_cnt, assume_dir)
  185. else:
  186. file = self.__realpath(file, root, loop_cnt, assume_dir)[0]
  187. except OSError as e:
  188. if e.errno == errno.ELOOP:
  189. # make ELOOP more readable; without catching it, there will
  190. # be printed a backtrace with 100s of OSError exceptions
  191. # else
  192. raise OSError(errno.ELOOP,
  193. "too much recursions while resolving '%s'; loop in '%s'" %
  194. (file, e.strerror))
  195. raise
  196. return file