From 83b4b8792f8d600ce7630fa6d917c02ac93c856e Mon Sep 17 00:00:00 2001 From: Zac Medico Date: Mon, 26 Nov 2007 11:23:17 +0000 Subject: [PATCH] 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. svn path=/main/trunk/; revision=8682 --- bin/ebuild.sh | 3 +- bin/isolated-functions.sh | 5 ++- bin/misc-functions.sh | 2 ++ pym/portage/__init__.py | 73 +++++++++++++++++++++++++++++++++++++-- 4 files changed, 79 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 100755 --- 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 2e860453e..450e71631 100755 --- a/bin/misc-functions.sh +++ b/bin/misc-functions.sh @@ -593,4 +593,6 @@ if [ -n "${MISC_FUNCTIONS_ARGS}" ]; then done fi +touch "${EBUILD_EXIT_STATUS_FILE}" &>/dev/null + : diff --git a/pym/portage/__init__.py b/pym/portage/__init__.py index 28d5eeed5..04b73b1b8 100644 --- a/pym/portage/__init__.py +++ b/pym/portage/__init__.py @@ -3560,8 +3560,18 @@ 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 + from portage.elog.messages import eerror + for l in wrap(msg, 72): + eerror(l, phase=mydo, key=mysettings.mycpv) if "userpriv" in mysettings.features and \ not kwargs["droppriv"] and secpass >= 2: @@ -3744,6 +3754,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"): @@ -3947,6 +3959,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 @@ -4089,6 +4132,19 @@ 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 + from portage.elog.messages import eerror + for l in wrap(msg, 72): + eerror(l, phase=mydo, key=mysettings.mycpv) + return retval + logfile=None builddir_lock = None tmpdir = None @@ -4209,6 +4265,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) @@ -4230,6 +4292,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.""" @@ -4240,6 +4303,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. @@ -4257,6 +4321,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. @@ -4270,8 +4335,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"])) @@ -4449,7 +4516,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. -- 2.26.2