/* 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 #include #include #include #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; }