#:mode=python: # -*- coding: utf-8 -*- ## Copyright (C) 2014 Red Hat, Inc. ## 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 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 send(data): """Send data to abrtd""" response = "" try: import socket s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) s.settimeout(5) s.connect(@VAR_RUN@ + "/abrt/abrt.socket") pre = "POST / HTTP/1.1\r\n\r\n" pre += "type=Python3\0" pre += "analyzer=abrt-python3-handler\0" s.sendall(pre.encode()) s.sendall(data.encode()) s.shutdown(socket.SHUT_WR) while True: buf = s.recv(256) if not buf: break response += buf.decode() s.close() except socket.timeout as ex: syslog("communication with ABRT daemon failed: {0}".format(ex)) except Exception as ex: syslog("can't communicate with ABRT daemon, is it running? {0}" .format(ex)) return response 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] data = "pid={0}\0".format(os.getpid()) data += "executable={0}\0".format(executable) data += "reason={0}\0".format(tb_text.splitlines()[0]) data += "backtrace={0}\0".format(tb_text) response = send(data) 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: {0}".format(response)) def require_abs_path(): """ Return True if absolute path requirement is enabled in configuration """ import problem try: conf = problem.load_plugin_conf_file("python3.conf") except OsError: return False return conf.get("RequireAbsolutePath", "yes") == "yes" def handle_exception(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 {0} ...)".format(sys.argv[0]) syslog("detected unhandled Python exception in 'interactive mode{0}'" .format(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 '{0}'" .format(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 is not None and etype != IndentationError: tblast = traceback.extract_tb(tb, limit=None) if tblast: tblast = tuple(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 text = "" for t in tblast: text += "{0}:".format(t) text += "{0}\n{1}".format(extxt[0], "".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 += "{0}: {1}\n".format(key, repr(val)) except: pass else: text = "{0}\n\n{1}".format(value, "".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 install_handler(): """ Install the exception handling function. """ sys.excepthook = lambda etype, value, tb: \ handle_exception(etype, value, tb) # install the exception handler when the abrt_exception_handler # module is imported try: install_handler() except Exception as e: pass if __name__ == '__main__': # test exception raised to show the effect div0 = 1 / 0 # pylint: disable-msg=W0612 sys.exit(0)