#!/usr/bin/python3 # 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, Fifth Floor, Boston, MA 02110-1301 USA. import os import sys from argparse import ArgumentParser from subprocess import call import dbus import dbus.lowlevel import problem import report from reportclient import (RETURN_OK, RETURN_FAILURE, RETURN_CANCEL_BY_USER, RETURN_STOP_EVENT_RUN, log1, set_verbosity) CD_DUMPDIR = "Directory" FILENAME_PACKAGE = "package" FILENAME_UID = "uid" FILENAME_UUID = "uuid" FILENAME_DUPHASH = "duphash" def run_event(event_name, dump_dir_name): ''' Run event with `event_name` on problem directory `dump_dir_name` ''' state, ret = report.run_event_on_problem_dir(dump_dir_name, event_name) if ret == 0 and state.children_count == 0: log1("Didn't find definition of event '%s'", event_name) return False return True def run_autoreport(problem_data, event_name): """Runs autoreporting event Requires CD_DUMPDIR key in problem_data. Keyword arguments: problem_data -- problem data of notified problems Returns None as it raises an exception on error Raises: KeyError -- if any of required elements is missing RuntimeError -- if event run fails """ dir_name = problem_data.get(CD_DUMPDIR) if dir_name is None: raise KeyError(CD_DUMPDIR) log1("Running autoreporting event: '{0}'".format(event_name)) res, ret = report.run_event_on_problem_dir(dir_name[0], event_name) if res.children_count == 0 and ret == 0: raise RuntimeError("No processing is specified for event '{0}'" .format(event_name)) if not ret in [RETURN_OK, RETURN_CANCEL_BY_USER, RETURN_STOP_EVENT_RUN]: raise RuntimeError("Event '{0}' exited with {1}" .format(event_name, ret)) def emit_crash_dbus_signal(problem_data): """Emits a Crash signal on D-Bus Problem bus Emits a signal with 5 members: package -- value of 'package' element in problem_data problem_id -- value of 'Directory' element in problem_data uid -- empty string if 'uid' element is not present in problem_data uuid -- empty string if 'uuid' element is not present in problem_data duphash -- empty string if 'duphash' element is not present in problem_data Keyword arguments: problem_data -- problem data of notified problems Returns None as it raises an exception on error Raises: RuntimeError -- for all D-Bus related errors KeyError -- if any of required elements is missing """ bus = None try: bus = dbus.SystemBus() msg = dbus.lowlevel.SignalMessage("/org/freedesktop/problems", "org.freedesktop.problems", "Crash") # List of tuples where the first member is element name and the second # member is a Boolean flag which is True if the element is required arguments = ((FILENAME_PACKAGE, False), (CD_DUMPDIR, True), (FILENAME_UID, False), (FILENAME_UUID, False), (FILENAME_DUPHASH, False)) for elem in arguments: itm = problem_data.get(elem[0]) if itm is None: if elem[1]: raise KeyError(elem[0]) msg.append("", signature="s") else: msg.append(itm[0], signature="s") bus.send_message(msg) except dbus.exceptions.DBusException as ex: raise RuntimeError("Failed to emit D-Bus Crash signal: {0}" .format(str(ex))) finally: if bus is not None: bus.close() def build_notification_problem_data(problem_dir): """Loads all necessary problem elements Keyword arguments: problem_dir -- an absolute file system path problem directory Returns an instance of report.problem_data Raises: ValueError -- if problem_dir is not an absolute path, if problem_dir cannot be opened and if any required problem element is missing. """ if not os.path.isabs(problem_dir): raise ValueError("problem directory must be absolute path") prblm_dt = report.problem_data() try: dump_dir = report.dd_opendir(problem_dir, report.DD_OPEN_READONLY) if not dump_dir: raise ValueError("cannot open problem directory") dd_load_flag = (report.DD_LOAD_TEXT_RETURN_NULL_ON_FAILURE | report.DD_FAIL_QUIETLY_ENOENT) pd_add_flag = report.CD_FLAG_TXT | report.CD_FLAG_ISNOTEDITABLE package = dump_dir.load_text(FILENAME_PACKAGE, dd_load_flag) if package: prblm_dt.add(FILENAME_PACKAGE, package, pd_add_flag) prblm_dt.add(CD_DUMPDIR, problem_dir, pd_add_flag) for element in (FILENAME_UID, FILENAME_UUID, FILENAME_DUPHASH): val = dump_dir.load_text(element, dd_load_flag) if val is not None: prblm_dt.add(element, val, pd_add_flag) finally: dump_dir.close() return prblm_dt if __name__ == "__main__": CMDARGS = ArgumentParser( description=("Announce a new or duplicated problem via" " all accessible channels"), epilog=("Reads the default configuration from 'abrt.conf' file")) CMDARGS.add_argument("-d", "--problem-dir", type=str, required=True, help="An absolute path to a new or duplicated problem directory") CMDARGS.add_argument("-v", "--verbose", action="count", dest="verbose", default=0, help="Be verbose") CMDARGS.add_argument("-a", "--autoreporting", action="store_true", dest="autoreporting", default=False, help="Force to run autoreporting event") CMDARGS.add_argument("-e", "--autoreporting-event", type=str, dest="autoreporting_event", help="Overwrite autoreporting event name") OPTIONS = CMDARGS.parse_args() DIR_PATH = OPTIONS.problem_dir verbose = 0 ABRT_VERBOSE = os.getenv("ABRT_VERBOSE") if ABRT_VERBOSE: try: verbose = int(ABRT_VERBOSE) except: pass verbose += OPTIONS.verbose set_verbosity(verbose) os.environ["ABRT_VERBOSE"] = str(verbose) try: conf = problem.load_conf_file("abrt.conf") except OSError as ex: sys.stderr.write("{0}".format(str(ex))) sys.exit(RETURN_FAILURE) try: PD = build_notification_problem_data(DIR_PATH) except ValueError as ex: sys.stderr.write("Cannot notify '{0}': {1}\n". format(DIR_PATH, str(ex))) sys.exit(RETURN_FAILURE) # The execution must continue because we should try to notify via all # configured channels. One of them might work properly. return_status = RETURN_OK try: emit_crash_dbus_signal(PD) except RuntimeError as ex: sys.stderr.write("Cannot notify '{0}' via D-Bus: {1}\n". format(DIR_PATH, str(ex))) return_status = RETURN_FAILURE except KeyError as ex: # this is a bug in build_notification_problem_data() sys.stderr.write("BUG: problem data misses required element '{0}'" " required for D-Bus notification\n" .format(str(ex))) return_status = RETURN_FAILURE if OPTIONS.autoreporting or conf.get("AutoreportingEnabled", "no") == "yes": event_name = OPTIONS.autoreporting_event if not event_name: if "AutoreportingEvent" in conf: event_name = conf["AutoreportingEvent"] else: sys.stderr.write("Autoreporting event is not configured\n") return_status = RETURN_FAILURE if event_name: try: run_autoreport(PD, event_name) except RuntimeError as ex: sys.stderr.write("Cannot notify '{0}' via uReport: {1}\n". format(DIR_PATH, str(ex))) return_status = RETURN_FAILURE except KeyError as ex: # this is a bug in build_notification_problem_data() sys.stderr.write( "BUG: problem data misses required element '{0}'" " required for uReport notification\n".format(str(ex))) return_status = RETURN_FAILURE # log problem to systemd journal with SYSLOG_IDENTIFIER=abrt-notification os.environ["REPORTER_JOURNAL_SYSLOG_ID"] = "abrt-notification" if not run_event("report_systemd-journal", DIR_PATH): # if report_systemd-journal is not defined, run 'reporter-systemd-journald' try: call(["reporter-systemd-journald", "-d " + DIR_PATH]) except FileNotFoundError as ex: # we expect the reporter doesn't have to be installed log1("reporter-systemd-journald was not found") sys.exit(return_status)