aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'portage_with_autodep/bin/ebuild-ipc.py')
-rwxr-xr-xportage_with_autodep/bin/ebuild-ipc.py276
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:]))