Package entropy :: Package client :: Package interfaces :: Package package :: Module preservedlibs

Source Code for Module entropy.client.interfaces.package.preservedlibs

  1  # -*- coding: utf-8 -*- 
  2  """ 
  3   
  4      @author: Fabio Erculiani <[email protected]> 
  5      @contact: [email protected] 
  6      @copyright: Fabio Erculiani 
  7      @license: GPL-2 
  8   
  9      B{Entropy Package Manager Client Package Interface}. 
 10   
 11  """ 
 12  import collections 
 13  import errno 
 14  import os 
 15  import stat 
 16   
 17  from entropy.const import const_convert_to_unicode 
 18   
 19   
20 -class PreservedLibraries(object):
21 """ 22 Preserved libraries management class. 23 24 This class can be used to determine if a library should be 25 preserved, garbage collect the currently preserved libraries, 26 retrieving the list of preserved libraries, etc. 27 28 An instance of this class should be used just for one package and then 29 thrown away. 30 31 Installed Packages Repository locking must be done externally. 32 """ 33
34 - def __init__(self, installed_repository, installed_package_id, 35 provided_libraries, root = None):
36 """ 37 Object constructor. 38 39 @param installed_repository: an EntropyRepository object pointing 40 to the installed packages repository 41 @type installed_repository: EntropyRepository 42 @param installed_package_id: the installed packages repository package 43 identifier 44 @type installed_package_id: int 45 @param provided_libraries: set of libraries that a package provides, 46 typically this is the data returned by 47 EntropyRepository.retrieveProvidedLibraries() 48 @type provided_libraries: set 49 @keyword root: path to the root directory minus the trailing "/". 50 For "/" it's just "" or None. 51 @type root: string 52 """ 53 self._inst_repo = installed_repository 54 self._package_id = installed_package_id 55 self._raw_provided = provided_libraries 56 self._provided = dict(((l_path, (library, elfclass, l_path)) for 57 library, l_path, elfclass in provided_libraries)) 58 self._root = root or const_convert_to_unicode("") 59 self._search_needed_cache = {}
60
61 - def installed_repository(self):
62 """ 63 Return the installed packages repository used by this object. 64 """ 65 return self._inst_repo
66
67 - def package_id(self):
68 """ 69 Return the installed packages repository package identifier. 70 """ 71 return self._package_id
72
73 - def resolve(self, library_path):
74 """ 75 Resolve the given library path into a (library, elfclass, path) tuple. 76 A tuple is returned iff it can be found in the provided libraries 77 metadata passed during initialization of this object, None otherwise. 78 79 @param library_path: path to a library that would be removed (without 80 the root prefix) 81 @type library_path: string 82 @return: a (library name, elf class, library path) tuple or None 83 """ 84 return self._provided.get(library_path)
85
86 - def determine(self, library_path):
87 """ 88 Determine if the passed library path requires protection. 89 90 The returned data is a set of paths that should be protected and 91 stored in the installed packages repository. 92 93 @param library_path: path to a library that would be removed (without 94 the root prefix) 95 @type library_path: string 96 @return: set of paths to protect 97 @rtype: set 98 """ 99 provided_path = self._provided.get(library_path) 100 paths = set() 101 102 if provided_path is None: 103 # the item should not be protected 104 return paths 105 106 library, elfclass, _path = provided_path 107 108 installed_package_ids = set(self._search_needed(library, elfclass)) 109 # drop myself from the list 110 installed_package_ids.discard(self._package_id) 111 112 if not installed_package_ids: 113 # no packages need this library 114 return paths 115 116 paths.update(self._follow(library_path)) 117 return paths
118
119 - def _follow(self, library_path):
120 """ 121 Follow library_path symlinks and generate a sequence of paths. 122 123 @param library_path: path to a library that would be removed (without 124 the root prefix) 125 @type library_path: string 126 @return: a sequence of paths 127 @rtype: collections.deque 128 """ 129 paths = collections.deque() 130 131 recursion = 128 132 root_library_path = self._root + library_path 133 symlinks = {} 134 hardlinks = set() 135 136 # Also see: 137 # portage.git commit: 32d19be14e22ada479963ba8627452f5f2d89b94 138 139 while recursion: 140 recursion -= 1 141 142 try: 143 l_stat = os.lstat(root_library_path) 144 except OSError as err: 145 if err.errno != errno.ENOENT: 146 raise 147 break 148 149 if stat.S_ISLNK(l_stat.st_mode): 150 path_link = os.readlink(root_library_path) 151 root_library_path = os.path.join( 152 os.path.dirname(root_library_path), 153 path_link) 154 155 # delay symlinks add, due to Gentoo bug #406837 156 symlinks[library_path] = path_link 157 158 library_path = os.path.join( 159 os.path.dirname(library_path), 160 path_link) 161 162 continue 163 164 elif stat.S_ISREG(l_stat.st_mode): 165 paths.append(library_path) 166 hardlinks.add(library_path) 167 168 break 169 170 for library_path, target in symlinks.items(): 171 target_path = os.path.join( 172 os.path.dirname(library_path), target) 173 if target_path in hardlinks: 174 paths.append(library_path) 175 176 return paths
177
178 - def _search_needed(self, library, elfclass):
179 """ 180 Search the package ids that need the given library. 181 """ 182 cache_key = (library, elfclass) 183 installed_package_ids = self._search_needed_cache.get( 184 cache_key) 185 if installed_package_ids is None: 186 installed_package_ids = self._inst_repo.searchNeeded( 187 library, elfclass = elfclass) 188 self._search_needed_cache[cache_key] = installed_package_ids 189 190 return installed_package_ids
191
192 - def needed(self, library_path):
193 """ 194 Return a set of installed packages identifiers that need the 195 given library. 196 197 @param library_path: path to a library that would be removed (without 198 the root prefix) 199 @type library_path: string 200 @return: set of package identifiers 201 @rtype: set 202 """ 203 provided_path = self._provided.get(library_path) 204 if provided_path is None: 205 return set() 206 207 library, elfclass, _path = provided_path 208 return self._search_needed(library, elfclass)
209
210 - def list(self):
211 """ 212 Return a list of all the preserved libraries items stored in the 213 registry. 214 The list is composed of a tuple (library, elfclass, path, atom). 215 216 @return: a list of preserved library items (library, 217 elfclass, path, atom) 218 @rtype: list 219 """ 220 return self._inst_repo.listAllPreservedLibraries()
221
222 - def collect(self):
223 """ 224 Return a list of collectable preserved libraries items that can be 225 removed from the registry in the installed packages repository. 226 227 @return: a list of preserved library items (library, elfclass, path) 228 @rtype: list 229 """ 230 collectables = [] 231 232 for library, elfclass, path, _atom in self.list(): 233 234 item = (library, elfclass, path) 235 root_path = self._root + path 236 237 # path no longer exists 238 if not os.path.lexists(root_path): 239 collectables.append(item) 240 continue 241 242 # broken symlink, gc 243 if not os.path.exists(root_path): 244 collectables.append(item) 245 continue 246 247 # are all installed packages happy? 248 package_ids = self._search_needed(library, elfclass) 249 if not package_ids: 250 collectables.append(item) 251 continue 252 253 # is the library provided by any package? 254 providers = self._inst_repo.resolveNeeded( 255 library, elfclass = elfclass, extended = True) 256 257 # There is a trade-off here. We would like to check if 258 # the provider is the same of path, but we would miss the 259 # libraries that moved from like /lib to /usr/lib. 260 # An alternative would be to check if all the packages 261 # in package_ids can reach at least one of the providers 262 # but this would be quite expensive to do. Since this is 263 # anyway a best-effort service and 99% of the cases are already 264 # handled on the packaging side ("server" side), we could 265 # just accept that providers, if any, are reachable by consumers. 266 if providers: 267 collectables.append(item) 268 continue 269 270 return collectables
271
272 - def remove(self, library_path):
273 """ 274 Remove the given preserved library element from the system. 275 This method will not unregister the element from the registry, 276 please use unregister(). 277 278 @param library_path: the path to the library 279 @type library_path: string 280 @return: a sequence of path that haven't been removed and their reasons 281 @rtype: collections.queue 282 """ 283 failed = collections.deque() 284 285 for lib_path in self._follow(library_path): 286 root_lib_path = self._root + lib_path 287 288 # Guard against cross package symlinking. Example: 289 # vmware-workstation pkg conaining symlinks to libssl.so.x 290 # See Sabayon bug #5182. 291 lib_path_pkg_ids = self._inst_repo.isFileAvailable( 292 lib_path, get_id=True) 293 if lib_path_pkg_ids: 294 pkg_atoms = [self._inst_repo.retrieveAtom(x) for x in lib_path_pkg_ids] 295 failed.append( 296 (root_lib_path, 297 '%s owned by %s' % (lib_path, ', '.join(pkg_atoms))) 298 ) 299 continue 300 301 try: 302 os.remove(root_lib_path) 303 except (OSError, IOError) as err: 304 if err.errno != errno.ENOENT: 305 failed.append((root_lib_path, err)) 306 307 return failed
308
309 - def register(self, library, elfclass, path, atom):
310 """ 311 Register the given preserved library element into the registry in 312 the installed packages repository. 313 314 @param library: the library name 315 @type library: string 316 @param elfclass: the ELF class of the library 317 @type elfclass: int 318 @param path: the path to the library 319 @type path: string 320 """ 321 return self._inst_repo.insertPreservedLibrary( 322 library, elfclass, path, atom)
323
324 - def unregister(self, library, elfclass, path):
325 """ 326 Unregister the given preserved library element from the registry in 327 the installed packages repository. 328 329 @param library: the library name 330 @type library: string 331 @param elfclass: the ELF class of the library 332 @type elfclass: int 333 @param path: the path to the library 334 @type path: string 335 """ 336 return self._inst_repo.removePreservedLibrary(library, elfclass, path)
337