Example #1
0
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);
}
Example #2
0
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;
}
Example #3
0
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;
}
Example #4
0
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;
}
Example #6
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);
}
Example #7
0
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);
}
Example #8
0
/* 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;
}
Example #10
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" */
}
Example #11
0
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;
}
Example #12
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;
}
Example #13
0
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;
}
Example #14
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;
}
Example #16
0
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;
}
Example #17
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);
}
Example #18
0
/* 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;
}