Blob Blame History Raw
#!/usr/bin/python3 -u
# WARNING: python -u means unbuffered I/O. Without it the messages are
# passed to the parent asynchronously which looks bad in clients.
#
# This script wraps reporter-ureport client and keeps number of sent
# uReports to a server consistent with number of problem ocurrences.

import sys
import os
import getopt
import augeas

from report import dd_opendir, DD_FAIL_QUIETLY_ENOENT, run_event_on_problem_dir
from reportclient import set_verbosity, error_msg_and_die, error_msg, log1, log_warning

GETTEXT_PROGNAME = "abrt"
import locale
import gettext

_ = lambda x: gettext.gettext(x)

def init_gettext():
    try:
        locale.setlocale(locale.LC_ALL, "")
    except locale.Error:
        os.environ['LC_ALL'] = 'C'
        locale.setlocale(locale.LC_ALL, "")
    # Defeat "AttributeError: 'module' object has no attribute 'nl_langinfo'"
    try:
        gettext.bind_textdomain_codeset(GETTEXT_PROGNAME, locale.nl_langinfo(locale.CODESET))
    except AttributeError:
        pass
    gettext.bindtextdomain(GETTEXT_PROGNAME, '/usr/share/locale')
    gettext.textdomain(GETTEXT_PROGNAME)


def get_augeas(module, file_path):
    """
    A function for efficient configuration of Augeas.
    Augeas modules are placed in /usr/share/augeas/lenses/dist
    """

    aug_obj = augeas.Augeas(flags=augeas.Augeas.NO_MODL_AUTOLOAD)
    aug_obj.set("/augeas/load/{0}/lens".format(module), "{0}.lns".format(module))
    aug_obj.set("/augeas/load/{0}/incl".format(module), file_path)
    aug_obj.load()
    return aug_obj


def spawn_and_wait(prog, args=None):
    if args is None:
        args = [prog]
    else:
        args.insert(0, prog)

    try:
        return os.spawnvpe(os.P_WAIT, prog, args, os.environ)
    except OSError as err:
        error_msg(_("Unable to start '%s', error message was: '%s'"),
                    " ".join(args), err)
        return -1

def try_parse_number(dd, filename):
    try:
        n = dd.load_text(filename, DD_FAIL_QUIETLY_ENOENT)
        if n == "":
            return 0
        return int(n)
    except:
        error_msg(_("Not a number in file '%s'"), filename)
        return 0

def get_bugzilla_reports(reported_to):
    bugs = set()
    for line in reported_to.split("\n"):
        if line.startswith("Bugzilla:"):
            bugs.add(line)
    return bugs

def run_event(event_name, dump_dir_name):
    state, ret = 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)

if __name__ == "__main__":
    # localization
    init_gettext()

    verbose = 0
    ABRT_VERBOSE = os.getenv("ABRT_VERBOSE")
    if ABRT_VERBOSE:
        try:
            verbose = int(ABRT_VERBOSE)
        except:
            pass

    progname = os.path.basename(sys.argv[0])
    help_text = _("Usage: %s [-v]") % progname
    try:
        opts, args = getopt.getopt(sys.argv[1:], "vh", ["help"])
    except getopt.GetoptError as err:
        error_msg(err)  # prints something like "option -a not recognized"
        error_msg_and_die(help_text)

    for opt, arg in opts:
        if opt in ("-h", "--help"):
            print(help_text)
            sys.exit(0)
        if opt == "-v":
            verbose += 1

    set_verbosity(verbose)
    os.environ["ABRT_VERBOSE"] = str(verbose)

    # getcwd might fail if cwd was deleted
    try:
        dirname = os.getcwd()
    except OSError as err:
        error_msg_and_die(_("Unable to get current working directory as"
                            " it was probably deleted"))

    dd = dd_opendir(dirname, 0)
    if not dd:
        sys.exit(1)

    report_type = dd.load_text("type", DD_FAIL_QUIETLY_ENOENT)

    # because of backward compatibility
    if not report_type:
        report_type = dd.load_text("analyzer", 0)

    core_backtrace_exists = dd.exist("core_backtrace")
    packaged = dd.exist("package")
    reported_to = dd.load_text("reported_to", DD_FAIL_QUIETLY_ENOENT)
    ureports_counter = try_parse_number(dd, "ureports_counter")
    count = try_parse_number(dd, "count")
    dd.close()

    # Send only if the problem is not yet reported
    #           if the count file is corrupted or
    #           if the number of ureports is lower then the number of occurrences
    if ureports_counter != 0 and count != 0 and ureports_counter >= count:
        log1("uReport has been already sent: '%s'", dirname)

        if reported_to and reported_to != "":
            bugs = get_bugzilla_reports(reported_to)
            if bugs:
                log_warning(_("A bug was already filed about this problem:"))
                bugs = sorted(bugs)
                for bug in bugs:
                    print(bug)
                sys.exit(70)  # EXIT_STOP_EVENT_RUN
            log1("Bug for '%s' not yet filed. Continuing.", dirname)
            sys.exit(0)
        else:
            log1("'%s/reported_to' doesn't exist", dirname)

        log_warning(_("uReport was already sent, not sending it again"))
        sys.exit(0)

    if report_type == "CCpp" and not core_backtrace_exists:
        exitcode = spawn_and_wait("abrt-action-generate-core-backtrace")
        if exitcode != 0:
            log1("uReport can't be sent without core_backtrace. Exiting.")
            sys.exit(1)

    if not packaged:
        log1("Problem comes from unpackaged executable.")

        process_unpackaged_str = os.getenv("uReport_ProcessUnpackaged")
        if not process_unpackaged_str:
            augeas_conf = get_augeas("libreport", "/etc/libreport/plugins/ureport.conf")
            process_unpackaged_str = augeas_conf.get("/files/etc/libreport/plugins/ureport.conf/ProcessUnpackaged")
        process_unpackaged = False
        if process_unpackaged_str:
            process_unpackaged = process_unpackaged_str.lower() in ['yes', '1', 'on']

        if not process_unpackaged:
            log_warning(_("Problem comes from unpackaged executable. Unable to create uReport."))
            sys.exit(1)

    exitcode = spawn_and_wait("reporter-ureport")
    if exitcode == 0 or exitcode == 70:
        dd = dd_opendir(dirname, 0)
        if not dd:
            sys.exit(1)
        dd.save_text("ureports_counter", str(ureports_counter + 1))
        reported_to = dd.load_text("reported_to", DD_FAIL_QUIETLY_ENOENT)
        dd.close()

        watch = os.getenv("uReport_WatchReportedBugs") or ""
        if exitcode == 70 and watch.lower() in ["yes", "on", "1"]:
            if reported_to and reported_to != "" and get_bugzilla_reports(reported_to):
                log_warning(_("Adding you to CC List of the existing bugzilla bug"))
                run_event("watch_Bugzilla", dirname)

        email = os.getenv("uReport_ContactEmail")
        if not email:
            augeas_conf = get_augeas("libreport", "/etc/libreport/plugins/ureport.conf")
            email = augeas_conf.get("/files/etc/libreport/plugins/ureport.conf/ContactEmail")

        if email:
            log1("Attaching ContactEmail: " + email)
            spawn_and_wait("reporter-ureport", ["-A", "-E"])

        sys.exit(exitcode)
    else:
        log1(_("reporter-ureport failed with exit code %d" % exitcode))
        sys.exit(exitcode)