/*
* Copyright (C) 2014 ABRT team
* Copyright (C) 2014 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.
*/
#include <satyr/stacktrace.h>
#include <satyr/koops/stacktrace.h>
#include <satyr/thread.h>
#include <satyr/koops/frame.h>
#include <satyr/frame.h>
#include <satyr/normalize.h>
#include "oops-utils.h"
#include "libabrt.h"
int abrt_oops_process_list(GList *oops_list, const char *dump_location, const char *analyzer, int flags)
{
unsigned errors = 0;
int oops_cnt = g_list_length(oops_list);
if (oops_cnt != 0)
{
log_warning("Found oopses: %d", oops_cnt);
if ((flags & ABRT_OOPS_PRINT_STDOUT))
{
int i = 0;
while (i < oops_cnt)
{
char *kernel_bt = (char*)g_list_nth_data(oops_list, i++);
char *tainted_short = kernel_tainted_short(kernel_bt);
if (tainted_short)
log_warning("Kernel is tainted '%s'", tainted_short);
free(tainted_short);
printf("\nVersion: %s", kernel_bt);
}
}
if (dump_location != NULL)
{
log_warning("Creating problem directories");
errors = abrt_oops_create_dump_dirs(oops_list, dump_location, analyzer, flags);
if (errors)
log_warning("%d errors while dumping oopses", errors);
/*
* This marker in syslog file prevents us from
* re-parsing old oopses. The only problem is that we
* can't be sure here that the file we are watching
* is the same file where syslog(xxx) stuff ends up.
*/
syslog(LOG_WARNING,
"Reported %u kernel oopses to Abrt",
oops_cnt
);
}
}
/* If we are run by a log watcher, this delays log rescan
* (because log watcher waits to us to terminate)
* and possibly prevents dreaded "abrt storm".
*/
int unreported_cnt = oops_cnt - ABRT_OOPS_MAX_DUMPED_COUNT;
if (g_abrt_oops_sleep_woke_up_on_signal <= 0 &&
(unreported_cnt > 0 && (flags & ABRT_OOPS_THROTTLE_CREATION)))
{
/* Quadratic throttle time growth, but careful to not overflow in "n*n" */
int n = unreported_cnt > 30 ? 30 : unreported_cnt;
n = n * n;
if (n > 9)
log_warning(_("Sleeping for %d seconds"), n);
abrt_oops_signaled_sleep(n); /* max 15 mins */
}
return errors;
}
/* returns number of errors */
unsigned abrt_oops_create_dump_dirs(GList *oops_list, const char *dump_location, const char *analyzer, int flags)
{
const int oops_cnt = g_list_length(oops_list);
unsigned countdown = ABRT_OOPS_MAX_DUMPED_COUNT; /* do not report hundreds of oopses */
log_notice("Saving %u oopses as problem dirs", oops_cnt >= countdown ? countdown : oops_cnt);
char *cmdline_str = xmalloc_fopen_fgetline_fclose("/proc/cmdline");
char *fips_enabled = xmalloc_fopen_fgetline_fclose("/proc/sys/crypto/fips_enabled");
char *proc_modules = xmalloc_open_read_close("/proc/modules", /*maxsize:*/ NULL);
char *suspend_stats = xmalloc_open_read_close("/sys/kernel/debug/suspend_stats", /*maxsize:*/ NULL);
time_t t = time(NULL);
const char *iso_date = iso_date_string(&t);
pid_t my_pid = getpid();
unsigned idx = 0;
unsigned errors = 0;
while (idx < oops_cnt)
{
char base[sizeof("oops-YYYY-MM-DD-hh:mm:ss-%lu-%lu") + 2 * sizeof(long)*3];
sprintf(base, "oops-%s-%lu-%lu", iso_date, (long)my_pid, (long)idx);
char *path = concat_path_file(dump_location, base);
struct dump_dir *dd = dd_create(path, /*fs owner*/0, DEFAULT_DUMP_DIR_MODE);
if (dd)
{
dd_create_basic_files(dd, /*no uid*/(uid_t)-1L, NULL);
abrt_oops_save_data_in_dump_dir(dd, (char*)g_list_nth_data(oops_list, idx++), proc_modules);
dd_save_text(dd, FILENAME_ABRT_VERSION, VERSION);
dd_save_text(dd, FILENAME_ANALYZER, "abrt-oops");
dd_save_text(dd, FILENAME_TYPE, "Kerneloops");
if (cmdline_str)
dd_save_text(dd, FILENAME_CMDLINE, cmdline_str);
if (proc_modules)
dd_save_text(dd, "proc_modules", proc_modules);
if (fips_enabled && strcmp(fips_enabled, "0") != 0)
dd_save_text(dd, "fips_enabled", fips_enabled);
if (suspend_stats)
dd_save_text(dd, "suspend_stats", suspend_stats);
if ((flags & ABRT_OOPS_WORLD_READABLE))
dd_set_no_owner(dd);
dd_close(dd);
notify_new_path(path);
}
else
errors++;
free(path);
if (--countdown == 0)
break;
if (dd && (flags & ABRT_OOPS_THROTTLE_CREATION))
if (abrt_oops_signaled_sleep(1) > 0)
break;
}
free(cmdline_str);
free(proc_modules);
free(fips_enabled);
free(suspend_stats);
return errors;
}
static char *abrt_oops_list_of_tainted_modules(const char *proc_modules)
{
struct strbuf *result = strbuf_new();
const char *p = proc_modules;
for (;;)
{
const char *end = strchrnul(p, '\n');
const char *paren = strchrnul(p, '(');
/* We look for a line with this format:
* "kvm_intel 126289 0 - Live 0xf829e000 (taint_flags)"
* where taint_flags have letters
* (flags '+' and '-' indicate (un)loading, we must ignore them).
*/
while (++paren < end)
{
if ((unsigned)(toupper(*paren) - 'A') <= 'Z'-'A')
{
strbuf_append_strf(result, result->len == 0 ? "%.*s" : ",%.*s",
(int)(strchrnul(p,' ') - p), p
);
break;
}
if (*paren == ')')
break;
}
if (*end == '\0')
break;
p = end + 1;
}
if (result->len == 0)
{
strbuf_free(result);
return NULL;
}
return strbuf_free_nobuf(result);
}
void abrt_oops_save_data_in_dump_dir(struct dump_dir *dd, char *oops, const char *proc_modules)
{
char *first_line = oops;
char *second_line = (char*)strchr(first_line, '\n'); /* never NULL */
*second_line++ = '\0';
if (first_line[0])
dd_save_text(dd, FILENAME_KERNEL, first_line);
dd_save_text(dd, FILENAME_BACKTRACE, second_line);
/* save crash_function into dumpdir */
char *error_message = NULL;
struct sr_stacktrace *stacktrace = sr_stacktrace_parse(SR_REPORT_KERNELOOPS,
(const char *)second_line, &error_message);
if (stacktrace)
{
sr_normalize_koops_stacktrace((struct sr_koops_stacktrace *)stacktrace);
/* stacktrace is the same as thread, there is no need to check return value */
struct sr_thread *thread = sr_stacktrace_find_crash_thread(stacktrace);
struct sr_koops_frame *frame = (struct sr_koops_frame *)sr_thread_frames(thread);
if (frame && frame->function_name)
dd_save_text(dd, FILENAME_CRASH_FUNCTION, frame->function_name);
sr_stacktrace_free(stacktrace);
}
else
{
error_msg("Can't parse stacktrace: %s", error_message);
free(error_message);
}
/* check if trace doesn't have line: 'Your BIOS is broken' */
if (strstr(second_line, "Your BIOS is broken"))
dd_save_text(dd, FILENAME_NOT_REPORTABLE,
_("A kernel problem occurred because of broken BIOS. "
"Unfortunately, such problems are not fixable by kernel maintainers."));
/* check if trace doesn't have line: 'Your hardware is unsupported' */
else if (strstr(second_line, "Your hardware is unsupported"))
dd_save_text(dd, FILENAME_NOT_REPORTABLE,
_("A kernel problem occurred, but your hardware is unsupported, "
"therefore kernel maintainers are unable to fix this problem."));
else
{
char *tainted_short = kernel_tainted_short(second_line);
if (tainted_short)
{
log_notice("Kernel is tainted '%s'", tainted_short);
dd_save_text(dd, FILENAME_TAINTED_SHORT, tainted_short);
char *tnt_long = kernel_tainted_long(tainted_short);
dd_save_text(dd, FILENAME_TAINTED_LONG, tnt_long);
struct strbuf *reason = strbuf_new();
const char *fmt = _("A kernel problem occurred, but your kernel has been "
"tainted (flags:%s). Explanation:\n%s"
"Kernel maintainers are unable to diagnose tainted reports.");
strbuf_append_strf(reason, fmt, tainted_short, tnt_long);
char *modlist = !proc_modules ? NULL : abrt_oops_list_of_tainted_modules(proc_modules);
if (modlist)
{
strbuf_append_strf(reason, _(" Tainted modules: %s."), modlist);
free(modlist);
}
dd_save_text(dd, FILENAME_NOT_REPORTABLE, reason->buf);
strbuf_free(reason);
free(tainted_short);
free(tnt_long);
}
}
// TODO: add "Kernel oops: " prefix, so that all oopses have recognizable FILENAME_REASON?
// kernel oops 1st line may look quite puzzling otherwise...
char *reason_pretty = NULL;
char *error = NULL;
struct sr_stacktrace *trace = sr_stacktrace_parse(SR_REPORT_KERNELOOPS, second_line, &error);
if (trace)
{
reason_pretty = sr_stacktrace_get_reason(trace);
sr_stacktrace_free(trace);
}
else
free(error);
if (reason_pretty)
{
dd_save_text(dd, FILENAME_REASON, reason_pretty);
free(reason_pretty);
}
else
dd_save_text(dd, FILENAME_REASON, second_line);
}
int abrt_oops_signaled_sleep(int seconds)
{
sigset_t set;
sigemptyset(&set);
sigaddset(&set, SIGTERM);
sigaddset(&set, SIGINT);
sigaddset(&set, SIGHUP);
struct timespec timeout;
timeout.tv_sec = seconds;
timeout.tv_nsec = 0;
return g_abrt_oops_sleep_woke_up_on_signal = sigtimedwait(&set, NULL, &timeout);
}
char *abrt_oops_string_filter_regex(void)
{
map_string_t *settings = new_map_string();
load_abrt_plugin_conf_file("oops.conf", settings);
int only_fatal_mce = 1;
try_get_map_string_item_as_bool(settings, "OnlyFatalMCE", &only_fatal_mce);
free_map_string(settings);
if (only_fatal_mce)
return xstrdup("^Machine .*$");
return NULL;
}