/* The function expects that FILENAME_COUNT dump dir element is created by * abrtd after all post-create events are successfully done. Thus if * FILENAME_COUNT element doesn't exist abrtd can consider the dump directory * as unprocessed. * * Relying on content of dump directory has one problem. If a hook provides * FILENAME_COUNT abrtd will consider the dump directory as processed. */ static void mark_unprocessed_dump_dirs_not_reportable(const char *path) { log_notice("Searching for unprocessed dump directories"); DIR *dp = opendir(path); if (!dp) { perror_msg("Can't open directory '%s'", path); return; } struct dirent *dent; while ((dent = readdir(dp)) != NULL) { if (dot_or_dotdot(dent->d_name)) continue; /* skip "." and ".." */ char *full_name = concat_path_file(path, dent->d_name); struct stat stat_buf; if (stat(full_name, &stat_buf) != 0) { perror_msg("Can't access path '%s'", full_name); goto next_dd; } if (S_ISDIR(stat_buf.st_mode) == 0) /* This is expected. The dump location contains some aux files */ goto next_dd; struct dump_dir *dd = dd_opendir(full_name, /*flags*/0); if (dd) { if (!problem_dump_dir_is_complete(dd) && !dd_exist(dd, FILENAME_NOT_REPORTABLE)) { log_warning("Marking '%s' not reportable (no '"FILENAME_COUNT"' item)", full_name); dd_save_text(dd, FILENAME_NOT_REPORTABLE, _("The problem data are " "incomplete. This usually happens when a problem " "is detected while computer is shutting down or " "user is logging out. In order to provide " "valuable problem reports, ABRT will not allow " "you to submit this problem. If you have time and " "want to help the developers in their effort to " "sort out this problem, please contact them directly.")); } dd_close(dd); } next_dd: free(full_name); } closedir(dp); }
void dump_lxc_info(struct dump_dir *dd, const char *lxc_cmd) { if (!dd_exist(dd, FILENAME_CONTAINER)) dd_save_text(dd, FILENAME_CONTAINER, "lxc"); char *mntnf_path = concat_path_file(dd->dd_dirname, FILENAME_MOUNTINFO); FILE *mntnf_file = fopen(mntnf_path, "r"); free(mntnf_path); struct mountinfo mntnf; int r = get_mountinfo_for_mount_point(mntnf_file, &mntnf, "/"); fclose(mntnf_file); if (r != 0) { error_msg("lxc processes must have re-mounted root"); goto dump_lxc_info_cleanup; } const char *mnt_root = MOUNTINFO_ROOT(mntnf); const char *last_slash = strrchr(mnt_root, '/'); if (last_slash == NULL || (strcmp("rootfs", last_slash +1) != 0)) { error_msg("Invalid root path '%s'", mnt_root); goto dump_lxc_info_cleanup; } if (last_slash == mnt_root) { error_msg("root path misses container id: '%s'", mnt_root); goto dump_lxc_info_cleanup; } const char *tmp = strrchr(last_slash - 1, '/'); if (tmp == NULL) { error_msg("root path misses first /: '%s'", mnt_root); goto dump_lxc_info_cleanup; } char *container_id = xstrndup(tmp + 1, (last_slash - tmp) - 1); dd_save_text(dd, FILENAME_CONTAINER_ID, container_id); dd_save_text(dd, FILENAME_CONTAINER_UUID, container_id); free(container_id); /* TODO: make a copy of 'config' */ /* get mount point for MOUNTINFO_MOUNT_SOURCE(mntnf) + MOUNTINFO_ROOT(mntnf) */ dump_lxc_info_cleanup: mountinfo_destroy(&mntnf); }
char *get_backtrace(const char *dump_dir_name, unsigned timeout_sec, const char *debuginfo_dirs) { INITIALIZE_LIBABRT(); struct dump_dir *dd = dd_opendir(dump_dir_name, /*flags:*/ 0); if (!dd) return NULL; char *executable = NULL; if (dd_exist(dd, FILENAME_BINARY)) executable = concat_path_file(dd->dd_dirname, FILENAME_BINARY); else executable = dd_load_text(dd, FILENAME_EXECUTABLE); dd_close(dd); /* Let user know what's going on */ log(_("Generating backtrace")); unsigned i = 0; char *args[25]; args[i++] = (char*)GDB; args[i++] = (char*)"-batch"; struct strbuf *set_debug_file_directory = strbuf_new(); unsigned auto_load_base_index = 0; if(debuginfo_dirs == NULL) { // set non-existent debug file directory to prevent resolving // function names - we need offsets for core backtrace. strbuf_append_str(set_debug_file_directory, "set debug-file-directory /"); } else { strbuf_append_str(set_debug_file_directory, "set debug-file-directory /usr/lib/debug"); struct strbuf *debug_directories = strbuf_new(); const char *p = debuginfo_dirs; while (1) { while (*p == ':') p++; if (*p == '\0') break; const char *colon_or_nul = strchrnul(p, ':'); strbuf_append_strf(debug_directories, "%s%.*s/usr/lib/debug", (debug_directories->len == 0 ? "" : ":"), (int)(colon_or_nul - p), p); p = colon_or_nul; } strbuf_append_strf(set_debug_file_directory, ":%s", debug_directories->buf); args[i++] = (char*)"-iex"; auto_load_base_index = i; args[i++] = xasprintf("add-auto-load-safe-path %s", debug_directories->buf); args[i++] = (char*)"-iex"; args[i++] = xasprintf("add-auto-load-scripts-directory %s", debug_directories->buf); strbuf_free(debug_directories); } args[i++] = (char*)"-ex"; const unsigned debug_dir_cmd_index = i++; args[debug_dir_cmd_index] = strbuf_free_nobuf(set_debug_file_directory); /* "file BINARY_FILE" is needed, without it gdb cannot properly * unwind the stack. Currently the unwind information is located * in .eh_frame which is stored only in binary, not in coredump * or debuginfo. * * Fedora GDB does not strictly need it, it will find the binary * by its build-id. But for binaries either without build-id * (= built on non-Fedora GCC) or which do not have * their debuginfo rpm installed gdb would not find BINARY_FILE * so it is still makes sense to supply "file BINARY_FILE". * * Unfortunately, "file BINARY_FILE" doesn't work well if BINARY_FILE * was deleted (as often happens during system updates): * gdb uses specified BINARY_FILE * even if it is completely unrelated to the coredump. * See https://bugzilla.redhat.com/show_bug.cgi?id=525721 * * TODO: check mtimes on COREFILE and BINARY_FILE and not supply * BINARY_FILE if it is newer (to at least avoid gdb complaining). */ args[i++] = (char*)"-ex"; const unsigned file_cmd_index = i++; args[file_cmd_index] = xasprintf("file %s", executable); free(executable); args[i++] = (char*)"-ex"; const unsigned core_cmd_index = i++; args[core_cmd_index] = xasprintf("core-file %s/"FILENAME_COREDUMP, dump_dir_name); args[i++] = (char*)"-ex"; const unsigned bt_cmd_index = i++; /*args[9] = ... see below */ args[i++] = (char*)"-ex"; args[i++] = (char*)"info sharedlib"; /* glibc's abort() stores its message in __abort_msg variable */ args[i++] = (char*)"-ex"; args[i++] = (char*)"print (char*)__abort_msg"; args[i++] = (char*)"-ex"; args[i++] = (char*)"print (char*)__glib_assert_msg"; args[i++] = (char*)"-ex"; args[i++] = (char*)"info all-registers"; args[i++] = (char*)"-ex"; const unsigned dis_cmd_index = i++; args[dis_cmd_index] = (char*)"disassemble"; args[i++] = NULL; /* Get the backtrace, but try to cap its size */ /* Limit bt depth. With no limit, gdb sometimes OOMs the machine */ unsigned bt_depth = 1024; const char *thread_apply_all = "thread apply all -ascending"; const char *full = " full"; char *bt = NULL; while (1) { args[bt_cmd_index] = xasprintf("%s backtrace %u%s", thread_apply_all, bt_depth, full); bt = exec_vp(args, /*redirect_stderr:*/ 1, timeout_sec, NULL); free(args[bt_cmd_index]); if ((bt && strnlen(bt, 256*1024) < 256*1024) || bt_depth <= 32) { break; } bt_depth /= 2; if (bt) log("Backtrace is too big (%u bytes), reducing depth to %u", (unsigned)strlen(bt), bt_depth); else /* (NB: in fact, current impl. of exec_vp() never returns NULL) */ log("Failed to generate backtrace, reducing depth to %u", bt_depth); free(bt); /* Replace -ex disassemble (which disasms entire function $pc points to) * to a version which analyzes limited, small patch of code around $pc. * (Users reported a case where bare "disassemble" attempted to process * entire .bss). * TODO: what if "$pc-N" underflows? in my test, this happens: * Dump of assembler code from 0xfffffffffffffff0 to 0x30: * End of assembler dump. * (IOW: "empty" dump) */ args[dis_cmd_index] = (char*)"disassemble $pc-20, $pc+64"; if (bt_depth <= 64 && thread_apply_all[0] != '\0') { /* This program likely has gazillion threads, dont try to bt them all */ bt_depth = 128; thread_apply_all = ""; } if (bt_depth <= 64 && full[0] != '\0') { /* Looks like there are gigantic local structures or arrays, disable "full" bt */ bt_depth = 128; full = ""; } } if (auto_load_base_index > 0) { free(args[auto_load_base_index]); free(args[auto_load_base_index + 2]); } free(args[debug_dir_cmd_index]); free(args[file_cmd_index]); free(args[core_cmd_index]); return bt; }
void dump_docker_info(struct dump_dir *dd, const char *root_dir) { if (!dd_exist(dd, FILENAME_CONTAINER)) dd_save_text(dd, FILENAME_CONTAINER, "docker"); json_object *json = NULL; char *mntnf_path = concat_path_file(dd->dd_dirname, FILENAME_MOUNTINFO); FILE *mntnf_file = fopen(mntnf_path, "r"); free(mntnf_path); struct mount_point { const char *name; enum mountinfo_fields { MOUNTINFO_ROOT, MOUNTINFO_SOURCE, } field; } mount_points[] = { { "/sys/fs/cgroup/memory", MOUNTINFO_ROOT }, { "/", MOUNTINFO_SOURCE }, }; char *container_id = NULL; char *output = NULL; /* initialized to 0 because we call mountinfo_destroy below */ struct mountinfo mntnf = {0}; for (size_t i = 0; i < ARRAY_SIZE(mount_points); ++i) { log_debug("Parsing container ID from mount point '%s'", mount_points[i].name); rewind(mntnf_file); /* get_mountinfo_for_mount_point() re-initializes &mntnf */ mountinfo_destroy(&mntnf); int r = get_mountinfo_for_mount_point(mntnf_file, &mntnf, mount_points[i].name); if (r != 0) { log_debug("Mount poin not found"); continue; } const char *mnt_info = NULL; switch(mount_points[i].field) { case MOUNTINFO_ROOT: mnt_info = MOUNTINFO_ROOT(mntnf); break; case MOUNTINFO_SOURCE: mnt_info = MOUNTINFO_MOUNT_SOURCE(mntnf); break; default: error_msg("BUG: forgotten MOUNTINFO field type"); abort(); } const char *last = strrchr(mnt_info, '/'); if (last == NULL || strncmp("/docker-", last, strlen("/docker-")) != 0) { log_debug("Mounted source is not a docker mount source: '%s'", mnt_info); continue; } last = strrchr(last, '-'); if (last == NULL) { log_debug("The docker mount point has unknown format"); continue; } ++last; /* Why we copy only 12 bytes here? * Because only the first 12 characters are used by docker as ID of the * container. */ container_id = xstrndup(last, 12); if (strlen(container_id) != 12) { log_debug("Failed to get container ID"); continue; } char *docker_inspect_cmdline = NULL; if (root_dir != NULL) docker_inspect_cmdline = xasprintf("chroot %s /bin/sh -c \"docker inspect %s\"", root_dir, container_id); else docker_inspect_cmdline = xasprintf("docker inspect %s", container_id); log_debug("Executing: '%s'", docker_inspect_cmdline); output = run_in_shell_and_save_output(0, docker_inspect_cmdline, "/", NULL); free(docker_inspect_cmdline); if (output == NULL || strcmp(output, "[]\n") == 0) { log_debug("Unsupported container ID: '%s'", container_id); free(container_id); container_id = NULL; free(output); output = NULL; continue; } break; } fclose(mntnf_file); if (container_id == NULL) { error_msg("Could not inspect the container"); goto dump_docker_info_cleanup; } dd_save_text(dd, FILENAME_CONTAINER_ID, container_id); dd_save_text(dd, FILENAME_DOCKER_INSPECT, output); json = json_tokener_parse(output); free(output); if (is_error(json)) { error_msg("Unable parse response from docker"); goto dump_docker_info_cleanup; } json_object *container = json_object_array_get_idx(json, 0); if (container == NULL) { error_msg("docker does not contain array of containers"); goto dump_docker_info_cleanup; } json_object *config = NULL; if (!json_object_object_get_ex(container, "Config", &config)) { error_msg("container does not have 'Config' member"); goto dump_docker_info_cleanup; } json_object *image = NULL; if (!json_object_object_get_ex(config, "Image", &image)) { error_msg("Config does not have 'Image' member"); goto dump_docker_info_cleanup; } char *name = strtrimch(xstrdup(json_object_to_json_string(image)), '"'); dd_save_text(dd, FILENAME_CONTAINER_IMAGE, name); free(name); dump_docker_info_cleanup: if (json != NULL) json_object_put(json); mountinfo_destroy(&mntnf); return; }