diff --git a/SOURCES/1010-move-problem_report-to-plugins.patch b/SOURCES/1010-move-problem_report-to-plugins.patch index 8416726..8dbbe0a 100644 --- a/SOURCES/1010-move-problem_report-to-plugins.patch +++ b/SOURCES/1010-move-problem_report-to-plugins.patch @@ -7,23 +7,19 @@ 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 | 334 ------------ - src/lib/Makefile.am | 7 +- - src/lib/problem_report.c | 1210 ------------------------------------------ - src/plugins/Makefile.am | 24 +- - src/plugins/problem_report.c | 1210 ++++++++++++++++++++++++++++++++++++++++++ - src/plugins/problem_report.h | 334 ++++++++++++ - tests/testsuite.at | 2 +- - 9 files changed, 1568 insertions(+), 1556 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 + po/POTFILES.in | 2 +- + src/include/Makefile.am | 1 - + src/lib/Makefile.am | 7 ++----- + src/plugins/Makefile.am | 24 +++++++++++++++++++---- + src/{lib => plugins}/problem_report.c | 0 + src/{include => plugins}/problem_report.h | 0 + tests/testsuite.at | 2 +- + 7 files changed, 24 insertions(+), 12 deletions(-) + rename src/{lib => plugins}/problem_report.c (100%) + rename src/{include => plugins}/problem_report.h (100%) diff --git a/po/POTFILES.in b/po/POTFILES.in -index 9cf8f72..8b62b59 100644 +index ee1c22f..f8be3e3 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -25,11 +25,11 @@ src/lib/ureport.c @@ -51,348 +47,8 @@ index 4d8c6a5..7a76cf4 100644 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 f2d41d8..0000000 ---- a/src/include/problem_report.h -+++ /dev/null -@@ -1,334 +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. -- -- @brief API for formating of problem data -- -- These functions can be used to convert a problem data to its string -- representation. -- -- The output format can be parsed from a string: -- -- problem_formatter_t *formatter = problem_formatter_new(); -- problem_formatter_load_string(formatter, MY_FORMAT_STRING); -- -- or loaded from a file: -- -- problem_formatter_t *formatter = problem_formatter_new(); -- problem_formatter_load_file(formatter, MY_FORMAT_FILE); -- -- Once you have configured your formatter you can convert problem_data to -- problem_report by calling: -- -- problem_report_t *report; -- if (problem_formatter_generate_report(formatter, data, &report) != 0) -- errx(EXIT_FAILURE, "Problem data cannot be converted to problem report."); -- -- Now you can print the report: -- -- printf("Problem: %s\n", problem_report_get_summary()); -- printf("%s\n", problem_report_get_description()); -- -- puts("Problem attachments:"); -- for (GList *a = problem_report_get_attachments(pr); a != NULL; a = g_list_next(a)) -- printf(" %s\n", a->data); -- -- Format description: -- -- ---- -- %summary:: summary format -- %attach:: elemnt1[,element2]... -- section:: element1[,element2]... -- The literal text line to be added to report. -- ---- -- -- Summary format is a line of text, where %element% is replaced by -- text element's content, and [[...%element%...]] block is used only if -- %element% exists. [[...]] blocks can nest. -- -- Sections can be: -- - %summary: bug summary format string. -- -- - %attach: a list of elements to attach. -- -- - text, double colon (::) and the list of comma-separated elements. -- Text can be empty (":: elem1, elem2, elem3" works), -- in this case "Text:" header line will be omitted. -- -- - %description: this section is implicit and contains all text -- sections unless another section was specified (%summary and %attach -- are ignored when determining text section's placement) -- -- - every text element belongs to the last specified section (%summary -- and %attach sections are ignored). If no section was specified, -- the text element belogns to %description. -- -- - If none of elements exists, the section will not be created. -- -- - Empty lines are NOT ignored. -- -- Elements can be: -- - problem directory element names, which get formatted as -- : -- or -- : -- : -- : -- : -- -- - problem directory element names prefixed by "%bare_", -- which is formatted as-is, without ":" and colons -- -- - %oneline, %multiline, %text wildcards, which select all corresponding -- elements for output or attachment -- -- - %binary wildcard, valid only for %attach section, instructs to attach -- binary elements -- -- - problem directory element names prefixed by "-", -- which excludes given element from all wildcards -- -- - Nonexistent elements are silently ignored. -- -- You can add your own section: -- -- problem_formatter_t *formatter = problem_formatter_new(); -- problem_formatter_add_section(formatter, "additional_info", PFFF_REQUIRED); -- -- and then you can use the section in the formatting string: -- -- problem_formatter_load_string(formatter, -- "::comment\n" -- "%additional_info:: maps"); -- problem_formatter_generate_report(formatter, data, &report); -- -- printf("Problem: %s\n", problem_report_get_summary()); -- printf("%s\n", problem_report_get_description()); -- printf("Additional info: %s\n", problem_report_get_section(report, "additiona_info")); -- -- The lines above are equivalent to the following lines: -- -- printf("Problem: %s\n", problem_data_get_content_or_NULL(data, "reason")); -- printf("%s\n", problem_data_get_content_or_NULL(data, "comment")); -- printf("Additional info: %s\n", problem_data_get_content_or_NULL(data, "maps")); --*/ --#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 d41e543..80a7c4d 100644 +index 41a9591..8471f30 100644 --- a/src/lib/Makefile.am +++ b/src/lib/Makefile.am @@ -38,7 +38,6 @@ libreport_la_SOURCES = \ @@ -403,7 +59,7 @@ index d41e543..80a7c4d 100644 create_dump_dir.c \ abrt_types.c \ parse_release.c \ -@@ -80,7 +79,6 @@ libreport_la_CPPFLAGS = \ +@@ -81,7 +80,6 @@ libreport_la_CPPFLAGS = \ $(GLIB_CFLAGS) \ $(GOBJECT_CFLAGS) \ $(AUGEAS_CFLAGS) \ @@ -411,7 +67,7 @@ index d41e543..80a7c4d 100644 -D_GNU_SOURCE libreport_la_LDFLAGS = \ -ltar \ -@@ -90,8 +88,7 @@ libreport_la_LIBADD = \ +@@ -92,8 +90,7 @@ libreport_la_LIBADD = \ $(GLIB_LIBS) \ $(JOURNAL_LIBS) \ $(GOBJECT_LIBS) \ @@ -421,7 +77,7 @@ index d41e543..80a7c4d 100644 libreportconfdir = $(CONF_DIR) dist_libreportconf_DATA = \ -@@ -152,8 +149,8 @@ libreport_web_la_LIBADD = \ +@@ -154,8 +151,8 @@ libreport_web_la_LIBADD = \ $(PROXY_LIBS) \ $(LIBXML_LIBS) \ $(JSON_C_LIBS) \ @@ -431,1227 +87,11 @@ index d41e543..80a7c4d 100644 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 2bf5530..0000000 ---- a/src/lib/problem_report.c -+++ /dev/null -@@ -1,1210 +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]) -- { -- if (fseek(result, old_pos[opt_depth], SEEK_SET) < 0) -- perror_msg_and_die("fseek"); -- len = old_pos[opt_depth]; -- } -- str += 2; -- } else { -- putc(*str, result); -- len++; -- str++; -- } -- break; -- case '%': ; -- char *nextpercent = strchr(++str, '%'); -- if (!nextpercent) -- { -- error_msg_and_die("Unterminated %%element%%: '%s'", str - 1); -- } -- -- *nextpercent = '\0'; -- const problem_item *item = problem_data_get_item_or_NULL(pd, str); -- *nextpercent = '%'; -- -- if (item && (item->flags & CD_FLAG_TXT)) -- { -- fputs(item->content, result); -- len += strlen(item->content); -- } -- else -- okay[opt_depth - 1] = 0; -- str = nextpercent + 1; -- break; -- } -- } -- -- if (opt_depth > 1) -- { -- error_msg_and_die("Unbalanced [[ ]] bracket"); -- } -- -- if (!okay[0]) -- { -- error_msg("Undefined variable outside of [[ ]] bracket"); -- } -- -- return 0; --} -- --/* BZ comment generation */ -- --static int --append_text(struct strbuf *result, const char *item_name, const char *content, bool print_item_name) --{ -- char *eol = strchrnul(content, '\n'); -- if (eol[0] == '\0' || eol[1] == '\0') -- { -- /* one-liner */ -- int pad = 16 - (strlen(item_name) + 2); -- if (pad < 0) -- pad = 0; -- if (print_item_name) -- strbuf_append_strf(result, -- eol[0] == '\0' ? "%s: %*s%s\n" : "%s: %*s%s", -- item_name, pad, "", content -- ); -- else -- strbuf_append_strf(result, -- eol[0] == '\0' ? "%s\n" : "%s", -- content -- ); -- } -- else -- { -- /* multi-line item */ -- if (print_item_name) -- strbuf_append_strf(result, "%s:\n", item_name); -- for (;;) -- { -- eol = strchrnul(content, '\n'); -- strbuf_append_strf(result, -- /* For %bare_multiline_item, we don't want to print colons */ -- (print_item_name ? ":%.*s\n" : "%.*s\n"), -- (int)(eol - content), content -- ); -- if (eol[0] == '\0' || eol[1] == '\0') -- break; -- content = eol + 1; -- } -- } -- return 1; --} -- --static int --append_short_backtrace(struct strbuf *result, problem_data_t *problem_data, 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 *type = problem_data_get_content_or_NULL(problem_data, FILENAME_TYPE); -- if (!type) -- { -- log_debug("Problem data does not contain '"FILENAME_TYPE"' file"); -- return 0; -- } -- -- /* For CCpp crashes, use the GDB-produced backtrace which should be -- * available by now. sr_abrt_type_from_analyzer returns SR_REPORT_CORE -- * by default for CCpp crashes. -- */ -- enum sr_report_type report_type = sr_abrt_type_from_analyzer(type); -- if (strcmp(type, "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 27194a4..f4f94ff 100644 +index e7363de..dd474f9 100644 --- a/src/plugins/Makefile.am +++ b/src/plugins/Makefile.am -@@ -125,6 +125,17 @@ EXTRA_DIST = $(reporters_extra_dist) \ +@@ -133,6 +133,17 @@ EXTRA_DIST = $(reporters_extra_dist) \ $(DESTDIR)/$(DEBUG_INFO_DIR): $(mkdir_p) '$@' @@ -1669,7 +109,7 @@ index 27194a4..f4f94ff 100644 if BUILD_BUGZILLA reporter_bugzilla_SOURCES = \ reporter-bugzilla.c rhbz.c rhbz.h -@@ -146,7 +157,8 @@ reporter_bugzilla_LDADD = \ +@@ -154,7 +165,8 @@ reporter_bugzilla_LDADD = \ $(GLIB_LIBS) \ $(XMLRPC_LIBS) $(XMLRPC_CLIENT_LIBS) \ ../lib/libreport-web.la \ @@ -1679,7 +119,7 @@ index 27194a4..f4f94ff 100644 endif if BUILD_MANTISBT -@@ -169,7 +181,8 @@ reporter_mantisbt_CPPFLAGS = \ +@@ -177,7 +189,8 @@ reporter_mantisbt_CPPFLAGS = \ reporter_mantisbt_LDADD = \ $(GLIB_LIBS) \ ../lib/libreport-web.la \ @@ -1689,7 +129,7 @@ index 27194a4..f4f94ff 100644 endif reporter_rhtsupport_SOURCES = \ -@@ -196,7 +209,8 @@ reporter_rhtsupport_LDADD = \ +@@ -204,7 +217,8 @@ reporter_rhtsupport_LDADD = \ $(GLIB_LIBS) \ $(LIBXML_LIBS) \ ../lib/libreport-web.la \ @@ -1699,7 +139,7 @@ index 27194a4..f4f94ff 100644 reporter_upload_SOURCES = \ reporter-upload.c -@@ -255,7 +269,9 @@ reporter_mailx_CPPFLAGS = \ +@@ -263,7 +277,9 @@ reporter_mailx_CPPFLAGS = \ $(LIBREPORT_CFLAGS) \ -D_GNU_SOURCE reporter_mailx_LDADD = \ @@ -1710,1564 +150,16 @@ index 27194a4..f4f94ff 100644 reporter_print_SOURCES = \ reporter-print.c -diff --git a/src/plugins/problem_report.c b/src/plugins/problem_report.c -new file mode 100644 -index 0000000..2bf5530 ---- /dev/null -+++ b/src/plugins/problem_report.c -@@ -0,0 +1,1210 @@ -+/* -+ 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]) -+ { -+ if (fseek(result, old_pos[opt_depth], SEEK_SET) < 0) -+ perror_msg_and_die("fseek"); -+ len = old_pos[opt_depth]; -+ } -+ str += 2; -+ } else { -+ putc(*str, result); -+ len++; -+ str++; -+ } -+ break; -+ case '%': ; -+ char *nextpercent = strchr(++str, '%'); -+ if (!nextpercent) -+ { -+ error_msg_and_die("Unterminated %%element%%: '%s'", str - 1); -+ } -+ -+ *nextpercent = '\0'; -+ const problem_item *item = problem_data_get_item_or_NULL(pd, str); -+ *nextpercent = '%'; -+ -+ if (item && (item->flags & CD_FLAG_TXT)) -+ { -+ fputs(item->content, result); -+ len += strlen(item->content); -+ } -+ else -+ okay[opt_depth - 1] = 0; -+ str = nextpercent + 1; -+ break; -+ } -+ } -+ -+ if (opt_depth > 1) -+ { -+ error_msg_and_die("Unbalanced [[ ]] bracket"); -+ } -+ -+ if (!okay[0]) -+ { -+ error_msg("Undefined variable outside of [[ ]] bracket"); -+ } -+ -+ return 0; -+} -+ -+/* BZ comment generation */ -+ -+static int -+append_text(struct strbuf *result, const char *item_name, const char *content, bool print_item_name) -+{ -+ char *eol = strchrnul(content, '\n'); -+ if (eol[0] == '\0' || eol[1] == '\0') -+ { -+ /* one-liner */ -+ int pad = 16 - (strlen(item_name) + 2); -+ if (pad < 0) -+ pad = 0; -+ if (print_item_name) -+ strbuf_append_strf(result, -+ eol[0] == '\0' ? "%s: %*s%s\n" : "%s: %*s%s", -+ item_name, pad, "", content -+ ); -+ else -+ strbuf_append_strf(result, -+ eol[0] == '\0' ? "%s\n" : "%s", -+ content -+ ); -+ } -+ else -+ { -+ /* multi-line item */ -+ if (print_item_name) -+ strbuf_append_strf(result, "%s:\n", item_name); -+ for (;;) -+ { -+ eol = strchrnul(content, '\n'); -+ strbuf_append_strf(result, -+ /* For %bare_multiline_item, we don't want to print colons */ -+ (print_item_name ? ":%.*s\n" : "%.*s\n"), -+ (int)(eol - content), content -+ ); -+ if (eol[0] == '\0' || eol[1] == '\0') -+ break; -+ content = eol + 1; -+ } -+ } -+ return 1; -+} -+ -+static int -+append_short_backtrace(struct strbuf *result, problem_data_t *problem_data, 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 *type = problem_data_get_content_or_NULL(problem_data, FILENAME_TYPE); -+ if (!type) -+ { -+ log_debug("Problem data does not contain '"FILENAME_TYPE"' file"); -+ return 0; -+ } -+ -+ /* For CCpp crashes, use the GDB-produced backtrace which should be -+ * available by now. sr_abrt_type_from_analyzer returns SR_REPORT_CORE -+ * by default for CCpp crashes. -+ */ -+ enum sr_report_type report_type = sr_abrt_type_from_analyzer(type); -+ if (strcmp(type, "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..f2d41d8 ---- /dev/null -+++ b/src/plugins/problem_report.h -@@ -0,0 +1,334 @@ -+/* -+ 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. -+ -+ @brief API for formating of problem data -+ -+ These functions can be used to convert a problem data to its string -+ representation. -+ -+ The output format can be parsed from a string: -+ -+ problem_formatter_t *formatter = problem_formatter_new(); -+ problem_formatter_load_string(formatter, MY_FORMAT_STRING); -+ -+ or loaded from a file: -+ -+ problem_formatter_t *formatter = problem_formatter_new(); -+ problem_formatter_load_file(formatter, MY_FORMAT_FILE); -+ -+ Once you have configured your formatter you can convert problem_data to -+ problem_report by calling: -+ -+ problem_report_t *report; -+ if (problem_formatter_generate_report(formatter, data, &report) != 0) -+ errx(EXIT_FAILURE, "Problem data cannot be converted to problem report."); -+ -+ Now you can print the report: -+ -+ printf("Problem: %s\n", problem_report_get_summary()); -+ printf("%s\n", problem_report_get_description()); -+ -+ puts("Problem attachments:"); -+ for (GList *a = problem_report_get_attachments(pr); a != NULL; a = g_list_next(a)) -+ printf(" %s\n", a->data); -+ -+ Format description: -+ -+ ---- -+ %summary:: summary format -+ %attach:: elemnt1[,element2]... -+ section:: element1[,element2]... -+ The literal text line to be added to report. -+ ---- -+ -+ Summary format is a line of text, where %element% is replaced by -+ text element's content, and [[...%element%...]] block is used only if -+ %element% exists. [[...]] blocks can nest. -+ -+ Sections can be: -+ - %summary: bug summary format string. -+ -+ - %attach: a list of elements to attach. -+ -+ - text, double colon (::) and the list of comma-separated elements. -+ Text can be empty (":: elem1, elem2, elem3" works), -+ in this case "Text:" header line will be omitted. -+ -+ - %description: this section is implicit and contains all text -+ sections unless another section was specified (%summary and %attach -+ are ignored when determining text section's placement) -+ -+ - every text element belongs to the last specified section (%summary -+ and %attach sections are ignored). If no section was specified, -+ the text element belogns to %description. -+ -+ - If none of elements exists, the section will not be created. -+ -+ - Empty lines are NOT ignored. -+ -+ Elements can be: -+ - problem directory element names, which get formatted as -+ : -+ or -+ : -+ : -+ : -+ : -+ -+ - problem directory element names prefixed by "%bare_", -+ which is formatted as-is, without ":" and colons -+ -+ - %oneline, %multiline, %text wildcards, which select all corresponding -+ elements for output or attachment -+ -+ - %binary wildcard, valid only for %attach section, instructs to attach -+ binary elements -+ -+ - problem directory element names prefixed by "-", -+ which excludes given element from all wildcards -+ -+ - Nonexistent elements are silently ignored. -+ -+ You can add your own section: -+ -+ problem_formatter_t *formatter = problem_formatter_new(); -+ problem_formatter_add_section(formatter, "additional_info", PFFF_REQUIRED); -+ -+ and then you can use the section in the formatting string: -+ -+ problem_formatter_load_string(formatter, -+ "::comment\n" -+ "%additional_info:: maps"); -+ problem_formatter_generate_report(formatter, data, &report); -+ -+ printf("Problem: %s\n", problem_report_get_summary()); -+ printf("%s\n", problem_report_get_description()); -+ printf("Additional info: %s\n", problem_report_get_section(report, "additiona_info")); -+ -+ The lines above are equivalent to the following lines: -+ -+ printf("Problem: %s\n", problem_data_get_content_or_NULL(data, "reason")); -+ printf("%s\n", problem_data_get_content_or_NULL(data, "comment")); -+ printf("Additional info: %s\n", problem_data_get_content_or_NULL(data, "maps")); -+*/ -+#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/problem_report.c b/src/plugins/problem_report.c +similarity index 100% +rename from src/lib/problem_report.c +rename to src/plugins/problem_report.c +diff --git a/src/include/problem_report.h b/src/plugins/problem_report.h +similarity index 100% +rename from src/include/problem_report.h +rename to src/plugins/problem_report.h diff --git a/tests/testsuite.at b/tests/testsuite.at -index ccb37d1..d4648d4 100644 +index 8ded735..31a440a 100644 --- a/tests/testsuite.at +++ b/tests/testsuite.at @@ -18,7 +18,7 @@ m4_include([report_python.at]) @@ -3280,5 +172,5 @@ index ccb37d1..d4648d4 100644 m4_include([global_config.at]) m4_include([iso_date.at]) -- -1.8.3.1 +2.18.2