Blame SOURCES/0199-lib-add-Problem-Format-API.patch

4b6aa8
From 7d128880f67f1e80f6fcc703054583fb1a69d7fd Mon Sep 17 00:00:00 2001
4b6aa8
From: Matej Habrnal <mhabrnal@redhat.com>
4b6aa8
Date: Tue, 29 Mar 2016 15:15:28 +0200
4b6aa8
Subject: [PATCH] lib: add Problem Format API
4b6aa8
4b6aa8
Related to #1261358
4b6aa8
4b6aa8
Signed-off-by: Jakub Filak <jfilak@redhat.com>
4b6aa8
Signed-off-by: Matej Habrnal <mhabrnal@redhat.com>
4b6aa8
---
4b6aa8
 po/POTFILES.in               |    1 +
4b6aa8
 src/include/Makefile.am      |    1 +
4b6aa8
 src/include/problem_report.h |  334 ++++++++++++
4b6aa8
 src/lib/Makefile.am          |    5 +-
4b6aa8
 src/lib/problem_report.c     | 1209 ++++++++++++++++++++++++++++++++++++++++++
4b6aa8
 tests/Makefile.am            |    3 +-
4b6aa8
 tests/problem_report.at      |  690 ++++++++++++++++++++++++
4b6aa8
 tests/testsuite.at           |    1 +
4b6aa8
 8 files changed, 2242 insertions(+), 2 deletions(-)
4b6aa8
 create mode 100644 src/include/problem_report.h
4b6aa8
 create mode 100644 src/lib/problem_report.c
4b6aa8
 create mode 100644 tests/problem_report.at
4b6aa8
4b6aa8
diff --git a/po/POTFILES.in b/po/POTFILES.in
4b6aa8
index d843de1..4246e06 100644
4b6aa8
--- a/po/POTFILES.in
4b6aa8
+++ b/po/POTFILES.in
4b6aa8
@@ -25,6 +25,7 @@ src/lib/ureport.c
4b6aa8
 src/lib/make_descr.c
4b6aa8
 src/lib/parse_options.c
4b6aa8
 src/lib/problem_data.c
4b6aa8
+src/lib/problem_report.c
4b6aa8
 src/lib/reported_to.c
4b6aa8
 src/lib/run_event.c
4b6aa8
 src/plugins/abrt_rh_support.c
4b6aa8
diff --git a/src/include/Makefile.am b/src/include/Makefile.am
4b6aa8
index 062bffb..87e5e60 100644
4b6aa8
--- a/src/include/Makefile.am
4b6aa8
+++ b/src/include/Makefile.am
4b6aa8
@@ -5,6 +5,7 @@ libreport_include_HEADERS = \
4b6aa8
     dump_dir.h \
4b6aa8
     event_config.h \
4b6aa8
     problem_data.h \
4b6aa8
+    problem_report.h \
4b6aa8
     report.h \
4b6aa8
     run_event.h \
4b6aa8
     libreport_curl.h \
4b6aa8
diff --git a/src/include/problem_report.h b/src/include/problem_report.h
4b6aa8
new file mode 100644
4b6aa8
index 0000000..f2d41d8
4b6aa8
--- /dev/null
4b6aa8
+++ b/src/include/problem_report.h
4b6aa8
@@ -0,0 +1,334 @@
4b6aa8
+/*
4b6aa8
+    Copyright (C) 2014  ABRT team
4b6aa8
+    Copyright (C) 2014  RedHat Inc
4b6aa8
+
4b6aa8
+    This program is free software; you can redistribute it and/or modify
4b6aa8
+    it under the terms of the GNU General Public License as published by
4b6aa8
+    the Free Software Foundation; either version 2 of the License, or
4b6aa8
+    (at your option) any later version.
4b6aa8
+
4b6aa8
+    This program is distributed in the hope that it will be useful,
4b6aa8
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
4b6aa8
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
4b6aa8
+    GNU General Public License for more details.
4b6aa8
+
4b6aa8
+    You should have received a copy of the GNU General Public License along
4b6aa8
+    with this program; if not, write to the Free Software Foundation, Inc.,
4b6aa8
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
4b6aa8
+
4b6aa8
+    @brief API for formating of problem data
4b6aa8
+
4b6aa8
+    These functions can be used to convert a problem data to its string
4b6aa8
+    representation.
4b6aa8
+
4b6aa8
+    The output format can be parsed from a string:
4b6aa8
+
4b6aa8
+        problem_formatter_t *formatter = problem_formatter_new();
4b6aa8
+        problem_formatter_load_string(formatter, MY_FORMAT_STRING);
4b6aa8
+
4b6aa8
+    or loaded from a file:
4b6aa8
+
4b6aa8
+        problem_formatter_t *formatter = problem_formatter_new();
4b6aa8
+        problem_formatter_load_file(formatter, MY_FORMAT_FILE);
4b6aa8
+
4b6aa8
+    Once you have configured your formatter you can convert problem_data to
4b6aa8
+    problem_report by calling:
4b6aa8
+
4b6aa8
+        problem_report_t *report;
4b6aa8
+        if (problem_formatter_generate_report(formatter, data, &report) != 0)
4b6aa8
+            errx(EXIT_FAILURE, "Problem data cannot be converted to problem report.");
4b6aa8
+
4b6aa8
+    Now you can print the report:
4b6aa8
+
4b6aa8
+        printf("Problem: %s\n", problem_report_get_summary());
4b6aa8
+        printf("%s\n",          problem_report_get_description());
4b6aa8
+
4b6aa8
+        puts("Problem attachments:");
4b6aa8
+        for (GList *a = problem_report_get_attachments(pr); a != NULL; a = g_list_next(a))
4b6aa8
+            printf(" %s\n", a->data);
4b6aa8
+
4b6aa8
+    Format description:
4b6aa8
+
4b6aa8
+         ----
4b6aa8
+         %summary:: summary format
4b6aa8
+         %attach:: elemnt1[,element2]...
4b6aa8
+         section:: element1[,element2]...
4b6aa8
+         The literal text line to be added to report.
4b6aa8
+         ----
4b6aa8
+
4b6aa8
+         Summary format is a line of text, where %element% is replaced by
4b6aa8
+         text element's content, and [[...%element%...]] block is used only if
4b6aa8
+         %element% exists. [[...]] blocks can nest.
4b6aa8
+
4b6aa8
+         Sections can be:
4b6aa8
+         - %summary: bug summary format string.
4b6aa8
+
4b6aa8
+         - %attach: a list of elements to attach.
4b6aa8
+
4b6aa8
+         - text, double colon (::) and the list of comma-separated elements.
4b6aa8
+           Text can be empty (":: elem1, elem2, elem3" works),
4b6aa8
+           in this case "Text:" header line will be omitted.
4b6aa8
+
4b6aa8
+         - %description: this section is implicit and contains all text
4b6aa8
+           sections unless another section was specified (%summary and %attach
4b6aa8
+           are ignored when determining text section's placement)
4b6aa8
+
4b6aa8
+         - every text element belongs to the last specified section (%summary
4b6aa8
+           and %attach sections are ignored). If no section was specified,
4b6aa8
+           the text element belogns to %description.
4b6aa8
+
4b6aa8
+         - If none of elements exists, the section will not be created.
4b6aa8
+
4b6aa8
+         - Empty lines are NOT ignored.
4b6aa8
+
4b6aa8
+         Elements can be:
4b6aa8
+         - problem directory element names, which get formatted as
4b6aa8
+           <element_name>: <contents>
4b6aa8
+           or
4b6aa8
+           <element_name>:
4b6aa8
+           :<contents>
4b6aa8
+           :<contents>
4b6aa8
+           :<contents>
4b6aa8
+
4b6aa8
+         - problem directory element names prefixed by "%bare_",
4b6aa8
+           which is formatted as-is, without "<element_name>:" and colons
4b6aa8
+
4b6aa8
+         - %oneline, %multiline, %text wildcards, which select all corresponding
4b6aa8
+           elements for output or attachment
4b6aa8
+
4b6aa8
+         - %binary wildcard, valid only for %attach section, instructs to attach
4b6aa8
+           binary elements
4b6aa8
+
4b6aa8
+         - problem directory element names prefixed by "-",
4b6aa8
+           which excludes given element from all wildcards
4b6aa8
+
4b6aa8
+         - Nonexistent elements are silently ignored.
4b6aa8
+
4b6aa8
+    You can add your own section:
4b6aa8
+
4b6aa8
+        problem_formatter_t *formatter = problem_formatter_new();
4b6aa8
+        problem_formatter_add_section(formatter, "additional_info", PFFF_REQUIRED);
4b6aa8
+
4b6aa8
+    and then you can use the section in the formatting string:
4b6aa8
+
4b6aa8
+        problem_formatter_load_string(formatter,
4b6aa8
+                "::comment\n"
4b6aa8
+                "%additional_info:: maps");
4b6aa8
+        problem_formatter_generate_report(formatter, data, &report);
4b6aa8
+
4b6aa8
+        printf("Problem: %s\n",         problem_report_get_summary());
4b6aa8
+        printf("%s\n",                  problem_report_get_description());
4b6aa8
+        printf("Additional info: %s\n", problem_report_get_section(report, "additiona_info"));
4b6aa8
+
4b6aa8
+    The lines above are equivalent to the following lines:
4b6aa8
+
4b6aa8
+        printf("Problem: %s\n",         problem_data_get_content_or_NULL(data, "reason"));
4b6aa8
+        printf("%s\n",                  problem_data_get_content_or_NULL(data, "comment"));
4b6aa8
+        printf("Additional info: %s\n", problem_data_get_content_or_NULL(data, "maps"));
4b6aa8
+*/
4b6aa8
+#ifndef LIBREPORT_PROBLEM_REPORT_H
4b6aa8
+#define LIBREPORT_PROBLEM_REPORT_H
4b6aa8
+
4b6aa8
+#include <glib.h>
4b6aa8
+#include <stdio.h>
4b6aa8
+#include "problem_data.h"
4b6aa8
+
4b6aa8
+#ifdef __cplusplus
4b6aa8
+extern "C" {
4b6aa8
+#endif
4b6aa8
+
4b6aa8
+#define PR_SEC_SUMMARY "summary"
4b6aa8
+#define PR_SEC_DESCRIPTION "description"
4b6aa8
+
4b6aa8
+/*
4b6aa8
+ * The problem report structure represents a problem data formatted according
4b6aa8
+ * to a format string.
4b6aa8
+ *
4b6aa8
+ * A problem report is composed of well-known sections:
4b6aa8
+ *   - summary
4b6aa8
+ *   - descritpion
4b6aa8
+ *   - attach
4b6aa8
+ *
4b6aa8
+ * and custom sections accessed by:
4b6aa8
+ *   problem_report_get_section();
4b6aa8
+ */
4b6aa8
+struct problem_report;
4b6aa8
+typedef struct problem_report problem_report_t;
4b6aa8
+
4b6aa8
+/*
4b6aa8
+ * Helpers for easily switching between FILE and struct strbuf
4b6aa8
+ */
4b6aa8
+
4b6aa8
+/*
4b6aa8
+ * Type of buffer used by Problem report
4b6aa8
+ */
4b6aa8
+typedef FILE problem_report_buffer;
4b6aa8
+
4b6aa8
+/*
4b6aa8
+ * Wrapper for the proble buffer's formated output function.
4b6aa8
+ */
4b6aa8
+#define problem_report_buffer_printf(buf, fmt, ...)\
4b6aa8
+    fprintf((buf), (fmt), ##__VA_ARGS__)
4b6aa8
+
4b6aa8
+
4b6aa8
+/*
4b6aa8
+ * Get a section buffer
4b6aa8
+ *
4b6aa8
+ * Use this function if you need to amend something to a formatted section.
4b6aa8
+ *
4b6aa8
+ * @param self Problem report
4b6aa8
+ * @param section_name Name of required section
4b6aa8
+ * @return Always valid pointer to a section buffer
4b6aa8
+ */
4b6aa8
+problem_report_buffer *problem_report_get_buffer(const problem_report_t *self,
4b6aa8
+        const char *section_name);
4b6aa8
+
4b6aa8
+/*
4b6aa8
+ * Get Summary string
4b6aa8
+ *
4b6aa8
+ * The returned pointer is valid as long as you perform no further output to
4b6aa8
+ * the summary buffer.
4b6aa8
+ *
4b6aa8
+ * @param self Problem report
4b6aa8
+ * @return Non-NULL pointer to summary data
4b6aa8
+ */
4b6aa8
+const char *problem_report_get_summary(const problem_report_t *self);
4b6aa8
+
4b6aa8
+/*
4b6aa8
+ * Get Description string
4b6aa8
+ *
4b6aa8
+ * The returned pointer is valid as long as you perform no further output to
4b6aa8
+ * the description buffer.
4b6aa8
+ *
4b6aa8
+ * @param self Problem report
4b6aa8
+ * @return Non-NULL pointer to description data
4b6aa8
+ */
4b6aa8
+const char *problem_report_get_description(const problem_report_t *self);
4b6aa8
+
4b6aa8
+/*
4b6aa8
+ * Get Section's string
4b6aa8
+ *
4b6aa8
+ * The returned pointer is valid as long as you perform no further output to
4b6aa8
+ * the section's buffer.
4b6aa8
+ *
4b6aa8
+ * @param self Problem report
4b6aa8
+ * @param section_name Name of the required section
4b6aa8
+ * @return Non-NULL pointer to description data
4b6aa8
+ */
4b6aa8
+const char *problem_report_get_section(const problem_report_t *self,
4b6aa8
+        const char *section_name);
4b6aa8
+
4b6aa8
+/*
4b6aa8
+ * Get GList of the problem data items that are to be attached
4b6aa8
+ *
4b6aa8
+ * @param self Problem report
4b6aa8
+ * @return A pointer to GList (NULL means empty list)
4b6aa8
+ */
4b6aa8
+GList *problem_report_get_attachments(const problem_report_t *self);
4b6aa8
+
4b6aa8
+/*
4b6aa8
+ * Releases all resources allocated by a problem report
4b6aa8
+ *
4b6aa8
+ * @param self Problem report
4b6aa8
+ */
4b6aa8
+void problem_report_free(problem_report_t *self);
4b6aa8
+
4b6aa8
+
4b6aa8
+/*
4b6aa8
+ * An enum of Extra section flags
4b6aa8
+ */
4b6aa8
+enum problem_formatter_section_flags {
4b6aa8
+    PFFF_REQUIRED = 1 << 0, ///< section must be present in the format spec
4b6aa8
+};
4b6aa8
+
4b6aa8
+/*
4b6aa8
+ * The problem formatter structure formats a problem data according to a format
4b6aa8
+ * string and stores result a problem report.
4b6aa8
+ *
4b6aa8
+ * The problem formatter uses '%reason%' as %summary section format string, if
4b6aa8
+ * %summary is not provided by a format string.
4b6aa8
+ */
4b6aa8
+struct problem_formatter;
4b6aa8
+typedef struct problem_formatter problem_formatter_t;
4b6aa8
+
4b6aa8
+/*
4b6aa8
+ * Constructs a new problem formatter.
4b6aa8
+ *
4b6aa8
+ * @return Non-NULL pointer to the new problem formatter
4b6aa8
+ */
4b6aa8
+problem_formatter_t *problem_formatter_new(void);
4b6aa8
+
4b6aa8
+/*
4b6aa8
+ * Releases all resources allocated by a problem formatter
4b6aa8
+ *
4b6aa8
+ * @param self Problem formatter
4b6aa8
+ */
4b6aa8
+void problem_formatter_free(problem_formatter_t *self);
4b6aa8
+
4b6aa8
+/*
4b6aa8
+ * Adds a new recognized section
4b6aa8
+ *
4b6aa8
+ * The problem formatter ignores a section in the format spec if the section is
4b6aa8
+ * not one of the default nor added by this function.
4b6aa8
+ *
4b6aa8
+ * How the problem formatter handles these extra sections:
4b6aa8
+ *
4b6aa8
+ * A custom section is something like %description section. %description is the
4b6aa8
+ * default section where all text (sub)sections are stored. If the formatter
4b6aa8
+ * finds the custom section in format string, then starts storing text
4b6aa8
+ * (sub)sections in the custom section.
4b6aa8
+ *
4b6aa8
+ * (%description)    |:: comment
4b6aa8
+ * (%description)    |
4b6aa8
+ * (%description)    |Package:: package
4b6aa8
+ * (%description)    |
4b6aa8
+ * (%additiona_info) |%additional_info::
4b6aa8
+ * (%additiona_info) |%reporter%
4b6aa8
+ * (%additiona_info) |User:: user_name,uid
4b6aa8
+ * (%additiona_info) |
4b6aa8
+ * (%additiona_info) |Directories:: root,cwd
4b6aa8
+ *
4b6aa8
+ *
4b6aa8
+ * @param self Problem formatter
4b6aa8
+ * @param name Name of the added section
4b6aa8
+ * @param flags Info about the added section
4b6aa8
+ * @return Zero on success. -EEXIST if the name is already known by the formatter
4b6aa8
+ */
4b6aa8
+int problem_formatter_add_section(problem_formatter_t *self, const char *name, int flags);
4b6aa8
+
4b6aa8
+/*
4b6aa8
+ * Loads a problem format from a string.
4b6aa8
+ *
4b6aa8
+ * @param self Problem formatter
4b6aa8
+ * @param fmt Format
4b6aa8
+ * @return Zero on success or number of warnings (e.g. missing section,
4b6aa8
+ * unrecognized section).
4b6aa8
+ */
4b6aa8
+int problem_formatter_load_string(problem_formatter_t* self, const char *fmt);
4b6aa8
+
4b6aa8
+/*
4b6aa8
+ * Loads a problem format from a file.
4b6aa8
+ *
4b6aa8
+ * @param self Problem formatter
4b6aa8
+ * @param pat Path to the format file
4b6aa8
+ * @return Zero on success or number of warnings (e.g. missing section,
4b6aa8
+ * unrecognized section).
4b6aa8
+ */
4b6aa8
+int problem_formatter_load_file(problem_formatter_t* self, const char *path);
4b6aa8
+
4b6aa8
+/*
4b6aa8
+ * Creates a new problem report, formats the data according to the loaded
4b6aa8
+ * format string and stores output in the report.
4b6aa8
+ *
4b6aa8
+ * @param self Problem formatter
4b6aa8
+ * @param data Problem data to format
4b6aa8
+ * @param report Pointer where the created problem report is to be stored
4b6aa8
+ * @return Zero on success, otherwise non-zero value.
4b6aa8
+ */
4b6aa8
+int problem_formatter_generate_report(const problem_formatter_t *self, problem_data_t *data, problem_report_t **report);
4b6aa8
+
4b6aa8
+#ifdef __cplusplus
4b6aa8
+}
4b6aa8
+#endif
4b6aa8
+
4b6aa8
+#endif // LIBREPORT_PROBLEM_REPORT_H
4b6aa8
diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am
4b6aa8
index b7e4781..c11a42d 100644
4b6aa8
--- a/src/lib/Makefile.am
4b6aa8
+++ b/src/lib/Makefile.am
4b6aa8
@@ -38,6 +38,7 @@ libreport_la_SOURCES = \
4b6aa8
     make_descr.c \
4b6aa8
     run_event.c \
4b6aa8
     problem_data.c \
4b6aa8
+    problem_report.c \
4b6aa8
     create_dump_dir.c \
4b6aa8
     abrt_types.c \
4b6aa8
     parse_release.c \
4b6aa8
@@ -78,6 +79,7 @@ libreport_la_CPPFLAGS = \
4b6aa8
     $(GLIB_CFLAGS) \
4b6aa8
     $(GOBJECT_CFLAGS) \
4b6aa8
     $(AUGEAS_CFLAGS) \
4b6aa8
+    $(SATYR_CFLAGS) \
4b6aa8
     -D_GNU_SOURCE
4b6aa8
 libreport_la_LDFLAGS = \
4b6aa8
     -ltar \
4b6aa8
@@ -87,7 +89,8 @@ libreport_la_LIBADD = \
4b6aa8
     $(GLIB_LIBS) \
4b6aa8
     $(JOURNAL_LIBS) \
4b6aa8
     $(GOBJECT_LIBS) \
4b6aa8
-    $(AUGEAS_LIBS)
4b6aa8
+    $(AUGEAS_LIBS) \
4b6aa8
+    $(SATYR_LIBS)
4b6aa8
 
4b6aa8
 libreportconfdir = $(CONF_DIR)
4b6aa8
 dist_libreportconf_DATA = \
4b6aa8
diff --git a/src/lib/problem_report.c b/src/lib/problem_report.c
4b6aa8
new file mode 100644
4b6aa8
index 0000000..6598c15
4b6aa8
--- /dev/null
4b6aa8
+++ b/src/lib/problem_report.c
4b6aa8
@@ -0,0 +1,1209 @@
4b6aa8
+/*
4b6aa8
+    Copyright (C) 2014  ABRT team
4b6aa8
+    Copyright (C) 2014  RedHat Inc
4b6aa8
+
4b6aa8
+    This program is free software; you can redistribute it and/or modify
4b6aa8
+    it under the terms of the GNU General Public License as published by
4b6aa8
+    the Free Software Foundation; either version 2 of the License, or
4b6aa8
+    (at your option) any later version.
4b6aa8
+
4b6aa8
+    This program is distributed in the hope that it will be useful,
4b6aa8
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
4b6aa8
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
4b6aa8
+    GNU General Public License for more details.
4b6aa8
+
4b6aa8
+    You should have received a copy of the GNU General Public License along
4b6aa8
+    with this program; if not, write to the Free Software Foundation, Inc.,
4b6aa8
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
4b6aa8
+*/
4b6aa8
+
4b6aa8
+#include "problem_report.h"
4b6aa8
+#include "internal_libreport.h"
4b6aa8
+
4b6aa8
+#include <satyr/stacktrace.h>
4b6aa8
+#include <satyr/abrt.h>
4b6aa8
+
4b6aa8
+#include <assert.h>
4b6aa8
+
4b6aa8
+#define DESTROYED_POINTER (void *)0xdeadbeef
4b6aa8
+
4b6aa8
+/* FORMAT:
4b6aa8
+ * |%summary:: Hello, world
4b6aa8
+ * |Problem description:: %bare_comment
4b6aa8
+ * |
4b6aa8
+ * |Package:: package
4b6aa8
+ * |
4b6aa8
+ * |%attach: %binary, backtrace
4b6aa8
+ * |
4b6aa8
+ * |%additional_info::
4b6aa8
+ * |%reporter%
4b6aa8
+ * |User:: user_name,uid
4b6aa8
+ * |
4b6aa8
+ * |Directories:: root,cwd
4b6aa8
+ *
4b6aa8
+ * PARSED DATA (list of struct section_t):
4b6aa8
+ * {
4b6aa8
+ *   section_t {
4b6aa8
+ *      .name     = '%summary';
4b6aa8
+ *      .items    = { 'Hello, world' };
4b6aa8
+ *      .children = NULL;
4b6aa8
+ *   },
4b6aa8
+ *   section_t {
4b6aa8
+ *      .name     = '%attach'
4b6aa8
+ *      .items    = { '%binary', 'backtrace' };
4b6aa8
+ *      .children = NULL;
4b6aa8
+ *   },
4b6aa8
+ *   section_t {
4b6aa8
+ *      .name     = '%description'
4b6aa8
+ *      .items    = NULL;
4b6aa8
+ *      .children = {
4b6aa8
+ *        section_t {
4b6aa8
+ *          .name     = 'Problem description:';
4b6aa8
+ *          .items    = { '%bare_comment' };
4b6aa8
+ *          .children = NULL;
4b6aa8
+ *        },
4b6aa8
+ *        section_t {
4b6aa8
+ *          .name     = '';
4b6aa8
+ *          .items    = NULL;
4b6aa8
+ *          .children = NULL;
4b6aa8
+ *        },
4b6aa8
+ *        section_t {
4b6aa8
+ *          .name     = 'Package:';
4b6aa8
+ *          .items    = { 'package' };
4b6aa8
+ *          .children = NULL;
4b6aa8
+ *        },
4b6aa8
+ *      }
4b6aa8
+ *   },
4b6aa8
+ *   section_t {
4b6aa8
+ *      .name     = '%additional_info'
4b6aa8
+ *      .items    = { '%reporter%' };
4b6aa8
+ *      .children = {
4b6aa8
+ *        section_t {
4b6aa8
+ *          .name     = 'User:';
4b6aa8
+ *          .items    = { 'user_name', 'uid' };
4b6aa8
+ *          .children = NULL;
4b6aa8
+ *        },
4b6aa8
+ *        section_t {
4b6aa8
+ *          .name     = '';
4b6aa8
+ *          .items    = NULL;
4b6aa8
+ *          .children = NULL;
4b6aa8
+ *        },
4b6aa8
+ *        section_t {
4b6aa8
+ *          .name     = 'Directories:';
4b6aa8
+ *          .items    = { 'root', 'cwd' };
4b6aa8
+ *          .children = NULL;
4b6aa8
+ *        },
4b6aa8
+ *      }
4b6aa8
+ *   }
4b6aa8
+ * }
4b6aa8
+ */
4b6aa8
+struct section_t {
4b6aa8
+    char *name;      ///< name or output text (%summar, 'Package version:');
4b6aa8
+    GList *items;    ///< list of file names and special items (%reporter, %binar, ...)
4b6aa8
+    GList *children; ///< list of sub sections (struct section_t)
4b6aa8
+};
4b6aa8
+
4b6aa8
+typedef struct section_t section_t;
4b6aa8
+
4b6aa8
+static section_t *
4b6aa8
+section_new(const char *name)
4b6aa8
+{
4b6aa8
+    section_t *self = xmalloc(sizeof(*self));
4b6aa8
+    self->name = xstrdup(name);
4b6aa8
+    self->items = NULL;
4b6aa8
+    self->children = NULL;
4b6aa8
+
4b6aa8
+    return self;
4b6aa8
+}
4b6aa8
+
4b6aa8
+static void
4b6aa8
+section_free(section_t *self)
4b6aa8
+{
4b6aa8
+    if (self == NULL)
4b6aa8
+        return;
4b6aa8
+
4b6aa8
+    free(self->name);
4b6aa8
+    g_list_free_full(self->items, free);
4b6aa8
+    g_list_free_full(self->children, (GDestroyNotify)section_free);
4b6aa8
+
4b6aa8
+    free(self);
4b6aa8
+}
4b6aa8
+
4b6aa8
+static int
4b6aa8
+section_name_cmp(section_t *lhs, const char *rhs)
4b6aa8
+{
4b6aa8
+    return strcmp((lhs->name + 1), rhs);
4b6aa8
+}
4b6aa8
+
4b6aa8
+/* Utility functions */
4b6aa8
+
4b6aa8
+static GList*
4b6aa8
+split_string_on_char(const char *str, char ch)
4b6aa8
+{
4b6aa8
+    GList *list = NULL;
4b6aa8
+    for (;;)
4b6aa8
+    {
4b6aa8
+        const char *delim = strchrnul(str, ch);
4b6aa8
+        list = g_list_prepend(list, xstrndup(str, delim - str));
4b6aa8
+        if (*delim == '\0')
4b6aa8
+            break;
4b6aa8
+        str = delim + 1;
4b6aa8
+    }
4b6aa8
+    return g_list_reverse(list);
4b6aa8
+}
4b6aa8
+
4b6aa8
+static int
4b6aa8
+compare_item_name(const char *lookup, const char *name)
4b6aa8
+{
4b6aa8
+    if (lookup[0] == '-')
4b6aa8
+        lookup++;
4b6aa8
+    else if (strncmp(lookup, "%bare_", 6) == 0)
4b6aa8
+        lookup += 6;
4b6aa8
+    return strcmp(lookup, name);
4b6aa8
+}
4b6aa8
+
4b6aa8
+static int
4b6aa8
+is_item_name_in_section(const section_t *lookup, const char *name)
4b6aa8
+{
4b6aa8
+    if (g_list_find_custom(lookup->items, name, (GCompareFunc)compare_item_name))
4b6aa8
+        return 0; /* "found it!" */
4b6aa8
+    return 1;
4b6aa8
+}
4b6aa8
+
4b6aa8
+static bool is_explicit_or_forbidden(const char *name, GList *comment_fmt_spec);
4b6aa8
+
4b6aa8
+static int
4b6aa8
+is_explicit_or_forbidden_child(const section_t *master_section, const char *name)
4b6aa8
+{
4b6aa8
+    if (is_explicit_or_forbidden(name, master_section->children))
4b6aa8
+        return 0; /* "found it!" */
4b6aa8
+    return 1;
4b6aa8
+}
4b6aa8
+
4b6aa8
+/* For example: 'package' belongs to '%oneline', but 'package' is used in
4b6aa8
+ * 'Version of component', so it is not very helpful to include that file once
4b6aa8
+ * more in another section
4b6aa8
+ */
4b6aa8
+static bool
4b6aa8
+is_explicit_or_forbidden(const char *name, GList *comment_fmt_spec)
4b6aa8
+{
4b6aa8
+    return    g_list_find_custom(comment_fmt_spec, name, (GCompareFunc)is_item_name_in_section)
4b6aa8
+           || g_list_find_custom(comment_fmt_spec, name, (GCompareFunc)is_explicit_or_forbidden_child);
4b6aa8
+}
4b6aa8
+
4b6aa8
+static GList*
4b6aa8
+load_stream(FILE *fp)
4b6aa8
+{
4b6aa8
+    assert(fp);
4b6aa8
+
4b6aa8
+    GList *sections = NULL;
4b6aa8
+    section_t *master = section_new("%description");
4b6aa8
+    section_t *sec = NULL;
4b6aa8
+
4b6aa8
+    sections = g_list_append(sections, master);
4b6aa8
+
4b6aa8
+    char *line;
4b6aa8
+    while ((line = xmalloc_fgetline(fp)) != NULL)
4b6aa8
+    {
4b6aa8
+        /* Skip comments */
4b6aa8
+        char first = *skip_whitespace(line);
4b6aa8
+        if (first == '#')
4b6aa8
+            goto free_line;
4b6aa8
+
4b6aa8
+        /* Handle trailing backslash continuation */
4b6aa8
+ check_continuation: ;
4b6aa8
+        unsigned len = strlen(line);
4b6aa8
+        if (len && line[len-1] == '\\')
4b6aa8
+        {
4b6aa8
+            line[len-1] = '\0';
4b6aa8
+            char *next_line = xmalloc_fgetline(fp);
4b6aa8
+            if (next_line)
4b6aa8
+            {
4b6aa8
+                line = append_to_malloced_string(line, next_line);
4b6aa8
+                free(next_line);
4b6aa8
+                goto check_continuation;
4b6aa8
+            }
4b6aa8
+        }
4b6aa8
+
4b6aa8
+        /* We are reusing line buffer to form temporary
4b6aa8
+         * "key\0values\0..." in its beginning
4b6aa8
+         */
4b6aa8
+        bool summary_line = false;
4b6aa8
+        char *value = NULL;
4b6aa8
+        char *src;
4b6aa8
+        char *dst;
4b6aa8
+        for (src = dst = line; *src; src++)
4b6aa8
+        {
4b6aa8
+            char c = *src;
4b6aa8
+            /* did we reach the value list? */
4b6aa8
+            if (!value && c == ':' && src[1] == ':')
4b6aa8
+            {
4b6aa8
+                *dst++ = '\0'; /* terminate key */
4b6aa8
+                src += 1;
4b6aa8
+                value = dst; /* remember where value starts */
4b6aa8
+                summary_line = (strcmp(line, "%summary") == 0);
4b6aa8
+                if (summary_line)
4b6aa8
+                {
4b6aa8
+                    value = (src + 1);
4b6aa8
+                    break;
4b6aa8
+                }
4b6aa8
+                continue;
4b6aa8
+            }
4b6aa8
+            /* skip whitespace in value list */
4b6aa8
+            if (value && isspace(c))
4b6aa8
+                continue;
4b6aa8
+            *dst++ = c; /* store next key or value char */
4b6aa8
+        }
4b6aa8
+
4b6aa8
+        GList *item_list = NULL;
4b6aa8
+        if (summary_line)
4b6aa8
+        {
4b6aa8
+            /* %summary is special */
4b6aa8
+            item_list = g_list_append(NULL, xstrdup(skip_whitespace(value)));
4b6aa8
+        }
4b6aa8
+        else
4b6aa8
+        {
4b6aa8
+            *dst = '\0'; /* terminate value (or key) */
4b6aa8
+            if (value)
4b6aa8
+                item_list = split_string_on_char(value, ',');
4b6aa8
+        }
4b6aa8
+
4b6aa8
+        sec = section_new(line);
4b6aa8
+        sec->items = item_list;
4b6aa8
+
4b6aa8
+        if (sec->name[0] == '%')
4b6aa8
+        {
4b6aa8
+            if (!summary_line && strcmp(sec->name, "%attach") != 0)
4b6aa8
+            {
4b6aa8
+                master->children = g_list_reverse(master->children);
4b6aa8
+                master = sec;
4b6aa8
+            }
4b6aa8
+
4b6aa8
+            sections = g_list_prepend(sections, sec);
4b6aa8
+        }
4b6aa8
+        else
4b6aa8
+            master->children = g_list_prepend(master->children, sec);
4b6aa8
+
4b6aa8
+ free_line:
4b6aa8
+        free(line);
4b6aa8
+    }
4b6aa8
+
4b6aa8
+    /* If master equals sec, then master's children list was not yet reversed.
4b6aa8
+     *
4b6aa8
+     * %description is the default section (i.e is not explicitly mentioned)
4b6aa8
+     * and %summary nor %attach cause its children list to reverse.
4b6aa8
+     */
4b6aa8
+    if (master == sec || strcmp(master->name, "%description") == 0)
4b6aa8
+        master->children = g_list_reverse(master->children);
4b6aa8
+
4b6aa8
+    return sections;
4b6aa8
+}
4b6aa8
+
4b6aa8
+
4b6aa8
+/* Summary generation */
4b6aa8
+
4b6aa8
+#define MAX_OPT_DEPTH 10
4b6aa8
+static int
4b6aa8
+format_percented_string(const char *str, problem_data_t *pd, FILE *result)
4b6aa8
+{
4b6aa8
+    long old_pos[MAX_OPT_DEPTH] = { 0 };
4b6aa8
+    int okay[MAX_OPT_DEPTH] = { 1 };
4b6aa8
+    long len = 0;
4b6aa8
+    int opt_depth = 1;
4b6aa8
+
4b6aa8
+    while (*str) {
4b6aa8
+        switch (*str) {
4b6aa8
+        default:
4b6aa8
+            putc(*str, result);
4b6aa8
+            len++;
4b6aa8
+            str++;
4b6aa8
+            break;
4b6aa8
+        case '\\':
4b6aa8
+            if (str[1])
4b6aa8
+                str++;
4b6aa8
+            putc(*str, result);
4b6aa8
+            len++;
4b6aa8
+            str++;
4b6aa8
+            break;
4b6aa8
+        case '[':
4b6aa8
+            if (str[1] == '[' && opt_depth < MAX_OPT_DEPTH)
4b6aa8
+            {
4b6aa8
+                old_pos[opt_depth] = len;
4b6aa8
+                okay[opt_depth] = 1;
4b6aa8
+                opt_depth++;
4b6aa8
+                str += 2;
4b6aa8
+            } else {
4b6aa8
+                putc(*str, result);
4b6aa8
+                len++;
4b6aa8
+                str++;
4b6aa8
+            }
4b6aa8
+            break;
4b6aa8
+        case ']':
4b6aa8
+            if (str[1] == ']' && opt_depth > 1)
4b6aa8
+            {
4b6aa8
+                opt_depth--;
4b6aa8
+                if (!okay[opt_depth])
4b6aa8
+                {
4b6aa8
+                    fseek(result, old_pos[opt_depth], SEEK_SET);
4b6aa8
+                    len = old_pos[opt_depth];
4b6aa8
+                }
4b6aa8
+                str += 2;
4b6aa8
+            } else {
4b6aa8
+                putc(*str, result);
4b6aa8
+                len++;
4b6aa8
+                str++;
4b6aa8
+            }
4b6aa8
+            break;
4b6aa8
+        case '%': ;
4b6aa8
+            char *nextpercent = strchr(++str, '%');
4b6aa8
+            if (!nextpercent)
4b6aa8
+            {
4b6aa8
+                error_msg_and_die("Unterminated %%element%%: '%s'", str - 1);
4b6aa8
+            }
4b6aa8
+
4b6aa8
+            *nextpercent = '\0';
4b6aa8
+            const problem_item *item = problem_data_get_item_or_NULL(pd, str);
4b6aa8
+            *nextpercent = '%';
4b6aa8
+
4b6aa8
+            if (item && (item->flags & CD_FLAG_TXT))
4b6aa8
+            {
4b6aa8
+                fputs(item->content, result);
4b6aa8
+                len += strlen(item->content);
4b6aa8
+            }
4b6aa8
+            else
4b6aa8
+                okay[opt_depth - 1] = 0;
4b6aa8
+            str = nextpercent + 1;
4b6aa8
+            break;
4b6aa8
+        }
4b6aa8
+    }
4b6aa8
+
4b6aa8
+    if (opt_depth > 1)
4b6aa8
+    {
4b6aa8
+        error_msg_and_die("Unbalanced [[ ]] bracket");
4b6aa8
+    }
4b6aa8
+
4b6aa8
+    if (!okay[0])
4b6aa8
+    {
4b6aa8
+        error_msg("Undefined variable outside of [[ ]] bracket");
4b6aa8
+    }
4b6aa8
+
4b6aa8
+    return 0;
4b6aa8
+}
4b6aa8
+
4b6aa8
+/* BZ comment generation */
4b6aa8
+
4b6aa8
+static int
4b6aa8
+append_text(struct strbuf *result, const char *item_name, const char *content, bool print_item_name)
4b6aa8
+{
4b6aa8
+    char *eol = strchrnul(content, '\n');
4b6aa8
+    if (eol[0] == '\0' || eol[1] == '\0')
4b6aa8
+    {
4b6aa8
+        /* one-liner */
4b6aa8
+        int pad = 16 - (strlen(item_name) + 2);
4b6aa8
+        if (pad < 0)
4b6aa8
+            pad = 0;
4b6aa8
+        if (print_item_name)
4b6aa8
+            strbuf_append_strf(result,
4b6aa8
+                    eol[0] == '\0' ? "%s: %*s%s\n" : "%s: %*s%s",
4b6aa8
+                    item_name, pad, "", content
4b6aa8
+            );
4b6aa8
+        else
4b6aa8
+            strbuf_append_strf(result,
4b6aa8
+                    eol[0] == '\0' ? "%s\n" : "%s",
4b6aa8
+                    content
4b6aa8
+            );
4b6aa8
+    }
4b6aa8
+    else
4b6aa8
+    {
4b6aa8
+        /* multi-line item */
4b6aa8
+        if (print_item_name)
4b6aa8
+            strbuf_append_strf(result, "%s:\n", item_name);
4b6aa8
+        for (;;)
4b6aa8
+        {
4b6aa8
+            eol = strchrnul(content, '\n');
4b6aa8
+            strbuf_append_strf(result,
4b6aa8
+                    /* For %bare_multiline_item, we don't want to print colons */
4b6aa8
+                    (print_item_name ? ":%.*s\n" : "%.*s\n"),
4b6aa8
+                    (int)(eol - content), content
4b6aa8
+            );
4b6aa8
+            if (eol[0] == '\0' || eol[1] == '\0')
4b6aa8
+                break;
4b6aa8
+            content = eol + 1;
4b6aa8
+        }
4b6aa8
+    }
4b6aa8
+    return 1;
4b6aa8
+}
4b6aa8
+
4b6aa8
+static int
4b6aa8
+append_short_backtrace(struct strbuf *result, problem_data_t *problem_data, size_t max_text_size, bool print_item_name)
4b6aa8
+{
4b6aa8
+    const problem_item *item = problem_data_get_item_or_NULL(problem_data,
4b6aa8
+                                                             FILENAME_BACKTRACE);
4b6aa8
+    if (!item)
4b6aa8
+        return 0; /* "I did not print anything" */
4b6aa8
+    if (!(item->flags & CD_FLAG_TXT))
4b6aa8
+        return 0; /* "I did not print anything" */
4b6aa8
+
4b6aa8
+    char *truncated = NULL;
4b6aa8
+
4b6aa8
+    if (strlen(item->content) >= max_text_size)
4b6aa8
+    {
4b6aa8
+        log_debug("'backtrace' exceeds the text file size, going to append its short version");
4b6aa8
+
4b6aa8
+        char *error_msg = NULL;
4b6aa8
+        const char *type = problem_data_get_content_or_NULL(problem_data, FILENAME_TYPE);
4b6aa8
+        if (!type)
4b6aa8
+        {
4b6aa8
+            log_debug("Problem data does not contain '"FILENAME_TYPE"' file");
4b6aa8
+            return 0;
4b6aa8
+        }
4b6aa8
+
4b6aa8
+        /* For CCpp crashes, use the GDB-produced backtrace which should be
4b6aa8
+         * available by now. sr_abrt_type_from_analyzer returns SR_REPORT_CORE
4b6aa8
+         * by default for CCpp crashes.
4b6aa8
+         */
4b6aa8
+        enum sr_report_type report_type = sr_abrt_type_from_analyzer(type);
4b6aa8
+        if (strcmp(type, "CCpp") == 0)
4b6aa8
+        {
4b6aa8
+            log_debug("Successfully identified 'CCpp' abrt type");
4b6aa8
+            report_type = SR_REPORT_GDB;
4b6aa8
+        }
4b6aa8
+
4b6aa8
+        struct sr_stacktrace *backtrace = sr_stacktrace_parse(report_type,
4b6aa8
+                item->content, &error_msg);
4b6aa8
+
4b6aa8
+        if (!backtrace)
4b6aa8
+        {
4b6aa8
+            log(_("Can't parse backtrace: %s"), error_msg);
4b6aa8
+            free(error_msg);
4b6aa8
+            return 0;
4b6aa8
+        }
4b6aa8
+
4b6aa8
+        /* Get optimized thread stack trace for 10 top most frames */
4b6aa8
+        truncated = sr_stacktrace_to_short_text(backtrace, 10);
4b6aa8
+        sr_stacktrace_free(backtrace);
4b6aa8
+
4b6aa8
+        if (!truncated)
4b6aa8
+        {
4b6aa8
+            log(_("Can't generate stacktrace description (no crash thread?)"));
4b6aa8
+            return 0;
4b6aa8
+        }
4b6aa8
+    }
4b6aa8
+    else
4b6aa8
+    {
4b6aa8
+        log_debug("'backtrace' is small enough to be included as is");
4b6aa8
+    }
4b6aa8
+
4b6aa8
+    append_text(result,
4b6aa8
+                /*item_name:*/ truncated ? "truncated_backtrace" : FILENAME_BACKTRACE,
4b6aa8
+                /*content:*/   truncated ? truncated             : item->content,
4b6aa8
+                print_item_name
4b6aa8
+    );
4b6aa8
+    free(truncated);
4b6aa8
+    return 1;
4b6aa8
+}
4b6aa8
+
4b6aa8
+static int
4b6aa8
+append_item(struct strbuf *result, const char *item_name, problem_data_t *pd, GList *comment_fmt_spec)
4b6aa8
+{
4b6aa8
+    bool print_item_name = (strncmp(item_name, "%bare_", strlen("%bare_")) != 0);
4b6aa8
+    if (!print_item_name)
4b6aa8
+        item_name += strlen("%bare_");
4b6aa8
+
4b6aa8
+    if (item_name[0] != '%')
4b6aa8
+    {
4b6aa8
+        struct problem_item *item = problem_data_get_item_or_NULL(pd, item_name);
4b6aa8
+        if (!item)
4b6aa8
+            return 0; /* "I did not print anything" */
4b6aa8
+        if (!(item->flags & CD_FLAG_TXT))
4b6aa8
+            return 0; /* "I did not print anything" */
4b6aa8
+
4b6aa8
+        char *formatted = problem_item_format(item);
4b6aa8
+        char *content = formatted ? formatted : item->content;
4b6aa8
+        append_text(result, item_name, content, print_item_name);
4b6aa8
+        free(formatted);
4b6aa8
+        return 1; /* "I printed something" */
4b6aa8
+    }
4b6aa8
+
4b6aa8
+    /* Special item name */
4b6aa8
+
4b6aa8
+    /* Compat with previously-existed ad-hockery: %short_backtrace */
4b6aa8
+    if (strcmp(item_name, "%short_backtrace") == 0)
4b6aa8
+        return append_short_backtrace(result, pd, CD_TEXT_ATT_SIZE_BZ, print_item_name);
4b6aa8
+
4b6aa8
+    /* Compat with previously-existed ad-hockery: %reporter */
4b6aa8
+    if (strcmp(item_name, "%reporter") == 0)
4b6aa8
+        return append_text(result, "reporter", PACKAGE"-"VERSION, print_item_name);
4b6aa8
+
4b6aa8
+    /* %oneline,%multiline,%text */
4b6aa8
+    bool oneline   = (strcmp(item_name+1, "oneline"  ) == 0);
4b6aa8
+    bool multiline = (strcmp(item_name+1, "multiline") == 0);
4b6aa8
+    bool text      = (strcmp(item_name+1, "text"     ) == 0);
4b6aa8
+    if (!oneline && !multiline && !text)
4b6aa8
+    {
4b6aa8
+        log("Unknown or unsupported element specifier '%s'", item_name);
4b6aa8
+        return 0; /* "I did not print anything" */
4b6aa8
+    }
4b6aa8
+
4b6aa8
+    int printed = 0;
4b6aa8
+
4b6aa8
+    /* Iterate over _sorted_ items */
4b6aa8
+    GList *sorted_names = g_hash_table_get_keys(pd);
4b6aa8
+    sorted_names = g_list_sort(sorted_names, (GCompareFunc)strcmp);
4b6aa8
+
4b6aa8
+    /* %text => do as if %oneline, then repeat as if %multiline */
4b6aa8
+    if (text)
4b6aa8
+        oneline = 1;
4b6aa8
+
4b6aa8
+ again: ;
4b6aa8
+    GList *l = sorted_names;
4b6aa8
+    while (l)
4b6aa8
+    {
4b6aa8
+        const char *name = l->data;
4b6aa8
+        l = l->next;
4b6aa8
+        struct problem_item *item = g_hash_table_lookup(pd, name);
4b6aa8
+        if (!item)
4b6aa8
+            continue; /* paranoia, won't happen */
4b6aa8
+
4b6aa8
+        if (!(item->flags & CD_FLAG_TXT))
4b6aa8
+            continue;
4b6aa8
+
4b6aa8
+        if (is_explicit_or_forbidden(name, comment_fmt_spec))
4b6aa8
+            continue;
4b6aa8
+
4b6aa8
+        char *formatted = problem_item_format(item);
4b6aa8
+        char *content = formatted ? formatted : item->content;
4b6aa8
+        char *eol = strchrnul(content, '\n');
4b6aa8
+        bool is_oneline = (eol[0] == '\0' || eol[1] == '\0');
4b6aa8
+        if (oneline == is_oneline)
4b6aa8
+            printed |= append_text(result, name, content, print_item_name);
4b6aa8
+        free(formatted);
4b6aa8
+    }
4b6aa8
+    if (text && oneline)
4b6aa8
+    {
4b6aa8
+        /* %text, and we just did %oneline. Repeat as if %multiline */
4b6aa8
+        oneline = 0;
4b6aa8
+        /*multiline = 1; - not checked in fact, so why bother setting? */
4b6aa8
+        goto again;
4b6aa8
+    }
4b6aa8
+
4b6aa8
+    g_list_free(sorted_names); /* names themselves are not freed */
4b6aa8
+
4b6aa8
+    return printed;
4b6aa8
+}
4b6aa8
+
4b6aa8
+#define add_to_section_output(format, ...) \
4b6aa8
+    do { \
4b6aa8
+    for (; empty_lines > 0; --empty_lines) fputc('\n', result); \
4b6aa8
+    empty_lines = 0; \
4b6aa8
+    fprintf(result, format, __VA_ARGS__); \
4b6aa8
+    } while (0)
4b6aa8
+
4b6aa8
+static void
4b6aa8
+format_section(section_t *section, problem_data_t *pd, GList *comment_fmt_spec, FILE *result)
4b6aa8
+{
4b6aa8
+    int empty_lines = -1;
4b6aa8
+
4b6aa8
+    for (GList *iter = section->children; iter; iter = g_list_next(iter))
4b6aa8
+    {
4b6aa8
+        section_t *child = (section_t *)iter->data;
4b6aa8
+        if (child->items)
4b6aa8
+        {
4b6aa8
+            /* "Text: item[,item]..." */
4b6aa8
+            struct strbuf *output = strbuf_new();
4b6aa8
+            GList *item = child->items;
4b6aa8
+            while (item)
4b6aa8
+            {
4b6aa8
+                const char *str = item->data;
4b6aa8
+                item = item->next;
4b6aa8
+                if (str[0] == '-') /* "-name", ignore it */
4b6aa8
+                    continue;
4b6aa8
+                append_item(output, str, pd, comment_fmt_spec);
4b6aa8
+            }
4b6aa8
+
4b6aa8
+            if (output->len != 0)
4b6aa8
+                add_to_section_output((child->name[0] ? "%s:\n%s" : "%s%s"),
4b6aa8
+                                      child->name, output->buf);
4b6aa8
+
4b6aa8
+            strbuf_free(output);
4b6aa8
+        }
4b6aa8
+        else
4b6aa8
+        {
4b6aa8
+            /* Just "Text" (can be "") */
4b6aa8
+
4b6aa8
+            /* Filter out trailint empty lines */
4b6aa8
+            if (child->name[0] != '\0')
4b6aa8
+                add_to_section_output("%s\n", child->name);
4b6aa8
+            /* Do not count empty lines, if output wasn't yet produced */
4b6aa8
+            else if (empty_lines >= 0)
4b6aa8
+                ++empty_lines;
4b6aa8
+        }
4b6aa8
+    }
4b6aa8
+}
4b6aa8
+
4b6aa8
+static GList *
4b6aa8
+get_special_items(const char *item_name, problem_data_t *pd, GList *comment_fmt_spec)
4b6aa8
+{
4b6aa8
+    /* %oneline,%multiline,%text,%binary */
4b6aa8
+    bool oneline   = (strcmp(item_name+1, "oneline"  ) == 0);
4b6aa8
+    bool multiline = (strcmp(item_name+1, "multiline") == 0);
4b6aa8
+    bool text      = (strcmp(item_name+1, "text"     ) == 0);
4b6aa8
+    bool binary    = (strcmp(item_name+1, "binary"   ) == 0);
4b6aa8
+    if (!oneline && !multiline && !text && !binary)
4b6aa8
+    {
4b6aa8
+        log("Unknown or unsupported element specifier '%s'", item_name);
4b6aa8
+        return NULL;
4b6aa8
+    }
4b6aa8
+
4b6aa8
+    log_debug("Special item_name '%s', iterating for attach...", item_name);
4b6aa8
+    GList *result = 0;
4b6aa8
+
4b6aa8
+    /* Iterate over _sorted_ items */
4b6aa8
+    GList *sorted_names = g_hash_table_get_keys(pd);
4b6aa8
+    sorted_names = g_list_sort(sorted_names, (GCompareFunc)strcmp);
4b6aa8
+
4b6aa8
+    GList *l = sorted_names;
4b6aa8
+    while (l)
4b6aa8
+    {
4b6aa8
+        const char *name = l->data;
4b6aa8
+        l = l->next;
4b6aa8
+        struct problem_item *item = g_hash_table_lookup(pd, name);
4b6aa8
+        if (!item)
4b6aa8
+            continue; /* paranoia, won't happen */
4b6aa8
+
4b6aa8
+        if (is_explicit_or_forbidden(name, comment_fmt_spec))
4b6aa8
+            continue;
4b6aa8
+
4b6aa8
+        if ((item->flags & CD_FLAG_TXT) && !binary)
4b6aa8
+        {
4b6aa8
+            char *content = item->content;
4b6aa8
+            char *eol = strchrnul(content, '\n');
4b6aa8
+            bool is_oneline = (eol[0] == '\0' || eol[1] == '\0');
4b6aa8
+            if (text || oneline == is_oneline)
4b6aa8
+                result = g_list_append(result, xstrdup(name));
4b6aa8
+        }
4b6aa8
+        else if ((item->flags & CD_FLAG_BIN) && binary)
4b6aa8
+            result = g_list_append(result, xstrdup(name));
4b6aa8
+    }
4b6aa8
+
4b6aa8
+    g_list_free(sorted_names); /* names themselves are not freed */
4b6aa8
+
4b6aa8
+
4b6aa8
+    log_debug("...Done iterating over '%s' for attach", item_name);
4b6aa8
+
4b6aa8
+    return result;
4b6aa8
+}
4b6aa8
+
4b6aa8
+static GList *
4b6aa8
+get_attached_files(problem_data_t *pd, GList *items, GList *comment_fmt_spec)
4b6aa8
+{
4b6aa8
+    GList *result = NULL;
4b6aa8
+    GList *item = items;
4b6aa8
+    while (item != NULL)
4b6aa8
+    {
4b6aa8
+        const char *item_name = item->data;
4b6aa8
+        item = item->next;
4b6aa8
+        if (item_name[0] == '-') /* "-name", ignore it */
4b6aa8
+            continue;
4b6aa8
+
4b6aa8
+        if (item_name[0] != '%')
4b6aa8
+        {
4b6aa8
+            result = g_list_append(result, xstrdup(item_name));
4b6aa8
+            continue;
4b6aa8
+        }
4b6aa8
+
4b6aa8
+        GList *special = get_special_items(item_name, pd, comment_fmt_spec);
4b6aa8
+        if (special == NULL)
4b6aa8
+        {
4b6aa8
+            log_notice("No attachment found for '%s'", item_name);
4b6aa8
+            continue;
4b6aa8
+        }
4b6aa8
+
4b6aa8
+        result = g_list_concat(result, special);
4b6aa8
+    }
4b6aa8
+
4b6aa8
+    return result;
4b6aa8
+}
4b6aa8
+
4b6aa8
+/*
4b6aa8
+ * Problem Report - memor stream
4b6aa8
+ *
4b6aa8
+ * A wrapper for POSIX memory stream.
4b6aa8
+ *
4b6aa8
+ * A memory stream is presented as FILE *.
4b6aa8
+ *
4b6aa8
+ * A memory stream is associated with a pointer to written data and a pointer
4b6aa8
+ * to size of the written data.
4b6aa8
+ *
4b6aa8
+ * This structure holds all of the used pointers.
4b6aa8
+ */
4b6aa8
+struct memstream_buffer
4b6aa8
+{
4b6aa8
+    char *msb_buffer;
4b6aa8
+    size_t msb_size;
4b6aa8
+    FILE *msb_stream;
4b6aa8
+};
4b6aa8
+
4b6aa8
+static struct memstream_buffer *
4b6aa8
+memstream_buffer_new()
4b6aa8
+{
4b6aa8
+    struct memstream_buffer *self = xmalloc(sizeof(*self));
4b6aa8
+
4b6aa8
+    self->msb_buffer = NULL;
4b6aa8
+    self->msb_stream = open_memstream(&(self->msb_buffer), &(self->msb_size));
4b6aa8
+
4b6aa8
+    return self;
4b6aa8
+}
4b6aa8
+
4b6aa8
+static void
4b6aa8
+memstream_buffer_free(struct memstream_buffer *self)
4b6aa8
+{
4b6aa8
+    if (self == NULL)
4b6aa8
+        return;
4b6aa8
+
4b6aa8
+    fclose(self->msb_stream);
4b6aa8
+    self->msb_stream = DESTROYED_POINTER;
4b6aa8
+
4b6aa8
+    free(self->msb_buffer);
4b6aa8
+    self->msb_buffer = DESTROYED_POINTER;
4b6aa8
+
4b6aa8
+    free(self);
4b6aa8
+}
4b6aa8
+
4b6aa8
+static FILE *
4b6aa8
+memstream_get_stream(struct memstream_buffer *self)
4b6aa8
+{
4b6aa8
+    assert(self != NULL);
4b6aa8
+
4b6aa8
+    return self->msb_stream;
4b6aa8
+}
4b6aa8
+
4b6aa8
+static const char *
4b6aa8
+memstream_get_string(struct memstream_buffer *self)
4b6aa8
+{
4b6aa8
+    assert(self != NULL);
4b6aa8
+    assert(self->msb_stream != NULL);
4b6aa8
+
4b6aa8
+    fflush(self->msb_stream);
4b6aa8
+
4b6aa8
+    return self->msb_buffer;
4b6aa8
+}
4b6aa8
+
4b6aa8
+
4b6aa8
+/*
4b6aa8
+ * Problem Report
4b6aa8
+ *
4b6aa8
+ * The formated strings are internaly stored in "buffer"s. If a programer wants
4b6aa8
+ * to get a formated section data, a getter function extracts those data from
4b6aa8
+ * the apropriate buffer and returns them in form of null-terminated string.
4b6aa8
+ *
4b6aa8
+ * Each section has own buffer.
4b6aa8
+ *
4b6aa8
+ * There are three common sections that are always present:
4b6aa8
+ * 1. summary
4b6aa8
+ * 2. description
4b6aa8
+ * 3. attach
4b6aa8
+ * Buffers of these sections has own structure member for the sake of
4b6aa8
+ * efficiency.
4b6aa8
+ *
4b6aa8
+ * The custom sections hash their buffers stored in a map where key is a
4b6aa8
+ * section's name and value is a section's buffer.
4b6aa8
+ *
4b6aa8
+ * Problem report provides the programers with the possibility to ammend
4b6aa8
+ * formated output to any section buffer.
4b6aa8
+ */
4b6aa8
+struct problem_report
4b6aa8
+{
4b6aa8
+    struct memstream_buffer *pr_sec_summ; ///< %summary buffer
4b6aa8
+    struct memstream_buffer *pr_sec_desc; ///< %description buffer
4b6aa8
+    GList            *pr_attachments;     ///< %attach - list of file names
4b6aa8
+    GHashTable       *pr_sec_custom;      ///< map : %(custom section) -> buffer
4b6aa8
+};
4b6aa8
+
4b6aa8
+static problem_report_t *
4b6aa8
+problem_report_new()
4b6aa8
+{
4b6aa8
+    problem_report_t *self = xmalloc(sizeof(*self));
4b6aa8
+
4b6aa8
+    self->pr_sec_summ = memstream_buffer_new();
4b6aa8
+    self->pr_sec_desc = memstream_buffer_new();
4b6aa8
+    self->pr_attachments = NULL;
4b6aa8
+    self->pr_sec_custom = NULL;
4b6aa8
+
4b6aa8
+    return self;
4b6aa8
+}
4b6aa8
+
4b6aa8
+static void
4b6aa8
+problem_report_initialize_custom_sections(problem_report_t *self)
4b6aa8
+{
4b6aa8
+    assert(self != NULL);
4b6aa8
+    assert(self->pr_sec_custom == NULL);
4b6aa8
+
4b6aa8
+    self->pr_sec_custom = g_hash_table_new_full(g_str_hash, g_str_equal, free,
4b6aa8
+                                                (GDestroyNotify)memstream_buffer_free);
4b6aa8
+}
4b6aa8
+
4b6aa8
+static void
4b6aa8
+problem_report_destroy_custom_sections(problem_report_t *self)
4b6aa8
+{
4b6aa8
+    assert(self != NULL);
4b6aa8
+    assert(self->pr_sec_custom != NULL);
4b6aa8
+
4b6aa8
+    g_hash_table_destroy(self->pr_sec_custom);
4b6aa8
+}
4b6aa8
+
4b6aa8
+static int
4b6aa8
+problem_report_add_custom_section(problem_report_t *self, const char *name)
4b6aa8
+{
4b6aa8
+    assert(self != NULL);
4b6aa8
+
4b6aa8
+    if (self->pr_sec_custom == NULL)
4b6aa8
+    {
4b6aa8
+        problem_report_initialize_custom_sections(self);
4b6aa8
+    }
4b6aa8
+
4b6aa8
+    if (problem_report_get_buffer(self, name))
4b6aa8
+    {
4b6aa8
+        log_warning("Custom section already exists : '%s'", name);
4b6aa8
+        return -EEXIST;
4b6aa8
+    }
4b6aa8
+
4b6aa8
+    log_debug("Problem report enriched with section : '%s'", name);
4b6aa8
+    g_hash_table_insert(self->pr_sec_custom, xstrdup(name), memstream_buffer_new());
4b6aa8
+    return 0;
4b6aa8
+}
4b6aa8
+
4b6aa8
+static struct memstream_buffer *
4b6aa8
+problem_report_get_section_buffer(const problem_report_t *self, const char *section_name)
4b6aa8
+{
4b6aa8
+    if (self->pr_sec_custom == NULL)
4b6aa8
+    {
4b6aa8
+        log_debug("Couldn't find section '%s': no custom section added", section_name);
4b6aa8
+        return NULL;
4b6aa8
+    }
4b6aa8
+
4b6aa8
+    return (struct memstream_buffer *)g_hash_table_lookup(self->pr_sec_custom, section_name);
4b6aa8
+}
4b6aa8
+
4b6aa8
+problem_report_buffer *
4b6aa8
+problem_report_get_buffer(const problem_report_t *self, const char *section_name)
4b6aa8
+{
4b6aa8
+    assert(self != NULL);
4b6aa8
+    assert(section_name != NULL);
4b6aa8
+
4b6aa8
+    if (strcmp(PR_SEC_SUMMARY, section_name) == 0)
4b6aa8
+        return memstream_get_stream(self->pr_sec_summ);
4b6aa8
+
4b6aa8
+    if (strcmp(PR_SEC_DESCRIPTION, section_name) == 0)
4b6aa8
+        return memstream_get_stream(self->pr_sec_desc);
4b6aa8
+
4b6aa8
+    struct memstream_buffer *buf = problem_report_get_section_buffer(self, section_name);
4b6aa8
+    return buf == NULL ? NULL : memstream_get_stream(buf);
4b6aa8
+}
4b6aa8
+
4b6aa8
+const char *
4b6aa8
+problem_report_get_summary(const problem_report_t *self)
4b6aa8
+{
4b6aa8
+    assert(self != NULL);
4b6aa8
+
4b6aa8
+    return memstream_get_string(self->pr_sec_summ);
4b6aa8
+}
4b6aa8
+
4b6aa8
+const char *
4b6aa8
+problem_report_get_description(const problem_report_t *self)
4b6aa8
+{
4b6aa8
+    assert(self != NULL);
4b6aa8
+
4b6aa8
+    return memstream_get_string(self->pr_sec_desc);
4b6aa8
+}
4b6aa8
+
4b6aa8
+const char *
4b6aa8
+problem_report_get_section(const problem_report_t *self, const char *section_name)
4b6aa8
+{
4b6aa8
+    assert(self != NULL);
4b6aa8
+    assert(section_name);
4b6aa8
+
4b6aa8
+    struct memstream_buffer *buf = problem_report_get_section_buffer(self, section_name);
4b6aa8
+
4b6aa8
+    if (buf == NULL)
4b6aa8
+        return NULL;
4b6aa8
+
4b6aa8
+    return memstream_get_string(buf);
4b6aa8
+}
4b6aa8
+
4b6aa8
+static void
4b6aa8
+problem_report_set_attachments(problem_report_t *self, GList *attachments)
4b6aa8
+{
4b6aa8
+    assert(self != NULL);
4b6aa8
+    assert(self->pr_attachments == NULL);
4b6aa8
+
4b6aa8
+    self->pr_attachments = attachments;
4b6aa8
+}
4b6aa8
+
4b6aa8
+GList *
4b6aa8
+problem_report_get_attachments(const problem_report_t *self)
4b6aa8
+{
4b6aa8
+    assert(self != NULL);
4b6aa8
+
4b6aa8
+    return self->pr_attachments;
4b6aa8
+}
4b6aa8
+
4b6aa8
+void
4b6aa8
+problem_report_free(problem_report_t *self)
4b6aa8
+{
4b6aa8
+    if (self == NULL)
4b6aa8
+        return;
4b6aa8
+
4b6aa8
+    memstream_buffer_free(self->pr_sec_summ);
4b6aa8
+    self->pr_sec_summ = DESTROYED_POINTER;
4b6aa8
+
4b6aa8
+    memstream_buffer_free(self->pr_sec_desc);
4b6aa8
+    self->pr_sec_desc = DESTROYED_POINTER;
4b6aa8
+
4b6aa8
+    g_list_free_full(self->pr_attachments, free);
4b6aa8
+    self->pr_attachments = DESTROYED_POINTER;
4b6aa8
+
4b6aa8
+    if (self->pr_sec_custom)
4b6aa8
+    {
4b6aa8
+        problem_report_destroy_custom_sections(self);
4b6aa8
+        self->pr_sec_custom = DESTROYED_POINTER;
4b6aa8
+    }
4b6aa8
+
4b6aa8
+    free(self);
4b6aa8
+}
4b6aa8
+
4b6aa8
+/*
4b6aa8
+ * Problem Formatter - extra section
4b6aa8
+ */
4b6aa8
+struct extra_section
4b6aa8
+{
4b6aa8
+    char *pfes_name;    ///< name with % prefix
4b6aa8
+    int   pfes_flags;   ///< whether is required or not
4b6aa8
+};
4b6aa8
+
4b6aa8
+static struct extra_section *
4b6aa8
+extra_section_new(const char *name, int flags)
4b6aa8
+{
4b6aa8
+    struct extra_section *self = xmalloc(sizeof(*self));
4b6aa8
+
4b6aa8
+    self->pfes_name = xstrdup(name);
4b6aa8
+    self->pfes_flags = flags;
4b6aa8
+
4b6aa8
+    return self;
4b6aa8
+}
4b6aa8
+
4b6aa8
+static void
4b6aa8
+extra_section_free(struct extra_section *self)
4b6aa8
+{
4b6aa8
+    if (self == NULL)
4b6aa8
+        return;
4b6aa8
+
4b6aa8
+    free(self->pfes_name);
4b6aa8
+    self->pfes_name = DESTROYED_POINTER;
4b6aa8
+
4b6aa8
+    free(self);
4b6aa8
+}
4b6aa8
+
4b6aa8
+static int
4b6aa8
+extra_section_name_cmp(struct extra_section *lhs, const char *rhs)
4b6aa8
+{
4b6aa8
+    return strcmp(lhs->pfes_name, rhs);
4b6aa8
+}
4b6aa8
+
4b6aa8
+/*
4b6aa8
+ * Problem Formatter
4b6aa8
+ *
4b6aa8
+ * Holds parsed sections lists.
4b6aa8
+ */
4b6aa8
+struct problem_formatter
4b6aa8
+{
4b6aa8
+    GList *pf_sections;         ///< parsed sections (struct section_t)
4b6aa8
+    GList *pf_extra_sections;   ///< user configured sections (struct extra_section)
4b6aa8
+    char  *pf_default_summary;  ///< default summary format
4b6aa8
+};
4b6aa8
+
4b6aa8
+problem_formatter_t *
4b6aa8
+problem_formatter_new(void)
4b6aa8
+{
4b6aa8
+    problem_formatter_t *self = xzalloc(sizeof(*self));
4b6aa8
+
4b6aa8
+    self->pf_default_summary = xstrdup("%reason%");
4b6aa8
+
4b6aa8
+    return self;
4b6aa8
+}
4b6aa8
+
4b6aa8
+void
4b6aa8
+problem_formatter_free(problem_formatter_t *self)
4b6aa8
+{
4b6aa8
+    if (self == NULL)
4b6aa8
+        return;
4b6aa8
+
4b6aa8
+    g_list_free_full(self->pf_sections, (GDestroyNotify)section_free);
4b6aa8
+    self->pf_sections = DESTROYED_POINTER;
4b6aa8
+
4b6aa8
+    g_list_free_full(self->pf_extra_sections, (GDestroyNotify)extra_section_free);
4b6aa8
+    self->pf_extra_sections = DESTROYED_POINTER;
4b6aa8
+
4b6aa8
+    free(self->pf_default_summary);
4b6aa8
+    self->pf_default_summary = DESTROYED_POINTER;
4b6aa8
+
4b6aa8
+    free(self);
4b6aa8
+}
4b6aa8
+
4b6aa8
+static int
4b6aa8
+problem_formatter_is_section_known(problem_formatter_t *self, const char *name)
4b6aa8
+{
4b6aa8
+  return    strcmp(name, "summary")     == 0
4b6aa8
+         || strcmp(name, "attach")      == 0
4b6aa8
+         || strcmp(name, "description") == 0
4b6aa8
+         || NULL != g_list_find_custom(self->pf_extra_sections, name, (GCompareFunc)extra_section_name_cmp);
4b6aa8
+}
4b6aa8
+
4b6aa8
+// i.e additional_info -> no flags
4b6aa8
+int
4b6aa8
+problem_formatter_add_section(problem_formatter_t *self, const char *name, int flags)
4b6aa8
+{
4b6aa8
+    /* Do not add already added sections */
4b6aa8
+    if (problem_formatter_is_section_known(self, name))
4b6aa8
+    {
4b6aa8
+        log_debug("Extra section already exists : '%s' ", name);
4b6aa8
+        return -EEXIST;
4b6aa8
+    }
4b6aa8
+
4b6aa8
+    self->pf_extra_sections = g_list_prepend(self->pf_extra_sections,
4b6aa8
+                                             extra_section_new(name, flags));
4b6aa8
+
4b6aa8
+    return 0;
4b6aa8
+}
4b6aa8
+
4b6aa8
+// check format validity and produce warnings
4b6aa8
+static int
4b6aa8
+problem_formatter_validate(problem_formatter_t *self)
4b6aa8
+{
4b6aa8
+    int retval = 0;
4b6aa8
+
4b6aa8
+    /* Go through all (struct extra_section)s and check whete those having flag
4b6aa8
+     * PFFF_REQUIRED are present in the parsed (struct section_t)s.
4b6aa8
+     */
4b6aa8
+    for (GList *iter = self->pf_extra_sections; iter; iter = g_list_next(iter))
4b6aa8
+    {
4b6aa8
+        struct extra_section *section = (struct extra_section *)iter->data;
4b6aa8
+
4b6aa8
+        log_debug("Validating extra section : '%s'", section->pfes_name);
4b6aa8
+
4b6aa8
+        if (   (PFFF_REQUIRED & section->pfes_flags)
4b6aa8
+            && NULL == g_list_find_custom(self->pf_sections, section->pfes_name, (GCompareFunc)section_name_cmp))
4b6aa8
+        {
4b6aa8
+            log_warning("Problem format misses required section : '%s'", section->pfes_name);
4b6aa8
+            ++retval;
4b6aa8
+        }
4b6aa8
+    }
4b6aa8
+
4b6aa8
+    /* Go through all the parsed (struct section_t)s check whether are all
4b6aa8
+     * known, i.e. each section is either one of the common sections (summary,
4b6aa8
+     * description, attach) or is present in the (struct extra_section)s.
4b6aa8
+     */
4b6aa8
+    for (GList *iter = self->pf_sections; iter; iter = g_list_next(iter))
4b6aa8
+    {
4b6aa8
+        section_t *section = (section_t *)iter->data;
4b6aa8
+
4b6aa8
+        if (!problem_formatter_is_section_known(self, (section->name + 1)))
4b6aa8
+        {
4b6aa8
+            log_warning("Problem format contains unrecognized section : '%s'", section->name);
4b6aa8
+            ++retval;
4b6aa8
+        }
4b6aa8
+    }
4b6aa8
+
4b6aa8
+    return retval;
4b6aa8
+}
4b6aa8
+
4b6aa8
+int
4b6aa8
+problem_formatter_load_string(problem_formatter_t *self, const char *fmt)
4b6aa8
+{
4b6aa8
+    const size_t len = strlen(fmt);
4b6aa8
+    if (len != 0)
4b6aa8
+    {
4b6aa8
+        FILE *fp = fmemopen((void *)fmt, len, "r");
4b6aa8
+        if (fp == NULL)
4b6aa8
+        {
4b6aa8
+            error_msg("Not enough memory to open a stream for reading format string.");
4b6aa8
+            return -ENOMEM;
4b6aa8
+        }
4b6aa8
+
4b6aa8
+        self->pf_sections = load_stream(fp);
4b6aa8
+        fclose(fp);
4b6aa8
+    }
4b6aa8
+
4b6aa8
+    return problem_formatter_validate(self);
4b6aa8
+}
4b6aa8
+
4b6aa8
+int
4b6aa8
+problem_formatter_load_file(problem_formatter_t *self, const char *path)
4b6aa8
+{
4b6aa8
+    FILE *fp = stdin;
4b6aa8
+    if (strcmp(path, "-") != 0)
4b6aa8
+    {
4b6aa8
+        fp = fopen(path, "r");
4b6aa8
+        if (!fp)
4b6aa8
+            return -ENOENT;
4b6aa8
+    }
4b6aa8
+
4b6aa8
+    self->pf_sections = load_stream(fp);
4b6aa8
+
4b6aa8
+    if (fp != stdin)
4b6aa8
+        fclose(fp);
4b6aa8
+
4b6aa8
+    return problem_formatter_validate(self);
4b6aa8
+}
4b6aa8
+
4b6aa8
+// generates report
4b6aa8
+int
4b6aa8
+problem_formatter_generate_report(const problem_formatter_t *self, problem_data_t *data, problem_report_t **report)
4b6aa8
+{
4b6aa8
+    problem_report_t *pr = problem_report_new();
4b6aa8
+
4b6aa8
+    for (GList *iter = self->pf_extra_sections; iter; iter = g_list_next(iter))
4b6aa8
+        problem_report_add_custom_section(pr, ((struct extra_section *)iter->data)->pfes_name);
4b6aa8
+
4b6aa8
+    bool has_summary = false;
4b6aa8
+    for (GList *iter = self->pf_sections; iter; iter = g_list_next(iter))
4b6aa8
+    {
4b6aa8
+        section_t *section = (section_t *)iter->data;
4b6aa8
+
4b6aa8
+        /* %summary is something special */
4b6aa8
+        if (strcmp(section->name, "%summary") == 0)
4b6aa8
+        {
4b6aa8
+            has_summary = true;
4b6aa8
+            format_percented_string((const char *)section->items->data, data,
4b6aa8
+                                    problem_report_get_buffer(pr, PR_SEC_SUMMARY));
4b6aa8
+        }
4b6aa8
+        /* %attach as well */
4b6aa8
+        else if (strcmp(section->name, "%attach") == 0)
4b6aa8
+        {
4b6aa8
+            problem_report_set_attachments(pr, get_attached_files(data, section->items, self->pf_sections));
4b6aa8
+        }
4b6aa8
+        else /* %description or a custom section (e.g. %additional_info) */
4b6aa8
+        {
4b6aa8
+            FILE *buffer = problem_report_get_buffer(pr, section->name + 1);
4b6aa8
+
4b6aa8
+            if (buffer != NULL)
4b6aa8
+            {
4b6aa8
+                log_debug("Formatting section : '%s'", section->name);
4b6aa8
+                format_section(section, data, self->pf_sections, buffer);
4b6aa8
+            }
4b6aa8
+            else
4b6aa8
+                log_warning("Unsupported section '%s'", section->name);
4b6aa8
+        }
4b6aa8
+    }
4b6aa8
+
4b6aa8
+    if (!has_summary) {
4b6aa8
+        log_debug("Problem format misses section '%%summary'. Using the default one : '%s'.",
4b6aa8
+                    self->pf_default_summary);
4b6aa8
+
4b6aa8
+        format_percented_string(self->pf_default_summary,
4b6aa8
+                   data, problem_report_get_buffer(pr, PR_SEC_SUMMARY));
4b6aa8
+    }
4b6aa8
+
4b6aa8
+    *report = pr;
4b6aa8
+    return 0;
4b6aa8
+}
4b6aa8
diff --git a/tests/Makefile.am b/tests/Makefile.am
4b6aa8
index 9bfc2b6..b45f2d9 100644
4b6aa8
--- a/tests/Makefile.am
4b6aa8
+++ b/tests/Makefile.am
4b6aa8
@@ -47,7 +47,8 @@ TESTSUITE_AT = \
4b6aa8
   global_config.at \
4b6aa8
   iso_date.at \
4b6aa8
   uriparser.at \
4b6aa8
-  event_config.at
4b6aa8
+  event_config.at \
4b6aa8
+  problem_report.at
4b6aa8
 
4b6aa8
 TESTSUITE_AT_IN = \
4b6aa8
   bugzilla_plugin.at
4b6aa8
diff --git a/tests/problem_report.at b/tests/problem_report.at
4b6aa8
new file mode 100644
4b6aa8
index 0000000..8bc469f
4b6aa8
--- /dev/null
4b6aa8
+++ b/tests/problem_report.at
4b6aa8
@@ -0,0 +1,690 @@
4b6aa8
+# -*- Autotest -*-
4b6aa8
+
4b6aa8
+AT_BANNER([problem report])
4b6aa8
+
4b6aa8
+## ------- ##
4b6aa8
+## summary ##
4b6aa8
+## ------- ##
4b6aa8
+
4b6aa8
+AT_TESTFUN([summary],
4b6aa8
+[[
4b6aa8
+#include "problem_report.h"
4b6aa8
+#include "internal_libreport.h"
4b6aa8
+#include <assert.h>
4b6aa8
+
4b6aa8
+int main(int argc, char **argv)
4b6aa8
+{
4b6aa8
+    const char *const test_format_result[][2] = {
4b6aa8
+        {
4b6aa8
+            "%summary:: [abrt] trivial string",
4b6aa8
+            "[abrt] trivial string"
4b6aa8
+        },
4b6aa8
+
4b6aa8
+        {
4b6aa8
+            "%summary:: [abrt] %package%",
4b6aa8
+            "[abrt] libreport"
4b6aa8
+        },
4b6aa8
+
4b6aa8
+        {
4b6aa8
+            "%summary:: [abrt] %package%[[ : %crash_function%()]][[ : %does_not_exist%]][[ : %reason%]][[ TAINTED: %taint_flags%]]",
4b6aa8
+            "[abrt] libreport : run_event() : Killed by SIGSEGV"
4b6aa8
+        },
4b6aa8
+
4b6aa8
+        {
4b6aa8
+            "%summary:: [abrt] %package%[[ : %crash_function%[[ : %does_not_exist%]]()]][[ : %reason%]]",
4b6aa8
+            "[abrt] libreport : run_event() : Killed by SIGSEGV"
4b6aa8
+        },
4b6aa8
+
4b6aa8
+        {
4b6aa8
+            "%summary:: [abrt] %package%[[ : %does_not_exist%[[ : %crash_function%()]]]][[ : %reason%]]",
4b6aa8
+            "[abrt] libreport : Killed by SIGSEGV"
4b6aa8
+        },
4b6aa8
+
4b6aa8
+        {
4b6aa8
+            "%summary:: [[%does_not_exist%]][[%once_more%]][abrt] %package%",
4b6aa8
+            "[abrt] libreport"
4b6aa8
+        },
4b6aa8
+
4b6aa8
+        {
4b6aa8
+            "",
4b6aa8
+            "Killed by SIGSEGV"
4b6aa8
+        },
4b6aa8
+    };
4b6aa8
+
4b6aa8
+    g_verbose = 3;
4b6aa8
+
4b6aa8
+    problem_data_t *data = problem_data_new();
4b6aa8
+    problem_data_add_text_noteditable(data, "package", "libreport");
4b6aa8
+    problem_data_add_text_noteditable(data, "crash_function", "run_event");
4b6aa8
+    problem_data_add_text_noteditable(data, "reason", "Killed by SIGSEGV");
4b6aa8
+
4b6aa8
+    for (size_t i = 0; i < sizeof(test_format_result)/sizeof(*test_format_result); ++i) {
4b6aa8
+        problem_formatter_t *pf = problem_formatter_new();
4b6aa8
+        assert(!problem_formatter_load_string(pf, test_format_result[i][0]));
4b6aa8
+
4b6aa8
+        problem_report_t *pr = NULL;
4b6aa8
+        assert(!problem_formatter_generate_report(pf, data, &pr);;
4b6aa8
+        assert(pr != NULL);
4b6aa8
+
4b6aa8
+        const char *summary = problem_report_get_summary(pr);
4b6aa8
+        assert(summary != NULL);
4b6aa8
+
4b6aa8
+        fprintf(stderr, "expected: '%s'\n", test_format_result[i][1]);
4b6aa8
+        fprintf(stderr, "result  : '%s'\n", summary);
4b6aa8
+
4b6aa8
+        assert(strcmp(test_format_result[i][1], summary) == 0);
4b6aa8
+
4b6aa8
+        problem_report_free(pr);
4b6aa8
+        problem_formatter_free(pf);
4b6aa8
+    }
4b6aa8
+
4b6aa8
+    problem_data_free(data);
4b6aa8
+
4b6aa8
+    return 0;
4b6aa8
+}
4b6aa8
+]])
4b6aa8
+
4b6aa8
+## ---------- ##
4b6aa8
+## desciption ##
4b6aa8
+## ---------- ##
4b6aa8
+
4b6aa8
+AT_TESTFUN([description],
4b6aa8
+[[
4b6aa8
+#include "problem_report.h"
4b6aa8
+#include "internal_libreport.h"
4b6aa8
+#include <assert.h>
4b6aa8
+
4b6aa8
+int main(int argc, char **argv)
4b6aa8
+{
4b6aa8
+    const char *const test_format_result[][2] = {
4b6aa8
+        {
4b6aa8
+            "\n"\
4b6aa8
+            "\n"\
4b6aa8
+            "\n"\
4b6aa8
+            "Single line\n"
4b6aa8
+            "\n"\
4b6aa8
+            "\n"\
4b6aa8
+            "\n",
4b6aa8
+
4b6aa8
+            "Single line\n"
4b6aa8
+        },
4b6aa8
+
4b6aa8
+        {
4b6aa8
+            "\n"\
4b6aa8
+            "\n"\
4b6aa8
+            "\n"\
4b6aa8
+            "Comment:: %bare_comment"
4b6aa8
+            "\n"\
4b6aa8
+            "\n"\
4b6aa8
+            "\n"\
4b6aa8
+            "Ad-hoc line\n"
4b6aa8
+            "\n"\
4b6aa8
+            "\n"\
4b6aa8
+            "\n"\
4b6aa8
+            "Additional:: package,\\\n"
4b6aa8
+            "uuid,cwd\\\\"
4b6aa8
+            ",user_name"
4b6aa8
+            "\n"\
4b6aa8
+            "\n",
4b6aa8
+
4b6aa8
+            "Comment:\n" \
4b6aa8
+            "Hello, world!\n"
4b6aa8
+            "\n"\
4b6aa8
+            "\n"\
4b6aa8
+            "Ad-hoc line\n"
4b6aa8
+            "\n"\
4b6aa8
+            "\n"\
4b6aa8
+            "\n"\
4b6aa8
+            "Additional:\n"\
4b6aa8
+            "package:        libreport\n"\
4b6aa8
+            "uuid:           123456789ABCDEF\n"\
4b6aa8
+            "user_name:      abrt\n",
4b6aa8
+        },
4b6aa8
+
4b6aa8
+        {
4b6aa8
+            ":: %bare_description",
4b6aa8
+
4b6aa8
+            "I run will_segfault and\n"\
4b6aa8
+            "it crashed as expected\n"
4b6aa8
+        },
4b6aa8
+
4b6aa8
+        {
4b6aa8
+            "User:: %bare_user_name,uid\n"\
4b6aa8
+            "Additional info:: -uuid,%oneline,-comment,-package",
4b6aa8
+
4b6aa8
+            "User:\n"\
4b6aa8
+            "abrt\n"\
4b6aa8
+            "uid:            69\n"\
4b6aa8
+            "Additional info:\n"\
4b6aa8
+            "analyzer:       CCpp\n"\
4b6aa8
+            "root:           /var/run/mock/abrt\n"\
4b6aa8
+            "type:           CCpp\n"
4b6aa8
+        },
4b6aa8
+
4b6aa8
+        {
4b6aa8
+            ":: %reporter",
4b6aa8
+            NULL /* do no check results*/
4b6aa8
+        },
4b6aa8
+
4b6aa8
+        {
4b6aa8
+            "Truncated backtrace:: %bare_%short_backtrace",
4b6aa8
+
4b6aa8
+            "Truncated backtrace:\n"
4b6aa8
+            "Thread no. 0 (7 frames)\n"
4b6aa8
+            " #1 crash at will_segfault.c:19\n"
4b6aa8
+            " #2 varargs at will_segfault.c:31\n"
4b6aa8
+            " #3 inlined at will_segfault.c:40\n"
4b6aa8
+            " #4 f at will_segfault.c:45\n"
4b6aa8
+            " #5 callback at will_segfault.c:50\n"
4b6aa8
+            " #6 call_me_back at libwillcrash.c:8\n"
4b6aa8
+            " #7 recursive at will_segfault.c:59\n"
4b6aa8
+        },
4b6aa8
+    };
4b6aa8
+
4b6aa8
+    g_verbose = 3;
4b6aa8
+
4b6aa8
+    problem_data_t *data = problem_data_new();
4b6aa8
+    problem_data_add_text_noteditable(data, "package", "libreport");
4b6aa8
+    problem_data_add_text_noteditable(data, "analyzer", "CCpp");
4b6aa8
+    problem_data_add_text_noteditable(data, "type", "CCpp");
4b6aa8
+    problem_data_add_text_noteditable(data, "comment", "Hello, world!");
4b6aa8
+    problem_data_add_text_noteditable(data, "uuid", "123456789ABCDEF");
4b6aa8
+    problem_data_add_text_noteditable(data, "uid", "69");
4b6aa8
+    problem_data_add_text_noteditable(data, "user_name", "abrt");
4b6aa8
+    problem_data_add_text_noteditable(data, "root", "/var/run/mock/abrt");
4b6aa8
+    problem_data_add_text_noteditable(data, "description", "I run will_segfault and\nit crashed as expected\n");
4b6aa8
+    problem_data_add_text_noteditable(data, "backtrace",
4b6aa8
+"Thread 1 (LWP 11865):\n"\
4b6aa8
+"#0  printf (__fmt=0x400acf \"Result: %d\\n\") at /usr/include/bits/stdio2.h:104\n"\
4b6aa8
+"No locals.\n"\
4b6aa8
+"#1  crash (p=p@entry=0x0) at will_segfault.c:19\n"\
4b6aa8
+"i = <error reading variable i (Cannot access memory at address 0x0)>\n"\
4b6aa8
+"#2  0x0000000000400964 in varargs (num_args=1, num_args@entry=2) at will_segfault.c:31\n"\
4b6aa8
+"p = <optimized out>\n"\
4b6aa8
+"ap = {{gp_offset = 24, fp_offset = 32767, overflow_arg_area = 0x7fff4fc8a0c0, reg_save_area = 0x7fff4fc8a080}}\n"\
4b6aa8
+"#3  0x00000000004009be in inlined (p=0x0) at will_segfault.c:40\n"\
4b6aa8
+"num = 42\n"\
4b6aa8
+"#4  f (p=p@entry=0x0) at will_segfault.c:45\n"\
4b6aa8
+"No locals.\n"\
4b6aa8
+"#5  0x00000000004009e9 in callback (data=data@entry=0x0) at will_segfault.c:50\n"\
4b6aa8
+"No locals.\n"\
4b6aa8
+"#6  0x00000032f76006f9 in call_me_back (cb=cb@entry=0x4009e0 <callback>, data=data@entry=0x0) at libwillcrash.c:8\n"\
4b6aa8
+"res = <optimized out>\n"\
4b6aa8
+"#7  0x0000000000400a14 in recursive (i=i@entry=0) at will_segfault.c:59\n"\
4b6aa8
+"p = <optimized out>\n"\
4b6aa8
+"#8  0x0000000000400a00 in recursive (i=i@entry=1) at will_segfault.c:66\n"\
4b6aa8
+"No locals.\n"\
4b6aa8
+"#9  0x0000000000400a00 in recursive (i=i@entry=2) at will_segfault.c:66\n"\
4b6aa8
+"No locals.\n"\
4b6aa8
+"#10 0x0000000000400775 in main (argc=<optimized out>, argv=<optimized out>) at will_segfault.c:83\n"\
4b6aa8
+"No locals.\n"\
4b6aa8
+"From                To                  Syms Read   Shared Object Library\n"\
4b6aa8
+"0x00000032f76005f0  0x00000032f7600712  Yes         /lib64/libwillcrash.so.0\n"\
4b6aa8
+"0x0000003245c1f4f0  0x0000003245d6aca4  Yes         /lib64/libc.so.6\n"\
4b6aa8
+"0x0000003245800b10  0x000000324581b6d0  Yes         /lib64/ld-linux-x86-64.so.2\n"\
4b6aa8
+"$1 = 0x0\n"
4b6aa8
+"No symbol \"__glib_assert_msg\" in current context.\n"\
4b6aa8
+"rax            0xf      15\n"\
4b6aa8
+"rbx            0x0      0\n"\
4b6aa8
+"rcx            0x7ff96f752000   140709293531136\n"\
4b6aa8
+"rdx            0x3245fb9a40     215922481728\n"\
4b6aa8
+"rsi            0x7ff96f752000   140709293531136\n"\
4b6aa8
+"rdi            0x1      1\n"\
4b6aa8
+"rbp            0x400a30 0x400a30 <__libc_csu_init>\n"\
4b6aa8
+"rsp            0x7fff4fc8a050   0x7fff4fc8a050\n"\
4b6aa8
+"r8             0xffffffff       4294967295\n"\
4b6aa8
+"r9             0x0      0\n"\
4b6aa8
+"r10            0x22     34\n"\
4b6aa8
+"r11            0x246    582\n"\
4b6aa8
+"r12            0x40079f 4196255\n"\
4b6aa8
+"r13            0x7fff4fc8a210   140734531936784\n"\
4b6aa8
+"r14            0x0      0\n"\
4b6aa8
+"r15            0x0      0\n"\
4b6aa8
+"rip            0x4008ae 0x4008ae <crash+14>\n"\
4b6aa8
+"eflags         0x10246  [ PF ZF IF RF ]\n"\
4b6aa8
+"cs             0x33     51\n"\
4b6aa8
+"ss             0x2b     43\n"\
4b6aa8
+"ds             0x0      0\n"\
4b6aa8
+"es             0x0      0\n"\
4b6aa8
+"fs             0x0      0\n"\
4b6aa8
+"gs             0x0      0\n"\
4b6aa8
+"st0            0        (raw 0x00000000000000000000)\n"\
4b6aa8
+"st1            0        (raw 0x00000000000000000000)\n"\
4b6aa8
+"st2            0        (raw 0x00000000000000000000)\n"\
4b6aa8
+"st3            0        (raw 0x00000000000000000000)\n"\
4b6aa8
+"st4            0        (raw 0x00000000000000000000)\n"\
4b6aa8
+"st5            0        (raw 0x00000000000000000000)\n"\
4b6aa8
+"st6            0        (raw 0x00000000000000000000)\n"\
4b6aa8
+"st7            0        (raw 0x00000000000000000000)\n"\
4b6aa8
+"fctrl          0x37f    895\n"\
4b6aa8
+"fstat          0x0      0\n"\
4b6aa8
+"ftag           0xffff   65535\n"\
4b6aa8
+"fiseg          0x0      0\n"\
4b6aa8
+"fioff          0x0      0\n"\
4b6aa8
+"foseg          0x0      0\n"\
4b6aa8
+"fooff          0x0      0\n"\
4b6aa8
+"fop            0x0      0\n"\
4b6aa8
+"xmm0           {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 <repeats 16 times>}, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x0, 0x0, 0x0, 0x0}, v2_int64 = {0x0, 0x0}, uint128 = 0x00000000000000000000000000000000}\n"\
4b6aa8
+"xmm1           {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x2f <repeats 16 times>}, v8_int16 = {0x2f2f, 0x2f2f, 0x2f2f, 0x2f2f, 0x2f2f, 0x2f2f, 0x2f2f, 0x2f2f}, v4_int32 = {0x2f2f2f2f, 0x2f2f2f2f, 0x2f2f2f2f, 0x2f2f2f2f}, v2_int64 = {0x2f2f2f2f2f2f2f2f, 0x2f2f2f2f2f2f2f2f}, uint128 = 0x2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f2f}\n"\
4b6aa8
+"xmm2           {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 <repeats 16 times>}, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x0, 0x0, 0x0, 0x0}, v2_int64 = {0x0, 0x0}, uint128 = 0x00000000000000000000000000000000}\n"\
4b6aa8
+"xmm3           {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 <repeats 13 times>, 0xff, 0x0, 0x0}, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff00, 0x0}, v4_int32 = {0x0, 0x0, 0x0, 0xff00}, v2_int64 = {0x0, 0xff0000000000}, uint128 = 0x0000ff00000000000000000000000000}\n"\
4b6aa8
+"xmm4           {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0x0, 0x0, 0x0, 0x0, 0x0}, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0xff, 0x0, 0x0}, v4_int32 = {0x0, 0x0, 0xff0000, 0x0}, v2_int64 = {0x0, 0xff0000}, uint128 = 0x0000000000ff00000000000000000000}\n"\
4b6aa8
+"xmm5           {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 <repeats 16 times>}, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x0, 0x0, 0x0, 0x0}, v2_int64 = {0x0, 0x0}, uint128 = 0x00000000000000000000000000000000}\n"\
4b6aa8
+"xmm6           {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 <repeats 16 times>}, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x0, 0x0, 0x0, 0x0}, v2_int64 = {0x0, 0x0}, uint128 = 0x00000000000000000000000000000000}\n"\
4b6aa8
+"xmm7           {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 <repeats 16 times>}, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x0, 0x0, 0x0, 0x0}, v2_int64 = {0x0, 0x0}, uint128 = 0x00000000000000000000000000000000}\n"\
4b6aa8
+"xmm8           {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 <repeats 16 times>}, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x0, 0x0, 0x0, 0x0}, v2_int64 = {0x0, 0x0}, uint128 = 0x00000000000000000000000000000000}\n"\
4b6aa8
+"xmm9           {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 <repeats 16 times>}, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x0, 0x0, 0x0, 0x0}, v2_int64 = {0x0, 0x0}, uint128 = 0x00000000000000000000000000000000}\n"\
4b6aa8
+"xmm10          {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 <repeats 16 times>}, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x0, 0x0, 0x0, 0x0}, v2_int64 = {0x0, 0x0}, uint128 = 0x00000000000000000000000000000000}\n"\
4b6aa8
+"xmm11          {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 <repeats 16 times>}, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x0, 0x0, 0x0, 0x0}, v2_int64 = {0x0, 0x0}, uint128 = 0x00000000000000000000000000000000}\n"\
4b6aa8
+"xmm12          {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 <repeats 14 times>, 0xff, 0x0}, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xff}, v4_int32 = {0x0, 0x0, 0x0, 0xff0000}, v2_int64 = {0x0, 0xff000000000000}, uint128 = 0x00ff0000000000000000000000000000}\n"\
4b6aa8
+"xmm13          {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 <repeats 16 times>}, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x0, 0x0, 0x0, 0x0}, v2_int64 = {0x0, 0x0}, uint128 = 0x00000000000000000000000000000000}\n"\
4b6aa8
+"xmm14          {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 <repeats 16 times>}, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x0, 0x0, 0x0, 0x0}, v2_int64 = {0x0, 0x0}, uint128 = 0x00000000000000000000000000000000}\n"\
4b6aa8
+"xmm15          {v4_float = {0x0, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x0 <repeats 16 times>}, v8_int16 = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x0, 0x0, 0x0, 0x0}, v2_int64 = {0x0, 0x0}, uint128 = 0x00000000000000000000000000000000}\n"\
4b6aa8
+"mxcsr          0x1f80   [ IM DM ZM OM UM PM ]\n"\
4b6aa8
+"Dump of assembler code for function crash:\n"\
4b6aa8
+"   0x00000000004008a0 <+0>:     push   %rbx\n"\
4b6aa8
+"   0x00000000004008a1 <+1>:     mov    %rdi,%rbx\n"\
4b6aa8
+"   0x00000000004008a4 <+4>:     mov    $0x400ac0,%edi\n"\
4b6aa8
+"   0x00000000004008a9 <+9>:     callq  0x4006f0 <puts@plt>\n"\
4b6aa8
+"   => 0x00000000004008ae <+14>:    mov    (%rbx),%edx\n"\
4b6aa8
+"   0x00000000004008b0 <+16>:    mov    $0x400acf,%esi\n"\
4b6aa8
+"   0x00000000004008b5 <+21>:    mov    $0x1,%edi\n"\
4b6aa8
+"   0x00000000004008ba <+26>:    xor    %eax,%eax\n"\
4b6aa8
+"   0x00000000004008bc <+28>:    callq  0x400740 <__printf_chk@plt>\n"\
4b6aa8
+"   0x00000000004008c1 <+33>:    pop    %rbx\n"\
4b6aa8
+"   0x00000000004008c2 <+34>:    retq\n"\
4b6aa8
+"End of assembler dump.\n"
4b6aa8
+            );
4b6aa8
+
4b6aa8
+    for (size_t i = 0; i < sizeof(test_format_result)/sizeof(*test_format_result); ++i) {
4b6aa8
+        problem_formatter_t *pf = problem_formatter_new();
4b6aa8
+        assert(!problem_formatter_load_string(pf, test_format_result[i][0]));
4b6aa8
+
4b6aa8
+        problem_report_t *pr = NULL;
4b6aa8
+        assert(!problem_formatter_generate_report(pf, data, &pr);;
4b6aa8
+        assert(pr != NULL);
4b6aa8
+
4b6aa8
+        const char *summary = problem_report_get_description(pr);
4b6aa8
+        assert(summary != NULL);
4b6aa8
+
4b6aa8
+        if (test_format_result[i][1] != NULL) {
4b6aa8
+            fprintf(stderr, "####\n");
4b6aa8
+            fprintf(stderr, "expected: '%s'\n", test_format_result[i][1]);
4b6aa8
+            fprintf(stderr, "====\n");
4b6aa8
+            fprintf(stderr, "result  : '%s'\n", summary);
4b6aa8
+            fprintf(stderr, "####\n");
4b6aa8
+
4b6aa8
+            assert(strcmp(test_format_result[i][1], summary) == 0);
4b6aa8
+        }
4b6aa8
+
4b6aa8
+        problem_report_free(pr);
4b6aa8
+        problem_formatter_free(pf);
4b6aa8
+    }
4b6aa8
+
4b6aa8
+    problem_data_free(data);
4b6aa8
+
4b6aa8
+    return 0;
4b6aa8
+}
4b6aa8
+]])
4b6aa8
+
4b6aa8
+## ------ ##
4b6aa8
+## attach ##
4b6aa8
+## ------ ##
4b6aa8
+
4b6aa8
+AT_TESTFUN([attach],
4b6aa8
+[[
4b6aa8
+#include "problem_report.h"
4b6aa8
+#include "internal_libreport.h"
4b6aa8
+#include <assert.h>
4b6aa8
+
4b6aa8
+int main(int argc, char **argv)
4b6aa8
+{
4b6aa8
+    g_verbose = 3;
4b6aa8
+
4b6aa8
+    const char *fst[] = { "backtrace", "screenshot", "description", NULL };
4b6aa8
+
4b6aa8
+    struct test_case {
4b6aa8
+        const char *format;
4b6aa8
+        const char *const *files;
4b6aa8
+    } cases [] = {
4b6aa8
+        {
4b6aa8
+            .format = "%attach:: %multiline,%binary,-coredump",
4b6aa8
+            .files = fst,
4b6aa8
+        }
4b6aa8
+    };
4b6aa8
+
4b6aa8
+    problem_data_t *data = problem_data_new();
4b6aa8
+    problem_data_add_text_noteditable(data, "package", "libreport");
4b6aa8
+    problem_data_add_text_noteditable(data, "analyzer", "CCpp");
4b6aa8
+    problem_data_add_text_noteditable(data, "type", "CCpp");
4b6aa8
+    problem_data_add_text_noteditable(data, "comment", "Hello, world!");
4b6aa8
+    problem_data_add_text_noteditable(data, "uuid", "123456789ABCDEF");
4b6aa8
+    problem_data_add_text_noteditable(data, "uid", "69");
4b6aa8
+    problem_data_add_text_noteditable(data, "user_name", "abrt");
4b6aa8
+    problem_data_add_text_noteditable(data, "root", "/var/run/mock/abrt");
4b6aa8
+    problem_data_add_text_noteditable(data, "description", "I run will_segfault and\nit crashed as expected\n");
4b6aa8
+    problem_data_add_file(data, "coredump", "/what/ever/path/to/coredump");
4b6aa8
+    problem_data_add_file(data, "screenshot", "/what/ever/path/to/screenshot");
4b6aa8
+    problem_data_add_text_noteditable(data, "backtrace",
4b6aa8
+"Thread 1 (LWP 11865):\n"\
4b6aa8
+"#0  printf (__fmt=0x400acf \"Result: %d\\n\") at /usr/include/bits/stdio2.h:104\n"\
4b6aa8
+"No locals.\n"\
4b6aa8
+"#1  crash (p=p@entry=0x0) at will_segfault.c:19\n"\
4b6aa8
+"i = <error reading variable i (Cannot access memory at address 0x0)>\n"\
4b6aa8
+"#2  0x0000000000400964 in varargs (num_args=1, num_args@entry=2) at will_segfault.c:31\n"\
4b6aa8
+"p = <optimized out>\n"\
4b6aa8
+"ap = {{gp_offset = 24, fp_offset = 32767, overflow_arg_area = 0x7fff4fc8a0c0, reg_save_area = 0x7fff4fc8a080}}\n"\
4b6aa8
+"#3  0x00000000004009be in inlined (p=0x0) at will_segfault.c:40\n"\
4b6aa8
+"num = 42\n"\
4b6aa8
+"#4  f (p=p@entry=0x0) at will_segfault.c:45\n"\
4b6aa8
+"No locals.\n"\
4b6aa8
+"#5  0x00000000004009e9 in callback (data=data@entry=0x0) at will_segfault.c:50\n"\
4b6aa8
+"No locals.\n"\
4b6aa8
+"#6  0x00000032f76006f9 in call_me_back (cb=cb@entry=0x4009e0 <callback>, data=data@entry=0x0) at libwillcrash.c:8\n"\
4b6aa8
+"res = <optimized out>\n"\
4b6aa8
+"#7  0x0000000000400a14 in recursive (i=i@entry=0) at will_segfault.c:59\n"\
4b6aa8
+"p = <optimized out>\n"\
4b6aa8
+"#8  0x0000000000400a00 in recursive (i=i@entry=1) at will_segfault.c:66\n"\
4b6aa8
+"No locals.\n"\
4b6aa8
+"#9  0x0000000000400a00 in recursive (i=i@entry=2) at will_segfault.c:66\n"\
4b6aa8
+"No locals.\n"\
4b6aa8
+"#10 0x0000000000400775 in main (argc=<optimized out>, argv=<optimized out>) at will_segfault.c:83\n"\
4b6aa8
+"No locals.\n");
4b6aa8
+
4b6aa8
+    for (size_t i = 0; i < sizeof(cases)/sizeof(*cases); ++i) {
4b6aa8
+        fprintf(stderr, "%s", cases[i].format);
4b6aa8
+
4b6aa8
+        problem_formatter_t *pf = problem_formatter_new();
4b6aa8
+        assert(!problem_formatter_load_string(pf, cases[i].format));
4b6aa8
+
4b6aa8
+        problem_report_t *pr = NULL;
4b6aa8
+        assert(!problem_formatter_generate_report(pf, data, &pr);;
4b6aa8
+        assert(pr != NULL);
4b6aa8
+
4b6aa8
+        const char *const *iter = cases[i].files;
4b6aa8
+
4b6aa8
+        if (*iter != NULL) {
4b6aa8
+            assert(problem_report_get_attachments(pr));
4b6aa8
+        }
4b6aa8
+
4b6aa8
+        GList *clone = g_list_copy_deep(problem_report_get_attachments(pr), (GCopyFunc)xstrdup, NULL);
4b6aa8
+
4b6aa8
+        while (*iter) {
4b6aa8
+            GList *item = g_list_find_custom(clone, *iter, (GCompareFunc)strcmp);
4b6aa8
+
4b6aa8
+            if (item == NULL) {
4b6aa8
+                fprintf(stderr, "format:       '%s'\n", cases[i].format);
4b6aa8
+                fprintf(stderr, "missing file: '%s'\n", *iter);
4b6aa8
+                abort();
4b6aa8
+            }
4b6aa8
+            else {
4b6aa8
+                free(item->data);
4b6aa8
+                clone = g_list_delete_link(clone, item);
4b6aa8
+            }
4b6aa8
+
4b6aa8
+            ++iter;
4b6aa8
+        }
4b6aa8
+
4b6aa8
+        if (clone != NULL) {
4b6aa8
+            for (GList *iter = clone; iter; iter = g_list_next(iter)) {
4b6aa8
+                fprintf(stderr, "extra : '%s'\n", (const char *)iter->data);
4b6aa8
+            }
4b6aa8
+            abort();
4b6aa8
+        }
4b6aa8
+
4b6aa8
+        problem_report_free(pr);
4b6aa8
+        problem_formatter_free(pf);
4b6aa8
+    }
4b6aa8
+
4b6aa8
+    problem_data_free(data);
4b6aa8
+
4b6aa8
+    return 0;
4b6aa8
+}
4b6aa8
+]])
4b6aa8
+
4b6aa8
+
4b6aa8
+## -------------- ##
4b6aa8
+## custom_section ##
4b6aa8
+## -------------- ##
4b6aa8
+
4b6aa8
+AT_TESTFUN([custom_section],
4b6aa8
+[[
4b6aa8
+#include "problem_report.h"
4b6aa8
+#include "internal_libreport.h"
4b6aa8
+#include <assert.h>
4b6aa8
+
4b6aa8
+int main(int argc, char **argv)
4b6aa8
+{
4b6aa8
+    g_verbose = 3;
4b6aa8
+
4b6aa8
+    struct test_case;
4b6aa8
+    struct test_case {
4b6aa8
+        const char *section_name;
4b6aa8
+        int section_flags;
4b6aa8
+        int retval_load;
4b6aa8
+        int retval_generate;
4b6aa8
+        const char *format;
4b6aa8
+        const char *output;
4b6aa8
+    } cases [] = {
4b6aa8
+        {   .section_name = NULL,
4b6aa8
+            .section_flags = 0,
4b6aa8
+            .retval_load = 1,
4b6aa8
+            .retval_generate = 0,
4b6aa8
+            .format =
4b6aa8
+                "%additional_info::\n"
4b6aa8
+                ":: package,\\\n"
4b6aa8
+                "uuid,cwd\\\\"
4b6aa8
+                ",user_name"
4b6aa8
+                ,
4b6aa8
+            .output = NULL,
4b6aa8
+        },
4b6aa8
+
4b6aa8
+        {   .section_name = "additional_info",
4b6aa8
+            .section_flags = 0,
4b6aa8
+            .retval_load = 0,
4b6aa8
+            .retval_generate = 0,
4b6aa8
+            .format =
4b6aa8
+                "%additional_info::\n"
4b6aa8
+                ":: package,\\\n"
4b6aa8
+                "uuid,cwd\\\\"
4b6aa8
+                ",user_name"
4b6aa8
+                ,
4b6aa8
+            .output =
4b6aa8
+                "package:        libreport\n"
4b6aa8
+                "uuid:           0123456789ABCDEF\n"
4b6aa8
+                "user_name:      abrt\n"
4b6aa8
+                ,
4b6aa8
+        },
4b6aa8
+
4b6aa8
+        {   .section_name = "additional_info",
4b6aa8
+            .section_flags = PFFF_REQUIRED,
4b6aa8
+            .retval_load = 1,
4b6aa8
+            .retval_generate = 0,
4b6aa8
+            .format = "%summary:: [abrt] %package%\n:: %reporter\n",
4b6aa8
+            .output = NULL,
4b6aa8
+        },
4b6aa8
+
4b6aa8
+        {   .section_name = "additional_info",
4b6aa8
+            .section_flags = 0,
4b6aa8
+            .retval_load = 0,
4b6aa8
+            .retval_generate = 0,
4b6aa8
+            .format = "%summary:: [abrt] %package%\n:: %reporter\n",
4b6aa8
+            .output = "",
4b6aa8
+        },
4b6aa8
+
4b6aa8
+        {   .section_name = "additional_info",
4b6aa8
+            .section_flags = 0,
4b6aa8
+            .retval_load = 0,
4b6aa8
+            .retval_generate = 0,
4b6aa8
+            .format =
4b6aa8
+                "%additional_info:: root\n"
4b6aa8
+                "Info:: package,\\\n"
4b6aa8
+                "uuid,cwd\\\\"
4b6aa8
+                ",user_name"
4b6aa8
+                ,
4b6aa8
+            .output =
4b6aa8
+                "Info:\n"
4b6aa8
+                "package:        libreport\n"
4b6aa8
+                "uuid:           0123456789ABCDEF\n"
4b6aa8
+                "user_name:      abrt\n"
4b6aa8
+                ,
4b6aa8
+        },
4b6aa8
+    };
4b6aa8
+
4b6aa8
+    problem_data_t *data = problem_data_new();
4b6aa8
+    problem_data_add_text_noteditable(data, "package", "libreport");
4b6aa8
+    problem_data_add_text_noteditable(data, "crash_function", "run_event");
4b6aa8
+    problem_data_add_text_noteditable(data, "reason", "Killed by SIGSEGV");
4b6aa8
+    problem_data_add_text_noteditable(data, "uuid", "0123456789ABCDEF");
4b6aa8
+    problem_data_add_text_noteditable(data, "user_name", "abrt");
4b6aa8
+
4b6aa8
+    for (size_t i = 0; i < sizeof(cases)/sizeof(*cases); ++i) {
4b6aa8
+        fprintf(stderr, "#### Test case %zd ####\n", i + 1);
4b6aa8
+
4b6aa8
+        struct test_case *tcase = cases + i;
4b6aa8
+        problem_formatter_t *pf = problem_formatter_new();
4b6aa8
+
4b6aa8
+        if (tcase->section_name != NULL) {
4b6aa8
+            problem_formatter_add_section(pf, tcase->section_name, tcase->section_flags);
4b6aa8
+        }
4b6aa8
+
4b6aa8
+        int r = problem_formatter_load_string(pf, tcase->format);
4b6aa8
+        if (r != tcase->retval_load) {
4b6aa8
+            fprintf(stderr, "Load : expected : %d , got : %d\n", tcase->retval_load, r);
4b6aa8
+            abort();
4b6aa8
+        }
4b6aa8
+
4b6aa8
+        if (tcase->retval_load != 0) {
4b6aa8
+            goto next_test_case;
4b6aa8
+        }
4b6aa8
+
4b6aa8
+        problem_report_t *pr = NULL;
4b6aa8
+        r = problem_formatter_generate_report(pf, data, &pr);
4b6aa8
+        if (r != tcase->retval_generate) {
4b6aa8
+            fprintf(stderr, "Generaet : expected : %d , got : %d\n", tcase->retval_generate, r);
4b6aa8
+            abort();
4b6aa8
+        }
4b6aa8
+
4b6aa8
+        if (tcase->retval_generate != 0) {
4b6aa8
+            goto next_test_case;
4b6aa8
+        }
4b6aa8
+
4b6aa8
+        const char *output = problem_report_get_section(pr, tcase->section_name);
4b6aa8
+        assert(output);
4b6aa8
+
4b6aa8
+        if (strcmp(output, tcase->output) != 0) {
4b6aa8
+            fprintf(stderr, "expected:\n'%s'\n", tcase->output);
4b6aa8
+            fprintf(stderr, "result  :\n'%s'\n", output);
4b6aa8
+            abort();
4b6aa8
+        }
4b6aa8
+
4b6aa8
+        problem_report_free(pr);
4b6aa8
+
4b6aa8
+next_test_case:
4b6aa8
+        problem_formatter_free(pf);
4b6aa8
+        fprintf(stderr, "#### Finished - Test case %zd ####\n", i + 1);
4b6aa8
+    }
4b6aa8
+
4b6aa8
+    problem_data_free(data);
4b6aa8
+
4b6aa8
+    return 0;
4b6aa8
+}
4b6aa8
+]])
4b6aa8
+
4b6aa8
+## ------ ##
4b6aa8
+## sanity ##
4b6aa8
+## ------ ##
4b6aa8
+
4b6aa8
+AT_TESTFUN([sanity],
4b6aa8
+[[
4b6aa8
+#include "problem_report.h"
4b6aa8
+#include "internal_libreport.h"
4b6aa8
+#include <errno.h>
4b6aa8
+#include <assert.h>
4b6aa8
+
4b6aa8
+void assert_equal_strings(const char *res, const char *exp)
4b6aa8
+{
4b6aa8
+    if (    (res == NULL && exp != NULL)
4b6aa8
+        ||  (res != NULL && exp == NULL)
4b6aa8
+        || ((res != NULL && exp != NULL) && strcmp(res, exp) != 0)
4b6aa8
+        ) {
4b6aa8
+        fprintf(stderr, "expected : '%s'\n", exp);
4b6aa8
+        fprintf(stderr, "result   : '%s'\n", res);
4b6aa8
+        abort();
4b6aa8
+    }
4b6aa8
+}
4b6aa8
+
4b6aa8
+int main(int argc, char **argv)
4b6aa8
+{
4b6aa8
+    g_verbose = 3;
4b6aa8
+
4b6aa8
+    problem_formatter_t *pf = problem_formatter_new();
4b6aa8
+
4b6aa8
+    assert(problem_formatter_add_section(pf, "summary", 0) == -EEXIST);
4b6aa8
+    assert(problem_formatter_add_section(pf, "description", 0) == -EEXIST);
4b6aa8
+    assert(problem_formatter_add_section(pf, "attach", 0) == -EEXIST);
4b6aa8
+
4b6aa8
+    assert(problem_formatter_add_section(pf, "additional_info", 0) == 0);
4b6aa8
+    assert(problem_formatter_add_section(pf, "additional_info", 0) == -EEXIST);
4b6aa8
+
4b6aa8
+    problem_data_t *data = problem_data_new();
4b6aa8
+    problem_data_add_text_noteditable(data, "package", "libreport");
4b6aa8
+    problem_data_add_text_noteditable(data, "crash_function", "run_event");
4b6aa8
+    problem_data_add_text_noteditable(data, "reason", "Killed by SIGSEGV");
4b6aa8
+    problem_data_add_text_noteditable(data, "comment", "Hello, world!");
4b6aa8
+    problem_data_add_text_noteditable(data, "root", "/var/run/mock/abrt");
4b6aa8
+    problem_data_add_text_noteditable(data, "user_name", "abrt");
4b6aa8
+    problem_data_add_file(data, "screenshot", "/what/ever/path/to/screenshot");
4b6aa8
+
4b6aa8
+    problem_formatter_load_string(pf,
4b6aa8
+            "%summary:: [abrt] %package% : %reason%\n\n"
4b6aa8
+            "Description:: %bare_comment\n\n"
4b6aa8
+            "%attach:: screenshot\n\n"
4b6aa8
+            "%additional_info::\n\n"
4b6aa8
+            "User:: root,user_name,uid\n\n\n"
4b6aa8
+            );
4b6aa8
+
4b6aa8
+    problem_report_t *pr = NULL;
4b6aa8
+    problem_formatter_generate_report(pf, data, &pr);
4b6aa8
+
4b6aa8
+    assert_equal_strings(problem_report_get_summary(pr),
4b6aa8
+                         "[abrt] libreport : Killed by SIGSEGV");
4b6aa8
+
4b6aa8
+    problem_report_buffer_printf(problem_report_get_buffer(pr, PR_SEC_SUMMARY), " - test");
4b6aa8
+
4b6aa8
+    assert_equal_strings(problem_report_get_summary(pr),
4b6aa8
+                         "[abrt] libreport : Killed by SIGSEGV - test");
4b6aa8
+
4b6aa8
+
4b6aa8
+    assert_equal_strings(problem_report_get_description(pr),
4b6aa8
+            "Description:\nHello, world!\n");
4b6aa8
+
4b6aa8
+    problem_report_buffer_printf(problem_report_get_buffer(pr, PR_SEC_DESCRIPTION), "Test line\n");
4b6aa8
+
4b6aa8
+    assert_equal_strings(problem_report_get_description(pr),
4b6aa8
+            "Description:\nHello, world!\nTest line\n");
4b6aa8
+
4b6aa8
+
4b6aa8
+    assert_equal_strings(problem_report_get_section(pr, "additional_info"),
4b6aa8
+            "User:\n"
4b6aa8
+            "root:           /var/run/mock/abrt\n"
4b6aa8
+            "user_name:      abrt\n"
4b6aa8
+            );
4b6aa8
+
4b6aa8
+    problem_report_buffer_printf(problem_report_get_buffer(pr, "additional_info"), "uid:            42\n");
4b6aa8
+
4b6aa8
+    assert_equal_strings(problem_report_get_section(pr, "additional_info"),
4b6aa8
+            "User:\n"
4b6aa8
+            "root:           /var/run/mock/abrt\n"
4b6aa8
+            "user_name:      abrt\n"
4b6aa8
+            "uid:            42\n"
4b6aa8
+            );
4b6aa8
+
4b6aa8
+
4b6aa8
+    problem_report_free(pr);
4b6aa8
+    problem_data_free(data);
4b6aa8
+    problem_formatter_free(pf);
4b6aa8
+
4b6aa8
+    return 0;
4b6aa8
+}
4b6aa8
+]])
4b6aa8
diff --git a/tests/testsuite.at b/tests/testsuite.at
4b6aa8
index 392c3db..ccb37d1 100644
4b6aa8
--- a/tests/testsuite.at
4b6aa8
+++ b/tests/testsuite.at
4b6aa8
@@ -17,6 +17,7 @@ m4_include([xml_definition.at])
4b6aa8
 m4_include([report_python.at])
4b6aa8
 m4_include([string_list.at])
4b6aa8
 m4_include([ureport.at])
4b6aa8
+m4_include([problem_report.at])
4b6aa8
 m4_include([dump_dir.at])
4b6aa8
 m4_include([global_config.at])
4b6aa8
 m4_include([iso_date.at])
4b6aa8
-- 
4b6aa8
1.8.3.1
4b6aa8