~darkxst/apport/per-ppa-config

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
'''Python sys.excepthook hook to generate apport crash dumps.'''

# Copyright (c) 2006 - 2009 Canonical Ltd.
# Authors: Robert Collins <robert@ubuntu.com>
#          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
import sys
import re
from glob import glob

CONFIG = '/etc/default/apport'


def enabled():
    '''Return whether Apport should generate crash reports.'''

    # This doesn't use apport.packaging.enabled() because it is too heavyweight
    # See LP: #528355
    try:
        with open(CONFIG) as f:
            conf = f.read()
        return re.search('^\s*enabled\s*=\s*0\s*$', conf, re.M) is None
    except IOError:
        # if the file does not exist, assume it's enabled
        return True


def apport_excepthook(exc_type, exc_obj, exc_tb):
    '''Catch an uncaught exception and make a traceback.'''

    # create and save a problem report. Note that exceptions in this code
    # are bad, and we probably need a per-thread reentrancy guard to
    # prevent that happening. However, on Ubuntu there should never be
    # a reason for an exception here, other than [say] a read only var
    # or some such. So what we do is use a try - finally to ensure that
    # the original excepthook is invoked, and until we get bug reports
    # ignore the other issues.

    # import locally here so that there is no routine overhead on python
    # startup time - only when a traceback occurs will this trigger.
    try:
        # ignore 'safe' exit types.
        if exc_type in (KeyboardInterrupt, ):
            return

        # do not do anything if apport was disabled
        if not enabled():
            return

        try:
            from cStringIO import StringIO
            StringIO  # pyflakes
        except ImportError:
            from io import StringIO

        import re, traceback
        from apport.fileutils import likely_packaged, get_recent_crashes

        # apport will look up the package from the executable path.
        try:
            binary = os.path.realpath(os.path.join(os.getcwd(), sys.argv[0]))
        except (TypeError, AttributeError, IndexError):
            # the module has mutated sys.argv, plan B
            try:
                binary = os.readlink('/proc/%i/exe' % os.getpid())
            except OSError:
                return

        # for interactive python sessions, sys.argv[0] == ''; catch that and
        # other irregularities
        if not os.access(binary, os.X_OK) or not os.path.isfile(binary):
            return

        # filter out binaries in user accessible paths
        if not likely_packaged(binary):
            return

        import apport.report

        pr = apport.report.Report()

        # special handling of dbus-python exceptions
        if hasattr(exc_obj, 'get_dbus_name'):
            if exc_obj.get_dbus_name() == 'org.freedesktop.DBus.Error.NoReply':
                # NoReply is an useless crash, we do not even get the method it
                # was trying to call; needs actual crash from D-BUS backend (LP #914220)
                return
            if exc_obj.get_dbus_name() == 'org.freedesktop.DBus.Error.ServiceUnknown':
                dbus_service_unknown_analysis(exc_obj, pr)

        # append a basic traceback. In future we may want to include
        # additional data such as the local variables, loaded modules etc.
        tb_file = StringIO()
        traceback.print_exception(exc_type, exc_obj, exc_tb, file=tb_file)
        pr['Traceback'] = tb_file.getvalue().strip()
        pr.add_proc_info(extraenv=['PYTHONPATH', 'PYTHONHOME'])
        pr.add_user_info()
        # override the ExecutablePath with the script that was actually running
        pr['ExecutablePath'] = binary
        if 'ExecutableTimestamp' in pr:
            pr['ExecutableTimestamp'] = str(int(os.stat(binary).st_mtime))
        try:
            pr['PythonArgs'] = '%r' % sys.argv
        except AttributeError:
            pass
        if pr.check_ignored():
            return
        mangled_program = re.sub('/', '_', binary)
        # get the uid for now, user name later
        user = os.getuid()
        pr_filename = '%s/%s.%i.crash' % (os.environ.get(
            'APPORT_REPORT_DIR', '/var/crash'), mangled_program, user)
        crash_counter = 0
        if os.path.exists(pr_filename):
            if apport.fileutils.seen_report(pr_filename):
                # flood protection
                with open(pr_filename, 'rb') as f:
                    crash_counter = get_recent_crashes(f) + 1
                if crash_counter > 1:
                    return

                # remove the old file, so that we can create the new one with
                # os.O_CREAT|os.O_EXCL
                os.unlink(pr_filename)
            else:
                # don't clobber existing report
                return

        if crash_counter:
            pr['CrashCounter'] = str(crash_counter)
        with os.fdopen(os.open(pr_filename,
                               os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o640), 'wb') as f:
            pr.write(f)

    finally:
        # resume original processing to get the default behaviour,
        # but do not trigger an AttributeError on interpreter shutdown.
        if sys:
            sys.__excepthook__(exc_type, exc_obj, exc_tb)


def dbus_service_unknown_analysis(exc_obj, report):
    import subprocess
    try:
        from configparser import ConfigParser, NoSectionError, NoOptionError
        (ConfigParser, NoSectionError, NoOptionError)  # pyflakes
    except ImportError:
        # Python 2
        from ConfigParser import ConfigParser, NoSectionError, NoOptionError

    # determine D-BUS name
    m = re.search('name\s+(\S+)\s+was not provided by any .service',
                  exc_obj.get_dbus_message())
    if not m:
        if sys.stderr:
            sys.stderr.write('Error: cannot parse D-BUS name from exception: '
                             + exc_obj.get_dbus_message())
            return

    dbus_name = m.group(1)

    # determine .service file and Exec name for the D-BUS name
    services = []  # tuples of (service file, exe name, running)
    for f in glob('/usr/share/dbus-1/*services/*.service'):
        cp = ConfigParser(interpolation=None)
        cp.read(f, encoding='UTF-8')
        try:
            if cp.get('D-BUS Service', 'Name') == dbus_name:
                exe = cp.get('D-BUS Service', 'Exec')
                running = (subprocess.call(['pidof', '-sx', exe], stdout=subprocess.PIPE) == 0)
                services.append((f, exe, running))
        except (NoSectionError, NoOptionError):
            if sys.stderr:
                sys.stderr.write('Invalid D-BUS .service file %s: %s' % (
                    f, exc_obj.get_dbus_message()))
            continue

    if not services:
        report['DbusErrorAnalysis'] = 'no service file providing ' + dbus_name
    else:
        report['DbusErrorAnalysis'] = 'provided by'
        for (service, exe, running) in services:
            report['DbusErrorAnalysis'] += ' %s (%s is %srunning)' % (
                service, exe, ('' if running else 'not '))


def install():
    '''Install the python apport hook.'''

    sys.excepthook = apport_excepthook