~brian-murray/apport/test-fix

1679 by Martin Pitt
Update all copyright and description headers and consistently format them.
1
'''Python sys.excepthook hook to generate apport crash dumps.'''
2
3
# Copyright (c) 2006 - 2009 Canonical Ltd.
4
# Authors: Robert Collins <robert@ubuntu.com>
5
#          Martin Pitt <martin.pitt@ubuntu.com>
2189 by Martin Pitt
remove trailing whitespace
6
#
1679 by Martin Pitt
Update all copyright and description headers and consistently format them.
7
# This program is free software; you can redistribute it and/or modify it
8
# under the terms of the GNU General Public License as published by the
9
# Free Software Foundation; either version 2 of the License, or (at your
10
# option) any later version.  See http://www.gnu.org/copyleft/gpl.html for
11
# the full text of the license.
359 by martin at piware
* Add apport/python_hook.py: Default exception handler for Python, to create
12
13
import os
14
import sys
15
1896 by Martin Pitt
apport-checkreports: Exit with status 2 if there are new reports, but apport is disabled. This helps crash notification GUIs to not display new crash reports in that case. Thanks to Michael Vogt for the original patch.
16
CONFIG = '/etc/default/apport'
17
2277 by Martin Pitt
* Fix the whole code to be PEP-8 compatible, and enforce this in test/run by running the "pep8" tool.
18
1896 by Martin Pitt
apport-checkreports: Exit with status 2 if there are new reports, but apport is disabled. This helps crash notification GUIs to not display new crash reports in that case. Thanks to Michael Vogt for the original patch.
19
def enabled():
20
    '''Return whether Apport should generate crash reports.'''
21
2277 by Martin Pitt
* Fix the whole code to be PEP-8 compatible, and enforce this in test/run by running the "pep8" tool.
22
    # This doesn't use apport.packaging.enabled() because it is too heavyweight
23
    # See LP: #528355
2787 by Martin Pitt
* Delay the import of the glob and re modules in the python apport hook, and only import them when needed. Speeds up interpreter startup time by 50%. Thanks Matthias Klose! (LP: #1307684)
24
    import re
1896 by Martin Pitt
apport-checkreports: Exit with status 2 if there are new reports, but apport is disabled. This helps crash notification GUIs to not display new crash reports in that case. Thanks to Michael Vogt for the original patch.
25
    try:
2304.1.2 by Martin Pitt
More Python 3 fixes
26
        with open(CONFIG) as f:
27
            conf = f.read()
1896 by Martin Pitt
apport-checkreports: Exit with status 2 if there are new reports, but apport is disabled. This helps crash notification GUIs to not display new crash reports in that case. Thanks to Michael Vogt for the original patch.
28
        return re.search('^\s*enabled\s*=\s*0\s*$', conf, re.M) is None
29
    except IOError:
30
        # if the file does not exist, assume it's enabled
31
        return True
1714 by Martin Pitt
apport_python_hook.py: Directly check /etc/default/apport instead of
32
2277 by Martin Pitt
* Fix the whole code to be PEP-8 compatible, and enforce this in test/run by running the "pep8" tool.
33
359 by martin at piware
* Add apport/python_hook.py: Default exception handler for Python, to create
34
def apport_excepthook(exc_type, exc_obj, exc_tb):
35
    '''Catch an uncaught exception and make a traceback.'''
36
37
    # create and save a problem report. Note that exceptions in this code
688 by Martin Pitt
* Remove trailing white space in all Python files.
38
    # are bad, and we probably need a per-thread reentrancy guard to
359 by martin at piware
* Add apport/python_hook.py: Default exception handler for Python, to create
39
    # prevent that happening. However, on Ubuntu there should never be
40
    # a reason for an exception here, other than [say] a read only var
41
    # or some such. So what we do is use a try - finally to ensure that
688 by Martin Pitt
* Remove trailing white space in all Python files.
42
    # the original excepthook is invoked, and until we get bug reports
359 by martin at piware
* Add apport/python_hook.py: Default exception handler for Python, to create
43
    # ignore the other issues.
44
45
    # import locally here so that there is no routine overhead on python
46
    # startup time - only when a traceback occurs will this trigger.
47
    try:
48
        # ignore 'safe' exit types.
49
        if exc_type in (KeyboardInterrupt, ):
50
            return
1111 by Martin Pitt
* apport_python_hook.py: Do not create reports if Apport is disabled (in
51
52
        # do not do anything if apport was disabled
1896 by Martin Pitt
apport-checkreports: Exit with status 2 if there are new reports, but apport is disabled. This helps crash notification GUIs to not display new crash reports in that case. Thanks to Michael Vogt for the original patch.
53
        if not enabled():
1111 by Martin Pitt
* apport_python_hook.py: Do not create reports if Apport is disabled (in
54
            return
55
1827 by Martin Pitt
Python 3 compatible "print" (not everything yet)
56
        try:
57
            from cStringIO import StringIO
2337 by Martin Pitt
* Clean up module imports. * test/run: Run pyflakes, if available.
58
            StringIO  # pyflakes
1827 by Martin Pitt
Python 3 compatible "print" (not everything yet)
59
        except ImportError:
60
            from io import StringIO
61
2134.2.22 by Evan Dandrea
Unused imports.
62
        import re, traceback
2015 by Martin Pitt
apport_python_hook: Limit successive crashes per program and user to 3 per day, just like signal crashes. (LP: #603503)
63
        from apport.fileutils import likely_packaged, get_recent_crashes
359 by martin at piware
* Add apport/python_hook.py: Default exception handler for Python, to create
64
65
        # apport will look up the package from the executable path.
1083 by Martin Pitt
* apport_python_hook.py: If the Python script has mutilated sys.argv so that
66
        try:
2314 by Martin Pitt
apport_python_hook.py: Fix for Python 3
67
            binary = os.path.realpath(os.path.join(os.getcwd(), sys.argv[0]))
1087 by Martin Pitt
* apport_python_hook.py: Catch IndexError for invalid sys.argv[0], too.
68
        except (TypeError, AttributeError, IndexError):
1083 by Martin Pitt
* apport_python_hook.py: If the Python script has mutilated sys.argv so that
69
            # the module has mutated sys.argv, plan B
70
            try:
71
                binary = os.readlink('/proc/%i/exe' % os.getpid())
72
            except OSError:
73
                return
802 by Martin Pitt
* apport_python_hook.py: Move the apport.* imports into the try: block and
74
687 by Martin Pitt
* Replace tabs with spaces in all Python files. (LP: #93561)
75
        # for interactive python sessions, sys.argv[0] == ''; catch that and
76
        # other irregularities
77
        if not os.access(binary, os.X_OK) or not os.path.isfile(binary):
78
            return
802 by Martin Pitt
* apport_python_hook.py: Move the apport.* imports into the try: block and
79
80
        # filter out binaries in user accessible paths
81
        if not likely_packaged(binary):
82
            return
83
84
        import apport.report
85
86
        pr = apport.report.Report()
2430 by Martin Pitt
* apport_python_hook.py: For org.freedesktop.DBus.Error.ServiceUnknown exceptions, add a 'DbusErrorAnalysis' field to the report which points out whether any .service file provides the service it tried to talk to, and whether the processes for those are running. This helps to determine the root cause for such errors (missing dependencies, broken .service files, talking to the wrong bus, etc.) (LP: #1020572)
87
88
        # special handling of dbus-python exceptions
89
        if hasattr(exc_obj, 'get_dbus_name'):
90
            if exc_obj.get_dbus_name() == 'org.freedesktop.DBus.Error.NoReply':
91
                # NoReply is an useless crash, we do not even get the method it
92
                # was trying to call; needs actual crash from D-BUS backend (LP #914220)
93
                return
94
            if exc_obj.get_dbus_name() == 'org.freedesktop.DBus.Error.ServiceUnknown':
95
                dbus_service_unknown_analysis(exc_obj, pr)
96
359 by martin at piware
* Add apport/python_hook.py: Default exception handler for Python, to create
97
        # append a basic traceback. In future we may want to include
98
        # additional data such as the local variables, loaded modules etc.
99
        tb_file = StringIO()
100
        traceback.print_exception(exc_type, exc_obj, exc_tb, file=tb_file)
101
        pr['Traceback'] = tb_file.getvalue().strip()
2494.1.1 by Brian Murray
include pythonhome and pythonpath in procenviron for python crashes
102
        pr.add_proc_info(extraenv=['PYTHONPATH', 'PYTHONHOME'])
1009 by martin at piware
* apport_python_hook.py: Add user info, too. Also add check for this to the
103
        pr.add_user_info()
2277 by Martin Pitt
* Fix the whole code to be PEP-8 compatible, and enforce this in test/run by running the "pep8" tool.
104
        # override the ExecutablePath with the script that was actually running
359 by martin at piware
* Add apport/python_hook.py: Default exception handler for Python, to create
105
        pr['ExecutablePath'] = binary
2570 by Martin Pitt
* apport_python_hook.py: Update "ExecutableTimestamp" field when mangling "ExecutablePath". (LP: #1077253)
106
        if 'ExecutableTimestamp' in pr:
107
            pr['ExecutableTimestamp'] = str(int(os.stat(binary).st_mtime))
1546 by Martin Pitt
apport_python_hook.py: Protect against nonexisting sys.argv. (LP: #418051)
108
        try:
109
            pr['PythonArgs'] = '%r' % sys.argv
110
        except AttributeError:
111
            pass
538 by Martin Pitt
* apport/python_hook: Do not create a report if the binary is ignored. Add
112
        if pr.check_ignored():
113
            return
359 by martin at piware
* Add apport/python_hook.py: Default exception handler for Python, to create
114
        mangled_program = re.sub('/', '_', binary)
115
        # get the uid for now, user name later
116
        user = os.getuid()
2407 by Martin Pitt
* Fix PEP-8 violations picked up by latest pep8 checker.
117
        pr_filename = '%s/%s.%i.crash' % (os.environ.get(
118
            'APPORT_REPORT_DIR', '/var/crash'), mangled_program, user)
2015 by Martin Pitt
apport_python_hook: Limit successive crashes per program and user to 3 per day, just like signal crashes. (LP: #603503)
119
        crash_counter = 0
1320 by Martin Pitt
apport_python_hook.py: Fix crash for already existing reports, and make
120
        if os.path.exists(pr_filename):
121
            if apport.fileutils.seen_report(pr_filename):
2015 by Martin Pitt
apport_python_hook: Limit successive crashes per program and user to 3 per day, just like signal crashes. (LP: #603503)
122
                # flood protection
2302 by Martin Pitt
problem_report.py: Fix for Python 3
123
                with open(pr_filename, 'rb') as f:
124
                    crash_counter = get_recent_crashes(f) + 1
2015 by Martin Pitt
apport_python_hook: Limit successive crashes per program and user to 3 per day, just like signal crashes. (LP: #603503)
125
                if crash_counter > 1:
126
                    return
127
1320 by Martin Pitt
apport_python_hook.py: Fix crash for already existing reports, and make
128
                # remove the old file, so that we can create the new one with
129
                # os.O_CREAT|os.O_EXCL
130
                os.unlink(pr_filename)
131
            else:
132
                # don't clobber existing report
133
                return
2015 by Martin Pitt
apport_python_hook: Limit successive crashes per program and user to 3 per day, just like signal crashes. (LP: #603503)
134
135
        if crash_counter:
136
            pr['CrashCounter'] = str(crash_counter)
2302 by Martin Pitt
problem_report.py: Fix for Python 3
137
        with os.fdopen(os.open(pr_filename,
2407 by Martin Pitt
* Fix PEP-8 violations picked up by latest pep8 checker.
138
                               os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o640), 'wb') as f:
2302 by Martin Pitt
problem_report.py: Fix for Python 3
139
            pr.write(f)
359 by martin at piware
* Add apport/python_hook.py: Default exception handler for Python, to create
140
141
    finally:
1096.1.2 by Daniel Hahler
Fix uncaught exceptions in apport itself (LP: #215929):
142
        # resume original processing to get the default behaviour,
143
        # but do not trigger an AttributeError on interpreter shutdown.
144
        if sys:
145
            sys.__excepthook__(exc_type, exc_obj, exc_tb)
359 by martin at piware
* Add apport/python_hook.py: Default exception handler for Python, to create
146
147
2430 by Martin Pitt
* apport_python_hook.py: For org.freedesktop.DBus.Error.ServiceUnknown exceptions, add a 'DbusErrorAnalysis' field to the report which points out whether any .service file provides the service it tried to talk to, and whether the processes for those are running. This helps to determine the root cause for such errors (missing dependencies, broken .service files, talking to the wrong bus, etc.) (LP: #1020572)
148
def dbus_service_unknown_analysis(exc_obj, report):
2787 by Martin Pitt
* Delay the import of the glob and re modules in the python apport hook, and only import them when needed. Speeds up interpreter startup time by 50%. Thanks Matthias Klose! (LP: #1307684)
149
    from glob import glob
150
    import subprocess, re
2430 by Martin Pitt
* apport_python_hook.py: For org.freedesktop.DBus.Error.ServiceUnknown exceptions, add a 'DbusErrorAnalysis' field to the report which points out whether any .service file provides the service it tried to talk to, and whether the processes for those are running. This helps to determine the root cause for such errors (missing dependencies, broken .service files, talking to the wrong bus, etc.) (LP: #1020572)
151
    try:
152
        from configparser import ConfigParser, NoSectionError, NoOptionError
153
        (ConfigParser, NoSectionError, NoOptionError)  # pyflakes
154
    except ImportError:
155
        # Python 2
156
        from ConfigParser import ConfigParser, NoSectionError, NoOptionError
157
158
    # determine D-BUS name
159
    m = re.search('name\s+(\S+)\s+was not provided by any .service',
160
                  exc_obj.get_dbus_message())
161
    if not m:
162
        if sys.stderr:
163
            sys.stderr.write('Error: cannot parse D-BUS name from exception: '
164
                             + exc_obj.get_dbus_message())
165
            return
166
167
    dbus_name = m.group(1)
168
169
    # determine .service file and Exec name for the D-BUS name
170
    services = []  # tuples of (service file, exe name, running)
171
    for f in glob('/usr/share/dbus-1/*services/*.service'):
172
        cp = ConfigParser(interpolation=None)
173
        cp.read(f, encoding='UTF-8')
174
        try:
175
            if cp.get('D-BUS Service', 'Name') == dbus_name:
176
                exe = cp.get('D-BUS Service', 'Exec')
177
                running = (subprocess.call(['pidof', '-sx', exe], stdout=subprocess.PIPE) == 0)
178
                services.append((f, exe, running))
179
        except (NoSectionError, NoOptionError):
180
            if sys.stderr:
181
                sys.stderr.write('Invalid D-BUS .service file %s: %s' % (
182
                    f, exc_obj.get_dbus_message()))
183
            continue
184
185
    if not services:
186
        report['DbusErrorAnalysis'] = 'no service file providing ' + dbus_name
187
    else:
188
        report['DbusErrorAnalysis'] = 'provided by'
189
        for (service, exe, running) in services:
190
            report['DbusErrorAnalysis'] += ' %s (%s is %srunning)' % (
191
                service, exe, ('' if running else 'not '))
192
193
359 by martin at piware
* Add apport/python_hook.py: Default exception handler for Python, to create
194
def install():
195
    '''Install the python apport hook.'''
196
197
    sys.excepthook = apport_excepthook