From d294305686573f49fa0ad1c14238ceb249c1d803 Mon Sep 17 00:00:00 2001 From: Jakub Filak Date: Mar 25 2015 18:20:46 +0000 Subject: get rid of unnecessary dependencies satyr pulls in tons of libraries and anaconda's process fails to load all of them due to glibc's hard limit for DTV slots This patch just moves problem_report.c from libreport to plugins. https://sourceware.org/ml/libc-alpha/2014-10/msg00134.html Signed-off-by: Jakub Filak --- 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