Blame src/daemon/abrt-handle-event.c

Packit 8ea169
/*
Packit 8ea169
    Copyright (C) 2011  ABRT Team
Packit 8ea169
    Copyright (C) 2011  RedHat 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 along
Packit 8ea169
    with this program; if not, write to the Free Software Foundation, Inc.,
Packit 8ea169
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Packit 8ea169
*/
Packit 8ea169
#include <satyr/thread.h>
Packit 8ea169
#include <satyr/stacktrace.h>
Packit 8ea169
#include <satyr/distance.h>
Packit 8ea169
#include <satyr/abrt.h>
Packit 8ea169
Packit 8ea169
#include "libabrt.h"
Packit 8ea169
#include <libreport/run_event.h>
Packit 8ea169
Packit 8ea169
/* 70 % similarity */
Packit 8ea169
#define BACKTRACE_DUP_THRESHOLD 0.3
Packit 8ea169
Packit 8ea169
static char *uid = NULL;
Packit 8ea169
static char *uuid = NULL;
Packit 8ea169
static struct sr_stacktrace *corebt = NULL;
Packit 8ea169
static char *type = NULL;
Packit 8ea169
static char *executable = NULL;
Packit 8ea169
static char *crash_dump_dup_name = NULL;
Packit 8ea169
Packit 8ea169
static void dup_corebt_fini(void);
Packit 8ea169
Packit 8ea169
static char* load_backtrace(const struct dump_dir *dd)
Packit 8ea169
{
Packit 8ea169
    const char *filename = FILENAME_BACKTRACE;
Packit 8ea169
    if (strcmp(type, "CCpp") == 0)
Packit 8ea169
    {
Packit 8ea169
        filename = FILENAME_CORE_BACKTRACE;
Packit 8ea169
    }
Packit 8ea169
Packit 8ea169
    return dd_load_text_ext(dd, filename,
Packit 8ea169
        DD_FAIL_QUIETLY_ENOENT|DD_LOAD_TEXT_RETURN_NULL_ON_FAILURE);
Packit 8ea169
}
Packit 8ea169
Packit 8ea169
static int core_backtrace_is_duplicate(struct sr_stacktrace *bt1,
Packit 8ea169
                                       const char *bt2_text)
Packit 8ea169
{
Packit 8ea169
    struct sr_thread *thread1 = sr_stacktrace_find_crash_thread(bt1);
Packit 8ea169
Packit 8ea169
    if (thread1 == NULL)
Packit 8ea169
    {
Packit 8ea169
        log_notice("New stacktrace has no crash thread, disabling core stacktrace deduplicate");
Packit 8ea169
        dup_corebt_fini();
Packit 8ea169
        return 0;
Packit 8ea169
    }
Packit 8ea169
Packit 8ea169
    int result;
Packit 8ea169
    char *error_message;
Packit 8ea169
    struct sr_stacktrace *bt2 = sr_stacktrace_parse(sr_abrt_type_from_type(type),
Packit 8ea169
                                                    bt2_text, &error_message);
Packit 8ea169
    if (bt2 == NULL)
Packit 8ea169
    {
Packit 8ea169
        log_notice("Failed to parse backtrace, considering it not duplicate: %s", error_message);
Packit 8ea169
        free(error_message);
Packit 8ea169
        return 0;
Packit 8ea169
    }
Packit 8ea169
Packit 8ea169
    struct sr_thread *thread2 = sr_stacktrace_find_crash_thread(bt2);
Packit 8ea169
Packit 8ea169
    if (thread2 == NULL)
Packit 8ea169
    {
Packit 8ea169
        log_notice("Failed to get crash thread, considering it not duplicate");
Packit 8ea169
        result = 0;
Packit 8ea169
        goto end;
Packit 8ea169
    }
Packit 8ea169
Packit 8ea169
    int length2 = sr_thread_frame_count(thread2);
Packit 8ea169
Packit 8ea169
    if (length2 <= 0)
Packit 8ea169
    {
Packit 8ea169
        log_notice("Core backtrace has zero frames, considering it not duplicate");
Packit 8ea169
        result = 0;
Packit 8ea169
        goto end;
Packit 8ea169
    }
Packit 8ea169
Packit 8ea169
    float distance = sr_distance(SR_DISTANCE_DAMERAU_LEVENSHTEIN, thread1, thread2);
Packit 8ea169
    log_info("Distance between backtraces: %f", distance);
Packit 8ea169
    result = (distance <= BACKTRACE_DUP_THRESHOLD);
Packit 8ea169
Packit 8ea169
end:
Packit 8ea169
    sr_stacktrace_free(bt2);
Packit 8ea169
Packit 8ea169
    return result;
Packit 8ea169
}
Packit 8ea169
Packit 8ea169
static void dup_uuid_init(const struct dump_dir *dd)
Packit 8ea169
{
Packit 8ea169
    if (uuid)
Packit 8ea169
        return; /* we already loaded it, don't do it again */
Packit 8ea169
Packit 8ea169
    uuid = dd_load_text_ext(dd, FILENAME_UUID,
Packit 8ea169
                            DD_FAIL_QUIETLY_ENOENT + DD_LOAD_TEXT_RETURN_NULL_ON_FAILURE
Packit 8ea169
    );
Packit 8ea169
}
Packit 8ea169
Packit 8ea169
static int dup_uuid_compare(const struct dump_dir *dd)
Packit 8ea169
{
Packit 8ea169
    char *dd_uuid;
Packit 8ea169
    int different;
Packit 8ea169
Packit 8ea169
    if (!uuid)
Packit 8ea169
        return 0;
Packit 8ea169
Packit 8ea169
    /* don't do uuid-based check on crashes that have backtrace available (and
Packit 8ea169
     * nonempty)
Packit 8ea169
     * XXX: this relies on the fact that backtrace is created in the same event
Packit 8ea169
     * as UUID
Packit 8ea169
     */
Packit 8ea169
    if (corebt)
Packit 8ea169
        return 0;
Packit 8ea169
Packit 8ea169
    dd_uuid = dd_load_text_ext(dd, FILENAME_UUID, DD_FAIL_QUIETLY_ENOENT);
Packit 8ea169
    different = strcmp(uuid, dd_uuid);
Packit 8ea169
    free(dd_uuid);
Packit 8ea169
Packit 8ea169
    if (!different)
Packit 8ea169
        log_notice("Duplicate: UUID");
Packit 8ea169
Packit 8ea169
    return !different;
Packit 8ea169
}
Packit 8ea169
Packit 8ea169
static void dup_uuid_fini(void)
Packit 8ea169
{
Packit 8ea169
    free(uuid);
Packit 8ea169
    uuid = NULL;
Packit 8ea169
}
Packit 8ea169
Packit 8ea169
static void dup_corebt_init(const struct dump_dir *dd)
Packit 8ea169
{
Packit 8ea169
    if (corebt)
Packit 8ea169
        return; /* already loaded */
Packit 8ea169
Packit 8ea169
    char *corebt_text = load_backtrace(dd);
Packit 8ea169
    if (!corebt_text)
Packit 8ea169
        return; /* no backtrace */
Packit 8ea169
Packit 8ea169
    enum sr_report_type report_type = sr_abrt_type_from_type(type);
Packit 8ea169
    if (report_type == SR_REPORT_INVALID)
Packit 8ea169
    {
Packit 8ea169
        log_notice("Can't load stacktrace because of unsupported type: %s",
Packit 8ea169
                  type);
Packit 8ea169
        return;
Packit 8ea169
    }
Packit 8ea169
Packit 8ea169
    /* sr_stacktrace_parse moves the pointer */
Packit 8ea169
    char *error_message;
Packit 8ea169
    corebt = sr_stacktrace_parse(report_type, corebt_text, &error_message);
Packit 8ea169
    if (!corebt)
Packit 8ea169
    {
Packit 8ea169
        log_notice("Failed to load core stacktrace: %s", error_message);
Packit 8ea169
        free(error_message);
Packit 8ea169
    }
Packit 8ea169
Packit 8ea169
    free(corebt_text);
Packit 8ea169
}
Packit 8ea169
Packit 8ea169
static int dup_corebt_compare(const struct dump_dir *dd)
Packit 8ea169
{
Packit 8ea169
    if (!corebt)
Packit 8ea169
        return 0;
Packit 8ea169
Packit 8ea169
    int isdup;
Packit 8ea169
Packit 8ea169
    char *dd_corebt = load_backtrace(dd);
Packit 8ea169
    if (!dd_corebt)
Packit 8ea169
        return 0;
Packit 8ea169
Packit 8ea169
    isdup = core_backtrace_is_duplicate(corebt, dd_corebt);
Packit 8ea169
    free(dd_corebt);
Packit 8ea169
Packit 8ea169
    if (isdup)
Packit 8ea169
        log_notice("Duplicate: core backtrace");
Packit 8ea169
Packit 8ea169
    return isdup;
Packit 8ea169
}
Packit 8ea169
Packit 8ea169
static void dup_corebt_fini(void)
Packit 8ea169
{
Packit 8ea169
    sr_stacktrace_free(corebt);
Packit 8ea169
    corebt = NULL;
Packit 8ea169
}
Packit 8ea169
Packit 8ea169
/* This function is run after each post-create event is finished (there may be
Packit 8ea169
 * multiple such events).
Packit 8ea169
 *
Packit 8ea169
 * It first checks if there is CORE_BACKTRACE or UUID item in the dump dir
Packit 8ea169
 * we are processing.
Packit 8ea169
 *
Packit 8ea169
 * If there is a CORE_BACKTRACE, it iterates over all other dump
Packit 8ea169
 * directories and computes similarity to their core backtraces (if any).
Packit 8ea169
 * If one of them is similar enough to be considered duplicate, the function
Packit 8ea169
 * saves the path to the dump directory in question and returns 1 to indicate
Packit 8ea169
 * that we have indeed found a duplicate of currently processed dump directory.
Packit 8ea169
 * No more events are processed and program prints the path to the other
Packit 8ea169
 * directory and returns failure.
Packit 8ea169
 *
Packit 8ea169
 * If there is an UUID item (and no core backtrace), the function again
Packit 8ea169
 * iterates over all other dump directories and compares this UUID to their
Packit 8ea169
 * UUID. If there is a match, the path to the duplicate is saved and 1 is returned.
Packit 8ea169
 *
Packit 8ea169
 * If duplicate is not found as described above, the function returns 0 and we
Packit 8ea169
 * either process remaining events if there are any, or successfully terminate
Packit 8ea169
 * processing of the current dump directory.
Packit 8ea169
 */
Packit 8ea169
static int is_crash_a_dup(const char *dump_dir_name, void *param)
Packit 8ea169
{
Packit 8ea169
    int retval = 0; /* defaults to no dup found, "run_event, please continue iterating" */
Packit 8ea169
Packit 8ea169
    struct dump_dir *dd = dd_opendir(dump_dir_name, DD_OPEN_READONLY);
Packit 8ea169
    if (!dd)
Packit 8ea169
        return 0; /* wtf? (error, but will be handled elsewhere later) */
Packit 8ea169
    free(type);
Packit 8ea169
    type = dd_load_text(dd, FILENAME_TYPE);
Packit 8ea169
    free(executable);
Packit 8ea169
    executable = dd_load_text_ext(dd, FILENAME_EXECUTABLE, DD_FAIL_QUIETLY_ENOENT);
Packit 8ea169
    char *container_id = dd_load_text_ext(dd, FILENAME_CONTAINER_ID, DD_FAIL_QUIETLY_ENOENT);
Packit 8ea169
    dup_uuid_init(dd);
Packit 8ea169
    dup_corebt_init(dd);
Packit 8ea169
    dd_close(dd);
Packit 8ea169
Packit 8ea169
    /* dump_dir_name can be relative */
Packit 8ea169
    dump_dir_name = realpath(dump_dir_name, NULL);
Packit 8ea169
Packit 8ea169
    DIR *dir = opendir(g_settings_dump_location);
Packit 8ea169
    if (dir == NULL)
Packit 8ea169
        goto end;
Packit 8ea169
Packit 8ea169
    /* Scan crash dumps looking for a dup */
Packit 8ea169
    //TODO: explain why this is safe wrt concurrent runs
Packit 8ea169
    struct dirent *dent;
Packit 8ea169
    while ((dent = readdir(dir)) != NULL && crash_dump_dup_name == NULL)
Packit 8ea169
    {
Packit 8ea169
        if (dot_or_dotdot(dent->d_name))
Packit 8ea169
            continue; /* skip "." and ".." */
Packit 8ea169
        const char *ext = strrchr(dent->d_name, '.');
Packit 8ea169
        if (ext && strcmp(ext, ".new") == 0)
Packit 8ea169
            continue; /* skip anything named "<dirname>.new" */
Packit 8ea169
Packit 8ea169
        dd = NULL;
Packit 8ea169
Packit 8ea169
        char *tmp_concat_path = concat_path_file(g_settings_dump_location, dent->d_name);
Packit 8ea169
Packit 8ea169
        char *dump_dir_name2 = realpath(tmp_concat_path, NULL);
Packit 8ea169
        if (g_verbose > 1 && !dump_dir_name2)
Packit 8ea169
            perror_msg("realpath(%s)", tmp_concat_path);
Packit 8ea169
Packit 8ea169
        free(tmp_concat_path);
Packit 8ea169
Packit 8ea169
        if (!dump_dir_name2)
Packit 8ea169
            continue;
Packit 8ea169
Packit 8ea169
        char *dd_uid = NULL, *dd_type = NULL;
Packit 8ea169
        char *dd_executable = NULL, *dd_container_id = NULL;
Packit 8ea169
Packit 8ea169
        if (strcmp(dump_dir_name, dump_dir_name2) == 0)
Packit 8ea169
            goto next; /* we are never a dup of ourself */
Packit 8ea169
Packit 8ea169
        int sv_logmode = logmode;
Packit 8ea169
        /* Silently ignore any error in the silent log level. */
Packit 8ea169
        logmode = g_verbose == 0 ? 0 : sv_logmode;
Packit 8ea169
        dd = dd_opendir(dump_dir_name2, /*flags:*/ DD_FAIL_QUIETLY_ENOENT | DD_OPEN_READONLY);
Packit 8ea169
        logmode = sv_logmode;
Packit 8ea169
        if (!dd)
Packit 8ea169
            goto next;
Packit 8ea169
Packit 8ea169
        /* problems from different containers are not duplicates */
Packit 8ea169
        if (container_id != NULL)
Packit 8ea169
        {
Packit 8ea169
            dd_container_id = dd_load_text_ext(dd, FILENAME_CONTAINER_ID, DD_FAIL_QUIETLY_ENOENT);
Packit 8ea169
            if (dd_container_id != NULL && strcmp(container_id, dd_container_id) != 0)
Packit 8ea169
            {
Packit 8ea169
                goto next;
Packit 8ea169
            }
Packit 8ea169
        }
Packit 8ea169
Packit 8ea169
        /* crashes of different users are not considered duplicates */
Packit 8ea169
        dd_uid = dd_load_text_ext(dd, FILENAME_UID, DD_FAIL_QUIETLY_ENOENT);
Packit 8ea169
        if (strcmp(uid, dd_uid))
Packit 8ea169
        {
Packit 8ea169
            goto next;
Packit 8ea169
        }
Packit 8ea169
Packit 8ea169
        /* different crash types are not duplicates */
Packit 8ea169
        dd_type = dd_load_text_ext(dd, FILENAME_TYPE, DD_FAIL_QUIETLY_ENOENT);
Packit 8ea169
        if (strcmp(type, dd_type))
Packit 8ea169
        {
Packit 8ea169
            goto next;
Packit 8ea169
        }
Packit 8ea169
Packit 8ea169
        /* different executables are not duplicates */
Packit 8ea169
        dd_executable = dd_load_text_ext(dd, FILENAME_EXECUTABLE, DD_FAIL_QUIETLY_ENOENT);
Packit 8ea169
        if (     (executable != NULL && dd_executable == NULL)
Packit 8ea169
             ||  (executable == NULL && dd_executable != NULL)
Packit 8ea169
             || ((executable != NULL && dd_executable != NULL)
Packit 8ea169
                  && strcmp(executable, dd_executable) != 0))
Packit 8ea169
        {
Packit 8ea169
            goto next;
Packit 8ea169
        }
Packit 8ea169
Packit 8ea169
        if (dup_uuid_compare(dd)
Packit 8ea169
         || dup_corebt_compare(dd)
Packit 8ea169
        ) {
Packit 8ea169
            crash_dump_dup_name = dump_dir_name2;
Packit 8ea169
            dump_dir_name2 = NULL;
Packit 8ea169
            retval = 1; /* "run_event, please stop iterating" */
Packit 8ea169
            /* sonce crash_dump_dup_name != NULL now, we exit the loop */
Packit 8ea169
        }
Packit 8ea169
Packit 8ea169
next:
Packit 8ea169
        free(dump_dir_name2);
Packit 8ea169
        dd_close(dd);
Packit 8ea169
        free(dd_uid);
Packit 8ea169
        free(dd_type);
Packit 8ea169
        free(dd_container_id);
Packit 8ea169
    }
Packit 8ea169
    closedir(dir);
Packit 8ea169
Packit 8ea169
end:
Packit 8ea169
    free((char*)dump_dir_name);
Packit 8ea169
    free(container_id);
Packit 8ea169
    return retval;
Packit 8ea169
}
Packit 8ea169
Packit 8ea169
static char *do_log(char *log_line, void *param)
Packit 8ea169
{
Packit 8ea169
    /* We pipe output of events to our log.
Packit 8ea169
     * Otherwise, errors on post-create result in
Packit 8ea169
     * "Corrupted or bad dump DIR, deleting" without adequate explanation why.
Packit 8ea169
     */
Packit 8ea169
    log_warning("%s", log_line);
Packit 8ea169
    return log_line;
Packit 8ea169
}
Packit 8ea169
Packit 8ea169
int main(int argc, char **argv)
Packit 8ea169
{
Packit 8ea169
    /* I18n */
Packit 8ea169
    setlocale(LC_ALL, "");
Packit 8ea169
#if ENABLE_NLS
Packit 8ea169
    bindtextdomain(PACKAGE, LOCALEDIR);
Packit 8ea169
    textdomain(PACKAGE);
Packit 8ea169
#endif
Packit 8ea169
Packit 8ea169
    abrt_init(argv);
Packit 8ea169
Packit 8ea169
    const char *program_usage_string = _(
Packit 8ea169
        "& [-v -i -n INCREMENT] -e|--event EVENT DIR..."
Packit 8ea169
        );
Packit 8ea169
Packit 8ea169
    char *event_name = NULL;
Packit 8ea169
    int interactive = 0; /* must be _int_, OPT_BOOL expects that! */
Packit 8ea169
    int nice_incr = 0;
Packit 8ea169
Packit 8ea169
    struct options program_options[] = {
Packit 8ea169
        OPT__VERBOSE(&g_verbose),
Packit 8ea169
        OPT_STRING('e', "event" , &event_name, "EVENT",  _("Run EVENT on DIR")),
Packit 8ea169
        OPT_BOOL('i', "interactive" , &interactive, _("Communicate directly to the user")),
Packit 8ea169
        OPT_INTEGER('n',     "nice" , &nice_incr,   _("Increment the nice value by INCREMENT")),
Packit 8ea169
        OPT_END()
Packit 8ea169
    };
Packit 8ea169
Packit 8ea169
    parse_opts(argc, argv, program_options, program_usage_string);
Packit 8ea169
    argv += optind;
Packit 8ea169
    if (!*argv || !event_name)
Packit 8ea169
        show_usage_and_die(program_usage_string, program_options);
Packit 8ea169
Packit 8ea169
    load_abrt_conf();
Packit 8ea169
Packit 8ea169
    const char *const opt_env_nice = getenv("ABRT_EVENT_NICE");
Packit 8ea169
    if (opt_env_nice != NULL && opt_env_nice[0] != '\0')
Packit 8ea169
    {
Packit 8ea169
        log_debug("Using ABRT_EVENT_NICE=%s to increment the nice value", opt_env_nice);
Packit 8ea169
        nice_incr = xatoi(opt_env_nice);
Packit 8ea169
    }
Packit 8ea169
Packit 8ea169
    if (nice_incr != 0)
Packit 8ea169
    {
Packit 8ea169
        log_debug("Incrementing the nice value by %d", nice_incr);
Packit 8ea169
        const int ret = nice(nice_incr);
Packit 8ea169
        if (ret == -1)
Packit 8ea169
            perror_msg_and_die("Failed to increment the nice value");
Packit 8ea169
    }
Packit 8ea169
Packit 8ea169
    bool post_create = (strcmp(event_name, "post-create") == 0);
Packit 8ea169
    char *dump_dir_name = NULL;
Packit 8ea169
    while (*argv)
Packit 8ea169
    {
Packit 8ea169
        dump_dir_name = xstrdup(*argv++);
Packit 8ea169
        int i = strlen(dump_dir_name);
Packit 8ea169
        while (--i >= 0)
Packit 8ea169
            if (dump_dir_name[i] != '/')
Packit 8ea169
                break;
Packit 8ea169
        dump_dir_name[++i] = '\0';
Packit 8ea169
Packit 8ea169
        struct dump_dir *dd = dd_opendir(dump_dir_name, /*flags:*/ DD_OPEN_READONLY);
Packit 8ea169
        if (!dd)
Packit 8ea169
            return 1;
Packit 8ea169
Packit 8ea169
        uid = dd_load_text_ext(dd, FILENAME_UID, DD_FAIL_QUIETLY_ENOENT);
Packit 8ea169
        dd_close(dd);
Packit 8ea169
Packit 8ea169
        struct run_event_state *run_state = new_run_event_state();
Packit 8ea169
        if (!interactive)
Packit 8ea169
            make_run_event_state_forwarding(run_state);
Packit 8ea169
        run_state->logging_callback = do_log;
Packit 8ea169
        if (post_create)
Packit 8ea169
            run_state->post_run_callback = is_crash_a_dup;
Packit 8ea169
Packit 8ea169
        int r = run_event_on_dir_name(run_state, dump_dir_name, event_name);
Packit 8ea169
Packit 8ea169
        const bool no_action_for_event = (r == 0 && run_state->children_count == 0);
Packit 8ea169
Packit 8ea169
        free_run_event_state(run_state);
Packit 8ea169
        /* Needed only if is_crash_a_dup() was called, but harmless
Packit 8ea169
         * even if it wasn't:
Packit 8ea169
         */
Packit 8ea169
        dup_uuid_fini();
Packit 8ea169
        dup_corebt_fini();
Packit 8ea169
Packit 8ea169
        if (no_action_for_event)
Packit 8ea169
            error_msg_and_die("No actions are found for event '%s'", event_name);
Packit 8ea169
Packit 8ea169
//TODO: consider this case:
Packit 8ea169
// new dump is created, post-create detects that it is a dup,
Packit 8ea169
// but then load_crash_info(dup_name) *FAILS*.
Packit 8ea169
// In this case, we later delete damaged dup_name (right?)
Packit 8ea169
// but new dump never gets its FILENAME_COUNT set!
Packit 8ea169
Packit 8ea169
        /* Is crash a dup? (In this case, is_crash_a_dup() should have
Packit 8ea169
         * aborted "post-create" event processing as soon as it saw uuid
Packit 8ea169
         * and determined that there is another crash with same uuid.
Packit 8ea169
         * In this case it sets crash_dump_dup_name)
Packit 8ea169
         */
Packit 8ea169
        if (crash_dump_dup_name)
Packit 8ea169
            error_msg_and_die("DUP_OF_DIR: %s", crash_dump_dup_name);
Packit 8ea169
Packit 8ea169
        /* Was there error on one of processing steps in run_event? */
Packit 8ea169
        if (r != 0)
Packit 8ea169
            return r; /* yes */
Packit 8ea169
Packit 8ea169
        free(dump_dir_name);
Packit 8ea169
        dump_dir_name = NULL;
Packit 8ea169
    }
Packit 8ea169
Packit 8ea169
    /* exit 0 means, that there is no duplicate of dump-dir */
Packit 8ea169
    return 0;
Packit 8ea169
}