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