From 4e22f276d10e28a4b05f451bbc52f032ee2ba6bc Mon Sep 17 00:00:00 2001 From: Zac Medico Date: Fri, 8 Aug 2008 21:51:50 +0000 Subject: Add new LinkageMap.listBrokenBinaries() and listProviders() methods. Thanks to Lucian Poston for submitting this patch along with the missing-rebuild package set posted on the gentoo-portage-dev mailing list. svn path=/main/trunk/; revision=11357 --- pym/portage/dbapi/vartree.py | 152 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) diff --git a/pym/portage/dbapi/vartree.py b/pym/portage/dbapi/vartree.py index 5c6d1dd60..6b71ad7ae 100644 --- a/pym/portage/dbapi/vartree.py +++ b/pym/portage/dbapi/vartree.py @@ -190,6 +190,158 @@ class LinkageMap(object): self._libs = libs self._obj_properties = obj_properties + def listBrokenBinaries(self): + """ + Find binaries and their needed sonames, which have no providers. + + @rtype: dict (example: {'/usr/bin/foo': set(['libbar.so'])}) + @return: The return value is an object -> set-of-sonames mapping, where + object is a broken binary and the set consists of sonames needed by + object that have no corresponding libraries to fulfill the dependency. + + """ + class LibraryCache(object): + + """ + Caches sonames and realpaths associated with paths. + + The purpose of this class is to prevent multiple calls of + os.path.realpath and os.path.isfile on the same paths. + + """ + + def __init__(cache_self): + cache_self.cache = {} + + def get(cache_self, path): + """ + Caches and returns the soname and realpath for a path. + + @param path: absolute path (can be symlink) + @type path: string (example: '/usr/lib/libfoo.so') + @rtype: 3-tuple with types (string or None, string, boolean) + @return: 3-tuple with the following components: + 1. soname as a string or None if it does not exist, + 2. realpath as a string, + 3. the result of os.path.isfile(realpath) + (example: ('libfoo.so.1', '/usr/lib/libfoo.so.1.5.1', True)) + + """ + if path in cache_self.cache: + return cache_self.cache[path] + else: + realpath = os.path.realpath(path) + # Check that the library exists on the filesystem. + if os.path.isfile(realpath): + # Get the soname from LinkageMap._obj_properties if it + # exists. Otherwise, None. + soname = self._obj_properties.get(realpath, (None,)*3)[3] + # Both path and realpath are cached and the result is + # returned. + cache_self.cache.setdefault(realpath, \ + (soname, realpath, True)) + return cache_self.cache.setdefault(path, \ + (soname, realpath, True)) + else: + # realpath is not cached here, because the majority of cases + # where realpath is not a file, path is the same as realpath. + # Thus storing twice slows down the cache performance. + return cache_self.cache.setdefault(path, \ + (None, realpath, False)) + + debug = False + rValue = {} + cache = LibraryCache() + providers = self.listProviders() + + # Iterate over all binaries and their providers. + for obj, sonames in providers.items(): + # Iterate over each needed soname and the set of library paths that + # fulfill the soname to determine if the dependency is broken. + for soname, libraries in sonames.items(): + # validLibraries is used to store libraries, which satisfy soname, + # so if no valid libraries are found, the soname is not satisfied + # for obj. Thus obj must be emerged. + validLibraries = set() + # It could be the case that the library to satisfy the soname is + # not in the obj's runpath, but a symlink to the library is (eg + # libnvidia-tls.so.1 in nvidia-drivers). Also, since LinkageMap + # does not catalog symlinks, broken or missing symlinks may go + # unnoticed. As a result of these cases, check that a file with + # the same name as the soname exists in obj's runpath. + path = self._obj_properties[obj][2] + self._defpath + for dir in path: + cachedSoname, cachedRealpath, cachedExists = \ + cache.get(os.path.join(dir, soname)) + # Check that the this library provides the needed soname. Doing + # this, however, will cause consumers of libraries missing + # sonames to be unnecessarily emerged. (eg libmix.so) + if cachedSoname == soname: + validLibraries.add(cachedRealpath) + if debug and cachedRealpath not in libraries: + print "Found provider outside of findProviders:", \ + os.path.join(dir, soname), "->", cachedRealpath + # A valid library has been found, so there is no need to + # continue. + break + if debug and cachedRealpath in self._obj_properties: + print "Broken symlink or missing/bad soname:", \ + os.path.join(dir, soname), '->', cachedRealpath, \ + "with soname", cachedSoname, "but expecting", soname + # This conditional checks if there are no libraries to satisfy the + # soname (empty set). + if not validLibraries: + rValue.setdefault(obj, set()).add(soname) + # If no valid libraries have been found by this point, then + # there are no files named with the soname within obj's runpath, + # but if there are libraries (from the providers mapping), it is + # likely that symlinks or the actual libraries are missing. + # Thus possible symlinks and missing libraries are added to + # rValue in order to emerge corrupt library packages. + for lib in libraries: + cachedSoname, cachedRealpath, cachedExists = cache.get(lib) + if not cachedExists: + # The library's package needs to be emerged to repair the + # missing library. + rValue.setdefault(lib, set()).add(soname) + else: + # A library providing the soname exists in the obj's + # runpath, but no file named as the soname exists, so add + # the path constructed from the lib's directory and the + # soname to rValue to fix cases of vanishing (or modified) + # symlinks. This path is not guaranteed to exist, but it + # follows the symlink convention found in the majority of + # packages. + rValue.setdefault(os.path.join(os.path.dirname(lib), \ + soname), set()).add(soname) + if debug: + if not cachedExists: + print "Missing library:", lib + else: + print "Possibly missing symlink:", \ + os.path.join(os.path.dirname(lib), soname) + + return rValue + + def listProviders(self): + """ + Find the providers for all binaries. + + @rtype: dict (example: + {'/usr/bin/foo': {'libbar.so': set(['/lib/libbar.so.1.5'])}}) + @return: The return value is an object -> providers mapping, where + providers is a mapping of soname -> set-of-library-paths returned + from the findProviders method. + + """ + rValue = {} + if not self._libs: + self.rebuild() + # Iterate over all binaries within LinkageMap. + for obj in self._obj_properties.keys(): + rValue.setdefault(obj, self.findProviders(obj)) + return rValue + def isMasterLink(self, obj): basename = os.path.basename(obj) if obj not in self._obj_properties: -- cgit v1.2.3-1-g7c22