Use EbuildIpcDaemon to replace the functionality of
authorZac Medico <zmedico@gentoo.org>
Fri, 13 Aug 2010 14:05:12 +0000 (07:05 -0700)
committerZac Medico <zmedico@gentoo.org>
Fri, 13 Aug 2010 14:05:12 +0000 (07:05 -0700)
EBUILD_EXIT_STATUS_FILE.

bin/ebuild.sh
bin/isolated-functions.sh
bin/misc-functions.sh
pym/_emerge/AbstractEbuildProcess.py
pym/portage/package/ebuild/_ipc/ExitCommand.py [new file with mode: 0644]
pym/portage/package/ebuild/_ipc/IpcCommand.py [new file with mode: 0644]
pym/portage/package/ebuild/_ipc/__init__.py [new file with mode: 0644]
pym/portage/package/ebuild/config.py
pym/portage/package/ebuild/doebuild.py
pym/portage/package/ebuild/prepare_build_dirs.py
pym/portage/tests/ebuild/test_ipc_daemon.py

index f9bfb3400942799078e39d3eb819aec92752df6b..8b458705dcea6ebbf3c3d8e9a7853479ce2c6fe7 100755 (executable)
@@ -289,10 +289,6 @@ register_success_hook() {
 if ! hasq "$EBUILD_PHASE" clean cleanrm depend help ; then
        cd "$PORTAGE_BUILDDIR" || \
                die "PORTAGE_BUILDDIR does not exist: '$PORTAGE_BUILDDIR'"
-else
-       # Don't try to create this when it's parent
-       # directory doesn't necessarily exist.
-       unset EBUILD_EXIT_STATUS_FILE
 fi
 
 #if no perms are specified, dirs/files will have decent defaults
@@ -749,9 +745,10 @@ dyn_clean() {
        fi
 
        if [[ $EMERGE_FROM = binary ]] || ! hasq keepwork $FEATURES; then
-               rm -f "$PORTAGE_BUILDDIR"/.{ebuild_changed,exit_status,logid,unpacked,prepared} \
+               rm -f "$PORTAGE_BUILDDIR"/.{ebuild_changed,logid,unpacked,prepared} \
                        "$PORTAGE_BUILDDIR"/.{configured,compiled,tested,packaged} \
-                       "$PORTAGE_BUILDDIR"/.die_hooks
+                       "$PORTAGE_BUILDDIR"/.die_hooks \
+                       "$PORTAGE_BUILDDIR"/.ipc_{in,out,lock}
 
                rm -rf "${PORTAGE_BUILDDIR}/build-info"
                rm -rf "${WORKDIR}"
@@ -2206,10 +2203,6 @@ ebuild_main() {
                exit 1
                ;;
        esac
-       if [ -n "$EBUILD_EXIT_STATUS_FILE" ] ; then
-               > "$EBUILD_EXIT_STATUS_FILE" || \
-                       die "failed to create '$EBUILD_EXIT_STATUS_FILE'"
-       fi
 }
 
 if [[ $EBUILD_PHASE = depend ]] ; then
@@ -2230,6 +2223,7 @@ elif [[ -n $EBUILD_SH_ARGS ]] ; then
                        chown portage:portage "$T/environment" &>/dev/null
                        chmod g+w "$T/environment" &>/dev/null
                fi
+               [[ -n $PORTAGE_IPC_DAEMON ]] && "$PORTAGE_BIN_PATH"/ebuild-ipc exit 0
                exit 0
        )
        exit $?
index ddcf8f0d7bc82df3c5978a030f48a23a2f179798..14ba58cc8507b2f9685cb01f4e32c83dbe7c5c93 100644 (file)
@@ -189,7 +189,7 @@ die() {
        fi
        eerror "S: '${S}'"
 
-       [ -n "$EBUILD_EXIT_STATUS_FILE" ] && > "$EBUILD_EXIT_STATUS_FILE"
+       [[ -n $PORTAGE_IPC_DAEMON ]] && "$PORTAGE_BIN_PATH"/ebuild-ipc exit 1
 
        # subshell die support
        [[ $BASHPID = $EBUILD_MASTER_PID ]] || kill -s SIGTERM $EBUILD_MASTER_PID
@@ -558,7 +558,7 @@ save_ebuild_env() {
                # portage config variables and variables set directly by portage
                unset ACCEPT_LICENSE BAD BRACKET BUILD_PREFIX COLS \
                        DISTCC_DIR DISTDIR DOC_SYMLINKS_DIR \
-                       EBUILD_EXIT_STATUS_FILE EBUILD_FORCE_TEST EBUILD_MASTER_PID \
+                       EBUILD_FORCE_TEST EBUILD_MASTER_PID \
                        ECLASSDIR ECLASS_DEPTH ENDCOL FAKEROOTKEY \
                        GOOD HILITE HOME \
                        LAST_E_CMD LAST_E_LEN LD_PRELOAD MISC_FUNCTIONS_ARGS MOPREFIX \
@@ -569,7 +569,8 @@ save_ebuild_env() {
                        PORTAGE_COLORMAP PORTAGE_CONFIGROOT PORTAGE_DEBUG \
                        PORTAGE_DEPCACHEDIR PORTAGE_GID \
                        PORTAGE_GRPNAME PORTAGE_INST_GID \
-                       PORTAGE_INST_UID PORTAGE_LOG_FILE PORTAGE_MASTER_PID \
+                       PORTAGE_INST_UID PORTAGE_IPC_DAEMON \
+                       PORTAGE_LOG_FILE PORTAGE_MASTER_PID \
                        PORTAGE_NONFATAL PORTAGE_QUIET \
                        PORTAGE_REPO_NAME PORTAGE_RESTRICT PORTAGE_UPDATE_ENV \
                        PORTAGE_USERNAME PORTAGE_VERBOSE PORTAGE_WORKDIR_MODE PORTDIR \
index 10d1931249615b7c76bda96ecab0aaf90aafd421..9777c99542b88e33346e2d833570fb7b7028182a 100755 (executable)
@@ -853,9 +853,7 @@ if [ -n "${MISC_FUNCTIONS_ARGS}" ]; then
                ${x}
        done
        unset x
+       [[ -n $PORTAGE_IPC_DAEMON ]] && "$PORTAGE_BIN_PATH"/ebuild-ipc exit 0
 fi
 
-[ -n "${EBUILD_EXIT_STATUS_FILE}" ] && \
-       touch "${EBUILD_EXIT_STATUS_FILE}" &>/dev/null
-
 :
index f4a87f68bce4731b728814a19e3d16c0860a4846..913a32d12a139d2fac2b41398a8c79f04e3c7536 100644 (file)
@@ -1,19 +1,65 @@
 # Copyright 1999-2009 Gentoo Foundation
 # Distributed under the terms of the GNU General Public License v2
 
+import textwrap
 from _emerge.SpawnProcess import SpawnProcess
-import portage
-portage.proxy.lazyimport.lazyimport(globals(),
-       'portage.package.ebuild.doebuild:_doebuild_exit_status_check_and_log'
-)
+from _emerge.EbuildIpcDaemon import EbuildIpcDaemon
+from portage.elog.messages import eerror
+from portage.localization import _
+from portage.package.ebuild._ipc.ExitCommand import ExitCommand
 from portage import os
 from portage.util._pty import _create_pty_or_pipe
 
 class AbstractEbuildProcess(SpawnProcess):
 
-       __slots__ = ('settings',)
+       __slots__ = ('settings',) + \
+               ('_ipc_daemon', '_exit_command',)
        _phases_without_builddir = ('clean', 'cleanrm', 'depend', 'help',)
 
+       def _get_phase(self):
+               phase = getattr(self, 'phase', None)
+               if not phase:
+                       phase = self.settings.get("EBUILD_PHASE")
+                       if not phase:
+                               phase = 'other'
+               return phase
+
+       def _start(self):
+
+               if self._get_phase() not in self._phases_without_builddir:
+                       envs = [self.settings]
+                       if self.env is not None:
+                               envs.append(self.env)
+                       for env in envs:
+                               env['PORTAGE_IPC_DAEMON'] = "1"
+                       self._exit_command = ExitCommand()
+                       self._exit_command.reply_hook = self._exit_command_callback
+                       input_fifo = os.path.join(
+                               self.settings['PORTAGE_BUILDDIR'], '.ipc_in')
+                       output_fifo = os.path.join(
+                               self.settings['PORTAGE_BUILDDIR'], '.ipc_out')
+                       commands = {'exit' : self._exit_command}
+                       self._ipc_daemon = EbuildIpcDaemon(commands=commands,
+                               input_fifo=input_fifo,
+                               output_fifo=output_fifo,
+                               scheduler=self.scheduler)
+                       self._ipc_daemon.start()
+
+               SpawnProcess._start(self)
+
+       def _exit_command_callback(self):
+               if self._registered:
+                       # Let the process exit naturally, if possible. This
+                       # doesn't really do any harm since it can return
+                       # long before the timeout expires.
+                       self.scheduler.schedule(self._reg_id, timeout=1000)
+                       if self._registered:
+                               # If it doesn't exit naturally in a reasonable amount
+                               # of time, kill it (solves bug #278895). We try to avoid
+                               # this when possible since it makes sandbox complain about
+                               # being killed by a signal.
+                               self.cancel()
+
        def _pipe(self, fd_pipes):
                stdout_pipe = fd_pipes.get(1)
                got_pty, master_fd, slave_fd = \
@@ -27,11 +73,40 @@ class AbstractEbuildProcess(SpawnProcess):
                return not ('sesandbox' in self.settings.features \
                        and self.settings.selinux_enabled()) or os.isatty(slave_fd)
 
+       def _unexpected_exit(self):
+
+               phase = self._get_phase()
+
+               msg = _("The ebuild phase '%s' has exited "
+               "unexpectedly. This type of behavior "
+               "is known to be triggered "
+               "by things such as failed variable "
+               "assignments (bug #190128) or bad substitution "
+               "errors (bug #200313). Normally, before exiting, bash should "
+               "have displayed an error message above. If bash did not "
+               "produce an error message above, it's possible "
+               "that the ebuild has called `exit` when it "
+               "should have called `die` instead. This behavior may also "
+               "be triggered by a corrupt bash binary or a hardware "
+               "problem such as memory or cpu malfunction. If the problem is not "
+               "reproducible or it appears to occur randomly, then it is likely "
+               "to be triggered by a hardware problem. "
+               "If you suspect a hardware problem then you should "
+               "try some basic hardware diagnostics such as memtest. "
+               "Please do not report this as a bug unless it is consistently "
+               "reproducible and you are sure that your bash binary and hardware "
+               "are functioning properly.") % phase
+
+               for l in textwrap.wrap(msg, 72):
+                       eerror(l, phase=phase, key=self.settings.mycpv)
+
        def _set_returncode(self, wait_retval):
                SpawnProcess._set_returncode(self, wait_retval)
-               phase = self.settings.get("EBUILD_PHASE")
-               if not phase:
-                       phase = 'other'
-               if phase not in self._phases_without_builddir:
-                       self.returncode = _doebuild_exit_status_check_and_log(
-                               self.settings, phase, self.returncode)
+
+               if self._ipc_daemon is not None:
+                       self._ipc_daemon.cancel()
+                       if self._exit_command.exitcode is not None:
+                               self.returncode = self._exit_command.exitcode
+                       else:
+                               self.returncode = 1
+                               self._unexpected_exit()
diff --git a/pym/portage/package/ebuild/_ipc/ExitCommand.py b/pym/portage/package/ebuild/_ipc/ExitCommand.py
new file mode 100644 (file)
index 0000000..f14050b
--- /dev/null
@@ -0,0 +1,27 @@
+# Copyright 2010 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+
+from portage.package.ebuild._ipc.IpcCommand import IpcCommand
+
+class ExitCommand(IpcCommand):
+
+       __slots__ = ('exitcode', 'reply_hook',)
+
+       def __init__(self):
+               IpcCommand.__init__(self)
+               self.reply_hook = None
+               self.exitcode = None
+
+       def __call__(self, argv):
+
+               if self.exitcode is not None:
+                       # Ignore all but the first call, since if die is called
+                       # then we certainly want to honor that exitcode, even
+                       # the ebuild process manages to send a second exit
+                       # command.
+                       self.reply_hook = None
+               else:
+                       self.exitcode = int(argv[1])
+
+               # (stdout, stderr, returncode)
+               return ('', '', 0)
diff --git a/pym/portage/package/ebuild/_ipc/IpcCommand.py b/pym/portage/package/ebuild/_ipc/IpcCommand.py
new file mode 100644 (file)
index 0000000..efb27f0
--- /dev/null
@@ -0,0 +1,9 @@
+# Copyright 2010 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+
+class IpcCommand(object):
+
+       __slots__ = ()
+
+       def __call__(self, argv):
+               raise NotImplementedError(self)
diff --git a/pym/portage/package/ebuild/_ipc/__init__.py b/pym/portage/package/ebuild/_ipc/__init__.py
new file mode 100644 (file)
index 0000000..21a391a
--- /dev/null
@@ -0,0 +1,2 @@
+# Copyright 2010 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
index a97dd33ac4a5eccf0410b96cf3e9cee426053373..3b8ee10b54bf0113ff82db2a9a666772597ba370 100644 (file)
@@ -168,7 +168,7 @@ class config(object):
        _environ_whitelist += [
                "ACCEPT_LICENSE", "BASH_ENV", "BUILD_PREFIX", "D",
                "DISTDIR", "DOC_SYMLINKS_DIR", "EAPI", "EBUILD",
-               "EBUILD_EXIT_STATUS_FILE", "EBUILD_FORCE_TEST",
+               "EBUILD_FORCE_TEST",
                "EBUILD_PHASE", "ECLASSDIR", "ECLASS_DEPTH", "ED",
                "EMERGE_FROM", "EPREFIX", "EROOT",
                "FEATURES", "FILESDIR", "HOME", "NOCOLOR", "PATH",
@@ -183,7 +183,7 @@ class config(object):
                "PORTAGE_CONFIGROOT", "PORTAGE_DEBUG", "PORTAGE_DEPCACHEDIR",
                "PORTAGE_GID", "PORTAGE_GRPNAME",
                "PORTAGE_INST_GID", "PORTAGE_INST_UID",
-               "PORTAGE_IUSE",
+               "PORTAGE_IPC_DAEMON", "PORTAGE_IUSE",
                "PORTAGE_LOG_FILE", "PORTAGE_MASTER_PID",
                "PORTAGE_PYM_PATH", "PORTAGE_QUIET",
                "PORTAGE_REPO_NAME", "PORTAGE_RESTRICT",
index 61eea40ee48a6ca0be6d71f840e198d909ba3fc6..7405b6f1256156595ddcde3b2f37ac4df56a4e1b 100644 (file)
@@ -213,8 +213,6 @@ def doebuild_environment(myebuild, mydo, myroot, mysettings,
                mysettings["PORTAGE_CONFIGROOT"], EBUILD_SH_ENV_FILE)
        mysettings["PM_EBUILD_HOOK_DIR"] = os.path.join(
                mysettings["PORTAGE_CONFIGROOT"], EBUILD_SH_ENV_DIR)
-       mysettings["EBUILD_EXIT_STATUS_FILE"] = os.path.join(
-               mysettings["PORTAGE_BUILDDIR"], ".exit_status")
 
        #set up KV variable -- DEP SPEEDUP :: Don't waste time. Keep var persistent.
        if not eapi_exports_KV(eapi):
@@ -240,61 +238,6 @@ def doebuild_environment(myebuild, mydo, myroot, mysettings,
                        (c, style_to_ansi_code(c)))
        mysettings["PORTAGE_COLORMAP"] = "\n".join(mycolors)
 
-def _doebuild_exit_status_check(mydo, settings):
-       """
-       Returns an error string if the shell appeared
-       to exit unsuccessfully, None otherwise.
-       """
-       exit_status_file = settings.get("EBUILD_EXIT_STATUS_FILE")
-       if not exit_status_file or \
-               os.path.exists(exit_status_file):
-               return None
-       msg = _("The ebuild phase '%s' has exited "
-       "unexpectedly. This type of behavior "
-       "is known to be triggered "
-       "by things such as failed variable "
-       "assignments (bug #190128) or bad substitution "
-       "errors (bug #200313). Normally, before exiting, bash should "
-       "have displayed an error message above. If bash did not "
-       "produce an error message above, it's possible "
-       "that the ebuild has called `exit` when it "
-       "should have called `die` instead. This behavior may also "
-       "be triggered by a corrupt bash binary or a hardware "
-       "problem such as memory or cpu malfunction. If the problem is not "
-       "reproducible or it appears to occur randomly, then it is likely "
-       "to be triggered by a hardware problem. "
-       "If you suspect a hardware problem then you should "
-       "try some basic hardware diagnostics such as memtest. "
-       "Please do not report this as a bug unless it is consistently "
-       "reproducible and you are sure that your bash binary and hardware "
-       "are functioning properly.") % mydo
-       return msg
-
-def _doebuild_exit_status_check_and_log(settings, mydo, retval):
-       msg = _doebuild_exit_status_check(mydo, settings)
-       if msg:
-               if retval == os.EX_OK:
-                       retval = 1
-               for l in wrap(msg, 72):
-                       eerror(l, phase=mydo, key=settings.mycpv)
-       return retval
-
-def _doebuild_exit_status_unlink(exit_status_file):
-       """
-       Double check to make sure it really doesn't exist
-       and raise an OSError if it still does (it shouldn't).
-       OSError if necessary.
-       """
-       if not exit_status_file:
-               return
-       try:
-               os.unlink(exit_status_file)
-       except OSError:
-               pass
-       if os.path.exists(exit_status_file):
-               os.unlink(exit_status_file)
-
-
 _doebuild_manifest_cache = None
 _doebuild_broken_ebuilds = set()
 _doebuild_broken_manifests = set()
@@ -721,8 +664,6 @@ def doebuild(myebuild, mydo, myroot, mysettings, debug=0, listonly=0,
                                        elog_process(mysettings.mycpv, mysettings)
                                        return 1
                        del env_file, env_stat, saved_env
-               else:
-                       mysettings.pop("EBUILD_EXIT_STATUS_FILE", None)
 
                # if any of these are being called, handle them -- running them out of
                # the sandbox -- and stop now.
@@ -1172,14 +1113,6 @@ def spawn(mystring, mysettings, debug=0, free=0, droppriv=0, sesandbox=0, fakero
                spawn_func = selinux.spawn_wrapper(spawn_func,
                        mysettings["PORTAGE_SANDBOX_T"])
 
-       phase = env.get('EBUILD_PHASE')
-       if phase not in EbuildSpawnProcess._phases_without_builddir:
-               # Don't try to unlink for phases that don't require
-               # PORTAGE_BUILDDIR, since the directory may not
-               # even belong to this process in that case.
-               _doebuild_exit_status_unlink(
-                       env.get("EBUILD_EXIT_STATUS_FILE"))
-
        if keywords.get("returnpid"):
                return spawn_func(mystring, env=env, **keywords)
 
index dc29eeeb83aec3854378277736022b508accf1bf..15e087121273ee47823a90453545aec0975cd082 100644 (file)
@@ -85,6 +85,25 @@ def prepare_build_dirs(myroot, mysettings, cleanup):
                writemsg(_("File Not Found: '%s'\n") % str(e), noiselevel=-1)
                return 1
 
+       for x in ('.ipc_in', '.ipc_out'):
+               p = os.path.join(mysettings['PORTAGE_BUILDDIR'], x)
+               st = None
+               try:
+                       st = os.lstat(p)
+               except OSError:
+                       os.mkfifo(p)
+               else:
+                       if not stat.S_ISFIFO(st.st_mode):
+                               st = None
+                               try:
+                                       os.unlink(p)
+                               except OSError:
+                                       pass
+                               os.mkfifo(p)
+               apply_secpass_permissions(p,
+                       uid=portage_uid, gid=portage_gid,
+                       mode=0o770, mask=0o2, stat_cached=st)
+
        # Reset state for things like noauto and keepwork in FEATURES.
        for x in ('.die_hooks',):
                try:
index 48412a1c3628f4373cb0e1776e8eff0d9aafbaa5..de548c6f14963311c8895c1a4d957964738da02f 100644 (file)
@@ -8,30 +8,11 @@ from portage.tests import TestCase
 from portage.const import PORTAGE_BIN_PATH
 from portage.const import PORTAGE_PYM_PATH
 from portage.const import BASH_BINARY
+from portage.package.ebuild._ipc.ExitCommand import ExitCommand
 from _emerge.SpawnProcess import SpawnProcess
 from _emerge.EbuildIpcDaemon import EbuildIpcDaemon
 from _emerge.TaskScheduler import TaskScheduler
 
-class ExitCommand(object):
-
-       def __init__(self):
-               self.reply_hook = None
-               self.exitcode = None
-
-       def __call__(self, argv):
-
-               if self.exitcode is not None:
-                       # Ignore all but the first call, since if die is called
-                       # then we certainly want to honor that exitcode, even
-                       # the ebuild process manages to send a second exit
-                       # command.
-                       self.reply_hook = None
-               else:
-                       self.exitcode = int(argv[1])
-
-               # (stdout, stderr, returncode)
-               return ('', '', 0)
-
 class IpcDaemonTestCase(TestCase):
 
        def testIpcDaemon(self):
@@ -58,8 +39,8 @@ class IpcDaemonTestCase(TestCase):
                                        '"$PORTAGE_BIN_PATH"/ebuild-ipc exit %d' % exitcode],
                                        env=env, scheduler=task_scheduler.sched_iface)
                                def exit_command_callback():
-                                       daemon.cancel()
                                        proc.cancel()
+                                       daemon.cancel()
                                exit_command.reply_hook = exit_command_callback
                                task_scheduler.add(daemon)
                                task_scheduler.add(proc)