void ignored_problems_remove(ignored_problems_t *set, const char *problem_id) { char *uuid = NULL; char *duphash = NULL; struct dump_dir *dd = dd_opendir(problem_id, IGN_DD_OPEN_FLAGS); if (dd) { uuid = dd_load_text_ext(dd, FILENAME_UUID, IGN_DD_LOAD_TEXT_FLAGS); duphash = dd_load_text_ext(dd, FILENAME_DUPHASH, IGN_DD_LOAD_TEXT_FLAGS); dd_close(dd); } else { /* We do not consider this as an error because the directory can be * deleted by other programs. This code expects that dd_opendir() * already emitted good explanatory message. This message * explains what the previous failure causes. */ VERB1 error_msg("Can't get UUID/DUPHASH from" " '%s' to remove it from the ignored problems:" " can't open the problem", problem_id); } ignored_problems_remove_row(set, problem_id, uuid, duphash); free(duphash); free(uuid); }
bool ignored_problems_contains(ignored_problems_t *set, const char *problem_id) { struct dump_dir *dd = dd_opendir(problem_id, IGN_DD_OPEN_FLAGS); if (!dd) { /* We do not consider this as an error because the directory can be * deleted by other programs. This code expects that dd_opendir() * already emitted good and explanatory message. This message attempts * to explain what the previous failure causes. */ VERB1 error_msg("Can't open '%s'." " Won't try to check whether it belongs to ignored problems", problem_id); return false; } char *uuid = dd_load_text_ext(dd, FILENAME_UUID, IGN_DD_LOAD_TEXT_FLAGS); char *duphash = dd_load_text_ext(dd, FILENAME_DUPHASH, IGN_DD_LOAD_TEXT_FLAGS); dd_close(dd); log_notice("Going to check if problem '%s' is in ignored problems '%s'", problem_id, set->ign_set_file_path); bool found = ignored_problems_file_contains(set, problem_id, uuid, duphash, /* (FILE **) */NULL, "r"); free(duphash); free(uuid); return found; }
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_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 ); }
int main(int argc, char **argv) { /* I18n */ setlocale(LC_ALL, ""); #if ENABLE_NLS bindtextdomain(PACKAGE, LOCALEDIR); textdomain(PACKAGE); #endif abrt_init(argv); const char *dump_dir_name = "."; const char *root_dir = NULL; /* Can't keep these strings/structs static: _() doesn't support that */ const char *program_usage_string = _( "& [-v] -d DIR\n" "\n" "Save container metadata" ); enum { OPT_v = 1 << 0, OPT_d = 1 << 1, }; /* Keep enum above and order of options below in sync! */ struct options program_options[] = { OPT__VERBOSE(&g_verbose), OPT_STRING('d', NULL, &dump_dir_name, "DIR" , _("Problem directory")), OPT_STRING('r', NULL, &root_dir, "ROOTDIR" , _("Root directory for running container commands")), OPT_END() }; /*unsigned opts =*/ parse_opts(argc, argv, program_options, program_usage_string); export_abrt_envvars(0); struct dump_dir *dd = dd_opendir(dump_dir_name, /* for writing */0); if (dd == NULL) xfunc_die(); char *container_cmdline = dd_load_text_ext(dd, FILENAME_CONTAINER_CMDLINE, DD_LOAD_TEXT_RETURN_NULL_ON_FAILURE); if (container_cmdline == NULL) error_msg_and_die("The crash didn't occur in container"); if (strstr("/docker ", container_cmdline) == 0) dump_docker_info(dd, root_dir); else if (strstr("/lxc-", container_cmdline) == 0) dump_lxc_info(dd, container_cmdline); else error_msg_and_die("Unsupported container technology"); free(container_cmdline); dd_close(dd); return 0; }
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); }
void ignored_problems_add(ignored_problems_t *set, const char *problem_id) { struct dump_dir *dd = dd_opendir(problem_id, IGN_DD_OPEN_FLAGS); if (!dd) { /* We do not consider this as an error because the directory can be * deleted by other programs. This code expects that dd_opendir() * already emitted good explanatory message. This message * explains what the previous failure causes. */ VERB1 log("Can't add problem '%s' to ignored problems:" " can't open the problem", problem_id); return; } char *uuid = dd_load_text_ext(dd, FILENAME_UUID, IGN_DD_LOAD_TEXT_FLAGS); char *duphash = dd_load_text_ext(dd, FILENAME_DUPHASH, IGN_DD_LOAD_TEXT_FLAGS); dd_close(dd); ignored_problems_add_row(set, problem_id, uuid, duphash); free(duphash); free(uuid); }
/* Remove dump dir */ int DeleteDebugDump(const char *dump_dir_name, long caller_uid) { /* If doesn't start with "DEBUG_DUMPS_DIR/"... */ if (strncmp(dump_dir_name, DEBUG_DUMPS_DIR"/", strlen(DEBUG_DUMPS_DIR"/")) != 0 /* or contains "/." anywhere (-> might contain ".." component) */ || strstr(dump_dir_name + strlen(DEBUG_DUMPS_DIR), "/.") ) { /* Then refuse to operate on it (someone is attacking us??) */ error_msg("Bad dump directory name '%s', not deleting", dump_dir_name); return MW_ERROR; } struct dump_dir *dd = dd_opendir(dump_dir_name, /*flags:*/ 0); if (!dd) return MW_NOENT_ERROR; if (caller_uid != 0) /* not called by root */ { char caller_uid_str[sizeof(long) * 3 + 2]; sprintf(caller_uid_str, "%ld", caller_uid); char *uid = dd_load_text_ext(dd, FILENAME_UID, DD_FAIL_QUIETLY_ENOENT | DD_LOAD_TEXT_RETURN_NULL_ON_FAILURE); /* we assume that the dump_dir can be handled by everyone if uid == NULL * e.g: kerneloops */ if (uid != NULL) { bool uid_matches = (strcmp(uid, caller_uid_str) == 0); free(uid); if (!uid_matches) { dd_close(dd); error_msg("Dump directory '%s' can't be accessed by user with uid %ld", dump_dir_name, caller_uid); return 1; } } } dd_delete(dd); return 0; /* success */ }
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 = _( "& [options] -d DIR\n" "\n" "Analyzes C/C++ backtrace, generates duplication hash, backtrace rating,\n" "and identifies crash function in problem directory DIR" ); enum { OPT_v = 1 << 0, OPT_d = 1 << 1 }; /* Keep enum above and order of options below in sync! */ struct options program_options[] = { OPT__VERBOSE(&g_verbose), OPT_STRING('d', NULL, &dump_dir_name, "DIR", _("Problem directory")), OPT_END() }; /*unsigned opts =*/ parse_opts(argc, argv, program_options, program_usage_string); export_abrt_envvars(0); struct dump_dir *dd = dd_opendir(dump_dir_name, /*flags:*/ 0); if (!dd) return 1; char *component = dd_load_text(dd, FILENAME_COMPONENT); /* Read backtrace */ char *backtrace_str = dd_load_text_ext(dd, FILENAME_BACKTRACE, DD_LOAD_TEXT_RETURN_NULL_ON_FAILURE); if (!backtrace_str) { dd_close(dd); return 1; } /* Compute backtrace hash */ struct sr_location location; sr_location_init(&location); const char *backtrace_str_ptr = backtrace_str; struct sr_gdb_stacktrace *backtrace = sr_gdb_stacktrace_parse(&backtrace_str_ptr, &location); free(backtrace_str); /* Store backtrace hash */ if (!backtrace) { /* * The parser failed. Compute the duphash from the executable * instead of a backtrace. * and component only. This is not supposed to happen often. */ log(_("Backtrace parsing failed for %s"), dump_dir_name); log("%d:%d: %s", location.line, location.column, location.message); struct strbuf *emptybt = strbuf_new(); char *executable = dd_load_text(dd, FILENAME_EXECUTABLE); strbuf_prepend_str(emptybt, executable); free(executable); strbuf_prepend_str(emptybt, component); log_debug("Generating duphash: %s", emptybt->buf); char hash_str[SHA1_RESULT_LEN*2 + 1]; str_to_sha1str(hash_str, emptybt->buf); dd_save_text(dd, FILENAME_DUPHASH, hash_str); /* * Other parts of ABRT assume that if no rating is available, * it is ok to allow reporting of the bug. To be sure no bad * backtrace is reported, rate the backtrace with the lowest * rating. */ dd_save_text(dd, FILENAME_RATING, "0"); strbuf_free(emptybt); free(component); dd_close(dd); /* Report success even if the parser failed, as the backtrace * has been created and rated. The failure is caused by a flaw * in the parser, not in the backtrace. */ return 0; } /* Compute duplication hash. */ struct sr_thread *crash_thread = (struct sr_thread *)sr_gdb_stacktrace_find_crash_thread(backtrace); if (crash_thread) { char *hash_str; if (g_verbose >= 3) { hash_str = sr_thread_get_duphash(crash_thread, 3, component, SR_DUPHASH_NOHASH); log("Generating duphash: %s", hash_str); free(hash_str); } hash_str = sr_thread_get_duphash(crash_thread, 3, component, SR_DUPHASH_NORMAL); dd_save_text(dd, FILENAME_DUPHASH, hash_str); free(hash_str); } else log(_("Crash thread not found")); /* Compute the backtrace rating. */ float quality = sr_gdb_stacktrace_quality_complex(backtrace); const char *rating; if (quality < 0.6f) rating = "0"; else if (quality < 0.7f) rating = "1"; else if (quality < 0.8f) rating = "2"; else if (quality < 0.9f) rating = "3"; else rating = "4"; dd_save_text(dd, FILENAME_RATING, rating); /* Get the function name from the crash frame. */ struct sr_gdb_frame *crash_frame = sr_gdb_stacktrace_get_crash_frame(backtrace); if (crash_frame) { if (crash_frame->function_name && 0 != strcmp(crash_frame->function_name, "??")) { dd_save_text(dd, FILENAME_CRASH_FUNCTION, crash_frame->function_name); } sr_gdb_frame_free(crash_frame); } sr_gdb_stacktrace_free(backtrace); dd_close(dd); free(component); return 0; }
static int is_crash_a_dup(const char *dump_dir_name, void *param) { struct cdump_state *state = (struct cdump_state *)param; if (state->uuid) return 0; /* we already checked it, don't do it again */ struct dump_dir *dd = dd_opendir(dump_dir_name, /*flags:*/ 0); if (!dd) return 0; /* wtf? (error, but will be handled elsewhere later) */ state->uuid = dd_load_text_ext(dd, FILENAME_UUID, DD_FAIL_QUIETLY_ENOENT + DD_LOAD_TEXT_RETURN_NULL_ON_FAILURE ); dd_close(dd); if (!state->uuid) { return 0; /* no uuid (yet), "run_event, please continue iterating" */ } /* Scan crash dumps looking for a dup */ //TODO: explain why this is safe wrt concurrent runs DIR *dir = opendir(DEBUG_DUMPS_DIR); if (dir != NULL) { struct dirent *dent; while ((dent = readdir(dir)) != NULL) { if (dot_or_dotdot(dent->d_name)) continue; /* skip "." and ".." */ int different; char *uid, *uuid; char *dump_dir_name2 = concat_path_file(DEBUG_DUMPS_DIR, dent->d_name); if (strcmp(dump_dir_name, dump_dir_name2) == 0) goto next; /* we are never a dup of ourself */ dd = dd_opendir(dump_dir_name2, /*flags:*/ DD_FAIL_QUIETLY_ENOENT); if (!dd) goto next; uid = dd_load_text(dd, FILENAME_UID); uuid = dd_load_text(dd, FILENAME_UUID); dd_close(dd); different = strcmp(state->uid, uid) || strcmp(state->uuid, uuid); free(uid); free(uuid); if (different) goto next; state->crash_dump_dup_name = dump_dir_name2; /* "run_event, please stop iterating": */ return 1; next: free(dump_dir_name2); } closedir(dir); } /* No dup found */ return 0; /* "run_event, please continue iterating" */ }
static int run_post_create(const char *dirname) { /* If doesn't start with "g_settings_dump_location/"... */ if (!dir_is_in_dump_location(dirname)) { /* Then refuse to operate on it (someone is attacking us??) */ error_msg("Bad problem directory name '%s', should start with: '%s'", dirname, g_settings_dump_location); return 400; /* Bad Request */ } if (!dir_has_correct_permissions(dirname, DD_PERM_EVENTS)) { error_msg("Problem directory '%s' has wrong owner or group", dirname); return 400; /* */ } /* Check completness */ { struct dump_dir *dd = dd_opendir(dirname, DD_OPEN_READONLY); char *provoker = NULL; const bool event_dir = dd && problem_dump_dir_was_provoked_by_abrt_event(dd, &provoker); if (event_dir) { if (g_settings_debug_level == 0) { error_msg("Removing problem provoked by ABRT(pid:%s): '%s'", provoker, dirname); dd_delete(dd); } else { char *dumpdir = NULL; char *event = NULL; char *reason = NULL; char *cmdline = NULL; /* Ignore errors */ dd_get_env_variable(dd, "DUMP_DIR", &dumpdir); dd_get_env_variable(dd, "EVENT", &event); reason = dd_load_text(dd, FILENAME_REASON); cmdline = dd_load_text(dd, FILENAME_CMDLINE); error_msg("ABRT_SERVER_PID=%s;DUMP_DIR='%s';EVENT='%s';REASON='%s';CMDLINE='%s'", provoker, dumpdir, event, reason, cmdline); } free(provoker); return 400; } const bool complete = dd && problem_dump_dir_is_complete(dd); dd_close(dd); if (complete) { error_msg("Problem directory '%s' has already been processed", dirname); return 403; } } int child_stdout_fd; int child_pid = spawn_event_handler_child(dirname, "post-create", &child_stdout_fd); char *dup_of_dir = NULL; struct strbuf *cmd_output = strbuf_new(); bool child_is_post_create = 1; /* else it is a notify child */ read_child_output: //log("Reading from event fd %d", child_stdout_fd); /* Read streamed data and split lines */ for (;;) { char buf[250]; /* usually we get one line, no need to have big buf */ errno = 0; int r = safe_read(child_stdout_fd, buf, sizeof(buf) - 1); if (r <= 0) break; buf[r] = '\0'; /* split lines in the current buffer */ char *raw = buf; char *newline; while ((newline = strchr(raw, '\n')) != NULL) { *newline = '\0'; strbuf_append_str(cmd_output, raw); char *msg = cmd_output->buf; if (child_is_post_create && prefixcmp(msg, "DUP_OF_DIR: ") == 0 ) { free(dup_of_dir); dup_of_dir = xstrdup(msg + strlen("DUP_OF_DIR: ")); } else log("%s", msg); strbuf_clear(cmd_output); /* jump to next line */ raw = newline + 1; } /* beginning of next line. the line continues by next read */ strbuf_append_str(cmd_output, raw); } /* EOF/error */ /* Wait for child to actually exit, collect status */ int status = 0; if (safe_waitpid(child_pid, &status, 0) <= 0) /* should not happen */ perror_msg("waitpid(%d)", child_pid); /* If it was a "notify[-dup]" event, then we're done */ if (!child_is_post_create) goto ret; /* exit 0 means "this is a good, non-dup dir" */ /* exit with 1 + "DUP_OF_DIR: dir" string => dup */ if (status != 0) { if (WIFSIGNALED(status)) { log("'post-create' on '%s' killed by signal %d", dirname, WTERMSIG(status)); goto delete_bad_dir; } /* else: it is WIFEXITED(status) */ if (!dup_of_dir) { log("'post-create' on '%s' exited with %d", dirname, WEXITSTATUS(status)); goto delete_bad_dir; } } const char *work_dir = (dup_of_dir ? dup_of_dir : dirname); /* Load problem_data (from the *first dir* if this one is a dup) */ struct dump_dir *dd = dd_opendir(work_dir, /*flags:*/ 0); if (!dd) /* dd_opendir already emitted error msg */ goto delete_bad_dir; /* Update count */ char *count_str = dd_load_text_ext(dd, FILENAME_COUNT, DD_FAIL_QUIETLY_ENOENT); unsigned long count = strtoul(count_str, NULL, 10); /* Don't increase crash count if we are working with newly uploaded * directory (remote crash) which already has its crash count set. */ if ((status != 0 && dup_of_dir) || count == 0) { count++; char new_count_str[sizeof(long)*3 + 2]; sprintf(new_count_str, "%lu", count); dd_save_text(dd, FILENAME_COUNT, new_count_str); /* This condition can be simplified to either * (status * != 0 && * dup_of_dir) or (count == 1). But the * chosen form is much more reliable and safe. We must not call * dd_opendir() to locked dd otherwise we go into a deadlock. */ if (strcmp(dd->dd_dirname, dirname) != 0) { /* Update the last occurrence file by the time file of the new problem */ struct dump_dir *new_dd = dd_opendir(dirname, DD_OPEN_READONLY); char *last_ocr = NULL; if (new_dd) { /* TIME must exists in a valid dump directory but we don't want to die * due to broken duplicated dump directory */ last_ocr = dd_load_text_ext(new_dd, FILENAME_TIME, DD_LOAD_TEXT_RETURN_NULL_ON_FAILURE | DD_FAIL_QUIETLY_ENOENT); dd_close(new_dd); } else { /* dd_opendir() already produced a message with good information about failure */ error_msg("Can't read the last occurrence file from the new dump directory."); } if (!last_ocr) { /* the new dump directory may lie in the dump location for some time */ log("Using current time for the last occurrence file which may be incorrect."); time_t t = time(NULL); last_ocr = xasprintf("%lu", (long)t); } dd_save_text(dd, FILENAME_LAST_OCCURRENCE, last_ocr); free(last_ocr); } } /* Reset mode/uig/gid to correct values for all files created by event run */ dd_sanitize_mode_and_owner(dd); dd_close(dd); if (!dup_of_dir) log_notice("New problem directory %s, processing", work_dir); else { log_warning("Deleting problem directory %s (dup of %s)", strrchr(dirname, '/') + 1, strrchr(dup_of_dir, '/') + 1); delete_dump_dir(dirname); } /* Run "notify[-dup]" event */ int fd; child_pid = spawn_event_handler_child( work_dir, (dup_of_dir ? "notify-dup" : "notify"), &fd ); //log("Started notify, fd %d -> %d", fd, child_stdout_fd); xmove_fd(fd, child_stdout_fd); child_is_post_create = 0; strbuf_clear(cmd_output); free(dup_of_dir); dup_of_dir = NULL; goto read_child_output; delete_bad_dir: log_warning("Deleting problem directory '%s'", dirname); delete_dump_dir(dirname); ret: strbuf_free(cmd_output); free(dup_of_dir); close(child_stdout_fd); return 0; }
/* 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; }
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; }
mw_result_t LoadDebugDump(const char *dump_dir_name, problem_data_t **problem_data) { mw_result_t res; struct dump_dir *dd = dd_opendir(dump_dir_name, /*flags:*/ 0); if (!dd) return MW_ERROR; struct cdump_state state; state.uid = dd_load_text(dd, FILENAME_UID); state.uuid = NULL; state.crash_dump_dup_name = NULL; char *analyzer = dd_load_text(dd, FILENAME_ANALYZER); dd_close(dd); res = MW_ERROR; /* Run post-create event handler(s) */ struct run_event_state *run_state = new_run_event_state(); run_state->post_run_callback = is_crash_a_dup; run_state->post_run_param = &state; run_state->logging_callback = do_log; int r = run_event_on_dir_name(run_state, dump_dir_name, "post-create"); free_run_event_state(run_state); //TODO: consider this case: // new dump is created, post-create detects that it is a dup, // but then FillCrashInfo(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 state.crash_dump_dup_name) */ if (!state.crash_dump_dup_name) { /* No. Was there error on one of processing steps in run_event? */ if (r != 0) goto ret; /* yes */ /* Was uuid created after all? (In this case, is_crash_a_dup() * should have fetched it and created state.uuid) */ if (!state.uuid) { /* no */ log("Dump directory '%s' has no UUID element", dump_dir_name); goto ret; } } else { dump_dir_name = state.crash_dump_dup_name; } /* Loads problem_data (from the *first debugdump dir* if this one is a dup) * Returns: * MW_OCCURRED: "crash count is != 1" (iow: it is > 1 - dup) * MW_OK: "crash count is 1" (iow: this is a new crash, not a dup) * else: an error code */ { dd = dd_opendir(dump_dir_name, /*flags:*/ 0); if (!dd) { res = MW_ERROR; goto ret; } /* Reset mode/uig/gid to correct values for all files created by event run */ dd_sanitize_mode_and_owner(dd); /* Update count */ char *count_str = dd_load_text_ext(dd, FILENAME_COUNT, DD_FAIL_QUIETLY_ENOENT); unsigned long count = strtoul(count_str, NULL, 10); count++; char new_count_str[sizeof(long)*3 + 2]; sprintf(new_count_str, "%lu", count); dd_save_text(dd, FILENAME_COUNT, new_count_str); dd_close(dd); *problem_data = FillCrashInfo(dump_dir_name); if (*problem_data != NULL) { res = MW_OK; if (count > 1) { log("Dump directory is a duplicate of %s", dump_dir_name); res = MW_OCCURRED; } } } ret: free(state.uuid); free(state.uid); free(state.crash_dump_dup_name); free(analyzer); return res; }
static int SavePackageDescriptionToDebugDump(const char *dump_dir_name, const char *chroot) { struct dump_dir *dd = dd_opendir(dump_dir_name, /*flags:*/ 0); if (!dd) return 1; char *type = dd_load_text(dd, FILENAME_TYPE); if (!strcmp(type, "Kerneloops")) { dd_save_text(dd, FILENAME_PACKAGE, "kernel"); dd_save_text(dd, FILENAME_COMPONENT, "kernel"); dd_close(dd); free(type); return 0; } free(type); char *cmdline = NULL; char *executable = NULL; char *rootdir = NULL; char *package_short_name = NULL; struct pkg_envra *pkg_name = NULL; char *component = NULL; int error = 1; /* note: "goto ret" statements below free all the above variables, * but they don't dd_close(dd) */ cmdline = dd_load_text_ext(dd, FILENAME_CMDLINE, DD_FAIL_QUIETLY_ENOENT); executable = dd_load_text(dd, FILENAME_EXECUTABLE); if (chroot == NULL) chroot = rootdir = dd_load_text_ext(dd, FILENAME_ROOTDIR, DD_FAIL_QUIETLY_ENOENT | DD_LOAD_TEXT_RETURN_NULL_ON_FAILURE); /* Close dd while we query package database. It can take some time, * don't want to keep dd locked longer than necessary */ dd_close(dd); if (is_path_blacklisted(executable)) { log("Blacklisted executable '%s'", executable); goto ret; /* return 1 (failure) */ } pkg_name = rpm_get_package_nvr(executable, chroot); if (!pkg_name) { if (settings_bProcessUnpackaged) { log_info("Crash in unpackaged executable '%s', " "proceeding without packaging information", executable); goto ret0; /* no error */ } log("Executable '%s' doesn't belong to any package" " and ProcessUnpackaged is set to 'no'", executable ); goto ret; /* return 1 (failure) */ } /* Check well-known interpreter names */ const char *basename = strrchr(executable, '/'); if (basename) basename++; else basename = executable; /* if basename is known interpreter, we want to blame the running script * not the interpreter */ if (g_list_find_custom(settings_Interpreters, basename, (GCompareFunc)g_strcmp0)) { struct pkg_envra *script_pkg = get_script_name(cmdline, &executable, chroot); /* executable may have changed, check it again */ if (is_path_blacklisted(executable)) { log("Blacklisted executable '%s'", executable); goto ret; /* return 1 (failure) */ } if (!script_pkg) { /* Script name is not absolute, or it doesn't * belong to any installed package. */ if (!settings_bProcessUnpackaged) { log("Interpreter crashed, but no packaged script detected: '%s'", cmdline); goto ret; /* return 1 (failure) */ } /* Unpackaged script, but the settings says we want to keep it. * BZ plugin wont allow to report this anyway, because component * is missing, so there is no reason to mark it as not_reportable. * Someone might want to use abrt to report it using ftp. */ goto ret0; } free_pkg_envra(pkg_name); pkg_name = script_pkg; } package_short_name = xasprintf("%s", pkg_name->p_name); log_info("Package:'%s' short:'%s'", pkg_name->p_nvr, package_short_name); if (g_list_find_custom(settings_setBlackListedPkgs, package_short_name, (GCompareFunc)g_strcmp0)) { log("Blacklisted package '%s'", package_short_name); goto ret; /* return 1 (failure) */ } if (settings_bOpenGPGCheck) { if (!rpm_chk_fingerprint(package_short_name)) { log("Package '%s' isn't signed with proper key", package_short_name); goto ret; /* return 1 (failure) */ } /* We used to also check the integrity of the executable here: * if (!CheckHash(package_short_name.c_str(), executable)) BOOM(); * Checking the MD5 sum requires to run prelink to "un-prelink" the * binaries - this is considered potential security risk so we don't * do it now, until we find some non-intrusive way. */ } component = rpm_get_component(executable, chroot); dd = dd_opendir(dump_dir_name, /*flags:*/ 0); if (!dd) goto ret; /* return 1 (failure) */ if (pkg_name) { dd_save_text(dd, FILENAME_PACKAGE, pkg_name->p_nvr); dd_save_text(dd, FILENAME_PKG_EPOCH, pkg_name->p_epoch); dd_save_text(dd, FILENAME_PKG_NAME, pkg_name->p_name); dd_save_text(dd, FILENAME_PKG_VERSION, pkg_name->p_version); dd_save_text(dd, FILENAME_PKG_RELEASE, pkg_name->p_release); dd_save_text(dd, FILENAME_PKG_ARCH, pkg_name->p_arch); } if (component) dd_save_text(dd, FILENAME_COMPONENT, component); dd_close(dd); ret0: error = 0; ret: free(cmdline); free(executable); free(rootdir); free(package_short_name); free_pkg_envra(pkg_name); free(component); return error; }
int main(int argc, char **argv) { /* I18n */ setlocale(LC_ALL, ""); #if ENABLE_NLS bindtextdomain(PACKAGE, LOCALEDIR); textdomain(PACKAGE); #endif abrt_init(argv); const char *dump_dir_name = "."; /* Can't keep these strings/structs static: _() doesn't support that */ const char *program_usage_string = _( "& [-v] -d DIR\n" "\n" "Calculates and saves UUID of coredump in problem directory DIR" ); enum { OPT_v = 1 << 0, OPT_d = 1 << 1, }; /* Keep enum above and order of options below in sync! */ struct options program_options[] = { OPT__VERBOSE(&g_verbose), OPT_STRING('d', NULL, &dump_dir_name, "DIR", _("Problem directory")), OPT_END() }; /*unsigned opts =*/ parse_opts(argc, argv, program_options, program_usage_string); export_abrt_envvars(0); char *unstrip_n_output = NULL; char *coredump_path = xasprintf("%s/"FILENAME_COREDUMP, dump_dir_name); if (access(coredump_path, R_OK) == 0) unstrip_n_output = run_unstrip_n(dump_dir_name, /*timeout_sec:*/ 30); free(coredump_path); if (unstrip_n_output) { /* Run unstrip -n and trim its output, leaving only sizes and build ids */ /* modifies unstrip_n_output in-place: */ trim_unstrip_output(unstrip_n_output, unstrip_n_output); } else { /* bad dump_dir_name, can't run unstrip, etc... * or maybe missing coredump - try generating it from core_backtrace */ unstrip_n_output = build_ids_from_core_backtrace(dump_dir_name); } /* Hash package + executable + unstrip_n_output and save it as UUID */ struct dump_dir *dd = dd_opendir(dump_dir_name, /*flags:*/ 0); if (!dd) return 1; char *executable = dd_load_text(dd, FILENAME_EXECUTABLE); /* FILENAME_PACKAGE may be missing if ProcessUnpackaged = yes... */ char *package = dd_load_text_ext(dd, FILENAME_PACKAGE, DD_FAIL_QUIETLY_ENOENT); /* Package variable has "firefox-3.5.6-1.fc11[.1]" format */ /* Remove distro suffix and maybe least significant version number */ char *p = package; while (*p) { if (*p == '.' && (p[1] < '0' || p[1] > '9')) { /* We found "XXXX.nondigitXXXX", trim this part */ *p = '\0'; break; } p++; } char *first_dot = strchr(package, '.'); if (first_dot) { char *last_dot = strrchr(first_dot, '.'); if (last_dot != first_dot) { /* There are more than one dot: "1.2.3" * Strip last part, we don't want to distinquish crashes * in packages which differ only by minor release number. */ *last_dot = '\0'; } } char *string_to_hash = xasprintf("%s%s%s", package, executable, unstrip_n_output); /*free(package);*/ /*free(executable);*/ /*free(unstrip_n_output);*/ log_debug("String to hash: %s", string_to_hash); char hash_str[SHA1_RESULT_LEN*2 + 1]; str_to_sha1str(hash_str, string_to_hash); dd_save_text(dd, FILENAME_UUID, hash_str); dd_close(dd); return 0; }
static char *run_unstrip_n(const char *dump_dir_name, unsigned timeout_sec) { struct dump_dir *dd = dd_opendir(dump_dir_name, /*flags:*/ 0); if (!dd) return NULL; char *uid_str = dd_load_text_ext(dd, FILENAME_UID, DD_FAIL_QUIETLY_ENOENT | DD_LOAD_TEXT_RETURN_NULL_ON_FAILURE); dd_close(dd); uid_t uid = -1L; if (uid_str) { uid = xatoi_positive(uid_str); free(uid_str); if (uid == geteuid()) { uid = -1L; /* no need to setuid/gid if we are already under right uid */ } } int flags = EXECFLG_INPUT_NUL | EXECFLG_OUTPUT | EXECFLG_SETSID | EXECFLG_QUIET; if (uid != (uid_t)-1L) flags |= EXECFLG_SETGUID; VERB1 flags &= ~EXECFLG_QUIET; int pipeout[2]; char* args[4]; args[0] = (char*)"eu-unstrip"; args[1] = xasprintf("--core=%s/"FILENAME_COREDUMP, dump_dir_name); args[2] = (char*)"-n"; args[3] = NULL; pid_t child = fork_execv_on_steroids(flags, args, pipeout, /*env_vec:*/ NULL, /*dir:*/ NULL, uid); free(args[1]); /* Bugs in unstrip or corrupted coredumps can cause it to enter infinite loop. * Therefore we have a (largish) timeout, after which we kill the child. */ int t = time(NULL); /* int is enough, no need to use time_t */ int endtime = t + timeout_sec; struct strbuf *buf_out = strbuf_new(); while (1) { int timeout = endtime - t; if (timeout < 0) { kill(child, SIGKILL); strbuf_append_strf(buf_out, "\nTimeout exceeded: %u seconds, killing %s\n", timeout_sec, args[0]); break; } /* We don't check poll result - checking read result is enough */ struct pollfd pfd; pfd.fd = pipeout[0]; pfd.events = POLLIN; poll(&pfd, 1, timeout * 1000); char buff[1024]; int r = read(pipeout[0], buff, sizeof(buff) - 1); if (r <= 0) break; buff[r] = '\0'; strbuf_append_str(buf_out, buff); t = time(NULL); } close(pipeout[0]); /* Prevent having zombie child process */ int status; waitpid(child, &status, 0); if (status != 0) { /* unstrip didnt exit with exit code 0 */ strbuf_free(buf_out); return NULL; } return strbuf_free_nobuf(buf_out); }
/* Checks rules in *pp_rule_list, starting from first (remaining) rule, * until it finds a rule with all conditions satisfied. * In this case, it deletes this rule and returns this rule's cmd. * Else (if it didn't find such rule), it returns NULL. * In case of error (dump_dir can't be opened), returns NULL. * * Intended usage: * list = load_rule_list(...); * while ((cmd = pop_next_command(&list, ...)) != NULL) * run(cmd); */ static char* pop_next_command(GList **pp_rule_list, char **pp_event_name, /* reports EVENT value thru this, if not NULL on entry */ struct dump_dir **pp_dd, /* use *pp_dd for access to dump dir, if non-NULL */ const char *dump_dir_name, const char *pfx, unsigned pfx_len ) { char *command = NULL; struct dump_dir *dd = pp_dd ? *pp_dd : NULL; GList *rule_list = *pp_rule_list; while (rule_list) { struct rule *cur_rule = rule_list->data; GList *condition = cur_rule->conditions; while (condition) { const char *cond_str = condition->data; const char *eq_sign = strchr(cond_str, '='); /* Is it "EVENT=foo"? */ if (strncmp(cond_str, "EVENT=", 6) == 0) { if (strncmp(eq_sign + 1, pfx, pfx_len) != 0) goto next_rule; /* prefix doesn't match */ if (pp_event_name) { free(*pp_event_name); *pp_event_name = xstrdup(eq_sign + 1); } } else { /* Read from dump dir and compare */ if (!dd) { /* Without dir to match, we assume match for all conditions */ if (!dump_dir_name) goto next_cond; dd = dd_opendir(dump_dir_name, /*flags:*/ DD_OPEN_READONLY); if (!dd) { free_rule_list(rule_list); *pp_rule_list = NULL; goto ret; /* error (note: dd_opendir logged error msg) */ } } /* Is it "VAR~=REGEX"? */ int regex = (eq_sign > cond_str && eq_sign[-1] == '~'); /* Is it "VAR!=VAL"? */ int inverted = (eq_sign > cond_str && eq_sign[-1] == '!'); char *var_name = xstrndup(cond_str, eq_sign - cond_str - (regex|inverted)); char *real_val = dd_load_text_ext(dd, var_name, DD_FAIL_QUIETLY_ENOENT); free(var_name); int vals_differ = regex ? regcmp_lines(real_val, eq_sign + 1) : strcmp(real_val, eq_sign + 1); free(real_val); if (inverted) vals_differ = !vals_differ; /* Do values match? */ if (vals_differ) /* no */ { //log_debug("var '%s': '%.*s'!='%s', skipping line", // p, // (int)(strchrnul(real_val, '\n') - real_val), real_val, // eq_sign); goto next_rule; } } next_cond: /* We are here if current condition is satisfied */ condition = condition->next; } /* while (condition) */ /* We are here if all conditions are satisfied */ /* IOW, we found rule to run, delete it and return its command */ *pp_rule_list = g_list_remove(*pp_rule_list, cur_rule); list_free_with_free(cur_rule->conditions); command = cur_rule->command; /*free(cur_rule->command); - WRONG! we are returning it! */ free(cur_rule); break; next_rule: rule_list = rule_list->next; } /* while (rule_list) */ ret: if (pp_dd) *pp_dd = dd; else dd_close(dd); return command; }