#:mode=python:
# -*- coding: utf-8 -*-
## Copyright (C) 2001-2005 Red Hat, Inc.
## Copyright (C) 2001-2005 Harald Hoyer <harald@redhat.com>
## Copyright (C) 2009 Jiri Moskovcak <jmoskovc@redhat.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.
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
## GNU General Public License for more details.
## You should have received a copy of the GNU General Public License
## along with this program; if not, write to the Free Software
## Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335 USA
"""
Module for the ABRT exception handling hook
"""
import sys
import os
class RPMinfoError(Exception):
"""Exception class for RPMdb-querying related errors"""
pass
def syslog(msg):
"""Log message to system logger (journal)"""
from systemd import journal
# required as a workaround for rhbz#1023041
# where journal tries to log into non-existent log
# and fails (during %check in mock)
#
# try/except block should be removed when the bug is fixed
try:
journal.send(msg)
except:
pass
def write_dump(tb_text, tb):
if sys.argv[0][0] == "/":
executable = os.path.abspath(sys.argv[0])
else:
# We don't know the path.
# (BTW, we *can't* assume the script is in current directory.)
executable = sys.argv[0]
dso_list = None
# Trace back is None in case of SyntaxError exception.
if tb:
try:
import rpm
dso_list = get_dso_list(tb)
except ImportError as imperr:
syslog("RPM module not available, cannot query RPM db for package "\
"names")
# Open ABRT daemon's socket and write data to it
try:
import socket
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
s.settimeout(5)
try:
s.connect("/var/run" + "/abrt/abrt.socket")
s.sendall("POST / HTTP/1.1\r\n\r\n")
s.sendall("type=Python\0")
s.sendall("pid=%s\0" % os.getpid())
s.sendall("executable=%s\0" % executable)
# This handler puts a short(er) crash descr in 1st line of the backtrace.
# Example:
# CCMainWindow.py:1:<module>:ZeroDivisionError: integer division or modulo by zero
s.sendall("reason=%s\0" % tb_text.splitlines()[0])
s.sendall("backtrace=%s\0" % tb_text)
if dso_list:
s.sendall("dso_list=%s\0" % "\n".join(dso_list))
s.sendall("environ=")
for k,v in os.environ.iteritems():
s.sendall("%s=%s\n" % (k,v))
s.sendall("\0")
s.shutdown(socket.SHUT_WR)
# Read the response and log if there's anything wrong
response = ""
while True:
buf = s.recv(256)
if not buf:
break
response += buf
except socket.timeout as ex:
syslog("communication with ABRT daemon failed: %s" % str(ex))
s.close()
parts = response.split()
if (len(parts) < 2
or (not parts[0].startswith("HTTP/"))
or (not parts[1].isdigit())
or (int(parts[1]) >= 400)):
syslog("error sending data to ABRT daemon: %s" % response)
except Exception as ex:
syslog("can't communicate with ABRT daemon, is it running? %s" % str(ex))
def get_package_for_file(fpath):
"""
Returns package name for a given file.
@param fpath: filename
@type fpath: str
@return: package name for the file
@rtype: str
@throws RPMinfoError: if package for the file cannot be found
"""
import rpm
ts = rpm.TransactionSet()
mi = ts.dbMatch("basenames", fpath)
try:
header = mi.next()
except StopIteration:
raise RPMinfoError("Cannot get package and component for file "+
fpath)
package = "{0}-{1}-{2}.{3}".format(header["name"], header["version"],
header["release"], header["arch"])
return package
def get_dso_list(tb):
"""
Get the list of names of the packages whose files appear in the traceback.
@param tb: traceback
@type tb: traceback
@return: list of package names
@rtype: list
"""
import inspect
if inspect.istraceback(tb):
tb = inspect.getinnerframes(tb)
packages = set()
for (frame, fpath, lineno, func, ctx, idx) in tb:
try:
packages.add(get_package_for_file(fpath))
except RPMinfoError as rpmerr:
continue
# remove the package name of the executable itself
try:
packages.discard(get_package_for_file(sys.argv[0]))
except RPMinfoError as rpmerr:
pass
return list(packages)
def require_abs_path():
"""
Return True if absolute path requirement is enabled
in configuration
"""
import problem
try:
conf = problem.load_plugin_conf_file("python.conf")
except OsError:
return False
return conf.get("RequireAbsolutePath", "yes") == "yes"
def handleMyException((etype, value, tb)):
"""
The exception handling function.
progname - the name of the application
version - the version of the application
"""
try:
# Restore original exception handler
sys.excepthook = sys.__excepthook__ # pylint: disable-msg=E1101
import errno
# Ignore Ctrl-C
# SystemExit rhbz#636913 -> this exception is not an error
if etype in [KeyboardInterrupt, SystemExit]:
return sys.__excepthook__(etype, value, tb)
# Ignore EPIPE: it happens all the time
# Testcase: script.py | true, where script.py is:
## #!/usr/bin/python
## import os
## import time
## time.sleep(1)
## os.write(1, "Hello\n") # print "Hello" wouldn't be the same
#
if etype == IOError or etype == OSError:
if value.errno == errno.EPIPE:
return sys.__excepthook__(etype, value, tb)
# Ignore interactive Python and similar
# Check for first "-" is meant to catch "-c" which appears in this case:
## $ python -c 'import sys; print "argv0 is:%s" % sys.argv[0]'
## argv0 is:-c
# Are there other cases when sys.argv[0][0] is "-"?
if not sys.argv[0] or sys.argv[0][0] == "-":
einfo = "" if not sys.argv[0] else " (python %s ...)" % sys.argv[0]
syslog("detected unhandled Python exception in 'interactive mode%s'"
% einfo)
raise Exception
# Ignore scripts with relative path unless "RequireAbsolutePath = no".
# (In this case we can't reliably determine package)
syslog("detected unhandled Python exception in '%s'" % sys.argv[0])
if sys.argv[0][0] != "/":
if require_abs_path():
raise Exception
import traceback
elist = traceback.format_exception(etype, value, tb)
if tb != None and etype != IndentationError:
tblast = traceback.extract_tb(tb, limit=None)
if len(tblast):
tblast = tblast[len(tblast)-1]
extxt = traceback.format_exception_only(etype, value)
if tblast and len(tblast) > 3:
ll = []
ll.extend(tblast[:3])
ll[0] = os.path.basename(tblast[0])
tblast = ll
ntext = ""
for t in tblast:
ntext += str(t) + ":"
text = ntext
text += extxt[0]
text += "\n"
text += "".join(elist)
trace = tb
while trace.tb_next:
trace = trace.tb_next
frame = trace.tb_frame
text += ("\nLocal variables in innermost frame:\n")
try:
for (key, val) in frame.f_locals.items():
text += "%s: %s\n" % (key, repr(val))
except:
pass
else:
text = str(value) + "\n"
text += "\n"
text += "".join(elist)
# Send data to the daemon
write_dump(text, tb)
except:
# Silently ignore any error in this hook,
# to not interfere with other scripts
pass
return sys.__excepthook__(etype, value, tb)
def installExceptionHandler():
"""
Install the exception handling function.
"""
sys.excepthook = lambda etype, value, tb: handleMyException((etype, value, tb))
# install the exception handler when the abrt_exception_handler
# module is imported
try:
installExceptionHandler()
except Exception, e:
# TODO: log errors?
# OTOH, if abrt is deinstalled uncleanly
# and this file (sitecustomize.py) exists but
# abrt_exception_handler module does not exist, we probably
# don't want to irritate admins...
pass
if __name__ == '__main__':
# test exception raised to show the effect
div0 = 1 / 0 # pylint: disable-msg=W0612
sys.exit(0)
__author__ = "Harald Hoyer <harald@redhat.com>"