#!/usr/bin/env python from pprint import pprint import re import gtk import gtk.glade from backtrace import Backtrace class Bug(object): def __init__(self, bz, id): self.bz = bz self.id = id self._bug = bz.getbug(id) if 1: pprint(self._bug.__dict__) def get_backtrace(self): # Get the backtrace associated with this ABRT bug, as a Backtrace instance a = self._get_backtrace_info() if a is None: raise NoBacktrace() return Backtrace(self.bz.openattachment(a['id']).read()) def _get_backtrace_info(self): for a in self._bug.attachments: if a['filename'] == 'backtrace': return a def get_script(self): '''Parse the ABRT report, extracting the script''' # print repr(self._bug.longdescs[0]['body']) for line in self._bug.longdescs[0]['body'].splitlines(): m = re.match('cmdline: (.+)', line) if m: for arg in m.group(1).split()[1:]: if arg.startswith('/'): return arg if arg.endswith('.py'): return arg def get_abrt_reason(self): for line in self._bug.longdescs[0]['body'].splitlines(): m = re.match('reason: (.+)', line) if m: return m.group(1) def get_abrt_signal(self): reason = self.get_abrt_reason() if reason is None: return m = re.match('Process was terminated by signal ([0-9]+)', reason) if m: sig = int(m.group(1)) import signal for signame in dir(signal): if signame.startswith('SIG'): if getattr(signal, signame) == sig: return signame class NoBacktrace(Exception): pass class UnsupportedComponent(Exception): def __init__(self, path, url): self.path = path self.url = url def describe_thread(bt, thread): if len(bt._threads) == 1: return "the program's single thread" else: return "thread #%i" % thread.index def characterize_bt(bt, thread, script): '''Get (newsubject, commentblurb)''' bt_blurb = 'Looking at the backtrace, it looks like ' function = None for (i, frame) in enumerate(thread.framelist): # Get function name for deepest point in stack that has one: if function is None or function in ['??', 'vtable', '__kernel_vsyscall', 'raise', 'abort', 'g_log', 'g_logv']: function = frame.function if frame.function == 'gtk_style_realize': if 'ClearlooksStyle' in frame.info: return ('Crash in gtk_style_realize with "ClearlooksStyle"', None) if frame.function == 'abort': # print 'got abort!' for j in range(i, len(thread.framelist)): if thread.framelist[j].function == 'gdk_x_error': return ('Fatal error in gdk_x_error running %s' % script, (bt_blurb + 'a fatal error happened in frame %(f_idx)s of %(thread)s inside gdk_x_error.' % dict(thread = describe_thread(bt, thread), f_idx = j) ) ) if thread.framelist[j].function == '__assert_fail': return (('Assertion failure in %s inside %s running %s' % (thread.framelist[j+1].function, thread.framelist[j+2].function, script) ), (bt_blurb + 'an assertion failed inside frame %(f_idx)s of %(thread)s inside %(function)s.' % dict(thread = describe_thread(bt, thread), f_idx = j, function = thread.framelist[j+1].function) ) ) if frame.function in ['_XError', '__fortify_fail']: function = frame.function if frame.function in ['IA__g_assertion_message_expr']: function = thread.framelist[i+1].function if frame.function == 'Py_FatalError': if thread.framelist[i+1].function == 'PyEval_RestoreThread': return (('Incorrect thread usage in %s running %s' % (thread.framelist[i+2].function, script) ), (bt_blurb + "an incorrect usage of Python's internal thread API was detected in %(function)s in frame #%(f_idx)s of %(thread)s" % dict(function = thread.framelist[i+2].function, thread = describe_thread(bt, thread), f_idx = i+2) ) ) if frame.info == '() from /usr/lib/flash-plugin/libflashplayer.so': raise UnsupportedComponent('/usr/lib/flash-plugin/libflashplayer.so', 'https://fedoraproject.org/wiki/ForbiddenItems#Adobe_Flash_Player') bt_blurb += 'the problem occurred in %s' % describe_thread(bt, thread) if function: bt_blurb += ' in %s' % function if len(thread.framelist) == 1: bt_blurb += '. However the short length of the backtrace suggests that there was a problem generating the backtrace, so that may not be the true location of the error.' if function: summary = 'Fatal error in "%s" in %s' % (function, script) else: summary = 'Fatal error in %s' % script return (summary, bt_blurb) def what_provides_file(path): ''' Return a (subpackage, srpm) pair of names, or (None, None) ''' # I'm running on 32-bit: fixup archs in path down to 32-bit version: path = path.replace('/lib64/', '/lib/') import yum my = yum.YumBase() my.setCacheDir() if not path.startswith('/'): path = '*/' + path for pkg, path in my.searchPackageProvides([path]).iteritems(): print pkg.sourcerpm #print pkg.base_package_name import rpmUtils srpmName = rpmUtils.miscutils.splitFilename(pkg.sourcerpm)[0] return (pkg.name, srpmName) return (None, None) class Change(object): ''' Class representing a change to be made in Bugzilla. Somewhat analogous to a patch to be applied to a source tree. Currently only supports printing, but eventually ought to support being applied to the bug. I want to capture a change as an entity so that you can always do human review of the change, rather than automatically pushing blindly via XML-RPC ''' def __init__(self, bug, newsummary=None, newcomponent=None, comment=None, duplicate_id=None, status=None, resolution=None): self.bug = bug self.comment = comment self.newsummary = newsummary self.newcomponent = newcomponent self.duplicate_id = duplicate_id self.status = status self.resolution = resolution def __str__(self): result = '' if self.newsummary: result += '---- BEGIN SUMMARY ----\n' result += self.newsummary result += '\n---- END SUMMARY ----\n' if self.newcomponent: result += '---- BEGIN COMPONENT ----\n' result += '"python" -> "%s"\n' % self.newcomponent # is there a way to do this via XML-RPC? result += '---- END COMPONENT ----\n' if self.comment: result += '---- BEGIN COMMENT ----\n' result += self.comment result += '\n---- END COMMENT ----\n' if self.duplicate_id: result += '---- MARK AS DUPLICATE OF: %i ----\n' % self.duplicate_id if self.status: result += '---- SET status: %s ----\n' % self.status if self.resolution: result += '---- SET resolution: %s ----\n' % self.resolution return result class Duplicate(Change): def __init__(self, bug, other_bug_id): Change.__init__(self, bug, comment=( '''Thank you for the bug report. This particular bug has already been reported into our bug tracking system, but please feel free to report any further bugs you find.'''), duplicate_id = other_bug_id ) def get_change(bz, bug_id): bug = Bug(bz, bug_id) signal = bug.get_abrt_signal() if signal: issue = signal else: issue = 'Crash' script = bug.get_script() # script = '/usr/bin/deluged' subpackage, srpmname = None, None if script: subpackage, srpmname = what_provides_file(script) if subpackage is None: subpackage = '(unknown)' if srpmname is None: srpmname = '(unknown)' try: bt = bug.get_backtrace() (thread, frame) = bt.get_crash_site() (newsummary, bt_blurb) = characterize_bt(bt, thread, script) if script.endswith('ies4linux-gtk.py'): return Change(bug, newsummary='%s running %s' % (issue, 'ies4linux-gtk.py'), comment=('''Thank you for your report. This bug is in the ies4linux script you are using to run Internet Explorer. Fedora does not provide or support this script. We would suggest that you report the problem to the upstream project at http://www.tatanka.com.br/ies4linux/ , but it does not seem to have been updated since February 2008, so the effort may be wasted. There is unfortunately nothing the Fedora project can do to help you with this problem.'''), status='CLOSED', resolution='CANTFIX' ) except Duplicate, d: return d except NoBacktrace, e: return Change(bug, newsummary='%s running %s' % (issue, script), newcomponent = srpmname, comment=('''Thank you for the bug report. Unfortunately, without a stack trace from the crash it is impossible to determine what caused the crash. Please see http://fedoraproject.org/wiki/StackTraces for more information about getting a useful stack trace with debugging symbols. Even if you cannot reproduce this crash at will, you can prepare your system now to produce a good stack trace the next time you experience the crash. Thank you. ''') ) except UnsupportedComponent, e: return Change(bug, newsummary='%s in %s' % (issue, e.path), comment=('''Thank you for the bug report. Unfortunately the problem appears to be in %(path)s. The Fedora Project only ships and maintains Free and Open Source software. Issues such as these are beyond the control of Fedora developers. See %(url)s for more information You may find assistance in the Fedora community support forums or mailing list, or you might consider using a commercially supported product.''' % dict(path=e.path, url=e.url) ) ) comment = '''Thank you for reporting this bug. How reproducible is this problem? If you run the program from a terminal, is an error message printed? What version of %(subpackage)s do you have installed? %(bt_blurb)s Reassigning component from "python" to "%(subpackage)s" ''' % dict(subpackage=subpackage, bt_blurb = bt_blurb) if newsummary == 'Fatal error in "_XError" in /usr/share/virt-manager/virt-manager.py': return Duplicate(bug, 540810) if newsummary == 'Fatal error in "XFreeColormap" in /usr/bin/hp-systray': return Duplicate(bug, 543286) if newsummary == 'Crash in gtk_style_realize with "ClearlooksStyle"': return Duplicate(bug, 538799) ch = Change(bug, newsummary = newsummary, newcomponent = srpmname, comment = comment ) print '---- BEGIN THREAD ----' for id in sorted(thread.frames.keys()): f = thread.frames[id] if f.address: addr = hex(f.address) else: addr = None print '#%i %s %s' % (id, addr, f.function) print '---- END THREAD ----' return ch class ChangeGui(object): def __init__(self, change): self.change = change self._change_wnd = gtk.glade.XML ('triage.glade', 'change_window') self._window = self._change_wnd.get_widget('change_window') self._old_summary = self._change_wnd.get_widget('old_summary') self._new_summary = self._change_wnd.get_widget('new_summary') self._old_component = self._change_wnd.get_widget('old_component') self._new_component = self._change_wnd.get_widget('new_component') self._new_comment = self._change_wnd.get_widget('new_comment') print self.__dict__ for attr in self.__dict__: print attr #print getattr(self, attr).__dict__ print self._window.__dict__ self._window.set_title('Proposed changes for bug %i' % self.change.bug.id) self._old_summary.set_text(change.bug._bug.summary) if change.newsummary: self._new_summary.set_text(change.newsummary) self._old_component.set_text(change.bug._bug.component) if change.newcomponent: self._new_component.set_text(change.newcomponent) if change.comment: self._new_comment.get_buffer().set_text(change.comment) def main(): import bugzilla bz=bugzilla.Bugzilla(url='https://bugzilla.redhat.com/xmlrpc.cgi') import sys bug_id = int(sys.argv[1]) print '---- CHANGES FOR BUG %i ----' % bug_id change = get_change(bz, bug_id) print change if False: g = ChangeGui(change) gtk.main() main()