diff --git a/SOURCES/1005-move-problem_report-to-plugins.patch b/SOURCES/1005-move-problem_report-to-plugins.patch new file mode 100644 index 0000000..c31be18 --- /dev/null +++ b/SOURCES/1005-move-problem_report-to-plugins.patch @@ -0,0 +1,3039 @@ +From d66138020b4a0e930a4bfd652cf92f35cdd3434f Mon Sep 17 00:00:00 2001 +From: Jakub Filak +Date: Wed, 25 Mar 2015 16:43:19 +0100 +Subject: [LIBREPORT PATCH] move problem_report to plugins + +Get rid of satyr from libreport. + +Signed-off-by: Jakub Filak +--- + po/POTFILES.in | 2 +- + src/include/Makefile.am | 1 - + src/include/problem_report.h | 225 -------- + src/lib/Makefile.am | 7 +- + src/lib/problem_report.c | 1209 ------------------------------------------ + src/plugins/Makefile.am | 17 +- + src/plugins/problem_report.c | 1209 ++++++++++++++++++++++++++++++++++++++++++ + src/plugins/problem_report.h | 225 ++++++++ + tests/testsuite.at | 2 +- + 9 files changed, 1453 insertions(+), 1444 deletions(-) + delete mode 100644 src/include/problem_report.h + delete mode 100644 src/lib/problem_report.c + create mode 100644 src/plugins/problem_report.c + create mode 100644 src/plugins/problem_report.h + +diff --git a/po/POTFILES.in b/po/POTFILES.in +index 3415b03..485b116 100644 +--- a/po/POTFILES.in ++++ b/po/POTFILES.in +@@ -23,10 +23,10 @@ src/lib/ureport.c + src/lib/make_descr.c + src/lib/parse_options.c + src/lib/problem_data.c +-src/lib/problem_report.c + src/lib/reporters.c + src/lib/run_event.c + src/plugins/abrt_rh_support.c ++src/plugins/problem_report.c + src/plugins/report_Bugzilla.xml.in + src/plugins/report.c + src/plugins/reporter-bugzilla.c +diff --git a/src/include/Makefile.am b/src/include/Makefile.am +index a13e04d..578dba8 100644 +--- a/src/include/Makefile.am ++++ b/src/include/Makefile.am +@@ -5,7 +5,6 @@ libreport_include_HEADERS = \ + dump_dir.h \ + event_config.h \ + problem_data.h \ +- problem_report.h \ + report.h \ + run_event.h \ + libreport_curl.h \ +diff --git a/src/include/problem_report.h b/src/include/problem_report.h +deleted file mode 100644 +index 30781e6..0000000 +--- a/src/include/problem_report.h ++++ /dev/null +@@ -1,225 +0,0 @@ +-/* +- 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. +-*/ +-#ifndef LIBREPORT_PROBLEM_REPORT_H +-#define LIBREPORT_PROBLEM_REPORT_H +- +-#include +-#include +-#include "problem_data.h" +- +-#ifdef __cplusplus +-extern "C" { +-#endif +- +-#define PR_SEC_SUMMARY "summary" +-#define PR_SEC_DESCRIPTION "description" +- +-/* +- * The problem report structure represents a problem data formatted according +- * to a format string. +- * +- * A problem report is composed of well-known sections: +- * - summary +- * - descritpion +- * - attach +- * +- * and custom sections accessed by: +- * problem_report_get_section(); +- */ +-struct problem_report; +-typedef struct problem_report problem_report_t; +- +-/* +- * Helpers for easily switching between FILE and struct strbuf +- */ +- +-/* +- * Type of buffer used by Problem report +- */ +-typedef FILE problem_report_buffer; +- +-/* +- * Wrapper for the proble buffer's formated output function. +- */ +-#define problem_report_buffer_printf(buf, fmt, ...)\ +- fprintf((buf), (fmt), ##__VA_ARGS__) +- +- +-/* +- * Get a section buffer +- * +- * Use this function if you need to amend something to a formatted section. +- * +- * @param self Problem report +- * @param section_name Name of required section +- * @return Always valid pointer to a section buffer +- */ +-problem_report_buffer *problem_report_get_buffer(const problem_report_t *self, +- const char *section_name); +- +-/* +- * Get Summary string +- * +- * The returned pointer is valid as long as you perform no further output to +- * the summary buffer. +- * +- * @param self Problem report +- * @return Non-NULL pointer to summary data +- */ +-const char *problem_report_get_summary(const problem_report_t *self); +- +-/* +- * Get Description string +- * +- * The returned pointer is valid as long as you perform no further output to +- * the description buffer. +- * +- * @param self Problem report +- * @return Non-NULL pointer to description data +- */ +-const char *problem_report_get_description(const problem_report_t *self); +- +-/* +- * Get Section's string +- * +- * The returned pointer is valid as long as you perform no further output to +- * the section's buffer. +- * +- * @param self Problem report +- * @param section_name Name of the required section +- * @return Non-NULL pointer to description data +- */ +-const char *problem_report_get_section(const problem_report_t *self, +- const char *section_name); +- +-/* +- * Get GList of the problem data items that are to be attached +- * +- * @param self Problem report +- * @return A pointer to GList (NULL means empty list) +- */ +-GList *problem_report_get_attachments(const problem_report_t *self); +- +-/* +- * Releases all resources allocated by a problem report +- * +- * @param self Problem report +- */ +-void problem_report_free(problem_report_t *self); +- +- +-/* +- * An enum of Extra section flags +- */ +-enum problem_formatter_section_flags { +- PFFF_REQUIRED = 1 << 0, ///< section must be present in the format spec +-}; +- +-/* +- * The problem formatter structure formats a problem data according to a format +- * string and stores result a problem report. +- * +- * The problem formatter uses '%reason%' as %summary section format string, if +- * %summary is not provided by a format string. +- */ +-struct problem_formatter; +-typedef struct problem_formatter problem_formatter_t; +- +-/* +- * Constructs a new problem formatter. +- * +- * @return Non-NULL pointer to the new problem formatter +- */ +-problem_formatter_t *problem_formatter_new(void); +- +-/* +- * Releases all resources allocated by a problem formatter +- * +- * @param self Problem formatter +- */ +-void problem_formatter_free(problem_formatter_t *self); +- +-/* +- * Adds a new recognized section +- * +- * The problem formatter ignores a section in the format spec if the section is +- * not one of the default nor added by this function. +- * +- * How the problem formatter handles these extra sections: +- * +- * A custom section is something like %description section. %description is the +- * default section where all text (sub)sections are stored. If the formatter +- * finds the custom section in format string, then starts storing text +- * (sub)sections in the custom section. +- * +- * (%description) |:: comment +- * (%description) | +- * (%description) |Package:: package +- * (%description) | +- * (%additiona_info) |%additional_info:: +- * (%additiona_info) |%reporter% +- * (%additiona_info) |User:: user_name,uid +- * (%additiona_info) | +- * (%additiona_info) |Directories:: root,cwd +- * +- * +- * @param self Problem formatter +- * @param name Name of the added section +- * @param flags Info about the added section +- * @return Zero on success. -EEXIST if the name is already known by the formatter +- */ +-int problem_formatter_add_section(problem_formatter_t *self, const char *name, int flags); +- +-/* +- * Loads a problem format from a string. +- * +- * @param self Problem formatter +- * @param fmt Format +- * @return Zero on success or number of warnings (e.g. missing section, +- * unrecognized section). +- */ +-int problem_formatter_load_string(problem_formatter_t* self, const char *fmt); +- +- +-/* +- * Loads a problem format from a file. +- * +- * @param self Problem formatter +- * @param pat Path to the format file +- * @return Zero on success or number of warnings (e.g. missing section, +- * unrecognized section). +- */ +-int problem_formatter_load_file(problem_formatter_t* self, const char *path); +- +-/* +- * Creates a new problem report, formats the data according to the loaded +- * format string and stores output in the report. +- * +- * @param self Problem formatter +- * @param data Problem data to format +- * @param report Pointer where the created problem report is to be stored +- * @return Zero on success, otherwise non-zero value. +- */ +-int problem_formatter_generate_report(const problem_formatter_t *self, problem_data_t *data, problem_report_t **report); +- +-#ifdef __cplusplus +-} +-#endif +- +-#endif // LIBREPORT_PROBLEM_REPORT_H +diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am +index 3ec463f..b99036c 100644 +--- a/src/lib/Makefile.am ++++ b/src/lib/Makefile.am +@@ -38,7 +38,6 @@ libreport_la_SOURCES = \ + make_descr.c \ + run_event.c \ + problem_data.c \ +- problem_report.c \ + create_dump_dir.c \ + abrt_types.c \ + parse_release.c \ +@@ -78,7 +77,6 @@ libreport_la_CPPFLAGS = \ + $(GLIB_CFLAGS) \ + $(GOBJECT_CFLAGS) \ + $(AUGEAS_CFLAGS) \ +- $(SATYR_CFLAGS) \ + -D_GNU_SOURCE + libreport_la_LDFLAGS = \ + -version-info 0:1:0 +@@ -87,8 +85,7 @@ libreport_la_LIBADD = \ + $(GLIB_LIBS) \ + $(JOURNAL_LIBS) \ + $(GOBJECT_LIBS) \ +- $(AUGEAS_LIBS) \ +- $(SATYR_LIBS) ++ $(AUGEAS_LIBS) + + libreportconfdir = $(CONF_DIR) + dist_libreportconf_DATA = \ +@@ -149,8 +146,8 @@ libreport_web_la_LIBADD = \ + $(PROXY_LIBS) \ + $(LIBXML_LIBS) \ + $(JSON_C_LIBS) \ +- $(SATYR_LIBS) \ + $(XMLRPC_LIBS) $(XMLRPC_CLIENT_LIBS) \ ++ $(SATYR_LIBS) \ + libreport.la + + DEFS = -DLOCALEDIR=\"$(localedir)\" @DEFS@ +diff --git a/src/lib/problem_report.c b/src/lib/problem_report.c +deleted file mode 100644 +index 0afc1ca..0000000 +--- a/src/lib/problem_report.c ++++ /dev/null +@@ -1,1209 +0,0 @@ +-/* +- 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 +- +-#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]) +- { +- fseek(result, old_pos[opt_depth], SEEK_SET); +- 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, size_t max_text_size, bool print_item_name) +-{ +- const problem_item *item = problem_data_get_item_or_NULL(problem_data, +- FILENAME_BACKTRACE); +- if (!item) +- return 0; /* "I did not print anything" */ +- if (!(item->flags & CD_FLAG_TXT)) +- return 0; /* "I did not print anything" */ +- +- char *truncated = NULL; +- +- if (strlen(item->content) >= max_text_size) +- { +- log_debug("'backtrace' exceeds the text file size, going to append its short version"); +- +- char *error_msg = NULL; +- const char *analyzer = problem_data_get_content_or_NULL(problem_data, FILENAME_ANALYZER); +- if (!analyzer) +- { +- log_debug("Problem data does not contain '"FILENAME_ANALYZER"' file"); +- return 0; +- } +- +- /* For CCpp crashes, use the GDB-produced backtrace which should be +- * available by now. sr_abrt_type_from_analyzer returns SR_REPORT_CORE +- * by default for CCpp crashes. +- */ +- enum sr_report_type report_type = sr_abrt_type_from_analyzer(analyzer); +- if (strcmp(analyzer, "CCpp") == 0) +- { +- log_debug("Successfully identified 'CCpp' abrt type"); +- report_type = SR_REPORT_GDB; +- } +- +- struct sr_stacktrace *backtrace = sr_stacktrace_parse(report_type, +- item->content, &error_msg); +- +- if (!backtrace) +- { +- log(_("Can't parse backtrace: %s"), error_msg); +- free(error_msg); +- return 0; +- } +- +- /* Get optimized thread stack trace for 10 top most frames */ +- truncated = sr_stacktrace_to_short_text(backtrace, 10); +- sr_stacktrace_free(backtrace); +- +- if (!truncated) +- { +- log(_("Can't generate stacktrace description (no crash thread?)")); +- return 0; +- } +- } +- else +- { +- log_debug("'backtrace' is small enough to be included as is"); +- } +- +- append_text(result, +- /*item_name:*/ truncated ? "truncated_backtrace" : FILENAME_BACKTRACE, +- /*content:*/ truncated ? truncated : 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) +-{ +- 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, CD_TEXT_ATT_SIZE_BZ, print_item_name); +- +- /* 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("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) +-{ +- 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); +- } +- +- 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("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); +- +- 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_formatter_t * +-problem_formatter_new(void) +-{ +- problem_formatter_t *self = xzalloc(sizeof(*self)); +- +- self->pf_default_summary = xstrdup("%reason%"); +- +- 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_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); +- } +- 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; +-} +diff --git a/src/plugins/Makefile.am b/src/plugins/Makefile.am +index 77423c4..fc2fd46 100644 +--- a/src/plugins/Makefile.am ++++ b/src/plugins/Makefile.am +@@ -123,6 +123,17 @@ EXTRA_DIST = $(reporters_extra_dist) \ + $(DESTDIR)/$(DEBUG_INFO_DIR): + $(mkdir_p) '$@' + ++noinst_LIBRARIES = libreport-problem-report.a ++libreport_problem_report_a_SOURCES = \ ++ problem_report.c \ ++ problem_report.h ++libreport_problem_report_a_CFLAGS = \ ++ -I$(srcdir)/../include \ ++ $(LIBREPORT_CFLAGS) \ ++ $(GLIB_CFLAGS) \ ++ $(SATYR_CFLAGS) \ ++ -D_GNU_SOURCE ++ + if BUILD_BUGZILLA + reporter_bugzilla_SOURCES = \ + reporter-bugzilla.c rhbz.c rhbz.h +@@ -144,7 +155,8 @@ reporter_bugzilla_LDADD = \ + $(GLIB_LIBS) \ + $(XMLRPC_LIBS) $(XMLRPC_CLIENT_LIBS) \ + ../lib/libreport-web.la \ +- ../lib/libreport.la ++ ../lib/libreport.la \ ++ libreport-problem-report.a + endif + + if BUILD_MANTISBT +@@ -167,7 +179,8 @@ reporter_mantisbt_CPPFLAGS = \ + reporter_mantisbt_LDADD = \ + $(GLIB_LIBS) \ + ../lib/libreport-web.la \ +- ../lib/libreport.la ++ ../lib/libreport.la \ ++ libreport-problem-report.a + endif + + reporter_rhtsupport_SOURCES = \ +diff --git a/src/plugins/problem_report.c b/src/plugins/problem_report.c +new file mode 100644 +index 0000000..0afc1ca +--- /dev/null ++++ b/src/plugins/problem_report.c +@@ -0,0 +1,1209 @@ ++/* ++ 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 ++ ++#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]) ++ { ++ fseek(result, old_pos[opt_depth], SEEK_SET); ++ 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, size_t max_text_size, bool print_item_name) ++{ ++ const problem_item *item = problem_data_get_item_or_NULL(problem_data, ++ FILENAME_BACKTRACE); ++ if (!item) ++ return 0; /* "I did not print anything" */ ++ if (!(item->flags & CD_FLAG_TXT)) ++ return 0; /* "I did not print anything" */ ++ ++ char *truncated = NULL; ++ ++ if (strlen(item->content) >= max_text_size) ++ { ++ log_debug("'backtrace' exceeds the text file size, going to append its short version"); ++ ++ char *error_msg = NULL; ++ const char *analyzer = problem_data_get_content_or_NULL(problem_data, FILENAME_ANALYZER); ++ if (!analyzer) ++ { ++ log_debug("Problem data does not contain '"FILENAME_ANALYZER"' file"); ++ return 0; ++ } ++ ++ /* For CCpp crashes, use the GDB-produced backtrace which should be ++ * available by now. sr_abrt_type_from_analyzer returns SR_REPORT_CORE ++ * by default for CCpp crashes. ++ */ ++ enum sr_report_type report_type = sr_abrt_type_from_analyzer(analyzer); ++ if (strcmp(analyzer, "CCpp") == 0) ++ { ++ log_debug("Successfully identified 'CCpp' abrt type"); ++ report_type = SR_REPORT_GDB; ++ } ++ ++ struct sr_stacktrace *backtrace = sr_stacktrace_parse(report_type, ++ item->content, &error_msg); ++ ++ if (!backtrace) ++ { ++ log(_("Can't parse backtrace: %s"), error_msg); ++ free(error_msg); ++ return 0; ++ } ++ ++ /* Get optimized thread stack trace for 10 top most frames */ ++ truncated = sr_stacktrace_to_short_text(backtrace, 10); ++ sr_stacktrace_free(backtrace); ++ ++ if (!truncated) ++ { ++ log(_("Can't generate stacktrace description (no crash thread?)")); ++ return 0; ++ } ++ } ++ else ++ { ++ log_debug("'backtrace' is small enough to be included as is"); ++ } ++ ++ append_text(result, ++ /*item_name:*/ truncated ? "truncated_backtrace" : FILENAME_BACKTRACE, ++ /*content:*/ truncated ? truncated : 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) ++{ ++ 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, CD_TEXT_ATT_SIZE_BZ, print_item_name); ++ ++ /* 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("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) ++{ ++ 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); ++ } ++ ++ 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("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); ++ ++ 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_formatter_t * ++problem_formatter_new(void) ++{ ++ problem_formatter_t *self = xzalloc(sizeof(*self)); ++ ++ self->pf_default_summary = xstrdup("%reason%"); ++ ++ 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_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); ++ } ++ 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; ++} +diff --git a/src/plugins/problem_report.h b/src/plugins/problem_report.h +new file mode 100644 +index 0000000..30781e6 +--- /dev/null ++++ b/src/plugins/problem_report.h +@@ -0,0 +1,225 @@ ++/* ++ 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. ++*/ ++#ifndef LIBREPORT_PROBLEM_REPORT_H ++#define LIBREPORT_PROBLEM_REPORT_H ++ ++#include ++#include ++#include "problem_data.h" ++ ++#ifdef __cplusplus ++extern "C" { ++#endif ++ ++#define PR_SEC_SUMMARY "summary" ++#define PR_SEC_DESCRIPTION "description" ++ ++/* ++ * The problem report structure represents a problem data formatted according ++ * to a format string. ++ * ++ * A problem report is composed of well-known sections: ++ * - summary ++ * - descritpion ++ * - attach ++ * ++ * and custom sections accessed by: ++ * problem_report_get_section(); ++ */ ++struct problem_report; ++typedef struct problem_report problem_report_t; ++ ++/* ++ * Helpers for easily switching between FILE and struct strbuf ++ */ ++ ++/* ++ * Type of buffer used by Problem report ++ */ ++typedef FILE problem_report_buffer; ++ ++/* ++ * Wrapper for the proble buffer's formated output function. ++ */ ++#define problem_report_buffer_printf(buf, fmt, ...)\ ++ fprintf((buf), (fmt), ##__VA_ARGS__) ++ ++ ++/* ++ * Get a section buffer ++ * ++ * Use this function if you need to amend something to a formatted section. ++ * ++ * @param self Problem report ++ * @param section_name Name of required section ++ * @return Always valid pointer to a section buffer ++ */ ++problem_report_buffer *problem_report_get_buffer(const problem_report_t *self, ++ const char *section_name); ++ ++/* ++ * Get Summary string ++ * ++ * The returned pointer is valid as long as you perform no further output to ++ * the summary buffer. ++ * ++ * @param self Problem report ++ * @return Non-NULL pointer to summary data ++ */ ++const char *problem_report_get_summary(const problem_report_t *self); ++ ++/* ++ * Get Description string ++ * ++ * The returned pointer is valid as long as you perform no further output to ++ * the description buffer. ++ * ++ * @param self Problem report ++ * @return Non-NULL pointer to description data ++ */ ++const char *problem_report_get_description(const problem_report_t *self); ++ ++/* ++ * Get Section's string ++ * ++ * The returned pointer is valid as long as you perform no further output to ++ * the section's buffer. ++ * ++ * @param self Problem report ++ * @param section_name Name of the required section ++ * @return Non-NULL pointer to description data ++ */ ++const char *problem_report_get_section(const problem_report_t *self, ++ const char *section_name); ++ ++/* ++ * Get GList of the problem data items that are to be attached ++ * ++ * @param self Problem report ++ * @return A pointer to GList (NULL means empty list) ++ */ ++GList *problem_report_get_attachments(const problem_report_t *self); ++ ++/* ++ * Releases all resources allocated by a problem report ++ * ++ * @param self Problem report ++ */ ++void problem_report_free(problem_report_t *self); ++ ++ ++/* ++ * An enum of Extra section flags ++ */ ++enum problem_formatter_section_flags { ++ PFFF_REQUIRED = 1 << 0, ///< section must be present in the format spec ++}; ++ ++/* ++ * The problem formatter structure formats a problem data according to a format ++ * string and stores result a problem report. ++ * ++ * The problem formatter uses '%reason%' as %summary section format string, if ++ * %summary is not provided by a format string. ++ */ ++struct problem_formatter; ++typedef struct problem_formatter problem_formatter_t; ++ ++/* ++ * Constructs a new problem formatter. ++ * ++ * @return Non-NULL pointer to the new problem formatter ++ */ ++problem_formatter_t *problem_formatter_new(void); ++ ++/* ++ * Releases all resources allocated by a problem formatter ++ * ++ * @param self Problem formatter ++ */ ++void problem_formatter_free(problem_formatter_t *self); ++ ++/* ++ * Adds a new recognized section ++ * ++ * The problem formatter ignores a section in the format spec if the section is ++ * not one of the default nor added by this function. ++ * ++ * How the problem formatter handles these extra sections: ++ * ++ * A custom section is something like %description section. %description is the ++ * default section where all text (sub)sections are stored. If the formatter ++ * finds the custom section in format string, then starts storing text ++ * (sub)sections in the custom section. ++ * ++ * (%description) |:: comment ++ * (%description) | ++ * (%description) |Package:: package ++ * (%description) | ++ * (%additiona_info) |%additional_info:: ++ * (%additiona_info) |%reporter% ++ * (%additiona_info) |User:: user_name,uid ++ * (%additiona_info) | ++ * (%additiona_info) |Directories:: root,cwd ++ * ++ * ++ * @param self Problem formatter ++ * @param name Name of the added section ++ * @param flags Info about the added section ++ * @return Zero on success. -EEXIST if the name is already known by the formatter ++ */ ++int problem_formatter_add_section(problem_formatter_t *self, const char *name, int flags); ++ ++/* ++ * Loads a problem format from a string. ++ * ++ * @param self Problem formatter ++ * @param fmt Format ++ * @return Zero on success or number of warnings (e.g. missing section, ++ * unrecognized section). ++ */ ++int problem_formatter_load_string(problem_formatter_t* self, const char *fmt); ++ ++ ++/* ++ * Loads a problem format from a file. ++ * ++ * @param self Problem formatter ++ * @param pat Path to the format file ++ * @return Zero on success or number of warnings (e.g. missing section, ++ * unrecognized section). ++ */ ++int problem_formatter_load_file(problem_formatter_t* self, const char *path); ++ ++/* ++ * Creates a new problem report, formats the data according to the loaded ++ * format string and stores output in the report. ++ * ++ * @param self Problem formatter ++ * @param data Problem data to format ++ * @param report Pointer where the created problem report is to be stored ++ * @return Zero on success, otherwise non-zero value. ++ */ ++int problem_formatter_generate_report(const problem_formatter_t *self, problem_data_t *data, problem_report_t **report); ++ ++#ifdef __cplusplus ++} ++#endif ++ ++#endif // LIBREPORT_PROBLEM_REPORT_H +diff --git a/tests/testsuite.at b/tests/testsuite.at +index 8c33b74..7261fe3 100644 +--- a/tests/testsuite.at ++++ b/tests/testsuite.at +@@ -17,4 +17,4 @@ m4_include([xml_definition.at]) + m4_include([report_python.at]) + m4_include([string_list.at]) + m4_include([ureport.at]) +-m4_include([problem_report.at]) ++# m4_include([problem_report.at]) +-- +1.8.3.1 + diff --git a/SPECS/libreport.spec b/SPECS/libreport.spec index 1e41e79..e6065fa 100644 --- a/SPECS/libreport.spec +++ b/SPECS/libreport.spec @@ -3,7 +3,6 @@ %{!?python_sitearch: %define python_sitearch %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib(1)")} %define satyr_ver 0.13-7 - Summary: Generic library for reporting various problems Name: libreport Version: 2.1.11 @@ -150,6 +149,7 @@ Patch134: 0134-conf-changed-URL-for-sending-uReport.patch Patch1000: 1000-reporter-mantisbt-first-version-of-the-reporter-mant.patch Patch1001: 1001-reporter-mantisbt-change-default-formating-file-for-.patch Patch1003: 1003-reporter-mantisbt-adds-man-pages-for-reporter-mantis.patch +Patch1005: 1005-move-problem_report-to-plugins.patch # git is need for '%%autosetup -S git' which automatically applies all the # patches above. Please, be aware that the patches must be generated @@ -592,7 +592,6 @@ gtk-update-icon-cache %{_datadir}/icons/hicolor &>/dev/null || : %{_includedir}/libreport/dump_dir.h %{_includedir}/libreport/event_config.h %{_includedir}/libreport/problem_data.h -%{_includedir}/libreport/problem_report.h %{_includedir}/libreport/report.h %{_includedir}/libreport/run_event.h %{_includedir}/libreport/file_obj.h