| # Copyright 2010-2018 Gentoo Foundation |
| # Distributed under the terms of the GNU General Public License v2 |
| |
| import errno |
| import logging |
| import pickle |
| from portage import os |
| from portage.exception import TryAgain |
| from portage.localization import _ |
| from portage.locks import lockfile, unlockfile |
| from portage.util import writemsg_level |
| from _emerge.FifoIpcDaemon import FifoIpcDaemon |
| |
| |
| class EbuildIpcDaemon(FifoIpcDaemon): |
| """ |
| This class serves as an IPC daemon, which ebuild processes can use |
| to communicate with portage's main python process. |
| |
| Here are a few possible uses: |
| |
| 1) Robust subshell/subprocess die support. This allows the ebuild |
| environment to reliably die without having to rely on signal IPC. |
| |
| 2) Delegation of portageq calls to the main python process, eliminating |
| performance and userpriv permission issues. |
| |
| 3) Reliable ebuild termination in cases when the ebuild has accidentally |
| left orphan processes running in the background (as in bug #278895). |
| |
| 4) Detect cases in which bash has exited unexpectedly (as in bug #190128). |
| """ |
| |
| __slots__ = ("commands",) |
| |
| def _input_handler(self): |
| # Read the whole pickle in a single atomic read() call. |
| data = self._read_buf(self._files.pipe_in) |
| if data is None: |
| pass # EAGAIN |
| elif data: |
| try: |
| obj = pickle.loads(data) |
| except SystemExit: |
| raise |
| except Exception: |
| # The pickle module can raise practically |
| # any exception when given corrupt data. |
| pass |
| else: |
| |
| self._reopen_input() |
| |
| cmd_key = obj[0] |
| cmd_handler = self.commands[cmd_key] |
| reply = cmd_handler(obj) |
| try: |
| self._send_reply(reply) |
| except OSError as e: |
| if e.errno == errno.ENXIO: |
| # This happens if the client side has been killed. |
| pass |
| else: |
| raise |
| |
| # Allow the command to execute hooks after its reply |
| # has been sent. This hook is used by the 'exit' |
| # command to kill the ebuild process. For some |
| # reason, the ebuild-ipc helper hangs up the |
| # ebuild process if it is waiting for a reply |
| # when we try to kill the ebuild process. |
| reply_hook = getattr(cmd_handler, "reply_hook", None) |
| if reply_hook is not None: |
| reply_hook() |
| |
| else: # EIO/POLLHUP |
| # This can be triggered due to a race condition which happens when |
| # the previous _reopen_input() call occurs before the writer has |
| # closed the pipe (see bug #401919). It's not safe to re-open |
| # without a lock here, since it's possible that another writer will |
| # write something to the pipe just before we close it, and in that |
| # case the write will be lost. Therefore, try for a non-blocking |
| # lock, and only re-open the pipe if the lock is acquired. |
| lock_filename = os.path.join(os.path.dirname(self.input_fifo), "lock") |
| try: |
| lock_obj = lockfile(lock_filename, unlinkfile=True, flags=os.O_NONBLOCK) |
| except TryAgain: |
| # We'll try again when another IO_HUP event arrives. |
| pass |
| else: |
| try: |
| self._reopen_input() |
| finally: |
| unlockfile(lock_obj) |
| |
| def _send_reply(self, reply): |
| # File streams are in unbuffered mode since we do atomic |
| # read and write of whole pickles. Use non-blocking mode so |
| # we don't hang if the client is killed before we can send |
| # the reply. We rely on the client opening the other side |
| # of this fifo before it sends its request, since otherwise |
| # we'd have a race condition with this open call raising |
| # ENXIO if the client hasn't opened the fifo yet. |
| try: |
| output_fd = os.open(self.output_fifo, os.O_WRONLY | os.O_NONBLOCK) |
| try: |
| os.write(output_fd, pickle.dumps(reply)) |
| finally: |
| os.close(output_fd) |
| except OSError as e: |
| # This probably means that the client has been killed, |
| # which causes open to fail with ENXIO. |
| writemsg_level( |
| "!!! EbuildIpcDaemon %s: %s\n" % (_("failed to send reply"), e), |
| level=logging.ERROR, |
| noiselevel=-1, |
| ) |