| # Copyright 1998-2011 Gentoo Foundation |
| # Distributed under the terms of the GNU General Public License v2 |
| |
| import errno |
| import logging |
| import sys |
| |
| try: |
| import cPickle as pickle |
| except ImportError: |
| import pickle |
| |
| from portage import os |
| from portage import _encodings |
| from portage import _os_merge |
| from portage import _unicode_decode |
| from portage import _unicode_encode |
| from portage.exception import PermissionDenied |
| from portage.localization import _ |
| from portage.util import atomic_ofstream |
| from portage.util import writemsg_level |
| from portage.versions import cpv_getkey |
| from portage.locks import lockfile, unlockfile |
| |
| if sys.hexversion >= 0x3000000: |
| basestring = str |
| |
| class PreservedLibsRegistry(object): |
| """ This class handles the tracking of preserved library objects """ |
| def __init__(self, root, filename): |
| """ |
| @param root: root used to check existence of paths in pruneNonExisting |
| @type root: String |
| @param filename: absolute path for saving the preserved libs records |
| @type filename: String |
| """ |
| self._root = root |
| self._filename = filename |
| self._data = None |
| self._lock = None |
| |
| def lock(self): |
| """Grab an exclusive lock on the preserved libs registry.""" |
| if self._lock is not None: |
| raise AssertionError("already locked") |
| self._lock = lockfile(self._filename) |
| |
| def unlock(self): |
| """Release our exclusive lock on the preserved libs registry.""" |
| if self._lock is None: |
| raise AssertionError("not locked") |
| unlockfile(self._lock) |
| self._lock = None |
| |
| def load(self): |
| """ Reload the registry data from file """ |
| self._data = None |
| try: |
| self._data = pickle.load( |
| open(_unicode_encode(self._filename, |
| encoding=_encodings['fs'], errors='strict'), 'rb')) |
| except (ValueError, pickle.UnpicklingError) as e: |
| writemsg_level(_("!!! Error loading '%s': %s\n") % \ |
| (self._filename, e), level=logging.ERROR, noiselevel=-1) |
| except (EOFError, IOError) as e: |
| if isinstance(e, EOFError) or e.errno == errno.ENOENT: |
| pass |
| elif e.errno == PermissionDenied.errno: |
| raise PermissionDenied(self._filename) |
| else: |
| raise |
| if self._data is None: |
| self._data = {} |
| self._data_orig = self._data.copy() |
| self.pruneNonExisting() |
| |
| def store(self): |
| """ |
| Store the registry data to the file. The existing inode will be |
| replaced atomically, so if that inode is currently being used |
| for a lock then that lock will be rendered useless. Therefore, |
| it is important not to call this method until the current lock |
| is ready to be immediately released. |
| """ |
| if os.environ.get("SANDBOX_ON") == "1" or \ |
| self._data == self._data_orig: |
| return |
| try: |
| f = atomic_ofstream(self._filename, 'wb') |
| pickle.dump(self._data, f, protocol=2) |
| f.close() |
| except EnvironmentError as e: |
| if e.errno != PermissionDenied.errno: |
| writemsg_level("!!! %s %s\n" % (e, self._filename), |
| level=logging.ERROR, noiselevel=-1) |
| else: |
| self._data_orig = self._data.copy() |
| |
| def _normalize_counter(self, counter): |
| """ |
| For simplicity, normalize as a unicode string |
| and strip whitespace. This avoids the need for |
| int conversion and a possible ValueError resulting |
| from vardb corruption. |
| """ |
| if not isinstance(counter, basestring): |
| counter = str(counter) |
| return _unicode_decode(counter).strip() |
| |
| def register(self, cpv, slot, counter, paths): |
| """ Register new objects in the registry. If there is a record with the |
| same packagename (internally derived from cpv) and slot it is |
| overwritten with the new data. |
| @param cpv: package instance that owns the objects |
| @type cpv: CPV (as String) |
| @param slot: the value of SLOT of the given package instance |
| @type slot: String |
| @param counter: vdb counter value for the package instance |
| @type counter: String |
| @param paths: absolute paths of objects that got preserved during an update |
| @type paths: List |
| """ |
| cp = cpv_getkey(cpv) |
| cps = cp+":"+slot |
| counter = self._normalize_counter(counter) |
| if len(paths) == 0 and cps in self._data \ |
| and self._data[cps][0] == cpv and \ |
| self._normalize_counter(self._data[cps][1]) == counter: |
| del self._data[cps] |
| elif len(paths) > 0: |
| self._data[cps] = (cpv, counter, paths) |
| |
| def unregister(self, cpv, slot, counter): |
| """ Remove a previous registration of preserved objects for the given package. |
| @param cpv: package instance whose records should be removed |
| @type cpv: CPV (as String) |
| @param slot: the value of SLOT of the given package instance |
| @type slot: String |
| """ |
| self.register(cpv, slot, counter, []) |
| |
| def pruneNonExisting(self): |
| """ Remove all records for objects that no longer exist on the filesystem. """ |
| |
| os = _os_merge |
| |
| for cps in list(self._data): |
| cpv, counter, paths = self._data[cps] |
| paths = [f for f in paths \ |
| if os.path.exists(os.path.join(self._root, f.lstrip(os.sep)))] |
| if len(paths) > 0: |
| self._data[cps] = (cpv, counter, paths) |
| else: |
| del self._data[cps] |
| |
| def hasEntries(self): |
| """ Check if this registry contains any records. """ |
| if self._data is None: |
| self.load() |
| return len(self._data) > 0 |
| |
| def getPreservedLibs(self): |
| """ Return a mapping of packages->preserved objects. |
| @returns mapping of package instances to preserved objects |
| @rtype Dict cpv->list-of-paths |
| """ |
| if self._data is None: |
| self.load() |
| rValue = {} |
| for cps in self._data: |
| rValue[self._data[cps][0]] = self._data[cps][2] |
| return rValue |