diff options
Diffstat (limited to 'portage_with_autodep/bin/ebuild-ipc.py')
-rwxr-xr-x | portage_with_autodep/bin/ebuild-ipc.py | 276 |
1 files changed, 276 insertions, 0 deletions
diff --git a/portage_with_autodep/bin/ebuild-ipc.py b/portage_with_autodep/bin/ebuild-ipc.py new file mode 100755 index 0000000..68ad985 --- /dev/null +++ b/portage_with_autodep/bin/ebuild-ipc.py @@ -0,0 +1,276 @@ +#!/usr/bin/python +# Copyright 2010-2011 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 +# +# This is a helper which ebuild processes can use +# to communicate with portage's main python process. + +import errno +import logging +import os +import pickle +import select +import signal +import sys +import time + +def debug_signal(signum, frame): + import pdb + pdb.set_trace() +signal.signal(signal.SIGUSR1, debug_signal) + +# Avoid sandbox violations after python upgrade. +pym_path = os.path.join(os.path.dirname( + os.path.dirname(os.path.realpath(__file__))), "pym") +if os.environ.get("SANDBOX_ON") == "1": + sandbox_write = os.environ.get("SANDBOX_WRITE", "").split(":") + if pym_path not in sandbox_write: + sandbox_write.append(pym_path) + os.environ["SANDBOX_WRITE"] = \ + ":".join(filter(None, sandbox_write)) + +import portage +portage._disable_legacy_globals() + +class EbuildIpc(object): + + # Timeout for each individual communication attempt (we retry + # as long as the daemon process appears to be alive). + _COMMUNICATE_RETRY_TIMEOUT_SECONDS = 15 + _BUFSIZE = 4096 + + def __init__(self): + self.fifo_dir = os.environ['PORTAGE_BUILDDIR'] + self.ipc_in_fifo = os.path.join(self.fifo_dir, '.ipc_in') + self.ipc_out_fifo = os.path.join(self.fifo_dir, '.ipc_out') + self.ipc_lock_file = os.path.join(self.fifo_dir, '.ipc_lock') + + def _daemon_is_alive(self): + try: + builddir_lock = portage.locks.lockfile(self.fifo_dir, + wantnewlockfile=True, flags=os.O_NONBLOCK) + except portage.exception.TryAgain: + return True + else: + portage.locks.unlockfile(builddir_lock) + return False + + def communicate(self, args): + + # Make locks quiet since unintended locking messages displayed on + # stdout could corrupt the intended output of this program. + portage.locks._quiet = True + lock_obj = portage.locks.lockfile(self.ipc_lock_file, unlinkfile=True) + + try: + return self._communicate(args) + finally: + portage.locks.unlockfile(lock_obj) + + def _timeout_retry_msg(self, start_time, when): + time_elapsed = time.time() - start_time + portage.util.writemsg_level( + portage.localization._( + 'ebuild-ipc timed out %s after %d seconds,' + \ + ' retrying...\n') % (when, time_elapsed), + level=logging.ERROR, noiselevel=-1) + + def _no_daemon_msg(self): + portage.util.writemsg_level( + portage.localization._( + 'ebuild-ipc: daemon process not detected\n'), + level=logging.ERROR, noiselevel=-1) + + def _wait(self, pid, pr, msg): + """ + Wait on pid and return an appropriate exit code. This + may return unsuccessfully due to timeout if the daemon + process does not appear to be alive. + """ + + start_time = time.time() + + while True: + try: + events = select.select([pr], [], [], + self._COMMUNICATE_RETRY_TIMEOUT_SECONDS) + except select.error as e: + portage.util.writemsg_level( + "ebuild-ipc: %s: %s\n" % \ + (portage.localization._('during select'), e), + level=logging.ERROR, noiselevel=-1) + continue + + if events[0]: + break + + if self._daemon_is_alive(): + self._timeout_retry_msg(start_time, msg) + else: + self._no_daemon_msg() + try: + os.kill(pid, signal.SIGKILL) + os.waitpid(pid, 0) + except OSError as e: + portage.util.writemsg_level( + "ebuild-ipc: %s\n" % (e,), + level=logging.ERROR, noiselevel=-1) + return 2 + + try: + wait_retval = os.waitpid(pid, 0) + except OSError as e: + portage.util.writemsg_level( + "ebuild-ipc: %s: %s\n" % (msg, e), + level=logging.ERROR, noiselevel=-1) + return 2 + + if not os.WIFEXITED(wait_retval[1]): + portage.util.writemsg_level( + "ebuild-ipc: %s: %s\n" % (msg, + portage.localization._('subprocess failure: %s') % \ + wait_retval[1]), + level=logging.ERROR, noiselevel=-1) + return 2 + + return os.WEXITSTATUS(wait_retval[1]) + + def _receive_reply(self, input_fd): + + # Timeouts are handled by the parent process, so just + # block until input is available. For maximum portability, + # use a single atomic read. + buf = None + while True: + try: + events = select.select([input_fd], [], []) + except select.error as e: + portage.util.writemsg_level( + "ebuild-ipc: %s: %s\n" % \ + (portage.localization._('during select for read'), e), + level=logging.ERROR, noiselevel=-1) + continue + + if events[0]: + # For maximum portability, use os.read() here since + # array.fromfile() and file.read() are both known to + # erroneously return an empty string from this + # non-blocking fifo stream on FreeBSD (bug #337465). + try: + buf = os.read(input_fd, self._BUFSIZE) + except OSError as e: + if e.errno != errno.EAGAIN: + portage.util.writemsg_level( + "ebuild-ipc: %s: %s\n" % \ + (portage.localization._('read error'), e), + level=logging.ERROR, noiselevel=-1) + break + # Assume that another event will be generated + # if there's any relevant data. + continue + + # Only one (atomic) read should be necessary. + if buf: + break + + retval = 2 + + if not buf: + + portage.util.writemsg_level( + "ebuild-ipc: %s\n" % \ + (portage.localization._('read failed'),), + level=logging.ERROR, noiselevel=-1) + + else: + + try: + reply = pickle.loads(buf) + except SystemExit: + raise + except Exception as e: + # The pickle module can raise practically + # any exception when given corrupt data. + portage.util.writemsg_level( + "ebuild-ipc: %s\n" % (e,), + level=logging.ERROR, noiselevel=-1) + + else: + + (out, err, retval) = reply + + if out: + portage.util.writemsg_stdout(out, noiselevel=-1) + + if err: + portage.util.writemsg(err, noiselevel=-1) + + return retval + + def _communicate(self, args): + + if not self._daemon_is_alive(): + self._no_daemon_msg() + return 2 + + # Open the input fifo before the output fifo, in order to make it + # possible for the daemon to send a reply without blocking. This + # improves performance, and also makes it possible for the daemon + # to do a non-blocking write without a race condition. + input_fd = os.open(self.ipc_out_fifo, + os.O_RDONLY|os.O_NONBLOCK) + + # Use forks so that the child process can handle blocking IO + # un-interrupted, while the parent handles all timeout + # considerations. This helps to avoid possible race conditions + # from interference between timeouts and blocking IO operations. + pr, pw = os.pipe() + pid = os.fork() + + if pid == 0: + os.close(pr) + + # File streams are in unbuffered mode since we do atomic + # read and write of whole pickles. + output_file = open(self.ipc_in_fifo, 'wb', 0) + output_file.write(pickle.dumps(args)) + output_file.close() + os._exit(os.EX_OK) + + os.close(pw) + + msg = portage.localization._('during write') + retval = self._wait(pid, pr, msg) + os.close(pr) + + if retval != os.EX_OK: + portage.util.writemsg_level( + "ebuild-ipc: %s: %s\n" % (msg, + portage.localization._('subprocess failure: %s') % \ + retval), level=logging.ERROR, noiselevel=-1) + return retval + + if not self._daemon_is_alive(): + self._no_daemon_msg() + return 2 + + pr, pw = os.pipe() + pid = os.fork() + + if pid == 0: + os.close(pr) + retval = self._receive_reply(input_fd) + os._exit(retval) + + os.close(pw) + retval = self._wait(pid, pr, portage.localization._('during read')) + os.close(pr) + os.close(input_fd) + return retval + +def ebuild_ipc_main(args): + ebuild_ipc = EbuildIpc() + return ebuild_ipc.communicate(args) + +if __name__ == '__main__': + sys.exit(ebuild_ipc_main(sys.argv[1:])) |