From 12cbc8821476dcbac38464a41f7cb336da7ac0c9 Mon Sep 17 00:00:00 2001 From: Zac Medico Date: Fri, 13 Aug 2010 07:05:12 -0700 Subject: [PATCH] Use EbuildIpcDaemon to replace the functionality of EBUILD_EXIT_STATUS_FILE. --- bin/ebuild.sh | 14 +-- bin/isolated-functions.sh | 7 +- bin/misc-functions.sh | 4 +- pym/_emerge/AbstractEbuildProcess.py | 97 ++++++++++++++++--- .../package/ebuild/_ipc/ExitCommand.py | 27 ++++++ pym/portage/package/ebuild/_ipc/IpcCommand.py | 9 ++ pym/portage/package/ebuild/_ipc/__init__.py | 2 + pym/portage/package/ebuild/config.py | 4 +- pym/portage/package/ebuild/doebuild.py | 67 ------------- .../package/ebuild/prepare_build_dirs.py | 19 ++++ pym/portage/tests/ebuild/test_ipc_daemon.py | 23 +---- 11 files changed, 156 insertions(+), 117 deletions(-) create mode 100644 pym/portage/package/ebuild/_ipc/ExitCommand.py create mode 100644 pym/portage/package/ebuild/_ipc/IpcCommand.py create mode 100644 pym/portage/package/ebuild/_ipc/__init__.py diff --git a/bin/ebuild.sh b/bin/ebuild.sh index f9bfb3400..8b458705d 100755 --- a/bin/ebuild.sh +++ b/bin/ebuild.sh @@ -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 $? diff --git a/bin/isolated-functions.sh b/bin/isolated-functions.sh index ddcf8f0d7..14ba58cc8 100644 --- a/bin/isolated-functions.sh +++ b/bin/isolated-functions.sh @@ -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 \ diff --git a/bin/misc-functions.sh b/bin/misc-functions.sh index 10d193124..9777c9954 100755 --- a/bin/misc-functions.sh +++ b/bin/misc-functions.sh @@ -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 - : diff --git a/pym/_emerge/AbstractEbuildProcess.py b/pym/_emerge/AbstractEbuildProcess.py index f4a87f68b..913a32d12 100644 --- a/pym/_emerge/AbstractEbuildProcess.py +++ b/pym/_emerge/AbstractEbuildProcess.py @@ -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 index 000000000..f14050b91 --- /dev/null +++ b/pym/portage/package/ebuild/_ipc/ExitCommand.py @@ -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 index 000000000..efb27f0a2 --- /dev/null +++ b/pym/portage/package/ebuild/_ipc/IpcCommand.py @@ -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 index 000000000..21a391aee --- /dev/null +++ b/pym/portage/package/ebuild/_ipc/__init__.py @@ -0,0 +1,2 @@ +# Copyright 2010 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 diff --git a/pym/portage/package/ebuild/config.py b/pym/portage/package/ebuild/config.py index a97dd33ac..3b8ee10b5 100644 --- a/pym/portage/package/ebuild/config.py +++ b/pym/portage/package/ebuild/config.py @@ -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", diff --git a/pym/portage/package/ebuild/doebuild.py b/pym/portage/package/ebuild/doebuild.py index 61eea40ee..7405b6f12 100644 --- a/pym/portage/package/ebuild/doebuild.py +++ b/pym/portage/package/ebuild/doebuild.py @@ -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) diff --git a/pym/portage/package/ebuild/prepare_build_dirs.py b/pym/portage/package/ebuild/prepare_build_dirs.py index dc29eeeb8..15e087121 100644 --- a/pym/portage/package/ebuild/prepare_build_dirs.py +++ b/pym/portage/package/ebuild/prepare_build_dirs.py @@ -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: diff --git a/pym/portage/tests/ebuild/test_ipc_daemon.py b/pym/portage/tests/ebuild/test_ipc_daemon.py index 48412a1c3..de548c6f1 100644 --- a/pym/portage/tests/ebuild/test_ipc_daemon.py +++ b/pym/portage/tests/ebuild/test_ipc_daemon.py @@ -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) -- 2.26.2