/*
Copyright (C) 2014 ABRT team
Copyright (C) 2014 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 "problem_report.h"
#include "internal_libreport.h"
#include <satyr/stacktrace.h>
#include <satyr/thread.h>
#include <satyr/abrt.h>
#include <assert.h>
#define DESTROYED_POINTER (void *)0xdeadbeef
/* FORMAT:
* |%summary:: Hello, world
* |Problem description:: %bare_comment
* |
* |Package:: package
* |
* |%attach: %binary, backtrace
* |
* |%additional_info::
* |%reporter%
* |User:: user_name,uid
* |
* |Directories:: root,cwd
*
* PARSED DATA (list of struct section_t):
* {
* section_t {
* .name = '%summary';
* .items = { 'Hello, world' };
* .children = NULL;
* },
* section_t {
* .name = '%attach'
* .items = { '%binary', 'backtrace' };
* .children = NULL;
* },
* section_t {
* .name = '%description'
* .items = NULL;
* .children = {
* section_t {
* .name = 'Problem description:';
* .items = { '%bare_comment' };
* .children = NULL;
* },
* section_t {
* .name = '';
* .items = NULL;
* .children = NULL;
* },
* section_t {
* .name = 'Package:';
* .items = { 'package' };
* .children = NULL;
* },
* }
* },
* section_t {
* .name = '%additional_info'
* .items = { '%reporter%' };
* .children = {
* section_t {
* .name = 'User:';
* .items = { 'user_name', 'uid' };
* .children = NULL;
* },
* section_t {
* .name = '';
* .items = NULL;
* .children = NULL;
* },
* section_t {
* .name = 'Directories:';
* .items = { 'root', 'cwd' };
* .children = NULL;
* },
* }
* }
* }
*/
struct section_t {
char *name; ///< name or output text (%summar, 'Package version:');
GList *items; ///< list of file names and special items (%reporter, %binar, ...)
GList *children; ///< list of sub sections (struct section_t)
};
typedef struct section_t section_t;
static section_t *
section_new(const char *name)
{
section_t *self = xmalloc(sizeof(*self));
self->name = xstrdup(name);
self->items = NULL;
self->children = NULL;
return self;
}
static void
section_free(section_t *self)
{
if (self == NULL)
return;
free(self->name);
g_list_free_full(self->items, free);
g_list_free_full(self->children, (GDestroyNotify)section_free);
free(self);
}
static int
section_name_cmp(section_t *lhs, const char *rhs)
{
return strcmp((lhs->name + 1), rhs);
}
/* Utility functions */
static GList*
split_string_on_char(const char *str, char ch)
{
GList *list = NULL;
for (;;)
{
const char *delim = strchrnul(str, ch);
list = g_list_prepend(list, xstrndup(str, delim - str));
if (*delim == '\0')
break;
str = delim + 1;
}
return g_list_reverse(list);
}
static int
compare_item_name(const char *lookup, const char *name)
{
if (lookup[0] == '-')
lookup++;
else if (strncmp(lookup, "%bare_", 6) == 0)
lookup += 6;
return strcmp(lookup, name);
}
static int
is_item_name_in_section(const section_t *lookup, const char *name)
{
if (g_list_find_custom(lookup->items, name, (GCompareFunc)compare_item_name))
return 0; /* "found it!" */
return 1;
}
static bool is_explicit_or_forbidden(const char *name, GList *comment_fmt_spec);
static int
is_explicit_or_forbidden_child(const section_t *master_section, const char *name)
{
if (is_explicit_or_forbidden(name, master_section->children))
return 0; /* "found it!" */
return 1;
}
/* For example: 'package' belongs to '%oneline', but 'package' is used in
* 'Version of component', so it is not very helpful to include that file once
* more in another section
*/
static bool
is_explicit_or_forbidden(const char *name, GList *comment_fmt_spec)
{
return g_list_find_custom(comment_fmt_spec, name, (GCompareFunc)is_item_name_in_section)
|| g_list_find_custom(comment_fmt_spec, name, (GCompareFunc)is_explicit_or_forbidden_child);
}
static GList*
load_stream(FILE *fp)
{
assert(fp);
GList *sections = NULL;
section_t *master = section_new("%description");
section_t *sec = NULL;
sections = g_list_append(sections, master);
char *line;
while ((line = xmalloc_fgetline(fp)) != NULL)
{
/* Skip comments */
char first = *skip_whitespace(line);
if (first == '#')
goto free_line;
/* Handle trailing backslash continuation */
check_continuation: ;
unsigned len = strlen(line);
if (len && line[len-1] == '\\')
{
line[len-1] = '\0';
char *next_line = xmalloc_fgetline(fp);
if (next_line)
{
line = append_to_malloced_string(line, next_line);
free(next_line);
goto check_continuation;
}
}
/* We are reusing line buffer to form temporary
* "key\0values\0..." in its beginning
*/
bool summary_line = false;
char *value = NULL;
char *src;
char *dst;
for (src = dst = line; *src; src++)
{
char c = *src;
/* did we reach the value list? */
if (!value && c == ':' && src[1] == ':')
{
*dst++ = '\0'; /* terminate key */
src += 1;
value = dst; /* remember where value starts */
summary_line = (strcmp(line, "%summary") == 0);
if (summary_line)
{
value = (src + 1);
break;
}
continue;
}
/* skip whitespace in value list */
if (value && isspace(c))
continue;
*dst++ = c; /* store next key or value char */
}
GList *item_list = NULL;
if (summary_line)
{
/* %summary is special */
item_list = g_list_append(NULL, xstrdup(skip_whitespace(value)));
}
else
{
*dst = '\0'; /* terminate value (or key) */
if (value)
item_list = split_string_on_char(value, ',');
}
sec = section_new(line);
sec->items = item_list;
if (sec->name[0] == '%')
{
if (!summary_line && strcmp(sec->name, "%attach") != 0)
{
master->children = g_list_reverse(master->children);
master = sec;
}
sections = g_list_prepend(sections, sec);
}
else
master->children = g_list_prepend(master->children, sec);
free_line:
free(line);
}
/* If master equals sec, then master's children list was not yet reversed.
*
* %description is the default section (i.e is not explicitly mentioned)
* and %summary nor %attach cause its children list to reverse.
*/
if (master == sec || strcmp(master->name, "%description") == 0)
master->children = g_list_reverse(master->children);
return sections;
}
/* Summary generation */
#define MAX_OPT_DEPTH 10
static int
format_percented_string(const char *str, problem_data_t *pd, FILE *result)
{
long old_pos[MAX_OPT_DEPTH] = { 0 };
int okay[MAX_OPT_DEPTH] = { 1 };
long len = 0;
int opt_depth = 1;
while (*str) {
switch (*str) {
default:
putc(*str, result);
len++;
str++;
break;
case '\\':
if (str[1])
str++;
putc(*str, result);
len++;
str++;
break;
case '[':
if (str[1] == '[' && opt_depth < MAX_OPT_DEPTH)
{
old_pos[opt_depth] = len;
okay[opt_depth] = 1;
opt_depth++;
str += 2;
} else {
putc(*str, result);
len++;
str++;
}
break;
case ']':
if (str[1] == ']' && opt_depth > 1)
{
opt_depth--;
if (!okay[opt_depth])
{
if (fseek(result, old_pos[opt_depth], SEEK_SET) < 0)
perror_msg_and_die("fseek");
len = old_pos[opt_depth];
}
str += 2;
} else {
putc(*str, result);
len++;
str++;
}
break;
case '%': ;
char *nextpercent = strchr(++str, '%');
if (!nextpercent)
{
error_msg_and_die("Unterminated %%element%%: '%s'", str - 1);
}
*nextpercent = '\0';
const problem_item *item = problem_data_get_item_or_NULL(pd, str);
*nextpercent = '%';
if (item && (item->flags & CD_FLAG_TXT))
{
fputs(item->content, result);
len += strlen(item->content);
}
else
okay[opt_depth - 1] = 0;
str = nextpercent + 1;
break;
}
}
if (opt_depth > 1)
{
error_msg_and_die("Unbalanced [[ ]] bracket");
}
if (!okay[0])
{
error_msg("Undefined variable outside of [[ ]] bracket");
}
return 0;
}
/* BZ comment generation */
static int
append_text(struct strbuf *result, const char *item_name, const char *content, bool print_item_name)
{
char *eol = strchrnul(content, '\n');
if (eol[0] == '\0' || eol[1] == '\0')
{
/* one-liner */
int pad = 16 - (strlen(item_name) + 2);
if (pad < 0)
pad = 0;
if (print_item_name)
strbuf_append_strf(result,
eol[0] == '\0' ? "%s: %*s%s\n" : "%s: %*s%s",
item_name, pad, "", content
);
else
strbuf_append_strf(result,
eol[0] == '\0' ? "%s\n" : "%s",
content
);
}
else
{
/* multi-line item */
if (print_item_name)
strbuf_append_strf(result, "%s:\n", item_name);
for (;;)
{
eol = strchrnul(content, '\n');
strbuf_append_strf(result,
/* For %bare_multiline_item, we don't want to print colons */
(print_item_name ? ":%.*s\n" : "%.*s\n"),
(int)(eol - content), content
);
if (eol[0] == '\0' || eol[1] == '\0')
break;
content = eol + 1;
}
}
return 1;
}
static int
append_short_backtrace(struct strbuf *result, problem_data_t *problem_data, bool print_item_name, problem_report_settings_t *settings)
{
const problem_item *backtrace_item = problem_data_get_item_or_NULL(problem_data,
FILENAME_BACKTRACE);
const problem_item *core_stacktrace_item = NULL;
if (!backtrace_item || !(backtrace_item->flags & CD_FLAG_TXT))
{
backtrace_item = NULL;
core_stacktrace_item = problem_data_get_item_or_NULL(problem_data,
FILENAME_CORE_BACKTRACE);
if (!core_stacktrace_item || !(core_stacktrace_item->flags & CD_FLAG_TXT))
return 0;
}
char *truncated = NULL;
if (core_stacktrace_item || strlen(backtrace_item->content) >= settings->prs_shortbt_max_text_size)
{
log_debug("'backtrace' exceeds the text file size, going to append its short version");
char *error_msg = NULL;
const char *type = problem_data_get_content_or_NULL(problem_data, FILENAME_TYPE);
if (!type)
{
log_debug("Problem data does not contain '"FILENAME_TYPE"' file");
return 0;
}
/* For CCpp crashes, use the GDB-produced backtrace which should be
* available by now. sr_abrt_type_from_type returns SR_REPORT_CORE
* by default for CCpp crashes.
*/
enum sr_report_type report_type = sr_abrt_type_from_type(type);
if (backtrace_item && strcmp(type, "CCpp") == 0)
{
log_debug("Successfully identified 'CCpp' abrt type");
report_type = SR_REPORT_GDB;
}
const char *content = backtrace_item ? backtrace_item->content : core_stacktrace_item->content;
struct sr_stacktrace *backtrace = sr_stacktrace_parse(report_type, content, &error_msg);
if (!backtrace)
{
log_warning(_("Can't parse backtrace: %s"), error_msg);
free(error_msg);
return 0;
}
/* normalize */
struct sr_thread *thread = sr_stacktrace_find_crash_thread(backtrace);
sr_thread_normalize(thread);
/* Get optimized thread stack trace for max_frames top most frames */
truncated = sr_stacktrace_to_short_text(backtrace, settings->prs_shortbt_max_frames);
sr_stacktrace_free(backtrace);
if (!truncated)
{
log_warning(_("Can't generate stacktrace description (no crash thread?)"));
return 0;
}
}
else
{
log_debug("'backtrace' is small enough to be included as is");
}
/* full item content */
append_text(result,
/*item_name:*/ truncated ? "truncated_backtrace" : FILENAME_BACKTRACE,
/*content:*/ truncated ? truncated : backtrace_item->content,
print_item_name
);
free(truncated);
return 1;
}
static int
append_item(struct strbuf *result, const char *item_name, problem_data_t *pd, GList *comment_fmt_spec, problem_report_settings_t *settings)
{
bool print_item_name = (strncmp(item_name, "%bare_", strlen("%bare_")) != 0);
if (!print_item_name)
item_name += strlen("%bare_");
if (item_name[0] != '%')
{
struct problem_item *item = problem_data_get_item_or_NULL(pd, item_name);
if (!item)
return 0; /* "I did not print anything" */
if (!(item->flags & CD_FLAG_TXT))
return 0; /* "I did not print anything" */
char *formatted = problem_item_format(item);
char *content = formatted ? formatted : item->content;
append_text(result, item_name, content, print_item_name);
free(formatted);
return 1; /* "I printed something" */
}
/* Special item name */
/* Compat with previously-existed ad-hockery: %short_backtrace */
if (strcmp(item_name, "%short_backtrace") == 0)
return append_short_backtrace(result, pd, print_item_name, settings);
/* Compat with previously-existed ad-hockery: %reporter */
if (strcmp(item_name, "%reporter") == 0)
return append_text(result, "reporter", PACKAGE"-"VERSION, print_item_name);
/* %oneline,%multiline,%text */
bool oneline = (strcmp(item_name+1, "oneline" ) == 0);
bool multiline = (strcmp(item_name+1, "multiline") == 0);
bool text = (strcmp(item_name+1, "text" ) == 0);
if (!oneline && !multiline && !text)
{
log_warning("Unknown or unsupported element specifier '%s'", item_name);
return 0; /* "I did not print anything" */
}
int printed = 0;
/* Iterate over _sorted_ items */
GList *sorted_names = g_hash_table_get_keys(pd);
sorted_names = g_list_sort(sorted_names, (GCompareFunc)strcmp);
/* %text => do as if %oneline, then repeat as if %multiline */
if (text)
oneline = 1;
again: ;
GList *l = sorted_names;
while (l)
{
const char *name = l->data;
l = l->next;
struct problem_item *item = g_hash_table_lookup(pd, name);
if (!item)
continue; /* paranoia, won't happen */
if (!(item->flags & CD_FLAG_TXT))
continue;
if (is_explicit_or_forbidden(name, comment_fmt_spec))
continue;
char *formatted = problem_item_format(item);
char *content = formatted ? formatted : item->content;
char *eol = strchrnul(content, '\n');
bool is_oneline = (eol[0] == '\0' || eol[1] == '\0');
if (oneline == is_oneline)
printed |= append_text(result, name, content, print_item_name);
free(formatted);
}
if (text && oneline)
{
/* %text, and we just did %oneline. Repeat as if %multiline */
oneline = 0;
/*multiline = 1; - not checked in fact, so why bother setting? */
goto again;
}
g_list_free(sorted_names); /* names themselves are not freed */
return printed;
}
#define add_to_section_output(format, ...) \
do { \
for (; empty_lines > 0; --empty_lines) fputc('\n', result); \
empty_lines = 0; \
fprintf(result, format, __VA_ARGS__); \
} while (0)
static void
format_section(section_t *section, problem_data_t *pd, GList *comment_fmt_spec, FILE *result, problem_report_settings_t *settings)
{
int empty_lines = -1;
for (GList *iter = section->children; iter; iter = g_list_next(iter))
{
section_t *child = (section_t *)iter->data;
if (child->items)
{
/* "Text: item[,item]..." */
struct strbuf *output = strbuf_new();
GList *item = child->items;
while (item)
{
const char *str = item->data;
item = item->next;
if (str[0] == '-') /* "-name", ignore it */
continue;
append_item(output, str, pd, comment_fmt_spec, settings);
}
if (output->len != 0)
add_to_section_output((child->name[0] ? "%s:\n%s" : "%s%s"),
child->name, output->buf);
strbuf_free(output);
}
else
{
/* Just "Text" (can be "") */
/* Filter out trailint empty lines */
if (child->name[0] != '\0')
add_to_section_output("%s\n", child->name);
/* Do not count empty lines, if output wasn't yet produced */
else if (empty_lines >= 0)
++empty_lines;
}
}
}
static GList *
get_special_items(const char *item_name, problem_data_t *pd, GList *comment_fmt_spec)
{
/* %oneline,%multiline,%text,%binary */
bool oneline = (strcmp(item_name+1, "oneline" ) == 0);
bool multiline = (strcmp(item_name+1, "multiline") == 0);
bool text = (strcmp(item_name+1, "text" ) == 0);
bool binary = (strcmp(item_name+1, "binary" ) == 0);
if (!oneline && !multiline && !text && !binary)
{
log_warning("Unknown or unsupported element specifier '%s'", item_name);
return NULL;
}
log_debug("Special item_name '%s', iterating for attach...", item_name);
GList *result = 0;
/* Iterate over _sorted_ items */
GList *sorted_names = g_hash_table_get_keys(pd);
sorted_names = g_list_sort(sorted_names, (GCompareFunc)strcmp);
GList *l = sorted_names;
while (l)
{
const char *name = l->data;
l = l->next;
struct problem_item *item = g_hash_table_lookup(pd, name);
if (!item)
continue; /* paranoia, won't happen */
if (is_explicit_or_forbidden(name, comment_fmt_spec))
continue;
if ((item->flags & CD_FLAG_TXT) && !binary)
{
char *content = item->content;
char *eol = strchrnul(content, '\n');
bool is_oneline = (eol[0] == '\0' || eol[1] == '\0');
if (text || oneline == is_oneline)
result = g_list_append(result, xstrdup(name));
}
else if ((item->flags & CD_FLAG_BIN) && binary)
result = g_list_append(result, xstrdup(name));
}
g_list_free(sorted_names); /* names themselves are not freed */
log_debug("...Done iterating over '%s' for attach", item_name);
return result;
}
static GList *
get_attached_files(problem_data_t *pd, GList *items, GList *comment_fmt_spec)
{
GList *result = NULL;
GList *item = items;
while (item != NULL)
{
const char *item_name = item->data;
item = item->next;
if (item_name[0] == '-') /* "-name", ignore it */
continue;
if (item_name[0] != '%')
{
result = g_list_append(result, xstrdup(item_name));
continue;
}
GList *special = get_special_items(item_name, pd, comment_fmt_spec);
if (special == NULL)
{
log_notice("No attachment found for '%s'", item_name);
continue;
}
result = g_list_concat(result, special);
}
return result;
}
/*
* Problem Report - memor stream
*
* A wrapper for POSIX memory stream.
*
* A memory stream is presented as FILE *.
*
* A memory stream is associated with a pointer to written data and a pointer
* to size of the written data.
*
* This structure holds all of the used pointers.
*/
struct memstream_buffer
{
char *msb_buffer;
size_t msb_size;
FILE *msb_stream;
};
static struct memstream_buffer *
memstream_buffer_new()
{
struct memstream_buffer *self = xmalloc(sizeof(*self));
self->msb_buffer = NULL;
self->msb_stream = open_memstream(&(self->msb_buffer), &(self->msb_size));
return self;
}
static void
memstream_buffer_free(struct memstream_buffer *self)
{
if (self == NULL)
return;
fclose(self->msb_stream);
self->msb_stream = DESTROYED_POINTER;
free(self->msb_buffer);
self->msb_buffer = DESTROYED_POINTER;
free(self);
}
static FILE *
memstream_get_stream(struct memstream_buffer *self)
{
assert(self != NULL);
return self->msb_stream;
}
static const char *
memstream_get_string(struct memstream_buffer *self)
{
assert(self != NULL);
assert(self->msb_stream != NULL);
fflush(self->msb_stream);
self->msb_buffer[self->msb_size] = '\0';
return self->msb_buffer;
}
/*
* Problem Report
*
* The formated strings are internaly stored in "buffer"s. If a programer wants
* to get a formated section data, a getter function extracts those data from
* the apropriate buffer and returns them in form of null-terminated string.
*
* Each section has own buffer.
*
* There are three common sections that are always present:
* 1. summary
* 2. description
* 3. attach
* Buffers of these sections has own structure member for the sake of
* efficiency.
*
* The custom sections hash their buffers stored in a map where key is a
* section's name and value is a section's buffer.
*
* Problem report provides the programers with the possibility to ammend
* formated output to any section buffer.
*/
struct problem_report
{
struct memstream_buffer *pr_sec_summ; ///< %summary buffer
struct memstream_buffer *pr_sec_desc; ///< %description buffer
GList *pr_attachments; ///< %attach - list of file names
GHashTable *pr_sec_custom; ///< map : %(custom section) -> buffer
};
static problem_report_t *
problem_report_new()
{
problem_report_t *self = xmalloc(sizeof(*self));
self->pr_sec_summ = memstream_buffer_new();
self->pr_sec_desc = memstream_buffer_new();
self->pr_attachments = NULL;
self->pr_sec_custom = NULL;
return self;
}
static void
problem_report_initialize_custom_sections(problem_report_t *self)
{
assert(self != NULL);
assert(self->pr_sec_custom == NULL);
self->pr_sec_custom = g_hash_table_new_full(g_str_hash, g_str_equal, free,
(GDestroyNotify)memstream_buffer_free);
}
static void
problem_report_destroy_custom_sections(problem_report_t *self)
{
assert(self != NULL);
assert(self->pr_sec_custom != NULL);
g_hash_table_destroy(self->pr_sec_custom);
}
static int
problem_report_add_custom_section(problem_report_t *self, const char *name)
{
assert(self != NULL);
if (self->pr_sec_custom == NULL)
{
problem_report_initialize_custom_sections(self);
}
if (problem_report_get_buffer(self, name))
{
log_warning("Custom section already exists : '%s'", name);
return -EEXIST;
}
log_debug("Problem report enriched with section : '%s'", name);
g_hash_table_insert(self->pr_sec_custom, xstrdup(name), memstream_buffer_new());
return 0;
}
static struct memstream_buffer *
problem_report_get_section_buffer(const problem_report_t *self, const char *section_name)
{
if (self->pr_sec_custom == NULL)
{
log_debug("Couldn't find section '%s': no custom section added", section_name);
return NULL;
}
return (struct memstream_buffer *)g_hash_table_lookup(self->pr_sec_custom, section_name);
}
problem_report_buffer *
problem_report_get_buffer(const problem_report_t *self, const char *section_name)
{
assert(self != NULL);
assert(section_name != NULL);
if (strcmp(PR_SEC_SUMMARY, section_name) == 0)
return memstream_get_stream(self->pr_sec_summ);
if (strcmp(PR_SEC_DESCRIPTION, section_name) == 0)
return memstream_get_stream(self->pr_sec_desc);
struct memstream_buffer *buf = problem_report_get_section_buffer(self, section_name);
return buf == NULL ? NULL : memstream_get_stream(buf);
}
const char *
problem_report_get_summary(const problem_report_t *self)
{
assert(self != NULL);
return memstream_get_string(self->pr_sec_summ);
}
const char *
problem_report_get_description(const problem_report_t *self)
{
assert(self != NULL);
return memstream_get_string(self->pr_sec_desc);
}
const char *
problem_report_get_section(const problem_report_t *self, const char *section_name)
{
assert(self != NULL);
assert(section_name);
struct memstream_buffer *buf = problem_report_get_section_buffer(self, section_name);
if (buf == NULL)
return NULL;
return memstream_get_string(buf);
}
static void
problem_report_set_attachments(problem_report_t *self, GList *attachments)
{
assert(self != NULL);
assert(self->pr_attachments == NULL);
self->pr_attachments = attachments;
}
GList *
problem_report_get_attachments(const problem_report_t *self)
{
assert(self != NULL);
return self->pr_attachments;
}
void
problem_report_free(problem_report_t *self)
{
if (self == NULL)
return;
memstream_buffer_free(self->pr_sec_summ);
self->pr_sec_summ = DESTROYED_POINTER;
memstream_buffer_free(self->pr_sec_desc);
self->pr_sec_desc = DESTROYED_POINTER;
g_list_free_full(self->pr_attachments, free);
self->pr_attachments = DESTROYED_POINTER;
if (self->pr_sec_custom)
{
problem_report_destroy_custom_sections(self);
self->pr_sec_custom = DESTROYED_POINTER;
}
free(self);
}
/*
* Problem Formatter - extra section
*/
struct extra_section
{
char *pfes_name; ///< name with % prefix
int pfes_flags; ///< whether is required or not
};
static struct extra_section *
extra_section_new(const char *name, int flags)
{
struct extra_section *self = xmalloc(sizeof(*self));
self->pfes_name = xstrdup(name);
self->pfes_flags = flags;
return self;
}
static void
extra_section_free(struct extra_section *self)
{
if (self == NULL)
return;
free(self->pfes_name);
self->pfes_name = DESTROYED_POINTER;
free(self);
}
static int
extra_section_name_cmp(struct extra_section *lhs, const char *rhs)
{
return strcmp(lhs->pfes_name, rhs);
}
/*
* Problem Formatter
*
* Holds parsed sections lists.
*/
struct problem_formatter
{
GList *pf_sections; ///< parsed sections (struct section_t)
GList *pf_extra_sections; ///< user configured sections (struct extra_section)
char *pf_default_summary; ///< default summary format
problem_report_settings_t pf_settings; ///< settings for report generating
};
static problem_report_settings_t
problem_report_settings_init(void)
{
problem_report_settings_t settings = {
.prs_shortbt_max_frames = 10,
.prs_shortbt_max_text_size = CD_TEXT_ATT_SIZE_BZ,
};
return settings;
}
problem_report_settings_t problem_formatter_get_settings(const problem_formatter_t *self)
{
return self->pf_settings;
}
void problem_formatter_set_settings(problem_formatter_t *self, problem_report_settings_t settings)
{
self->pf_settings = settings;
}
problem_formatter_t *
problem_formatter_new(void)
{
problem_formatter_t *self = xzalloc(sizeof(*self));
self->pf_default_summary = xstrdup("%reason%");
self->pf_settings = problem_report_settings_init();
return self;
}
void
problem_formatter_free(problem_formatter_t *self)
{
if (self == NULL)
return;
g_list_free_full(self->pf_sections, (GDestroyNotify)section_free);
self->pf_sections = DESTROYED_POINTER;
g_list_free_full(self->pf_extra_sections, (GDestroyNotify)extra_section_free);
self->pf_extra_sections = DESTROYED_POINTER;
free(self->pf_default_summary);
self->pf_default_summary = DESTROYED_POINTER;
free(self);
}
static int
problem_formatter_is_section_known(problem_formatter_t *self, const char *name)
{
return strcmp(name, "summary") == 0
|| strcmp(name, "attach") == 0
|| strcmp(name, "description") == 0
|| NULL != g_list_find_custom(self->pf_extra_sections, name, (GCompareFunc)extra_section_name_cmp);
}
// i.e additional_info -> no flags
int
problem_formatter_add_section(problem_formatter_t *self, const char *name, int flags)
{
/* Do not add already added sections */
if (problem_formatter_is_section_known(self, name))
{
log_debug("Extra section already exists : '%s' ", name);
return -EEXIST;
}
self->pf_extra_sections = g_list_prepend(self->pf_extra_sections,
extra_section_new(name, flags));
return 0;
}
// check format validity and produce warnings
static int
problem_formatter_validate(problem_formatter_t *self)
{
int retval = 0;
/* Go through all (struct extra_section)s and check whete those having flag
* PFFF_REQUIRED are present in the parsed (struct section_t)s.
*/
for (GList *iter = self->pf_extra_sections; iter; iter = g_list_next(iter))
{
struct extra_section *section = (struct extra_section *)iter->data;
log_debug("Validating extra section : '%s'", section->pfes_name);
if ( (PFFF_REQUIRED & section->pfes_flags)
&& NULL == g_list_find_custom(self->pf_sections, section->pfes_name, (GCompareFunc)section_name_cmp))
{
log_warning("Problem format misses required section : '%s'", section->pfes_name);
++retval;
}
}
/* Go through all the parsed (struct section_t)s check whether are all
* known, i.e. each section is either one of the common sections (summary,
* description, attach) or is present in the (struct extra_section)s.
*/
for (GList *iter = self->pf_sections; iter; iter = g_list_next(iter))
{
section_t *section = (section_t *)iter->data;
if (!problem_formatter_is_section_known(self, (section->name + 1)))
{
log_warning("Problem format contains unrecognized section : '%s'", section->name);
++retval;
}
}
return retval;
}
int
problem_formatter_load_string(problem_formatter_t *self, const char *fmt)
{
const size_t len = strlen(fmt);
if (len != 0)
{
FILE *fp = fmemopen((void *)fmt, len, "r");
if (fp == NULL)
{
error_msg("Not enough memory to open a stream for reading format string.");
return -ENOMEM;
}
self->pf_sections = load_stream(fp);
fclose(fp);
}
return problem_formatter_validate(self);
}
int
problem_formatter_load_file(problem_formatter_t *self, const char *path)
{
FILE *fp = stdin;
if (strcmp(path, "-") != 0)
{
fp = fopen(path, "r");
if (!fp)
return -ENOENT;
}
self->pf_sections = load_stream(fp);
if (fp != stdin)
fclose(fp);
return problem_formatter_validate(self);
}
// generates report
int
problem_formatter_generate_report(const problem_formatter_t *self, problem_data_t *data, problem_report_t **report)
{
problem_report_settings_t settings = problem_formatter_get_settings(self);
problem_report_t *pr = problem_report_new();
for (GList *iter = self->pf_extra_sections; iter; iter = g_list_next(iter))
problem_report_add_custom_section(pr, ((struct extra_section *)iter->data)->pfes_name);
bool has_summary = false;
for (GList *iter = self->pf_sections; iter; iter = g_list_next(iter))
{
section_t *section = (section_t *)iter->data;
/* %summary is something special */
if (strcmp(section->name, "%summary") == 0)
{
has_summary = true;
format_percented_string((const char *)section->items->data, data,
problem_report_get_buffer(pr, PR_SEC_SUMMARY));
}
/* %attach as well */
else if (strcmp(section->name, "%attach") == 0)
{
problem_report_set_attachments(pr, get_attached_files(data, section->items, self->pf_sections));
}
else /* %description or a custom section (e.g. %additional_info) */
{
FILE *buffer = problem_report_get_buffer(pr, section->name + 1);
if (buffer != NULL)
{
log_debug("Formatting section : '%s'", section->name);
format_section(section, data, self->pf_sections, buffer, &settings);
}
else
log_warning("Unsupported section '%s'", section->name);
}
}
if (!has_summary) {
log_debug("Problem format misses section '%%summary'. Using the default one : '%s'.",
self->pf_default_summary);
format_percented_string(self->pf_default_summary,
data, problem_report_get_buffer(pr, PR_SEC_SUMMARY));
}
*report = pr;
return 0;
}