mirror of https://gitee.com/openkylin/apport.git
232 lines
8.9 KiB
Python
Executable File
232 lines
8.9 KiB
Python
Executable File
#!/usr/bin/python3
|
|
|
|
# Copyright (C) 2007 - 2011 Canonical Ltd.
|
|
# Author: Martin Pitt <martin.pitt@ubuntu.com>
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify it
|
|
# under the terms of the GNU General Public License as published by the
|
|
# Free Software Foundation; either version 2 of the License, or (at your
|
|
# option) any later version. See http://www.gnu.org/copyleft/gpl.html for
|
|
# the full text of the license.
|
|
|
|
import os, optparse, subprocess, sys, zlib, errno, shutil
|
|
|
|
import apport
|
|
from apport.crashdb import get_crashdb
|
|
|
|
|
|
#
|
|
# classes
|
|
#
|
|
|
|
class CrashDigger:
|
|
def __init__(self, config_dir, auth_file, cache_dir, sandbox_dir,
|
|
apport_retrace, verbose=False, dup_db=None, dupcheck_mode=False,
|
|
publish_dir=None, crash_db=None):
|
|
'''Initialize pools.'''
|
|
|
|
self.retrace_pool = set()
|
|
self.dupcheck_pool = set()
|
|
self.config_dir = config_dir
|
|
self.cache_dir = cache_dir
|
|
self.sandbox_dir = sandbox_dir
|
|
self.verbose = verbose
|
|
self.auth_file = auth_file
|
|
self.dup_db = dup_db
|
|
self.dupcheck_mode = dupcheck_mode
|
|
try:
|
|
self.crashdb = get_crashdb(auth_file, name=crash_db)
|
|
except KeyError:
|
|
apport.error('Crash database %s does not exist', crash_db)
|
|
sys.exit(1)
|
|
self.lp = False
|
|
try:
|
|
if self.crashdb.launchpad:
|
|
self.lp = True
|
|
except AttributeError:
|
|
pass
|
|
self.apport_retrace = apport_retrace
|
|
self.publish_dir = publish_dir
|
|
if config_dir:
|
|
self.releases = os.listdir(config_dir)
|
|
self.releases.sort()
|
|
apport.log('Available releases: %s' % str(self.releases), True)
|
|
else:
|
|
self.releases = None
|
|
|
|
if self.dup_db:
|
|
self.crashdb.init_duplicate_db(self.dup_db)
|
|
# this verified DB integrity; make a backup now
|
|
shutil.copy2(self.dup_db, self.dup_db + '.backup')
|
|
|
|
def fill_pool(self):
|
|
'''Query crash db for new IDs to process.'''
|
|
|
|
if self.dupcheck_mode:
|
|
self.dupcheck_pool.update(self.crashdb.get_dup_unchecked())
|
|
apport.log('fill_pool: dup check pool now: %s' % str(self.dupcheck_pool), True)
|
|
else:
|
|
self.retrace_pool.update(self.crashdb.get_unretraced())
|
|
apport.log('fill_pool: retrace pool now: %s' % str(self.retrace_pool), True)
|
|
|
|
def retrace_next(self):
|
|
'''Grab an ID from the retrace pool and retrace it.'''
|
|
|
|
id = self.retrace_pool.pop()
|
|
apport.log('retracing %s#%i (left in pool: %i)' %
|
|
("LP: " if self.lp else "", id, len(self.retrace_pool)), True)
|
|
|
|
try:
|
|
rel = self.crashdb.get_distro_release(id)
|
|
except ValueError:
|
|
apport.log('could not determine release -- no DistroRelease field?', True)
|
|
self.crashdb.mark_retraced(id)
|
|
return
|
|
if rel not in self.releases:
|
|
apport.log('crash is release %s which does not have a config available, skipping' % rel, True)
|
|
return
|
|
|
|
argv = [self.apport_retrace, '-S', self.config_dir, '--auth',
|
|
self.auth_file, '--timestamps']
|
|
if self.cache_dir:
|
|
argv += ['--cache', self.cache_dir]
|
|
if self.sandbox_dir:
|
|
argv += ['--sandbox-dir', self.sandbox_dir]
|
|
if self.dup_db:
|
|
argv += ['--duplicate-db', self.dup_db]
|
|
if self.verbose:
|
|
argv.append('-v')
|
|
argv.append(str(id))
|
|
|
|
result = subprocess.call(argv, stdout=sys.stdout, stderr=subprocess.STDOUT)
|
|
if result != 0:
|
|
apport.log('retracing %s#%i failed with status: %i' %
|
|
("LP: " if self.lp else "", id, result), True)
|
|
if result == 99:
|
|
self.retrace_pool = set()
|
|
apport.log('transient error reported; halting', True)
|
|
return
|
|
|
|
self.crashdb.mark_retraced(id)
|
|
|
|
def dupcheck_next(self):
|
|
'''Grab an ID from the dupcheck pool and process it.'''
|
|
|
|
id = self.dupcheck_pool.pop()
|
|
apport.log('checking %s#%i for duplicate (left in pool: %i)' %
|
|
("LP: " if self.lp else "", id, len(self.dupcheck_pool)), True)
|
|
|
|
try:
|
|
report = self.crashdb.download(id)
|
|
except (MemoryError, TypeError, ValueError, IOError, AssertionError, zlib.error) as e:
|
|
if str(e) == "bug description must contain standard apport format data":
|
|
apport.log('Cannot download report: ' + str(e), True)
|
|
apport.error('Cannot download report %i: %s', id, str(e))
|
|
return
|
|
apport.log('Cannot download report: ' + str(e), True)
|
|
apport.error('Cannot download report %i: %s', id, str(e))
|
|
return
|
|
|
|
res = self.crashdb.check_duplicate(id, report)
|
|
if res:
|
|
if res[1] is None:
|
|
apport.log('Report is a duplicate of #%i (not fixed yet)' % res[0], True)
|
|
elif res[1] == '':
|
|
apport.log('Report is a duplicate of #%i (fixed in latest version)' % res[0], True)
|
|
else:
|
|
apport.log('Report is a duplicate of #%i (fixed in version %s)' % res, True)
|
|
else:
|
|
apport.log('Duplicate check negative', True)
|
|
|
|
def run(self):
|
|
'''Process the work pools until they are empty.'''
|
|
|
|
self.fill_pool()
|
|
while self.dupcheck_pool:
|
|
self.dupcheck_next()
|
|
while self.retrace_pool:
|
|
self.retrace_next()
|
|
|
|
if self.publish_dir:
|
|
self.crashdb.duplicate_db_publish(self.publish_dir)
|
|
|
|
|
|
#
|
|
# functions
|
|
#
|
|
|
|
def parse_options():
|
|
'''Parse command line options and return (options, args) tuple.'''
|
|
|
|
optparser = optparse.OptionParser('%prog [options]')
|
|
optparser.add_option('-c', '--config-dir', metavar='DIR',
|
|
help='Packaging system configuration base directory.')
|
|
optparser.add_option('--sandbox-dir', metavar='DIR',
|
|
help='Directory for unpacked packages. Future runs will assume that any already downloaded package is also extracted to this sandbox.')
|
|
optparser.add_option('-C', '--cache', metavar='DIR',
|
|
help='Cache directory for packages downloaded in the sandbox')
|
|
optparser.add_option('-a', '--auth', dest='auth_file',
|
|
help='Path to a file with the crash database authentication information.')
|
|
optparser.add_option('-l', '--lock', dest='lockfile',
|
|
help='Lock file; will be created and removed on successful exit, and '
|
|
'program immediately aborts if it already exists')
|
|
optparser.add_option('-d', '--duplicate-db', dest='dup_db', metavar='PATH',
|
|
help='Path to the duplicate sqlite database (default: disabled)')
|
|
optparser.add_option('--crash-db', metavar='NAME',
|
|
help='Use a different crash database than the "default" in /etc/apport/crashdb.conf')
|
|
optparser.add_option('-D', '--dupcheck', dest='dupcheck_mode', default=False, action='store_true',
|
|
help='Only check duplicates for architecture independent crashes (like Python exceptions)')
|
|
optparser.add_option('-v', '--verbose', action='store_true', default=False,
|
|
help='Verbose operation (also passed to apport-retrace)')
|
|
optparser.add_option('--apport-retrace', metavar='PATH',
|
|
help='Path to apport-retrace script (default: directory of crash-digger or $PATH)')
|
|
optparser.add_option('--publish-db', metavar='DIR',
|
|
help='After processing all reports, publish duplicate database to given directory')
|
|
|
|
(opts, args) = optparser.parse_args()
|
|
|
|
if not opts.config_dir and not opts.dupcheck_mode:
|
|
apport.fatal('Error: --config-dir or --dupcheck needs to be given')
|
|
if not opts.auth_file:
|
|
apport.fatal('Error: -a/--auth needs to be given')
|
|
|
|
return (opts, args)
|
|
|
|
|
|
#
|
|
# main
|
|
#
|
|
|
|
opts, args = parse_options()
|
|
|
|
|
|
# support running from tree, then fall back to $PATH
|
|
if not opts.apport_retrace:
|
|
opts.apport_retrace = os.path.join(os.path.dirname(sys.argv[0]), 'apport-retrace')
|
|
if not os.access(opts.apport_retrace, os.X_OK):
|
|
opts.apport_retrace = 'apport-retrace'
|
|
|
|
if opts.lockfile:
|
|
try:
|
|
f = os.open(opts.lockfile, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o666)
|
|
os.write(f, ("%u\n" % os.getpid()).encode())
|
|
os.close(f)
|
|
except OSError as e:
|
|
if e.errno == errno.EEXIST:
|
|
sys.exit(0)
|
|
else:
|
|
raise
|
|
|
|
try:
|
|
CrashDigger(opts.config_dir, opts.auth_file, opts.cache, opts.sandbox_dir,
|
|
opts.apport_retrace, opts.verbose, opts.dup_db,
|
|
opts.dupcheck_mode, opts.publish_db, opts.crash_db).run()
|
|
except SystemExit as exit:
|
|
if exit.code == 99:
|
|
pass # fall through lock cleanup
|
|
else:
|
|
raise
|
|
|
|
if opts.lockfile:
|
|
os.unlink(opts.lockfile)
|