Blame src/hooks/abrt_exception_handler3.py

Packit 8ea169
#:mode=python:
Packit 8ea169
# -*- coding: utf-8 -*-
Packit 8ea169
## Copyright (C) 2014 Red Hat, Inc.
Packit 8ea169
Packit 8ea169
## This program is free software; you can redistribute it and/or modify
Packit 8ea169
## it under the terms of the GNU General Public License as published by
Packit 8ea169
## the Free Software Foundation; either version 2 of the License, or
Packit 8ea169
## (at your option) any later version.
Packit 8ea169
Packit 8ea169
## This program is distributed in the hope that it will be useful,
Packit 8ea169
## but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 8ea169
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit 8ea169
## GNU General Public License for more details.
Packit 8ea169
Packit 8ea169
## You should have received a copy of the GNU General Public License
Packit 8ea169
## along with this program; if not, write to the Free Software
Packit 8ea169
## Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA  02110-1335  USA
Packit 8ea169
Packit 8ea169
"""
Packit 8ea169
Module for the ABRT exception handling hook
Packit 8ea169
"""
Packit 8ea169
Packit 8ea169
import sys
Packit 8ea169
import os
Packit 8ea169
Packit 8ea169
Packit 8ea169
def syslog(msg):
Packit 8ea169
    """Log message to system logger (journal)"""
Packit 8ea169
Packit 8ea169
    from systemd import journal
Packit 8ea169
Packit 8ea169
    # required as a workaround for rhbz#1023041
Packit 8ea169
    # where journal tries to log into non-existent log
Packit 8ea169
    # and fails (during %check in mock)
Packit 8ea169
    #
Packit 8ea169
    # try/except block should be removed when the bug is fixed
Packit 8ea169
Packit 8ea169
    try:
Packit 8ea169
        journal.send(msg)
Packit 8ea169
    except:
Packit 8ea169
        pass
Packit 8ea169
Packit 8ea169
Packit 8ea169
def send(data):
Packit 8ea169
    """Send data to abrtd"""
Packit 8ea169
Packit 8ea169
    response = ""
Packit 8ea169
Packit 8ea169
    try:
Packit 8ea169
        import socket
Packit 8ea169
        s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
Packit 8ea169
        s.settimeout(5)
Packit 8ea169
        s.connect("/var/run" + "/abrt/abrt.socket")
Packit 8ea169
        pre = "POST / HTTP/1.1\r\n\r\n"
Packit 8ea169
        pre += "type=Python3\0"
Packit 8ea169
        pre += "analyzer=abrt-python3-handler\0"
Packit 8ea169
        s.sendall(pre.encode())
Packit 8ea169
        s.sendall(data.encode())
Packit 8ea169
Packit 8ea169
        s.shutdown(socket.SHUT_WR)
Packit 8ea169
Packit 8ea169
        while True:
Packit 8ea169
            buf = s.recv(256)
Packit 8ea169
            if not buf:
Packit 8ea169
                break
Packit 8ea169
            response += buf.decode()
Packit 8ea169
Packit 8ea169
        s.close()
Packit 8ea169
Packit 8ea169
    except socket.timeout as ex:
Packit 8ea169
        syslog("communication with ABRT daemon failed: {0}".format(ex))
Packit 8ea169
Packit 8ea169
    except Exception as ex:
Packit 8ea169
        syslog("can't communicate with ABRT daemon, is it running? {0}"
Packit 8ea169
               .format(ex))
Packit 8ea169
Packit 8ea169
    return response
Packit 8ea169
Packit 8ea169
Packit 8ea169
def write_dump(tb_text, tb):
Packit 8ea169
    if sys.argv[0][0] == "/":
Packit 8ea169
        executable = os.path.abspath(sys.argv[0])
Packit 8ea169
    else:
Packit 8ea169
        # We don't know the path.
Packit 8ea169
        # (BTW, we *can't* assume the script is in current directory.)
Packit 8ea169
        executable = sys.argv[0]
Packit 8ea169
Packit 8ea169
    data = "pid={0}\0".format(os.getpid())
Packit 8ea169
    data += "executable={0}\0".format(executable)
Packit 8ea169
    data += "reason={0}\0".format(tb_text.splitlines()[0])
Packit 8ea169
    data += "backtrace={0}\0".format(tb_text)
Packit 8ea169
Packit 8ea169
    response = send(data)
Packit 8ea169
    parts = response.split()
Packit 8ea169
    if (len(parts) < 2
Packit 8ea169
            or (not parts[0].startswith("HTTP/"))
Packit 8ea169
            or (not parts[1].isdigit())
Packit 8ea169
            or (int(parts[1]) >= 400)):
Packit 8ea169
        syslog("error sending data to ABRT daemon: {0}".format(response))
Packit 8ea169
Packit 8ea169
Packit 8ea169
def require_abs_path():
Packit 8ea169
    """
Packit 8ea169
    Return True if absolute path requirement is enabled
Packit 8ea169
    in configuration
Packit 8ea169
    """
Packit 8ea169
Packit 8ea169
    import problem
Packit 8ea169
Packit 8ea169
    try:
Packit 8ea169
        conf = problem.load_plugin_conf_file("python3.conf")
Packit 8ea169
    except OsError:
Packit 8ea169
        return False
Packit 8ea169
Packit 8ea169
    return conf.get("RequireAbsolutePath", "yes") == "yes"
Packit 8ea169
Packit 8ea169
Packit 8ea169
def handle_exception(etype, value, tb):
Packit 8ea169
    """
Packit 8ea169
    The exception handling function.
Packit 8ea169
Packit 8ea169
    progname - the name of the application
Packit 8ea169
    version  - the version of the application
Packit 8ea169
    """
Packit 8ea169
Packit 8ea169
    try:
Packit 8ea169
        # Restore original exception handler
Packit 8ea169
        sys.excepthook = sys.__excepthook__  # pylint: disable-msg=E1101
Packit 8ea169
Packit 8ea169
        import errno
Packit 8ea169
Packit 8ea169
        # Ignore Ctrl-C
Packit 8ea169
        # SystemExit rhbz#636913 -> this exception is not an error
Packit 8ea169
        if etype in [KeyboardInterrupt, SystemExit]:
Packit 8ea169
            return sys.__excepthook__(etype, value, tb)
Packit 8ea169
Packit 8ea169
        # Ignore EPIPE: it happens all the time
Packit 8ea169
        # Testcase: script.py | true, where script.py is:
Packit 8ea169
        ## #!/usr/bin/python
Packit 8ea169
        ## import os
Packit 8ea169
        ## import time
Packit 8ea169
        ## time.sleep(1)
Packit 8ea169
        ## os.write(1, "Hello\n")  # print "Hello" wouldn't be the same
Packit 8ea169
        #
Packit 8ea169
        if etype == IOError or etype == OSError:
Packit 8ea169
            if value.errno == errno.EPIPE:
Packit 8ea169
                return sys.__excepthook__(etype, value, tb)
Packit 8ea169
Packit 8ea169
        # Ignore interactive Python and similar
Packit 8ea169
        # Check for first "-" is meant to catch "-c" which appears in this case:
Packit 8ea169
        ## $ python -c 'import sys; print "argv0 is:%s" % sys.argv[0]'
Packit 8ea169
        ## argv0 is:-c
Packit 8ea169
        # Are there other cases when sys.argv[0][0] is "-"?
Packit 8ea169
        if not sys.argv[0] or sys.argv[0][0] == "-":
Packit 8ea169
            einfo = "" if not sys.argv[0] else " (python {0} ...)".format(sys.argv[0])
Packit 8ea169
            syslog("detected unhandled Python exception in 'interactive mode{0}'"
Packit 8ea169
                   .format(einfo))
Packit 8ea169
            raise Exception
Packit 8ea169
Packit 8ea169
        # Ignore scripts with relative path unless "RequireAbsolutePath = no".
Packit 8ea169
        # (In this case we can't reliably determine package)
Packit 8ea169
        syslog("detected unhandled Python exception in '{0}'"
Packit 8ea169
               .format(sys.argv[0]))
Packit 8ea169
Packit 8ea169
        if sys.argv[0][0] != "/":
Packit 8ea169
            if require_abs_path():
Packit 8ea169
                raise Exception
Packit 8ea169
Packit 8ea169
        import traceback
Packit 8ea169
Packit 8ea169
        elist = traceback.format_exception(etype, value, tb)
Packit 8ea169
Packit 8ea169
        if tb is not None and etype != IndentationError:
Packit 8ea169
            tblast = traceback.extract_tb(tb, limit=None)
Packit 8ea169
            if tblast:
Packit 8ea169
                tblast = tuple(tblast[-1])
Packit 8ea169
            extxt = traceback.format_exception_only(etype, value)
Packit 8ea169
            if tblast and len(tblast) > 3:
Packit 8ea169
                ll = []
Packit 8ea169
                ll.extend(tblast[:3])
Packit 8ea169
                ll[0] = os.path.basename(tblast[0])
Packit 8ea169
                tblast = ll
Packit 8ea169
Packit 8ea169
            text = ""
Packit 8ea169
            for t in tblast:
Packit 8ea169
                text += "{0}:".format(t)
Packit 8ea169
Packit 8ea169
            text += "{0}\n{1}".format(extxt[0], "".join(elist))
Packit 8ea169
Packit 8ea169
            trace = tb
Packit 8ea169
            while trace.tb_next:
Packit 8ea169
                trace = trace.tb_next
Packit 8ea169
            frame = trace.tb_frame
Packit 8ea169
            text += ("\nLocal variables in innermost frame:\n")
Packit 8ea169
            try:
Packit 8ea169
                for (key, val) in frame.f_locals.items():
Packit 8ea169
                    text += "{0}: {1}\n".format(key, repr(val))
Packit 8ea169
            except:
Packit 8ea169
                pass
Packit 8ea169
        else:
Packit 8ea169
            text = "{0}\n\n{1}".format(value, "".join(elist))
Packit 8ea169
Packit 8ea169
        # Send data to the daemon
Packit 8ea169
        write_dump(text, tb)
Packit 8ea169
Packit 8ea169
    except:
Packit 8ea169
        # Silently ignore any error in this hook,
Packit 8ea169
        # to not interfere with other scripts
Packit 8ea169
        pass
Packit 8ea169
Packit 8ea169
    return sys.__excepthook__(etype, value, tb)
Packit 8ea169
Packit 8ea169
Packit 8ea169
def install_handler():
Packit 8ea169
    """
Packit 8ea169
    Install the exception handling function.
Packit 8ea169
    """
Packit 8ea169
    sys.excepthook = lambda etype, value, tb: \
Packit 8ea169
        handle_exception(etype, value, tb)
Packit 8ea169
Packit 8ea169
# install the exception handler when the abrt_exception_handler
Packit 8ea169
# module is imported
Packit 8ea169
try:
Packit 8ea169
    install_handler()
Packit 8ea169
except Exception as e:
Packit 8ea169
    pass
Packit 8ea169
Packit 8ea169
if __name__ == '__main__':
Packit 8ea169
    # test exception raised to show the effect
Packit 8ea169
    div0 = 1 / 0  # pylint: disable-msg=W0612
Packit 8ea169
    sys.exit(0)