diff options
Diffstat (limited to 'exception.py')
-rw-r--r-- | exception.py | 688 |
1 files changed, 77 insertions, 611 deletions
diff --git a/exception.py b/exception.py index dffb7cb59..f6ce416df 100644 --- a/exception.py +++ b/exception.py @@ -21,632 +21,98 @@ # Erik Troan <ewt@redhat.com> # Chris Lumens <clumens@redhat.com> # - -from constants import * -from filer import * -from network import hasActiveNetDev +from meh.handler import * +from meh.dump import * import isys import sys import os import shutil import signal -import traceback -import inspect -import iutil -import types -import bdb -from string import joinfields -from cPickle import Pickler from flags import flags import kickstart -import gettext -_ = lambda x: gettext.ldgettext("anaconda", x) - -import logging -log = logging.getLogger("anaconda") - -class AnacondaExceptionDump: - def __init__(self, type, value, stack): - self.type = type - self.value = value - - # this isn't used, but it's an option if we want to leave the - # two instantiations of this class as they are instead of - # passing in a stack - if inspect.istraceback(stack): - stack = inspect.getinnerframes(stack) - - self.stack = stack - - self.tbFile = None - - self._dumpHash = {} - - # Reverse the order that tracebacks are printed so people will hopefully quit - # giving us the least useful part of the exception in bug reports. - def __str__(self): - lst = self.format_stack() - lst.reverse() - lst.insert(0, "anaconda %s exception report\n" % os.getenv("ANACONDAVERSION")) - lst.insert(1, 'Traceback (most recent call first):\n') - - if self.type is not None and self.value is not None: - lst.extend(traceback.format_exception_only(self.type, self.value)) - - return joinfields(lst, "") - - def format_stack(self): - frames = [] - for (frame, file, lineno, func, ctx, idx) in self.stack: - if type(ctx) == type([]): - code = "".join(ctx) - else: - code = ctx - - frames.append((file, lineno, func, code)) - - return traceback.format_list(frames) - - # Create a string representation of a class and write it to fd. This - # method will recursively handle all attributes of the base given class. - def _dumpClass(self, instance, fd, level=0, parentkey="", skipList=[]): - # protect from loops - try: - if not self._dumpHash.has_key(instance): - self._dumpHash[instance] = None - else: - fd.write("Already dumped\n") - return - except TypeError: - fd.write("Cannot dump object\n") - return - - if (instance.__class__.__dict__.has_key("__str__") or - instance.__class__.__dict__.has_key("__repr__")): - fd.write("%s\n" % (instance,)) - return - fd.write("%s instance, containing members:\n" % - (instance.__class__.__name__)) - pad = ' ' * ((level) * 2) - - for key, value in instance.__dict__.items(): - if key.startswith("_%s__" % instance.__class__.__name__): - continue - - if parentkey != "": - curkey = parentkey + "." + key - else: - curkey = key - - # Don't dump objects that are in our skip list, though ones that are - # None are probably okay. - if eval("instance.%s is not None" % key) and \ - eval("id(instance.%s)" % key) in skipList: - continue - - if type(value) == types.ListType: - fd.write("%s%s: [" % (pad, curkey)) - first = 1 - for item in value: - if not first: - fd.write(", ") - else: - first = 0 - if type(item) == types.InstanceType: - self._dumpClass(item, fd, level + 1, skipList=skipList) - else: - fd.write("%s" % (item,)) - fd.write("]\n") - elif type(value) == types.DictType: - fd.write("%s%s: {" % (pad, curkey)) - first = 1 - for k, v in value.items(): - if not first: - fd.write(", ") - else: - first = 0 - if type(k) == types.StringType: - fd.write("'%s': " % (k,)) - else: - fd.write("%s: " % (k,)) - if type(v) == types.InstanceType: - self._dumpClass(v, fd, level + 1, parentkey = curkey, skipList=skipList) - else: - fd.write("%s" % (v,)) - fd.write("}\n") - elif type(value) == types.InstanceType: - fd.write("%s%s: " % (pad, curkey)) - self._dumpClass(value, fd, level + 1, parentkey=curkey, skipList=skipList) - else: - fd.write("%s%s: %s\n" % (pad, curkey, value)) - - # Dump the python traceback, internal state, and several files to the given - # file descriptor. - def dump (self, fd, anaconda): - skipList = [ "anaconda.backend.ayum", - "anaconda.backend.dlpkgs", - "anaconda.id.accounts", - "anaconda.id.bootloader.password", - "anaconda.id.comps", - "anaconda.id.dispatch", - "anaconda.id.hdList", - "anaconda.id.ksdata", - "anaconda.id.instLanguage.font", - "anaconda.id.instLanguage.kbd", - "anaconda.id.instLanguage.info", - "anaconda.id.instLanguage.localeInfo", - "anaconda.id.instLanguage.nativeLangNames", - "anaconda.id.instLanguage.tz", - "anaconda.id.keyboard._mods._modelDict", - "anaconda.id.keyboard.modelDict", - "anaconda.id.storage.encryptionPassphrase", - "anaconda.id.rootPassword", - "anaconda.id.tmpData", - "anaconda.intf.icw.buff", - "anaconda.intf.icw.currentWindow.storage.encryptionPassphrase", - "anaconda.intf.icw.stockButtons", - "dispatch.sack.excludes", - ] - idSkipList = [] - - # Skip any local variables that contain these words - localSkipList = [ "passphrase", "password" ] - - # Catch attributes that do not exist at the time we do the exception dump - # and ignore them. - for k in skipList: - try: - eval("idSkipList.append(id(%s))" % k) - except: - pass - - p = Pickler(fd) - - fd.write(str(self)) - - if self.stack: - frame = self.stack[-1][0] - fd.write ("\nLocal variables in innermost frame:\n") +class AnacondaExceptionHandler(ExceptionHandler): + def postWriteHook(self, (ty, value, tb), anaconda): + # See if /mnt/sysimage is present and put exception there as well + if os.access("/mnt/sysimage/root", os.X_OK): try: - for (key, value) in frame.f_locals.items(): - loweredKey = key.lower() - if len(filter(lambda s: loweredKey.find(s) != -1, localSkipList)) > 0: - continue - - fd.write ("%s: %s\n" % (key, value)) + shutil.copyfile("/tmp/anacdump.txt", "/mnt/sysimage/root/anacdump.txt") except: + log.error("Failed to copy anacdump.txt to /mnt/sysimage/root") pass + # run kickstart traceback scripts (if necessary) try: - fd.write("\n\n") - self._dumpClass(anaconda, fd, skipList=idSkipList) + if anaconda.isKickstart: + kickstart.runTracebackScripts(anaconda) except: - fd.write("\nException occurred during state dump:\n") - traceback.print_exc(None, fd) - - for file in ("/tmp/syslog", "/tmp/anaconda.log", - "/tmp/lvmout", "/tmp/resize.out", - "/tmp/program.log", "/tmp/storage.log", - "/tmp/yum.log", - anaconda.rootPath + "/root/install.log", - anaconda.rootPath + "/root/upgrade.log", - "/proc/cmdline"): - try: - f = open(file, 'r') - line = "\n\n%s:\n" % (file,) - while line: - fd.write(line) - line = f.readline() - f.close() - except IOError: - pass - except: - fd.write("\nException occurred during %s file copy:\n" % (file,)) - traceback.print_exc(None, fd) - - def hash(self): - import hashlib - s = "" - - for (file, lineno, func, text) in [f[1:5] for f in self.stack]: - if type(text) == type([]): - text = "".join(text) - s += "%s %s %s\n" % (os.path.basename(file), func, text) - - return hashlib.sha256(s).hexdigest() - - def write(self, anaconda): - self.tbFile = "/tmp/anacdump.txt" - fd = open(self.tbFile, "w") - self.dump(fd, anaconda) - fd.close() - -def scpAuthenticate(master, childpid, password): - while 1: - # Read up to password prompt. Propagate OSError exceptions, which - # can occur for anything that causes scp to immediately die (bad - # hostname, host down, etc.) - buf = os.read(master, 4096) - if buf.lower().find("password: ") != -1: - os.write(master, password+"\n") - # read the space and newline that get echoed back - os.read(master, 2) - break - - while 1: - buf = "" - try: - buf = os.read(master, 4096) - except (OSError, EOFError): - break - - (pid, childstatus) = os.waitpid (childpid, 0) - return childstatus - -# Save the traceback to a remote system via SCP. Returns success or not. -def copyExceptionToRemote(intf, scpInfo): - import pty - - (user, password, host, path) = scpInfo - - if host.find(":") != -1: - (host, port) = host.split(":") - - # Try to convert the port to an integer just as a check to see - # if it's a valid port number. If not, they'll get a chance to - # correct the information when scp fails. - try: - int(port) - portArgs = ["-P", port] - except ValueError: - portArgs = [] - else: - portArgs = [] - - # Thanks to Will Woods <wwoods@redhat.com> for the scp control - # here and in scpAuthenticate. - - # Fork ssh into its own pty - (childpid, master) = pty.fork() - if childpid < 0: - log.critical("Could not fork process to run scp") - return False - elif childpid == 0: - # child process - run scp - args = ["scp", "-oNumberOfPasswordPrompts=1", - "-oStrictHostKeyChecking=no"] + portArgs + \ - ["/tmp/anacdump.txt", "%s@%s:%s" % (user, host, path)] - os.execvp("scp", args) - - # parent process - try: - childstatus = scpAuthenticate(master, childpid, password) - except OSError: - return False - - os.close(master) - - if os.WIFEXITED(childstatus) and os.WEXITSTATUS(childstatus) == 0: - return True - else: - return False - -# Save the traceback to a removable storage device, such as a floppy disk -# or a usb/firewire drive. If there's no filesystem on the disk/partition, -# write a vfat one. -# Returns success or not. -def copyExceptionToDisk(anaconda, device): - # in test mode have save to disk option just copy to new name - if not flags.setupFilesystems: - try: - shutil.copyfile("/tmp/anacdump.txt", "/tmp/test-anacdump.txt") - except: - log.error("Failed to copy anacdump.txt to /tmp/test-anacdump.txt") pass - anaconda.intf.__del__ () - return True - - try: - fd = os.open(device, os.O_RDONLY) - except: - return False - - os.close(fd) - - fstype = isys.readFSType(device) - if fstype == None: - fstype = 'vfat' - try: - isys.mount(device, "/tmp/crash", fstype) - except SystemError: - if fstype != 'vfat': - return False - cmd = "/usr/sbin/mkdosfs" - - if os.access("/sbin/mkdosfs", os.X_OK): - cmd = "/sbin/mkdosfs" - - iutil.execWithRedirect (cmd, [device], stdout = '/dev/tty5', - stderr = '/dev/tty5') - - try: - isys.mount(device, "/tmp/crash", fstype) - except SystemError: - return False - - # copy trace dump we wrote to local storage to disk - try: - shutil.copyfile("/tmp/anacdump.txt", "/tmp/crash/anacdump.txt") - except: - log.error("Failed to copy anacdump.txt to device %s" % device) - return False - - isys.umount("/tmp/crash") - return True - -def saveToBugzilla(anaconda, exn, dest): - import bugzilla, xmlrpclib - import product, rpmUtils - - def withBugzillaDo(bz, fn): - try: - retval = fn(bz) - return retval - except CommunicationError, e: - msg = _("Your bug could not be filed due to the following error " - "when communicating with bugzilla:\n\n%s" % str(e)) - except (TypeError, ValueError), e: - msg = _("Your bug could not be filed due to bad information in " - "the bug fields. This is most likely an error in " - "anaconda:\n\n%s" % str(e)) - - anaconda.intf.messageWindow(_("Unable To File Bug"), msg) - return None - - filer = anaconda.id.instClass.bugFiler - - if not filer.supportsFiling() or not filer.bugUrl: - anaconda.intf.messageWindow(_("Bug Filing Not Supported"), - _("This distribution does not provide a " - "supported bug filing system, so you " - "cannot save your exception this way.")) - return False - - if dest[0].strip() == "" or dest[1].strip() == "" or dest[2].strip() == "": - anaconda.intf.messageWindow(_("Invalid Bug Information"), - _("Please provide a valid username, " - "password, and a short description of " - "the bug.")) - return False - - hash = exn.hash() - - if not exn.tbFile: - exn.write(anaconda) - - try: - cred = withBugzillaDo(filer, lambda b: b.login(dest[0], dest[1])) - except LoginError: - anaconda.intf.messageWindow(_("Unable To Login"), - _("There was an error logging into %s " - "using the provided username and " - "password.") % product.bugUrl) - return False - - # Are there any existing bugs with this hash value? If so we will just - # add this traceback to the bug report and put the reporter on the CC - # list. Otherwise, we need to create a new bug. - wb = "anaconda_trace_hash:%s" % hash - buglist = withBugzillaDo(filer, lambda b: b.query({'status_whiteboard': wb, - 'status_whiteboard_type':'allwordssubstr', - 'bug_status': []})) - if buglist is None: - return False - - if len(buglist) == 0: - bug = withBugzillaDo(filer, lambda b: b.createbug(product=filer.getproduct(product.productName), - component="anaconda", - version=filer.getversion(product.productVersion, - product.productName), - platform=rpmUtils.arch.getBaseArch(), - bug_severity="medium", - priority="medium", - op_sys="Linux", - bug_file_loc="http://", - summary=dest[2], - comment="The following was filed automatically by anaconda:\n%s" % str(exn), - status_whiteboard=wb)) - if bug is None: - return False - - withBugzillaDo(bug, lambda b: b.attachfile(exn.tbFile, - "Attached traceback automatically from anaconda.", - contenttype="text/plain", filename="anacdump.txt")) - - # Tell the user we created a new bug for them and that they should - # go add a descriptive comment. - anaconda.intf.messageWindow(_("Bug Created"), - _("A new bug has been created with the traceback attached. " - "Please add additional information such as what you were doing " - "when you encountered the bug, screenshots, and any other " - "relevant information to the following bug:\n\n%s/%s") % (bugzillaUrl, bug.id()), - type="custom", custom_icon="info", - custom_buttons=[_("_Exit installer")]) - sys.exit(0) - else: - bug = buglist[0] - withBugzillaDo(bug, lambda b: b.attachfile(exn.tbFile, - "Attached traceback automatically from anaconda.", - contenttype="text/plain", filename="anacdump.txt")) - withBugzillaDo(bug, lambda b: b.addCC(dest[0])) - - # Tell the user which bug they've been CC'd on and that they should - # go add a descriptive comment. - anaconda.intf.messageWindow(_("Bug Updated"), - _("A bug with this profile has already been filed. Your account " - "has been added to the CC list and your traceback added as a " - "comment. Please review the following bug, and add additional " - "information if necessary:\n\n%s/%s") % (bugzillaUrl, bug.id()), - type="custom", custom_icon="info", - custom_buttons=[_("_Exit installer")]) - sys.exit(0) - -def runSaveDialog(anaconda, exn): - saveWin = anaconda.intf.saveExceptionWindow(anaconda, exn.tbFile) - if not saveWin: - anaconda.intf.__del__() + def runDebug(self, (ty, value, tb)): + isys.vtActivate(1) + self.intf.__del__ () + + pidfl = "/tmp/vncshell.pid" + if os.path.exists(pidfl) and os.path.isfile(pidfl): + pf = open(pidfl, "r") + for pid in pf.readlines(): + if not int(pid) == os.getpid(): + os.kill(int(pid), signal.SIGKILL) + pf.close() + + if not flags.test: + os.open("/dev/console", os.O_RDWR) # reclaim stdin + os.dup2(0, 1) # reclaim stdout + os.dup2(0, 2) # reclaim stderr + # ^ + # | + # +------ dup2 is magic, I tells ya! + + # bring back the echo + import termios + si = sys.stdin.fileno() + attr = termios.tcgetattr(si) + attr[3] = attr[3] & termios.ECHO + termios.tcsetattr(si, termios.TCSADRAIN, attr) + + print("\nEntering debugger...") + import pdb + pdb.post_mortem (tb) os.kill(os.getpid(), signal.SIGKILL) - while 1: - saveWin.run() - rc = saveWin.getrc() - - if rc == EXN_OK: - if saveWin.saveToDisk(): - device = saveWin.getDest() - cpSucceeded = copyExceptionToDisk(anaconda, device) - - if cpSucceeded: - anaconda.intf.messageWindow(_("Dump Written"), - _("System state has been successfully written to " - "the disk. The installer will now exit."), - type="custom", custom_icon="info", - custom_buttons=[_("_Exit installer")]) - sys.exit(0) - else: - anaconda.intf.messageWindow(_("Dump Not Written"), - _("There was a problem writing the system state to the " - "disk.")) - continue - elif saveWin.saveToLocal(): - dest = saveWin.getDest() - try: - shutil.copyfile(exn.tbFile, "%s/InstallError.txt" %(dest,)) - anaconda.intf.messageWindow(_("Dump Written"), - _("System state has been successfully written to " - "the disk. The installer will now exit."), - type="custom", custom_icon="info", - custom_buttons=[_("_Exit installer")]) - sys.exit(0) - except Exception, e: - log.error("Failed to copy %s to %s/anacdump.txt: %s" %(exn.tbFile, dest, e)) - else: - anaconda.intf.messageWindow(_("Dump Not Written"), - _("There was a problem writing the system state to the " - "disk.")) - continue - elif saveWin.saveToRemote(): - if not hasActiveNetDev(): - if not anaconda.intf.enableNetwork(anaconda): - anaconda.intf.messageWindow(_("No Network Available"), - _("Cannot save a bug report since there is no " - "active networking device available.")) - continue - - scpInfo = saveWin.getDest() - scpSucceeded = copyExceptionToRemote(anaconda.intf, scpInfo) - - if scpSucceeded: - anaconda.intf.messageWindow(_("Dump Written"), - _("System state has been successfully transferred to " - "the remote host. The installer will now exit."), - type="custom", custom_icon="info", - custom_buttons=[_("_Exit installer")]) - sys.exit(0) - else: - anaconda.intf.messageWindow(_("Dump Not Written"), - _("There was a problem transferring the system state " - "to the remote host.")) - continue - else: - if not hasActiveNetDev(): - if not anaconda.intf.enableNetwork(anaconda): - anaconda.intf.messageWindow(_("No Network Available"), - _("Cannot save a bug report since there is no " - "active networking device available.")) - continue - - if not saveToBugzilla(anaconda, exn, saveWin.getDest()): - continue - elif rc == EXN_CANCEL: - break - - saveWin.pop() - -def handleException(anaconda, (type, value, tb)): - if isinstance(value, bdb.BdbQuit): - sys.exit(1) - - # restore original exception handler - sys.excepthook = sys.__excepthook__ - - # convert the traceback to a stack - stack = inspect.getinnerframes(tb) - - # Save the exception file to local storage first. - exn = AnacondaExceptionDump(type, value, stack) - exn.write(anaconda) - text = str(exn) - - # see if /mnt/sysimage is present and put exception there as well - if os.access("/mnt/sysimage/root", os.X_OK): - try: - shutil.copyfile("/tmp/anacdump.txt", "/mnt/sysimage/root/anacdump.txt") - except: - log.error("Failed to copy anacdump.txt to /mnt/sysimage/root") - pass - - # run kickstart traceback scripts (if necessary) - try: - if anaconda.isKickstart: - kickstart.runTracebackScripts(anaconda) - except: - pass - - mainWin = anaconda.intf.mainExceptionWindow(text, exn.tbFile) - if not mainWin: - anaconda.intf.__del__() - os.kill(os.getpid(), signal.SIGKILL) - - while 1: - mainWin.run() - rc = mainWin.getrc() - - if rc == EXN_OK: - anaconda.intf.__del__ () - os.kill(os.getpid(), signal.SIGKILL) - elif rc == EXN_DEBUG: - anaconda.intf.__del__ () - print(text) - - pidfl = "/tmp/vncshell.pid" - if os.path.exists(pidfl) and os.path.isfile(pidfl): - pf = open(pidfl, "r") - for pid in pf.readlines(): - if not int(pid) == os.getpid(): - os.kill(int(pid), signal.SIGKILL) - pf.close() - - if not flags.test: - os.open("/dev/console", os.O_RDWR) # reclaim stdin - os.dup2(0, 1) # reclaim stdout - os.dup2(0, 2) # reclaim stderr - # ^ - # | - # +------ dup2 is magic, I tells ya! - - # bring back the echo - import termios - si = sys.stdin.fileno() - attr = termios.tcgetattr(si) - attr[3] = attr[3] & termios.ECHO - termios.tcsetattr(si, termios.TCSADRAIN, attr) - - print("\nEntering debugger...") - import pdb - pdb.post_mortem (tb) - os.kill(os.getpid(), signal.SIGKILL) - elif rc == EXN_SAVE: - runSaveDialog(anaconda, exn) +def initExceptionHandling(anaconda): + conf = Config(programName="anaconda", programVersion=os.environ["ANACONDAVERSION"], + bugFiler=anaconda.id.instClass.bugFiler, + attrSkipList=[ "anaconda.backend.ayum", + "anaconda.backend.dlpkgs", + "anaconda.id.accounts", + "anaconda.id.bootloader.password", + "anaconda.id.comps", + "anaconda.id.dispatch", + "anaconda.id.hdList", + "anaconda.id.ksdata", + "anaconda.id.instLanguage.font", + "anaconda.id.instLanguage.kbd", + "anaconda.id.instLanguage.info", + "anaconda.id.instLanguage.localeInfo", + "anaconda.id.instLanguage.nativeLangNames", + "anaconda.id.instLanguage.tz", + "anaconda.id.keyboard._mods._modelDict", + "anaconda.id.keyboard.modelDict", + "anaconda.id.storage.encryptionPassphrase", + "anaconda.id.rootPassword", + "anaconda.id.tmpData", + "anaconda.intf.icw.buff", + "anaconda.intf.icw.currentWindow.storage.encryptionPassphrase", + "anaconda.intf.icw.stockButtons", + "dispatch.sack.excludes", + ], + localSkipList=[ "passphrase", "password" ], + fileList=[ "/tmp/syslog", "/tmp/anaconda.log", "/tmp/lvmout", + "/tmp/resize.out", "/tmp/program.log", + "/tmp/storage.log", "/tmp/yum.log", + anaconda.rootPath + "/root/install.log", + anaconda.rootPath + "/root/upgrade.log", + "/proc/cmdline"]) + handler = AnacondaExceptionHandler(conf, anaconda.intf, ReverseExceptionDump) + handler.install(anaconda) |