/usr/bin/crash-digger is in apport-retrace 2.0.1-0ubuntu5.
This file is owned by root:root, with mode 0o755.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 | #!/usr/bin/python
# 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, time, optparse, subprocess, sys, signal, zlib, errno, stat, shutil
import apport
from apport.crashdb import get_crashdb
#
# classes
#
class CrashDigger:
def __init__(self, config_dir, auth_file, cache_dir, apport_retrace,
verbose=False, dup_db=None, dupcheck_mode=False, publish_dir=None):
'''Initialize pools.'''
self.retrace_pool = set()
self.dupcheck_pool = set()
self.config_dir = config_dir
self.cache_dir = cache_dir
self.verbose = verbose
self.auth_file = auth_file
self.dup_db = dup_db
self.dupcheck_mode = dupcheck_mode
self.crashdb = get_crashdb(auth_file)
self.apport_retrace = apport_retrace
self.publish_dir = publish_dir
if config_dir:
self.releases = os.listdir(config_dir)
self.releases.sort()
self.log('Available releases: %s' % str(self.releases))
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 log(self, str):
'''If verbosity is enabled, log the given string to stdout, and prepend
the current date and time.'''
sys.stdout.write('%s: %s\n' % (time.strftime('%x %X'), str))
sys.stdout.flush()
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())
self.log('fill_pool: dup check pool now: %s' % str(self.dupcheck_pool))
else:
self.retrace_pool.update(self.crashdb.get_unretraced())
self.log('fill_pool: retrace pool now: %s' % str(self.retrace_pool))
def retrace_next(self):
'''Grab an ID from the retrace pool and retrace it.'''
id = self.retrace_pool.pop()
self.log('retracing #%i (left in pool: %i)' % (id, len(self.retrace_pool)))
try:
rel = self.crashdb.get_distro_release(id)
except ValueError:
self.log('could not determine release -- no DistroRelease field?')
self.crashdb.mark_retraced(id)
return
if rel not in self.releases:
self.log('crash is release %s which does not have a config available, skipping' % rel)
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.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:
self.log('retracing #%i failed with status: %i' % (id, result))
if result == 99:
self.retrace_pool = set()
self.log('transient error reported; halting')
return
raise SystemError('retracing #%i failed' % id)
self.crashdb.mark_retraced(id)
def dupcheck_next(self):
'''Grab an ID from the dupcheck pool and process it.'''
id = self.dupcheck_pool.pop()
self.log('checking #%i for duplicate (left in pool: %i)' % (id, len(self.dupcheck_pool)))
try:
report = self.crashdb.download(id)
except (MemoryError, TypeError, ValueError, IOError, zlib.error) as e:
self.log('Cannot download report: ' + str(e))
apport.error('Cannot download report %i: %s', id, str(e))
return
res = self.crashdb.check_duplicate(id, report)
if res:
if res[1] == None:
self.log('Report is a duplicate of #%i (not fixed yet)' % res[0])
elif res[1] == '':
self.log('Report is a duplicate of #%i (fixed in latest version)' % res[0])
else:
self.log('Report is a duplicate of #%i (fixed in version %s)' % res)
else:
self.log('Duplicate check negative')
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('-C', '--cache', metavar='DIR',
help='Cache directory for packages downloaded in the sandbox')
optparser.add_option('-a', '--auth',
help='Path to a file with the crash database authentication information.',
action='store', type='string', dest='auth_file', default=None)
optparser.add_option('-l', '--lock',
help='Lock file; will be created and removed on successful exit, and '
'program immediately aborts if it already exists',
action='store', dest='lockfile', default=None)
optparser.add_option('-d', '--duplicate-db',
help='Path to the duplicate sqlite database (default: disabled)',
action='store', type='string', dest='dup_db', metavar='PATH',
default=None)
optparser.add_option('-D', '--dupcheck',
help='Only check duplicates for architecture independent crashes (like Python exceptions)',
action='store_true', dest='dupcheck_mode', default=False)
optparser.add_option('-v', '--verbose',
help='Verbose operation (also passed to apport-retrace)',
action='store_true', dest='verbose', default=False)
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',
help='After processing all reports, publish duplicate database to given directory',
metavar='DIR', default=None)
(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.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.apport_retrace, opts.verbose, opts.dup_db,
opts.dupcheck_mode, opts.publish_db).run()
except SystemExit as exit:
if exit.code == 99:
pass # fall through lock cleanup
else:
raise
if opts.lockfile:
os.unlink(opts.lockfile)
|