# Copyright 1999-2012 Gentoo Foundation # Distributed under the terms of the GNU General Public License v2 import functools from _emerge.AsynchronousLock import AsynchronousLock import portage from portage import os from portage.exception import PortageException from portage.util.SlotObject import SlotObject class EbuildBuildDir(SlotObject): __slots__ = ("scheduler", "settings", "locked", "_catdir", "_lock_obj") def __init__(self, **kwargs): SlotObject.__init__(self, **kwargs) self.locked = False def _assert_lock(self, async_lock): if async_lock.returncode != os.EX_OK: # TODO: create a better way to propagate this error to the caller raise AssertionError( f"AsynchronousLock failed with returncode {async_lock.returncode}" ) def clean_log(self): """Discard existing log. The log will not be be discarded in cases when it would not make sense, like when FEATURES=keepwork is enabled.""" settings = self.settings if "keepwork" in settings.features: return log_file = settings.get("PORTAGE_LOG_FILE") if log_file is not None and os.path.isfile(log_file): try: os.unlink(log_file) except OSError: pass def async_lock(self): """ Acquire the lock asynchronously. Notification is available via the add_done_callback method of the returned Future instance. This raises an AlreadyLocked exception if async_lock() is called while a lock is already held. In order to avoid this, call async_unlock() or check whether the "locked" attribute is True or False before calling async_lock(). @returns: Future, result is None """ if self._lock_obj is not None: raise self.AlreadyLocked((self._lock_obj,)) dir_path = self.settings.get("PORTAGE_BUILDDIR") if not dir_path: raise AssertionError("PORTAGE_BUILDDIR is unset") catdir = os.path.dirname(dir_path) self._catdir = catdir catdir_lock = AsynchronousLock(path=catdir, scheduler=self.scheduler) builddir_lock = AsynchronousLock(path=dir_path, scheduler=self.scheduler) result = self.scheduler.create_future() def catdir_locked(catdir_lock): try: self._assert_lock(catdir_lock) except AssertionError as e: result.set_exception(e) return try: portage.util.ensure_dirs( catdir, gid=portage.portage_gid, mode=0o70, mask=0 ) except PortageException as e: if not os.path.isdir(catdir): result.set_exception(e) return builddir_lock.addExitListener(builddir_locked) builddir_lock.start() def builddir_locked(builddir_lock): try: self._assert_lock(builddir_lock) except AssertionError as e: catdir_lock.async_unlock.add_done_callback( functools.partial(catdir_unlocked, exception=e) ) return self._lock_obj = builddir_lock self.locked = True self.settings["PORTAGE_BUILDDIR_LOCKED"] = "1" catdir_lock.async_unlock().add_done_callback(catdir_unlocked) def catdir_unlocked(future, exception=None): if not (exception is None and future.exception() is None): result.set_exception(exception or future.exception()) else: result.set_result(None) try: portage.util.ensure_dirs( os.path.dirname(catdir), gid=portage.portage_gid, mode=0o70, mask=0 ) except PortageException: if not os.path.isdir(os.path.dirname(catdir)): raise catdir_lock.addExitListener(catdir_locked) catdir_lock.start() return result def async_unlock(self): """ Release the lock asynchronously. Release notification is available via the add_done_callback method of the returned Future instance. @returns: Future, result is None """ result = self.scheduler.create_future() def builddir_unlocked(future): if future.exception() is not None: result.set_exception(future.exception()) else: self._lock_obj = None self.locked = False self.settings.pop("PORTAGE_BUILDDIR_LOCKED", None) catdir_lock = AsynchronousLock( path=self._catdir, scheduler=self.scheduler ) catdir_lock.addExitListener(catdir_locked) catdir_lock.start() def catdir_locked(catdir_lock): if catdir_lock.wait() != os.EX_OK: result.set_result(None) else: try: os.rmdir(self._catdir) except OSError: pass catdir_lock.async_unlock().add_done_callback(catdir_unlocked) def catdir_unlocked(future): if future.exception() is None: result.set_result(None) else: result.set_exception(future.exception()) if self._lock_obj is None: self.scheduler.call_soon(result.set_result, None) else: self._lock_obj.async_unlock().add_done_callback(builddir_unlocked) return result class AlreadyLocked(portage.exception.PortageException): pass