Пример #1
0
/* 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);
}
Пример #3
0
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;
}