/* * 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 "libabrt.h" #include "abrt-journal.h" #define ABRT_JOURNAL_WATCH_STATE_FILE VAR_STATE"/abrt-dump-journal-core.state" /* * A journal message is a set of key value pairs in the following format: * FIELD_NAME=${binary data} * * A journal message contains many fields useful in syslog but ABRT doesn't * need all of them. So the following list defines mapping between journal * fields and ABRT problem items. * * ABRT goes through the list and for each item reads journal field called * 'item.name' and saves its contents in $DUMP_DIRECTORY/'item.file'. */ struct field_mapping { const char *name; const char *file; } fields [] = { { .name = "COREDUMP_EXE", .file = FILENAME_EXECUTABLE, }, { .name = "COREDUMP_CMDLINE", .file = FILENAME_CMDLINE, }, { .name = "COREDUMP_PROC_STATUS", .file = FILENAME_PROC_PID_STATUS, }, { .name = "COREDUMP_PROC_MAPS", .file = FILENAME_MAPS, }, { .name = "COREDUMP_PROC_LIMITS", .file = FILENAME_LIMITS, }, { .name = "COREDUMP_PROC_CGROUP", .file = FILENAME_CGROUP, }, { .name = "COREDUMP_ENVIRON", .file = FILENAME_ENVIRON, }, { .name = "COREDUMP_CWD", .file = FILENAME_PWD, }, { .name = "COREDUMP_ROOT", .file = FILENAME_ROOTDIR, }, { .name = "COREDUMP_OPEN_FDS", .file = FILENAME_OPEN_FDS, }, { .name = "COREDUMP_UID", .file = FILENAME_UID, }, //{ .name = "COREDUMP_GID", .file = FILENAME_GID, }, { .name = "COREDUMP_PID", .file = FILENAME_PID, }, { .name = "COREDUMP_PROC_MOUNTINFO", .file = FILENAME_MOUNTINFO, }, }; /* * Something like 'struct problem_data' but optimized for copying data from * journald to ABRT. * * 'struct problem_data' allocates a new memory for every single item and I * found that very inefficient in this case. * * The following structure holds data that we already retreived from journald * so we won't need to retrieve the data again. * * Why we retrieve data before we store them? Because we do some checking * before we start saving data in ABRT. We check whether the signal is one of * those we are interested in or whether the executable crashes too often to * ignore the current crash ... */ struct crash_info { abrt_journal_t *ci_journal; int ci_signal_no; const char *ci_signal_name; char *ci_executable_path; ///< /full/path/to/executable const char *ci_executable_name; ///< executable uid_t ci_uid; pid_t ci_pid; struct field_mapping *ci_mapping; size_t ci_mapping_items; }; /* * ABRT watch core configuration */ typedef struct { const char *awc_dump_location; int awc_throttle; } abrt_watch_core_conf_t; /* * A helper structured holding executable name and its last occurrence time. * * It is rather an array than a queue. Ii uses statically allocated array and * the head member points the next position for creating a new entry (the next * position may be already occupied and in such case the data shall be released). */ struct occurrence_queue { int oq_head; ///< the first empty index unsigned oq_size; ///< size of the queue struct last_occurrence { unsigned oqlc_stamp; char *oqlc_executable; } oq_occurrences[8]; } s_queue = { .oq_head = -1, .oq_size = 8, }; static unsigned abrt_journal_get_last_occurrence(const char *executable) { if (s_queue.oq_head < 0) return 0; unsigned index = s_queue.oq_head == 0 ? s_queue.oq_size - 1 : s_queue.oq_head - 1; for (unsigned i = 0; i < s_queue.oq_size; ++i) { if (s_queue.oq_occurrences[index].oqlc_executable == NULL) break; if (strcmp(executable, s_queue.oq_occurrences[index].oqlc_executable) == 0) return s_queue.oq_occurrences[index].oqlc_stamp; if (index-- == 0) index = s_queue.oq_size - 1; } return 0; } static void abrt_journal_update_occurrence(const char *executable, unsigned ts) { if (s_queue.oq_head < 0) s_queue.oq_head = 0; else { unsigned index = s_queue.oq_head == 0 ? s_queue.oq_size - 1 : s_queue.oq_head - 1; for (unsigned i = 0; i < s_queue.oq_size; ++i) { if (s_queue.oq_occurrences[index].oqlc_executable == NULL) break; if (strcmp(executable, s_queue.oq_occurrences[index].oqlc_executable) == 0) { /* Enhancement: move this entry right behind head */ s_queue.oq_occurrences[index].oqlc_stamp = ts; return; } if (index-- == 0) index = s_queue.oq_size - 1; } } s_queue.oq_occurrences[s_queue.oq_head].oqlc_stamp = ts; free(s_queue.oq_occurrences[s_queue.oq_head].oqlc_executable); s_queue.oq_occurrences[s_queue.oq_head].oqlc_executable = xstrdup(executable); if (++s_queue.oq_head >= s_queue.oq_size) s_queue.oq_head = 0; return; } /* * Converts a journal message into an intermediate ABRT problem (struct crash_info). * * Refuses to create the problem in the following cases: * - the crashed executable has 'abrt' prefix * - the signals is not fatal (see signal_is_fatal()) * - the journal message misses one of the following fields * - COREDUMP_SIGNAL * - COREDUMP_EXE * - COREDUMP_UID * - COREDUMP_PROC_STATUS * - if any data does not have an expected format */ static int abrt_journal_core_retrieve_information(abrt_journal_t *journal, struct crash_info *info) { if (abrt_journal_get_int_field(journal, "COREDUMP_SIGNAL", &(info->ci_signal_no)) != 0) { log_info("Failed to get signal number from journal message"); return -EINVAL; } if (!signal_is_fatal(info->ci_signal_no, &(info->ci_signal_name))) { log_info("Signal '%d' is not fatal: ignoring crash", info->ci_signal_no); return 1; } info->ci_executable_path = abrt_journal_get_string_field(journal, "COREDUMP_EXE", NULL); if (info->ci_executable_path == NULL) { log_notice("Could not get crashed 'executable'."); return -ENOENT; } info->ci_executable_name = strrchr(info->ci_executable_path, '/'); if (info->ci_executable_name == NULL) { info->ci_executable_name = info->ci_executable_path; } else if(strncmp(++(info->ci_executable_name), "abrt", 4) == 0) { error_msg("Ignoring crash of ABRT executable '%s'", info->ci_executable_path); return 1; } if (abrt_journal_get_unsigned_field(journal, "COREDUMP_UID", &(info->ci_uid))) { log_info("Failed to get UID from journal message"); return -EINVAL; } /* This is not fatal, the pid is used only in dumpdir name */ if (abrt_journal_get_int_field(journal, "COREDUMP_PID", &(info->ci_pid))) { log_notice("Failed to get PID from journal message."); info->ci_pid = getpid(); } char *proc_status = abrt_journal_get_string_field(journal, "COREDUMP_PROC_STATUS", NULL); if (proc_status == NULL) { log_info("Failed to get /proc/[pid]/status from journal message"); return -ENOENT; } uid_t tmp_fsuid = get_fsuid(proc_status); if (tmp_fsuid < 0) return -EINVAL; if (tmp_fsuid != info->ci_uid) { /* use root for suided apps unless it's explicitly set to UNSAFE */ info->ci_uid = (dump_suid_policy() != DUMP_SUID_UNSAFE) ? 0 : tmp_fsuid; } return 0; } /* * Initializes ABRT problem directory and save the relevant journal message * fileds in that directory. */ static int save_systemd_coredump_in_dump_directory(struct dump_dir *dd, struct crash_info *info) { char coredump_path[PATH_MAX + 1] = { '\0' }; if (coredump_path != abrt_journal_get_string_field(info->ci_journal, "COREDUMP_FILENAME", coredump_path)) log_debug("Processing coredumpctl entry without a real file"); const size_t len = strlen(coredump_path); if ( (len >= 3 && coredump_path[len - 3] == '.' && coredump_path[len - 2] == 'x' && coredump_path[len - 1] == 'z') || (len >= 4 && coredump_path[len - 4] == '.' && coredump_path[len - 3] == 'l' && coredump_path[len - 2] == 'z' && coredump_path[len - 1] == '4')) { if (dd_copy_file_unpack(dd, FILENAME_COREDUMP, coredump_path)) return -1; } else if (len > 0) { if (dd_copy_file(dd, FILENAME_COREDUMP, coredump_path)) return -1; } else { const char *data = NULL; size_t data_len = 0; int r = abrt_journal_get_field(info->ci_journal, "COREDUMP", (const void **)&data, &data_len); if (r < 0) { log_info("Ignoring coredumpctl entry without core dump file."); return -1; } dd_save_binary(dd, FILENAME_COREDUMP, data, data_len); } dd_save_text(dd, FILENAME_ABRT_VERSION, VERSION); dd_save_text(dd, FILENAME_TYPE, "CCpp"); dd_save_text(dd, FILENAME_ANALYZER, "abrt-journal-core"); char *reason; if (info->ci_signal_name == NULL) reason = xasprintf("%s killed by signal %d", info->ci_executable_name, info->ci_signal_no); else reason = xasprintf("%s killed by SIG%s", info->ci_executable_name, info->ci_signal_name); dd_save_text(dd, FILENAME_REASON, reason); free(reason); char *cursor = NULL; if (abrt_journal_get_cursor(info->ci_journal, &cursor) == 0) dd_save_text(dd, "journald_cursor", cursor); free(cursor); const char *data = NULL; size_t data_len = 0; if (!abrt_journal_get_field(info->ci_journal, "COREDUMP_CONTAINER_CMDLINE", (const void **)&data, &data_len)) { dd_save_binary(dd, FILENAME_CONTAINER_CMDLINE, data, data_len); } for (size_t i = 0; i < info->ci_mapping_items; ++i) { const char *data; size_t data_len; struct field_mapping *f = info->ci_mapping + i; if (abrt_journal_get_field(info->ci_journal, f->name, (const void **)&data, &data_len)) { log_info("systemd-coredump journald message misses field: '%s'", f->name); continue; } dd_save_binary(dd, f->file, data, data_len); } return 0; } static int abrt_journal_core_to_abrt_problem(struct crash_info *info, const char *dump_location) { struct dump_dir *dd = create_dump_dir_ext(dump_location, "ccpp", info->ci_pid, /*fs owner*/0, (save_data_call_back)save_systemd_coredump_in_dump_directory, info); if (dd != NULL) { char *path = xstrdup(dd->dd_dirname); dd_close(dd); notify_new_path(path); log_debug("ABRT daemon has been notified about directory: '%s'", path); free(path); } return dd == NULL; } /* * Creates an abrt problem from a journal message */ static int abrt_journal_dump_core(abrt_journal_t *journal, const char *dump_location) { struct crash_info info = { 0 }; info.ci_journal = journal; info.ci_mapping = fields; info.ci_mapping_items = sizeof(fields)/sizeof(*fields); /* Compatibility hack, a watch's callback gets the journal already moved * to a next message. */ abrt_journal_next(journal); /* This the watch call back mentioned in the comment above. We use the * following function also in abrt_journal_watch_cores(). */ int r = abrt_journal_core_retrieve_information(journal, &info); if (r != 0) { if (r < 0) error_msg(_("Failed to obtain all required information from journald")); goto dump_cleanup; } r = abrt_journal_core_to_abrt_problem(&info, dump_location); dump_cleanup: if (info.ci_executable_path != NULL) free(info.ci_executable_path); return r; } /* * A function called when a new journal core is detected. * * The function retrieves information from journal, checks the last occurrence * time of the crashed executable and if there was no recent occurrence creates * an ABRT problem from the journal message. Finally updates the last occurrence * time. */ static void abrt_journal_watch_cores(abrt_journal_watch_t *watch, void *user_data) { const abrt_watch_core_conf_t *conf = (const abrt_watch_core_conf_t *)user_data; struct crash_info info = { 0 }; info.ci_journal = abrt_journal_watch_get_journal(watch); info.ci_mapping = fields; info.ci_mapping_items = sizeof(fields)/sizeof(*fields); int r = abrt_journal_core_retrieve_information(abrt_journal_watch_get_journal(watch), &info); if (r) { if (r < 0) error_msg(_("Failed to obtain all required information from journald")); goto watch_cleanup; } // do not dump too often // ignore crashes of a single executable appearing in THROTTLE s (keep last 10 executable) const unsigned current = time(NULL); const unsigned last = abrt_journal_get_last_occurrence(info.ci_executable_path); if (current < last) { error_msg("BUG: current time stamp lower than an old one"); if (g_verbose > 2) abort(); goto watch_cleanup; } const unsigned sub = current - last; if (sub < conf->awc_throttle) { /* We don't want to update the counter here. */ error_msg(_("Not saving repeating crash after %ds (limit is %ds)"), sub, conf->awc_throttle); goto watch_cleanup; } if (abrt_journal_core_to_abrt_problem(&info, conf->awc_dump_location)) { error_msg(_("Failed to save detect problem data in abrt database")); goto watch_cleanup; } abrt_journal_update_occurrence(info.ci_executable_path, current); watch_cleanup: abrt_journal_save_current_position(info.ci_journal, ABRT_JOURNAL_WATCH_STATE_FILE); if (info.ci_executable_path != NULL) free(info.ci_executable_path); return; } static void watch_journald(abrt_journal_t *journal, abrt_watch_core_conf_t *conf) { abrt_journal_watch_t *watch = NULL; if (abrt_journal_watch_new(&watch, journal, abrt_journal_watch_cores, (void *)conf) < 0) error_msg_and_die(_("Failed to initialize systemd-journal watch")); abrt_journal_watch_run_sync(watch); abrt_journal_watch_free(watch); } int main(int argc, char *argv[]) { /* I18n */ setlocale(LC_ALL, ""); #if ENABLE_NLS bindtextdomain(PACKAGE, LOCALEDIR); textdomain(PACKAGE); #endif abrt_init(argv); /* Can't keep these strings/structs static: _() doesn't support that */ const char *program_usage_string = _( "& [-vsf] [-e]/[-c CURSOR] [-t INT]/[-T] [-d DIR]/[-D]\n" "\n" "Extract coredumps from systemd-journal\n" "\n" "-c and -e options conflicts because both specifies the first read message.\n" "\n" "-e is useful only for -f because the following of journal starts by reading \n" "the entire journal if the last seen possition is not available.\n" "\n" "The last seen position is saved in "ABRT_JOURNAL_WATCH_STATE_FILE"\n" ); enum { OPT_v = 1 << 0, OPT_s = 1 << 1, OPT_d = 1 << 2, OPT_D = 1 << 3, OPT_c = 1 << 4, OPT_e = 1 << 5, OPT_t = 1 << 6, OPT_T = 1 << 7, OPT_f = 1 << 8, }; char *cursor = NULL; char *dump_location = NULL; int throttle = 0; /* Keep enum above and order of options below in sync! */ struct options program_options[] = { OPT__VERBOSE(&g_verbose), OPT_BOOL( 's', NULL, NULL, _("Log to syslog")), OPT_STRING('d', NULL, &dump_location, "DIR", _("Create new problem directory in DIR for every coredump")), OPT_BOOL( 'D', NULL, NULL, _("Same as -d DumpLocation, DumpLocation is specified in abrt.conf")), OPT_STRING('c', NULL, &cursor, "CURSOR", _("Start reading systemd-journal from the CURSOR position")), OPT_BOOL( 'e', NULL, NULL, _("Start reading systemd-journal from the end")), OPT_INTEGER('t', NULL, &throttle, _("Throttle problem directory creation to 1 per INT second")), OPT_BOOL( 'T', NULL, NULL, _("Same as -t INT, INT is specified in plugins/CCpp.conf")), OPT_BOOL( 'f', NULL, NULL, _("Follow systemd-journal from the last seen position (if available)")), OPT_END() }; unsigned opts = parse_opts(argc, argv, program_options, program_usage_string); export_abrt_envvars(0); if ((opts & OPT_s) || getenv("ABRT_SYSLOG")) logmode = LOGMODE_JOURNAL; if ((opts & OPT_c) && (opts & OPT_e)) error_msg_and_die(_("You need to specify either -c CURSOR or -e")); /* Initialize ABRT configuration */ load_abrt_conf(); if (opts & OPT_D) { if (opts & OPT_d) show_usage_and_die(program_usage_string, program_options); dump_location = g_settings_dump_location; } { /* Load CCpp.conf */ map_string_t *settings = new_map_string(); load_abrt_plugin_conf_file("CCpp.conf", settings); const char *value; /* The following commented out lines are not supported by * abrt-dump-journal-core but are supported by abrt-hook-ccpp */ //value = get_map_string_item_or_NULL(settings, "MakeCompatCore"); //setting_MakeCompatCore = value && string_to_bool(value); //value = get_map_string_item_or_NULL(settings, "SaveBinaryImage"); //setting_SaveBinaryImage = value && string_to_bool(value); //value = get_map_string_item_or_NULL(settings, "SaveFullCore"); //setting_SaveFullCore = value ? string_to_bool(value) : true; value = get_map_string_item_or_NULL(settings, "VerboseLog"); if (value) g_verbose = xatoi_positive(value); free_map_string(settings); } /* systemd-coredump creates journal messages with SYSLOG_IDENTIFIER equals * 'systemd-coredump' and we are interested only in the systemd-coredump * messages. * * Of cores, it is possible to override this when need while debugging. */ const char *const env_journal_filter = getenv("ABRT_DUMP_JOURNAL_CORE_DEBUG_FILTER"); GList *coredump_journal_filter = NULL; coredump_journal_filter = g_list_append(coredump_journal_filter, (env_journal_filter ? (gpointer)env_journal_filter : (gpointer)"SYSLOG_IDENTIFIER=systemd-coredump")); abrt_journal_t *journal = NULL; if (abrt_journal_new(&journal)) error_msg_and_die(_("Cannot open systemd-journal")); if (abrt_journal_set_journal_filter(journal, coredump_journal_filter) < 0) error_msg_and_die(_("Cannot filter systemd-journal to systemd-coredump data only")); g_list_free(coredump_journal_filter); if ((opts & OPT_e) && abrt_journal_seek_tail(journal) < 0) error_msg_and_die(_("Cannot seek to the end of journal")); if (cursor && abrt_journal_set_cursor(journal, cursor)) error_msg_and_die(_("Failed to set systemd-journal cursor '%s'"), cursor); if ((opts & OPT_f)) { if (!cursor && !(opts & OPT_e)) { abrt_journal_restore_position(journal, ABRT_JOURNAL_WATCH_STATE_FILE); /* The stored position has already been seen, so move to the next one. */ abrt_journal_next(journal); } abrt_watch_core_conf_t conf = { .awc_dump_location = dump_location, .awc_throttle = throttle, }; watch_journald(journal, &conf); abrt_journal_save_current_position(journal, ABRT_JOURNAL_WATCH_STATE_FILE); } else abrt_journal_dump_core(journal, dump_location); abrt_journal_free(journal); free_abrt_conf_data(); return EXIT_SUCCESS; }