From 639741c0eb011ba39231e28bd6e76e5fc8ccdc0c Mon Sep 17 00:00:00 2001 From: Zac Medico Date: Mon, 26 Nov 2007 11:58:31 +0000 Subject: Bug #200313 - Detect and report when an ebuild phase exits unexpectedly. This is type of behavior is known to be triggered by things such as failed variable assignments (bug #190128) or bad substitution errors (bug #200313). We use a EBUILD_EXIT_STATUS_FILE environment variable to specify a file that the shell code is supposed to create when it exits in a normal manner. If the file does not get created like it's supposed to be then we can conclude that the shell has exited in some unexpected way. (trunk r8682) svn path=/main/branches/2.1.2/; revision=8684 --- bin/ebuild.sh | 3 +- bin/isolated-functions.sh | 5 ++- bin/misc-functions.sh | 2 ++ pym/portage.py | 83 +++++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 89 insertions(+), 4 deletions(-) diff --git a/bin/ebuild.sh b/bin/ebuild.sh index 46651e76a..a6969c9d8 100755 --- a/bin/ebuild.sh +++ b/bin/ebuild.sh @@ -1726,7 +1726,7 @@ if [ -n "${EBUILD_SH_ARGS}" ] ; then 9>&- fi set +f - #make sure it is writable by our group: + touch "${EBUILD_EXIT_STATUS_FILE}" &>/dev/null exit 0 ;; *) @@ -1737,6 +1737,7 @@ if [ -n "${EBUILD_SH_ARGS}" ] ; then exit 1 ;; esac + touch "${EBUILD_EXIT_STATUS_FILE}" &>/dev/null fi # Save the env only for relevant phases. diff --git a/bin/isolated-functions.sh b/bin/isolated-functions.sh index ae418389d..9eaf9bffb 100644 --- a/bin/isolated-functions.sh +++ b/bin/isolated-functions.sh @@ -124,6 +124,8 @@ diefunc() { done fi + touch "${EBUILD_EXIT_STATUS_FILE}" &>/dev/null + # subshell die support kill -s SIGTERM ${EBUILD_MASTER_PID} exit 1 @@ -416,7 +418,8 @@ save_ebuild_env() { # portage config variables and variables set directly by portage unset BAD BRACKET BUILD_PREFIX COLS \ - DISTCC_DIR DISTDIR DOC_SYMLINKS_DIR EBUILD_MASTER_PID \ + DISTCC_DIR DISTDIR DOC_SYMLINKS_DIR \ + EBUILD_EXIT_STATUS_FILE EBUILD_MASTER_PID \ ECLASSDIR ECLASS_DEPTH ENDCOL FAKEROOTKEY FEATURES \ GOOD HILITE HOME IMAGE \ KV LAST_E_CMD LAST_E_LEN LD_PRELOAD MOPREFIX \ diff --git a/bin/misc-functions.sh b/bin/misc-functions.sh index 31ad9d8e8..633b3c37d 100755 --- a/bin/misc-functions.sh +++ b/bin/misc-functions.sh @@ -587,4 +587,6 @@ if [ -n "${MISC_FUNCTIONS_ARGS}" ]; then done fi +touch "${EBUILD_EXIT_STATUS_FILE}" &>/dev/null + : diff --git a/pym/portage.py b/pym/portage.py index 31902c7e3..e62355a9a 100644 --- a/pym/portage.py +++ b/pym/portage.py @@ -3510,8 +3510,23 @@ def spawnebuild(mydo,actionmap,mysettings,debug,alwaysdep=0,logfile=None): return retval kwargs = actionmap[mydo]["args"] mysettings["EBUILD_PHASE"] = mydo + _doebuild_exit_status_unlink( + mysettings.get("EBUILD_EXIT_STATUS_FILE")) phase_retval = spawn(actionmap[mydo]["cmd"] % mydo, mysettings, debug=debug, logfile=logfile, **kwargs) mysettings["EBUILD_PHASE"] = "" + msg = _doebuild_exit_status_check( + mydo, mysettings.get("EBUILD_EXIT_STATUS_FILE")) + if msg: + phase_retval = 1 + from textwrap import wrap + cmd = "source '%s/isolated-functions.sh' ; " % \ + PORTAGE_BIN_PATH + for l in wrap(msg, 72): + cmd += "eerror \"%s\" ; " % l + mysettings["EBUILD_PHASE"] = mydo + portage_exec.spawn(["bash", "-c", cmd], + env=mysettings.environ()) + mysettings["EBUILD_PHASE"] = "" if "userpriv" in mysettings.features and \ not kwargs["droppriv"] and secpass >= 2: @@ -3693,6 +3708,8 @@ def doebuild_environment(myebuild, mydo, myroot, mysettings, debug, use_cache, m mysettings["PORTAGE_BASHRC"] = os.path.join( mysettings["PORTAGE_CONFIGROOT"], EBUILD_SH_ENV_FILE.lstrip(os.path.sep)) + 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 (mydo!="depend") or not mysettings.has_key("KV"): @@ -3896,6 +3913,37 @@ def prepare_build_dirs(myroot, mysettings, cleanup): mysettings["PORTAGE_LOG_FILE"] = os.path.join( mysettings["T"], "build.log") +def _doebuild_exit_status_check(mydo, exit_status_file): + """ + Returns an error string if the shell appeared + to exit unsuccessfully, None otherwise. + """ + if not exit_status_file or \ + os.path.exists(exit_status_file): + return None + msg = ("The ebuild phase '%s' has exited " % mydo) + \ + "unexpectedly. This is type of behavior " + \ + "is known to be triggered " + \ + "by things such as failed variable " + \ + "assignments (bug #190128) or bad substitution " + \ + "errors (bug #200313)." + return msg + +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_exempt_depend = 0 _doebuild_manifest_checked = None @@ -4038,6 +4086,24 @@ def doebuild(myebuild, mydo, myroot, mysettings, debug=0, listonly=0, return 1 _doebuild_manifest_checked = manifest_path + def exit_status_check(retval): + if retval != os.EX_OK: + return retval + msg = _doebuild_exit_status_check( + mydo, mysettings.get("EBUILD_EXIT_STATUS_FILE")) + if msg: + retval = 1 + from textwrap import wrap + cmd = "source '%s/isolated-functions.sh' ; " % \ + PORTAGE_BIN_PATH + for l in wrap(msg, 72): + cmd += "eerror \"%s\" ; " % l + mysettings["EBUILD_PHASE"] = mydo + portage_exec.spawn(["bash", "-c", cmd], + env=mysettings.environ()) + mysettings["EBUILD_PHASE"] = "" + return retval + logfile=None builddir_lock = None tmpdir = None @@ -4158,6 +4224,12 @@ def doebuild(myebuild, mydo, myroot, mysettings, debug=0, listonly=0, logfile = mysettings.get("PORTAGE_LOG_FILE") if logfile and not os.access(os.path.dirname(logfile), os.W_OK): logfile = None + + if have_build_dirs: + _doebuild_exit_status_unlink( + mysettings.get("EBUILD_EXIT_STATUS_FILE")) + else: + mysettings.pop("EBUILD_EXIT_STATUS_FILE", None) if mydo == "unmerge": return unmerge(mysettings["CATEGORY"], mysettings["PF"], myroot, mysettings, vartree=vartree) @@ -4179,6 +4251,7 @@ def doebuild(myebuild, mydo, myroot, mysettings, debug=0, listonly=0, mysettings.load_infodir(infodir) retval = spawn(EBUILD_SH_BINARY + " " + mydo, mysettings, debug=debug, free=1, logfile=logfile) + retval = exit_status_check(retval) if secpass >= 2: """ Privileged phases may have left files that need to be made writable to a less privileged user.""" @@ -4189,6 +4262,7 @@ def doebuild(myebuild, mydo, myroot, mysettings, debug=0, listonly=0, elif mydo == "preinst": phase_retval = spawn(" ".join((EBUILD_SH_BINARY, mydo)), mysettings, debug=debug, free=1, logfile=logfile) + phase_retval = exit_status_check(phase_retval) if phase_retval == os.EX_OK: # Post phase logic and tasks that have been factored out of # ebuild.sh. @@ -4206,6 +4280,7 @@ def doebuild(myebuild, mydo, myroot, mysettings, debug=0, listonly=0, mysettings.load_infodir(mysettings["O"]) phase_retval = spawn(" ".join((EBUILD_SH_BINARY, mydo)), mysettings, debug=debug, free=1, logfile=logfile) + phase_retval = exit_status_check(phase_retval) if phase_retval == os.EX_OK: # Post phase logic and tasks that have been factored out of # ebuild.sh. @@ -4219,8 +4294,10 @@ def doebuild(myebuild, mydo, myroot, mysettings, debug=0, listonly=0, return phase_retval elif mydo in ("prerm", "postrm", "config", "info"): mysettings.load_infodir(mysettings["O"]) - return spawn(EBUILD_SH_BINARY + " " + mydo, + retval = spawn(EBUILD_SH_BINARY + " " + mydo, mysettings, debug=debug, free=1, logfile=logfile) + retval = exit_status_check(retval) + return retval mycpv = "/".join((mysettings["CATEGORY"], mysettings["PF"])) @@ -4402,7 +4479,9 @@ def doebuild(myebuild, mydo, myroot, mysettings, debug=0, listonly=0, elif mydo=="merge": retval = spawnebuild("install", actionmap, mysettings, debug, alwaysdep=1, logfile=logfile) - if retval != os.EX_OK: + if retval == os.EX_OK: + retval = exit_status_check(retval) + else: # The merge phase handles this already. Callers don't know how # far this function got, so we have to call elog_process() here # so that it's only called once. -- cgit v1.2.3-1-g7c22