#!/usr/bin/python
# Author: Kees Cook <kees@canonical.com>
# Copyright (C) 2009 Canonical, Ltd.
# License: GPLv3
#
# This attempts to find crashes that lack the SegvAnalysis and SegvReason
# fields from an apport crash report, and adds them.

import lpl_common
import sys, tempfile, os
import parse_segv

lp = lpl_common.connect(use_edge=True)

filenames = ['Disassembly.txt','Registers.txt','ProcMaps.txt']
lastbug = int(file('.hunt').read().strip())

seen = set()
if len(sys.argv)>1:
    print "Fetching bug %s ..." % (sys.argv[1])
    tasks = lp.bugs[sys.argv[1]].bug_tasks_collection
    lastbug = None
else:
    print "Searching ...",
    sys.stdout.flush()
    tasks = lp.distributions['ubuntu'].searchTasks(tags='apport-crash', omit_duplicates=True, search_text="Signal: 11", order_by='-datecreated')
    print ""

for task in tasks:
    bug = task.bug

    if bug.id in seen:
        print "%d (Skipping: seen during this run)" % (bug.id)
        continue
    seen.add(bug.id)
    if lastbug and bug.id <= lastbug:
        print "%d (Skipping: seen in prior run)" % (bug.id)
        continue

    print bug.id, bug.title

    # search_text doesn't keep spaces, so check for signal manually...
    if 'Signal: 11' not in bug.description:
        print '\tSkipping: not a signal 11'
        continue

    # Have we already marked it up?
    if 'SegvAnalysis:' in bug.description:
        print '\tSkipping: already marked up'
        continue

    # only x86/x86_64
    if 'Architecture: i386' not in bug.description and 'Architecture: amd64' not in bug.description:
        print '\tSkipping: not x86'
        continue

    # Only pay attention to Jaunty for now
    if 'DistroRelease: Ubuntu 9.04' not in bug.description:
        print '\tSkipping: not reported in Jaunty'
        continue

    # Skip if all statuses are closed or closing
    bug_open = False
    for task in bug.bug_tasks_collection:
        if task.status not in ['Invalid', "Won't Fix", 'Fix Committed', 'Fix Released']:
            bug_open = True
            break
    if not bug_open:
        print '\tSkipping: no open task statuses'
        continue

    # Look for attachments
    info = {}
    for filename in filenames:
        info[filename] = None

    for attachment in bug.attachments_collection:
        if attachment.title in filenames:
            print '\treading: %s ...' % (attachment.title)
            info[attachment.title] = attachment.data.open().read()

    # Attempt to build a fake Disassembly.txt when it was so short it ended up in the Description
    if 'Disassembly: ' in bug.description and info['Disassembly.txt'] == None:
        disasm = ""
        gathering = False
        for line in bug.description.splitlines():
            if line.startswith('Disassembly: '):
                gathering = True
                disasm = line.split(' ',1)[1]
                if disasm != "":
                    disasm += "\n"
                continue
            if not gathering:
                continue
            if line.startswith(' '):
                disasm += line[1:] + "\n"
            else:
                gathering = False
                break
        if disasm != "":
            info['Disassembly.txt'] = disasm
            print '\tok: Disassembly.txt (pulled from description)'

    skip = False
    for filename in filenames:
        if info[filename] is None:
            print '\tFailure: missing "%s"' % (filename)
            skip = True
            break
    if skip:
        continue

    # Analyze
    try:
        segv = parse_segv.ParseSegv(info['Registers.txt'], info['Disassembly.txt'], info['ProcMaps.txt'])
        understood, reason, details = segv.report()
        print '\t' + details.strip().replace("\n","\n\t")
        print '\tReason: ' + reason

        bug.description += '\nSegvAnalysis:\n %s\nSegvReason: %s' % (details.strip().replace('\n','\n '), reason)
        lpl_common.save(bug)

    except BaseException, e:
        print info['Registers.txt']
        print info['Disassembly.txt']
        print info['ProcMaps.txt']
        raise

#    open('.hunt.new','w').write('%d\n' % (bug.id))
#    os.rename('.hunt.new','.hunt')
