Blame src/lib/ignored_problems.c

Packit 8ea169
/*
Packit 8ea169
    Copyright (C) 2013  ABRT Team
Packit 8ea169
    Copyright (C) 2013  RedHat inc.
Packit 8ea169
Packit 8ea169
    This program is free software; you can redistribute it and/or modify
Packit 8ea169
    it under the terms of the GNU General Public License as published by
Packit 8ea169
    the Free Software Foundation; either version 2 of the License, or
Packit 8ea169
    (at your option) any later version.
Packit 8ea169
Packit 8ea169
    This program is distributed in the hope that it will be useful,
Packit 8ea169
    but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 8ea169
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit 8ea169
    GNU General Public License for more details.
Packit 8ea169
Packit 8ea169
    You should have received a copy of the GNU General Public License along
Packit 8ea169
    with this program; if not, write to the Free Software Foundation, Inc.,
Packit 8ea169
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Packit 8ea169
*/
Packit 8ea169
Packit 8ea169
#include "internal_libabrt.h"
Packit 8ea169
Packit 8ea169
#define IGN_COLUMN_DELIMITER ';'
Packit 8ea169
#define IGN_DD_OPEN_FLAGS (DD_OPEN_READONLY | DD_FAIL_QUIETLY_ENOENT | DD_FAIL_QUIETLY_EACCES)
Packit 8ea169
#define IGN_DD_LOAD_TEXT_FLAGS (DD_LOAD_TEXT_RETURN_NULL_ON_FAILURE | DD_FAIL_QUIETLY_ENOENT | DD_FAIL_QUIETLY_EACCES)
Packit 8ea169
Packit 8ea169
struct ignored_problems
Packit 8ea169
{
Packit 8ea169
    char *ign_set_file_path;
Packit 8ea169
};
Packit 8ea169
Packit 8ea169
ignored_problems_t *ignored_problems_new(char *set_file_path)
Packit 8ea169
{
Packit 8ea169
    ignored_problems_t *set = xmalloc(sizeof(*set));
Packit 8ea169
    set->ign_set_file_path = set_file_path;
Packit 8ea169
    return set;
Packit 8ea169
}
Packit 8ea169
Packit 8ea169
void ignored_problems_free(ignored_problems_t *set)
Packit 8ea169
{
Packit 8ea169
    if (!set)
Packit 8ea169
        return;
Packit 8ea169
    free(set->ign_set_file_path);
Packit 8ea169
    free(set);
Packit 8ea169
}
Packit 8ea169
Packit 8ea169
static bool ignored_problems_eq(ignored_problems_t *set,
Packit 8ea169
        const char *problem_id, const char *uuid, const char *duphash,
Packit 8ea169
        const char *line, unsigned line_num)
Packit 8ea169
{
Packit 8ea169
    const char *ignored = line;
Packit 8ea169
    const char *ignored_end = strchrnul(ignored, IGN_COLUMN_DELIMITER);
Packit 8ea169
    size_t sz = ignored_end - ignored;
Packit 8ea169
    if (strncmp(problem_id, ignored, sz) == 0 && problem_id[sz] == '\0')
Packit 8ea169
    {
Packit 8ea169
        log_notice("Ignored id matches '%s'", problem_id);
Packit 8ea169
        return true;
Packit 8ea169
    }
Packit 8ea169
Packit 8ea169
    if (ignored_end[0] == '\0')
Packit 8ea169
    {
Packit 8ea169
        log_notice("No 2nd column (UUID) at line %d in ignored problems file '%s'",
Packit 8ea169
                line_num, set->ign_set_file_path);
Packit 8ea169
        return false;
Packit 8ea169
    }
Packit 8ea169
    ignored = ignored_end + 1;
Packit 8ea169
    ignored_end = strchrnul(ignored, IGN_COLUMN_DELIMITER);
Packit 8ea169
    sz = ignored_end - ignored;
Packit 8ea169
    if (uuid != NULL && strncmp(uuid, ignored, sz) == 0 && uuid[sz] == '\0')
Packit 8ea169
    {
Packit 8ea169
        log_notice("Ignored uuid '%s' matches uuid of problem '%s'", ignored, problem_id);
Packit 8ea169
        return true;
Packit 8ea169
    }
Packit 8ea169
Packit 8ea169
    if (ignored_end[0] == '\0')
Packit 8ea169
    {
Packit 8ea169
        log_notice("No 3rd column (DUPHASH) at line %d in ignored problems file '%s'",
Packit 8ea169
                line_num, set->ign_set_file_path);
Packit 8ea169
        return false;
Packit 8ea169
    }
Packit 8ea169
    ignored = ignored_end + 1;
Packit 8ea169
    ignored_end = strchrnul(ignored, IGN_COLUMN_DELIMITER);
Packit 8ea169
    sz = ignored_end - ignored;
Packit 8ea169
    if (duphash != NULL && strncmp(duphash, ignored, sz) == 0 && duphash[sz] == '\0')
Packit 8ea169
    {
Packit 8ea169
        log_notice("Ignored duphash '%s' matches duphash of problem '%s'", ignored, problem_id);
Packit 8ea169
        return true;
Packit 8ea169
    }
Packit 8ea169
Packit 8ea169
    return false;
Packit 8ea169
}
Packit 8ea169
Packit 8ea169
static bool ignored_problems_file_contains(ignored_problems_t *set,
Packit 8ea169
        const char *problem_id, const char *uuid, const char *duphash,
Packit 8ea169
        FILE **out_fp, const char *mode)
Packit 8ea169
{
Packit 8ea169
    bool found = false;
Packit 8ea169
    FILE *fp = fopen(set->ign_set_file_path, mode);
Packit 8ea169
    if (!fp)
Packit 8ea169
    {
Packit 8ea169
        if (errno != ENOENT)
Packit 8ea169
            pwarn_msg("Can't open ignored problems '%s' in mode '%s'", set->ign_set_file_path, mode);
Packit 8ea169
        goto ret_contains_end;
Packit 8ea169
    }
Packit 8ea169
Packit 8ea169
    unsigned line_num = 0;
Packit 8ea169
    while (!found)
Packit 8ea169
    {
Packit 8ea169
        char *line = xmalloc_fgetline(fp);
Packit 8ea169
        if (!line)
Packit 8ea169
            break;
Packit 8ea169
        ++line_num;
Packit 8ea169
        found = ignored_problems_eq(set, problem_id, uuid, duphash, line, line_num);
Packit 8ea169
        free(line);
Packit 8ea169
    }
Packit 8ea169
Packit 8ea169
 ret_contains_end:
Packit 8ea169
    if (out_fp)
Packit 8ea169
        *out_fp = fp;
Packit 8ea169
    else if (fp)
Packit 8ea169
        fclose(fp);
Packit 8ea169
Packit 8ea169
    return found;
Packit 8ea169
}
Packit 8ea169
Packit 8ea169
static void ignored_problems_add_row(ignored_problems_t *set, const char *problem_id,
Packit 8ea169
        const char *uuid, const char *duphash)
Packit 8ea169
{
Packit 8ea169
    log_notice("Going to add problem '%s' to ignored problems", problem_id);
Packit 8ea169
Packit 8ea169
    FILE *fp;
Packit 8ea169
    if (!ignored_problems_file_contains(set, problem_id, uuid, duphash, &fp, "a+"))
Packit 8ea169
    {
Packit 8ea169
        if (fp)
Packit 8ea169
        {
Packit 8ea169
            /* We can add write error checks here.
Packit 8ea169
             * However, what exactly can we *do* if we detect it?
Packit 8ea169
             */
Packit 8ea169
            fprintf(fp, "%s;%s;%s\n", problem_id, (uuid ? uuid : ""),
Packit 8ea169
                                      (duphash ? duphash : ""));
Packit 8ea169
        }
Packit 8ea169
        else
Packit 8ea169
        {
Packit 8ea169
            /* This is not a fatal problem. We are permissive because we don't want
Packit 8ea169
             * to scare users by strange error messages.
Packit 8ea169
             */
Packit 8ea169
            log_notice("Can't add problem '%s' to ignored problems:"
Packit 8ea169
                      " can't open the list", problem_id);
Packit 8ea169
        }
Packit 8ea169
    }
Packit 8ea169
    else
Packit 8ea169
    {
Packit 8ea169
        log_notice("Won't add problem '%s' to ignored problems:"
Packit 8ea169
                " it is already there", problem_id);
Packit 8ea169
    }
Packit 8ea169
Packit 8ea169
    if (fp)
Packit 8ea169
        fclose(fp);
Packit 8ea169
}
Packit 8ea169
Packit 8ea169
void ignored_problems_add_problem_data(ignored_problems_t *set, problem_data_t *pd)
Packit 8ea169
{
Packit 8ea169
    ignored_problems_add_row(set,
Packit 8ea169
            problem_data_get_content_or_NULL(pd, CD_DUMPDIR),
Packit 8ea169
            problem_data_get_content_or_NULL(pd, FILENAME_UUID),
Packit 8ea169
            problem_data_get_content_or_NULL(pd, FILENAME_DUPHASH)
Packit 8ea169
            );
Packit 8ea169
}
Packit 8ea169
Packit 8ea169
void ignored_problems_add(ignored_problems_t *set, const char *problem_id)
Packit 8ea169
{
Packit 8ea169
    struct dump_dir *dd = dd_opendir(problem_id, IGN_DD_OPEN_FLAGS);
Packit 8ea169
    if (!dd)
Packit 8ea169
    {
Packit 8ea169
        /* We do not consider this as an error because the directory can be
Packit 8ea169
         * deleted by other programs. This code expects that dd_opendir()
Packit 8ea169
         * already emitted good explanatory message. This message
Packit 8ea169
         * explains what the previous failure causes.
Packit 8ea169
         */
Packit 8ea169
        VERB1 log_warning("Can't add problem '%s' to ignored problems:"
Packit 8ea169
                " can't open the problem", problem_id);
Packit 8ea169
        return;
Packit 8ea169
    }
Packit 8ea169
    char *uuid = dd_load_text_ext(dd, FILENAME_UUID, IGN_DD_LOAD_TEXT_FLAGS);
Packit 8ea169
    char *duphash = dd_load_text_ext(dd, FILENAME_DUPHASH, IGN_DD_LOAD_TEXT_FLAGS);
Packit 8ea169
    dd_close(dd);
Packit 8ea169
Packit 8ea169
    ignored_problems_add_row(set, problem_id, uuid, duphash);
Packit 8ea169
Packit 8ea169
    free(duphash);
Packit 8ea169
    free(uuid);
Packit 8ea169
}
Packit 8ea169
Packit 8ea169
void ignored_problems_remove_row(ignored_problems_t *set, const char *problem_id,
Packit 8ea169
        const char *uuid, const char *duphash)
Packit 8ea169
{
Packit 8ea169
    INITIALIZE_LIBABRT();
Packit 8ea169
Packit 8ea169
    VERB1 log_warning("Going to remove problem '%s' from ignored problems", problem_id);
Packit 8ea169
Packit 8ea169
    FILE *orig_fp;
Packit 8ea169
    if (!ignored_problems_file_contains(set, problem_id, uuid, duphash, &orig_fp, "r"))
Packit 8ea169
    {
Packit 8ea169
        if (orig_fp)
Packit 8ea169
        {
Packit 8ea169
            log_notice("Won't remove problem '%s' from ignored problems:"
Packit 8ea169
                      " it is already removed", problem_id);
Packit 8ea169
            /* Close orig_fp here because it looks like much simpler than
Packit 8ea169
             * exetendig the set of goto labels at the end of this function */
Packit 8ea169
            fclose(orig_fp);
Packit 8ea169
        }
Packit 8ea169
        else
Packit 8ea169
        {
Packit 8ea169
            /* This is not a fatal problem. We are permissive because we don't want
Packit 8ea169
             * to scare users by strange error messages.
Packit 8ea169
             */
Packit 8ea169
            log_notice("Can't remove problem '%s' from ignored problems:"
Packit 8ea169
                      " can't open the list", problem_id);
Packit 8ea169
        }
Packit 8ea169
        return;
Packit 8ea169
    }
Packit 8ea169
Packit 8ea169
    /* orig_fp must be valid here because if ignored_problems_file_contains()
Packit 8ea169
     * returned TRUE the function ensures that orig_fp is set to a valid FILE*.
Packit 8ea169
     *
Packit 8ea169
     * But the function moved the file position indicator.
Packit 8ea169
     */
Packit 8ea169
    rewind(orig_fp);
Packit 8ea169
Packit 8ea169
    char *new_tempfile_name = xasprintf("%s.XXXXXX", set->ign_set_file_path);
Packit 8ea169
    int new_tempfile_fd = mkstemp(new_tempfile_name);
Packit 8ea169
    if (new_tempfile_fd < 0)
Packit 8ea169
    {
Packit 8ea169
        perror_msg(_("Can't create temporary file '%s'"), set->ign_set_file_path);
Packit 8ea169
        goto ret_close_files;
Packit 8ea169
    }
Packit 8ea169
Packit 8ea169
    unsigned line_num = 0;
Packit 8ea169
    char *line;
Packit 8ea169
    while ((line = xmalloc_fgetline(orig_fp)) != NULL)
Packit 8ea169
    {
Packit 8ea169
        ++line_num;
Packit 8ea169
        if (!ignored_problems_eq(set, problem_id, uuid, duphash, line, line_num))
Packit 8ea169
        {
Packit 8ea169
            ssize_t len = strlen(line);
Packit 8ea169
            line[len] = '\n';
Packit 8ea169
            if (full_write(new_tempfile_fd, line, len + 1) < 0)
Packit 8ea169
            {
Packit 8ea169
                /* Probably out of space */
Packit 8ea169
                line[len] = '\0';
Packit 8ea169
                perror_msg(_("Can't write to '%s'."
Packit 8ea169
                        " Problem '%s' will not be removed from the ignored"
Packit 8ea169
                        " problems '%s'"),
Packit 8ea169
                        new_tempfile_name, problem_id, set->ign_set_file_path);
Packit 8ea169
                free(line);
Packit 8ea169
                goto ret_unlink_new;
Packit 8ea169
            }
Packit 8ea169
        }
Packit 8ea169
        free(line);
Packit 8ea169
    }
Packit 8ea169
Packit 8ea169
    if (rename(new_tempfile_name, set->ign_set_file_path) < 0)
Packit 8ea169
    {
Packit 8ea169
        /* Something nefarious happened */
Packit 8ea169
        perror_msg(_("Can't rename '%s' to '%s'. Failed to remove problem '%s'"),
Packit 8ea169
                set->ign_set_file_path, new_tempfile_name, problem_id);
Packit 8ea169
 ret_unlink_new:
Packit 8ea169
        unlink(new_tempfile_name);
Packit 8ea169
    }
Packit 8ea169
Packit 8ea169
 ret_close_files:
Packit 8ea169
    fclose(orig_fp);
Packit 8ea169
    if (new_tempfile_fd >= 0)
Packit 8ea169
        close(new_tempfile_fd);
Packit 8ea169
    free(new_tempfile_name);
Packit 8ea169
Packit 8ea169
}
Packit 8ea169
Packit 8ea169
void ignored_problems_remove_problem_data(ignored_problems_t *set, problem_data_t *pd)
Packit 8ea169
{
Packit 8ea169
    ignored_problems_remove_row(set,
Packit 8ea169
            problem_data_get_content_or_NULL(pd, CD_DUMPDIR),
Packit 8ea169
            problem_data_get_content_or_NULL(pd, FILENAME_UUID),
Packit 8ea169
            problem_data_get_content_or_NULL(pd, FILENAME_DUPHASH)
Packit 8ea169
            );
Packit 8ea169
}
Packit 8ea169
Packit 8ea169
void ignored_problems_remove(ignored_problems_t *set, const char *problem_id)
Packit 8ea169
{
Packit 8ea169
    char *uuid = NULL;
Packit 8ea169
    char *duphash = NULL;
Packit 8ea169
    struct dump_dir *dd = dd_opendir(problem_id, IGN_DD_OPEN_FLAGS);
Packit 8ea169
    if (dd)
Packit 8ea169
    {
Packit 8ea169
        uuid = dd_load_text_ext(dd, FILENAME_UUID, IGN_DD_LOAD_TEXT_FLAGS);
Packit 8ea169
        duphash = dd_load_text_ext(dd, FILENAME_DUPHASH, IGN_DD_LOAD_TEXT_FLAGS);
Packit 8ea169
        dd_close(dd);
Packit 8ea169
    }
Packit 8ea169
    else
Packit 8ea169
    {
Packit 8ea169
        /* We do not consider this as an error because the directory can be
Packit 8ea169
         * deleted by other programs. This code expects that dd_opendir()
Packit 8ea169
         * already emitted good explanatory message. This message
Packit 8ea169
         * explains what the previous failure causes.
Packit 8ea169
         */
Packit 8ea169
        VERB1 error_msg("Can't get UUID/DUPHASH from"
Packit 8ea169
                " '%s' to remove it from the ignored problems:"
Packit 8ea169
                " can't open the problem", problem_id);
Packit 8ea169
    }
Packit 8ea169
Packit 8ea169
    ignored_problems_remove_row(set, problem_id, uuid, duphash);
Packit 8ea169
Packit 8ea169
    free(duphash);
Packit 8ea169
    free(uuid);
Packit 8ea169
}
Packit 8ea169
Packit 8ea169
bool ignored_problems_contains_problem_data(ignored_problems_t *set, problem_data_t *pd)
Packit 8ea169
{
Packit 8ea169
    return ignored_problems_file_contains(set,
Packit 8ea169
            problem_data_get_content_or_NULL(pd, CD_DUMPDIR),
Packit 8ea169
            problem_data_get_content_or_NULL(pd, FILENAME_UUID),
Packit 8ea169
            problem_data_get_content_or_NULL(pd, FILENAME_DUPHASH),
Packit 8ea169
            /* (FILE **) */NULL, "r"
Packit 8ea169
            );
Packit 8ea169
}
Packit 8ea169
Packit 8ea169
bool ignored_problems_contains(ignored_problems_t *set, const char *problem_id)
Packit 8ea169
{
Packit 8ea169
    struct dump_dir *dd = dd_opendir(problem_id, IGN_DD_OPEN_FLAGS);
Packit 8ea169
    if (!dd)
Packit 8ea169
    {
Packit 8ea169
        /* We do not consider this as an error because the directory can be
Packit 8ea169
         * deleted by other programs. This code expects that dd_opendir()
Packit 8ea169
         * already emitted good and explanatory message. This message attempts
Packit 8ea169
         * to explain what the previous failure causes.
Packit 8ea169
         */
Packit 8ea169
        VERB1 error_msg("Can't open '%s'."
Packit 8ea169
                " Won't try to check whether it belongs to ignored problems",
Packit 8ea169
                problem_id);
Packit 8ea169
        return false;
Packit 8ea169
    }
Packit 8ea169
    char *uuid = dd_load_text_ext(dd, FILENAME_UUID, IGN_DD_LOAD_TEXT_FLAGS);
Packit 8ea169
    char *duphash = dd_load_text_ext(dd, FILENAME_DUPHASH, IGN_DD_LOAD_TEXT_FLAGS);
Packit 8ea169
    dd_close(dd);
Packit 8ea169
Packit 8ea169
    log_notice("Going to check if problem '%s' is in ignored problems '%s'",
Packit 8ea169
            problem_id, set->ign_set_file_path);
Packit 8ea169
Packit 8ea169
    bool found = ignored_problems_file_contains(set, problem_id, uuid, duphash,
Packit 8ea169
                    /* (FILE **) */NULL, "r");
Packit 8ea169
Packit 8ea169
    free(duphash);
Packit 8ea169
    free(uuid);
Packit 8ea169
Packit 8ea169
    return found;
Packit 8ea169
}