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