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