diff --git a/README.debrand b/README.debrand deleted file mode 100644 index 01c46d2..0000000 --- a/README.debrand +++ /dev/null @@ -1,2 +0,0 @@ -Warning: This package was configured for automatic debranding, but the changes -failed to apply. diff --git a/SOURCES/1000-bugzilla-port-to-Problem-Format-API.patch b/SOURCES/1000-bugzilla-port-to-Problem-Format-API.patch new file mode 100644 index 0000000..5ab0a57 --- /dev/null +++ b/SOURCES/1000-bugzilla-port-to-Problem-Format-API.patch @@ -0,0 +1,781 @@ +From ff32b9ee7a7e396e33f1e9aeaa5bafd26ccbb273 Mon Sep 17 00:00:00 2001 +From: Jakub Filak <jfilak@redhat.com> +Date: Thu, 4 Dec 2014 08:45:07 +0100 +Subject: [PATCH 1000/1015] bugzilla: port to Problem Format API + +Related to #303 + +Signed-off-by: Jakub Filak <jfilak@redhat.com> +--- + src/plugins/reporter-bugzilla.c | 691 ++++------------------------------------ + 1 file changed, 59 insertions(+), 632 deletions(-) + +diff --git a/src/plugins/reporter-bugzilla.c b/src/plugins/reporter-bugzilla.c +index fbe7873..9ff3df3 100644 +--- a/src/plugins/reporter-bugzilla.c ++++ b/src/plugins/reporter-bugzilla.c +@@ -17,515 +17,11 @@ + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + #include "internal_libreport.h" ++#include "problem_report.h" + #include "client.h" + #include "abrt_xmlrpc.h" + #include "rhbz.h" + +-#include <satyr/stacktrace.h> +-#include <satyr/abrt.h> +- +-struct section_t { +- char *name; +- GList *items; +-}; +-typedef struct section_t section_t; +- +- +-/* Utility functions */ +- +-static +-GList* split_string_on_char(const char *str, char ch) +-{ +- GList *list = NULL; +- for (;;) +- { +- const char *delim = strchrnul(str, ch); +- list = g_list_prepend(list, xstrndup(str, delim - str)); +- if (*delim == '\0') +- break; +- str = delim + 1; +- } +- return g_list_reverse(list); +-} +- +-static +-int compare_item_name(const char *lookup, const char *name) +-{ +- if (lookup[0] == '-') +- lookup++; +- else if (strncmp(lookup, "%bare_", 6) == 0) +- lookup += 6; +- return strcmp(lookup, name); +-} +- +-static +-int is_item_name_in_section(const section_t *lookup, const char *name) +-{ +- if (g_list_find_custom(lookup->items, name, (GCompareFunc)compare_item_name)) +- return 0; /* "found it!" */ +- return 1; +-} +- +-static +-bool is_explicit_or_forbidden(const char *name, GList *comment_fmt_spec) +-{ +- return g_list_find_custom(comment_fmt_spec, name, (GCompareFunc)is_item_name_in_section); +-} +- +-static +-GList* load_bzrep_conf_file(const char *path) +-{ +- FILE *fp = stdin; +- if (strcmp(path, "-") != 0) +- { +- fp = fopen(path, "r"); +- if (!fp) +- return NULL; +- } +- +- GList *sections = NULL; +- +- char *line; +- while ((line = xmalloc_fgetline(fp)) != NULL) +- { +- /* Skip comments */ +- char first = *skip_whitespace(line); +- if (first == '#') +- goto free_line; +- +- /* Handle trailing backslash continuation */ +- check_continuation: ; +- unsigned len = strlen(line); +- if (len && line[len-1] == '\\') +- { +- line[len-1] = '\0'; +- char *next_line = xmalloc_fgetline(fp); +- if (next_line) +- { +- line = append_to_malloced_string(line, next_line); +- free(next_line); +- goto check_continuation; +- } +- } +- +- /* We are reusing line buffer to form temporary +- * "key\0values\0..." in its beginning +- */ +- bool summary_line = false; +- char *value = NULL; +- char *src; +- char *dst; +- for (src = dst = line; *src; src++) +- { +- char c = *src; +- /* did we reach the value list? */ +- if (!value && c == ':' && src[1] == ':') +- { +- *dst++ = '\0'; /* terminate key */ +- src += 2; +- value = dst; /* remember where value starts */ +- summary_line = (strcmp(line, "%summary") == 0); +- if (summary_line) +- { +- value = src; +- break; +- } +- continue; +- } +- /* skip whitespace in value list */ +- if (value && isspace(c)) +- continue; +- *dst++ = c; /* store next key or value char */ +- } +- +- GList *item_list = NULL; +- if (summary_line) +- { +- /* %summary is special */ +- item_list = g_list_append(NULL, xstrdup(skip_whitespace(value))); +- } +- else +- { +- *dst = '\0'; /* terminate value (or key) */ +- if (value) +- item_list = split_string_on_char(value, ','); +- } +- +- section_t *sec = xzalloc(sizeof(*sec)); +- sec->name = xstrdup(line); +- sec->items = item_list; +- sections = g_list_prepend(sections, sec); +- +- free_line: +- free(line); +- } +- +- if (fp != stdin) +- fclose(fp); +- +- return g_list_reverse(sections); +-} +- +- +-/* Summary generation */ +- +-#define MAX_OPT_DEPTH 10 +-static +-char *format_percented_string(const char *str, problem_data_t *pd) +-{ +- size_t old_pos[MAX_OPT_DEPTH] = { 0 }; +- int okay[MAX_OPT_DEPTH] = { 1 }; +- int opt_depth = 1; +- struct strbuf *result = strbuf_new(); +- +- while (*str) { +- switch (*str) { +- default: +- strbuf_append_char(result, *str); +- str++; +- break; +- case '\\': +- if (str[1]) +- str++; +- strbuf_append_char(result, *str); +- str++; +- break; +- case '[': +- if (str[1] == '[' && opt_depth < MAX_OPT_DEPTH) +- { +- old_pos[opt_depth] = result->len; +- okay[opt_depth] = 1; +- opt_depth++; +- str += 2; +- } else { +- strbuf_append_char(result, *str); +- str++; +- } +- break; +- case ']': +- if (str[1] == ']' && opt_depth > 1) +- { +- opt_depth--; +- if (!okay[opt_depth]) +- { +- result->len = old_pos[opt_depth]; +- result->buf[result->len] = '\0'; +- } +- str += 2; +- } else { +- strbuf_append_char(result, *str); +- str++; +- } +- break; +- case '%': ; +- char *nextpercent = strchr(++str, '%'); +- if (!nextpercent) +- { +- error_msg_and_die("Unterminated %%element%%: '%s'", str - 1); +- } +- +- *nextpercent = '\0'; +- const problem_item *item = problem_data_get_item_or_NULL(pd, str); +- *nextpercent = '%'; +- +- if (item && (item->flags & CD_FLAG_TXT)) +- strbuf_append_str(result, item->content); +- else +- okay[opt_depth - 1] = 0; +- str = nextpercent + 1; +- break; +- } +- } +- +- if (opt_depth > 1) +- { +- error_msg_and_die("Unbalanced [[ ]] bracket"); +- } +- +- if (!okay[0]) +- { +- error_msg("Undefined variable outside of [[ ]] bracket"); +- } +- +- return strbuf_free_nobuf(result); +-} +- +-static +-char *create_summary_string(problem_data_t *pd, GList *comment_fmt_spec) +-{ +- GList *l = comment_fmt_spec; +- while (l) +- { +- section_t *sec = l->data; +- l = l->next; +- +- /* Find %summary" */ +- if (strcmp(sec->name, "%summary") != 0) +- continue; +- +- GList *item = sec->items; +- if (!item) +- /* not supposed to happen, there will be at least "" */ +- error_msg_and_die("BUG in %%summary parser"); +- +- const char *str = item->data; +- return format_percented_string(str, pd); +- } +- +- return format_percented_string("%reason%", pd); +-} +- +- +-/* BZ comment generation */ +- +-static +-int append_text(struct strbuf *result, const char *item_name, const char *content, bool print_item_name) +-{ +- char *eol = strchrnul(content, '\n'); +- if (eol[0] == '\0' || eol[1] == '\0') +- { +- /* one-liner */ +- int pad = 16 - (strlen(item_name) + 2); +- if (pad < 0) +- pad = 0; +- if (print_item_name) +- strbuf_append_strf(result, +- eol[0] == '\0' ? "%s: %*s%s\n" : "%s: %*s%s", +- item_name, pad, "", content +- ); +- else +- strbuf_append_strf(result, +- eol[0] == '\0' ? "%s\n" : "%s", +- content +- ); +- } +- else +- { +- /* multi-line item */ +- if (print_item_name) +- strbuf_append_strf(result, "%s:\n", item_name); +- for (;;) +- { +- eol = strchrnul(content, '\n'); +- strbuf_append_strf(result, +- /* For %bare_multiline_item, we don't want to print colons */ +- (print_item_name ? ":%.*s\n" : "%.*s\n"), +- (int)(eol - content), content +- ); +- if (eol[0] == '\0' || eol[1] == '\0') +- break; +- content = eol + 1; +- } +- } +- return 1; +-} +- +-static +-int append_short_backtrace(struct strbuf *result, problem_data_t *problem_data, size_t max_text_size, bool print_item_name) +-{ +- const problem_item *item = problem_data_get_item_or_NULL(problem_data, +- FILENAME_BACKTRACE); +- if (!item) +- return 0; /* "I did not print anything" */ +- if (!(item->flags & CD_FLAG_TXT)) +- return 0; /* "I did not print anything" */ +- +- char *truncated = NULL; +- +- if (strlen(item->content) >= max_text_size) +- { +- char *error_msg = NULL; +- const char *analyzer = problem_data_get_content_or_NULL(problem_data, FILENAME_ANALYZER); +- if (!analyzer) +- return 0; +- +- /* For CCpp crashes, use the GDB-produced backtrace which should be +- * available by now. sr_abrt_type_from_analyzer returns SR_REPORT_CORE +- * by default for CCpp crashes. +- */ +- enum sr_report_type report_type = sr_abrt_type_from_analyzer(analyzer); +- if (strcmp(analyzer, "CCpp") == 0) +- report_type = SR_REPORT_GDB; +- +- struct sr_stacktrace *backtrace = sr_stacktrace_parse(report_type, +- item->content, &error_msg); +- +- if (!backtrace) +- { +- log(_("Can't parse backtrace: %s"), error_msg); +- free(error_msg); +- return 0; +- } +- +- /* Get optimized thread stack trace for 10 top most frames */ +- truncated = sr_stacktrace_to_short_text(backtrace, 10); +- sr_stacktrace_free(backtrace); +- +- if (!truncated) +- { +- log(_("Can't generate stacktrace description (no crash thread?)")); +- return 0; +- } +- } +- +- append_text(result, +- /*item_name:*/ truncated ? "truncated_backtrace" : FILENAME_BACKTRACE, +- /*content:*/ truncated ? truncated : item->content, +- print_item_name +- ); +- free(truncated); +- return 1; +-} +- +-static +-int append_item(struct strbuf *result, const char *item_name, problem_data_t *pd, GList *comment_fmt_spec) +-{ +- bool print_item_name = (strncmp(item_name, "%bare_", strlen("%bare_")) != 0); +- if (!print_item_name) +- item_name += strlen("%bare_"); +- +- if (item_name[0] != '%') +- { +- struct problem_item *item = problem_data_get_item_or_NULL(pd, item_name); +- if (!item) +- return 0; /* "I did not print anything" */ +- if (!(item->flags & CD_FLAG_TXT)) +- return 0; /* "I did not print anything" */ +- +- char *formatted = problem_item_format(item); +- char *content = formatted ? formatted : item->content; +- append_text(result, item_name, content, print_item_name); +- free(formatted); +- return 1; /* "I printed something" */ +- } +- +- /* Special item name */ +- +- /* Compat with previously-existed ad-hockery: %short_backtrace */ +- if (strcmp(item_name, "%short_backtrace") == 0) +- return append_short_backtrace(result, pd, CD_TEXT_ATT_SIZE_BZ, print_item_name); +- +- /* Compat with previously-existed ad-hockery: %reporter */ +- if (strcmp(item_name, "%reporter") == 0) +- return append_text(result, "reporter", PACKAGE"-"VERSION, print_item_name); +- +- /* %oneline,%multiline,%text */ +- bool oneline = (strcmp(item_name+1, "oneline" ) == 0); +- bool multiline = (strcmp(item_name+1, "multiline") == 0); +- bool text = (strcmp(item_name+1, "text" ) == 0); +- if (!oneline && !multiline && !text) +- { +- log("Unknown or unsupported element specifier '%s'", item_name); +- return 0; /* "I did not print anything" */ +- } +- +- int printed = 0; +- +- /* Iterate over _sorted_ items */ +- GList *sorted_names = g_hash_table_get_keys(pd); +- sorted_names = g_list_sort(sorted_names, (GCompareFunc)strcmp); +- +- /* %text => do as if %oneline, then repeat as if %multiline */ +- if (text) +- oneline = 1; +- +- again: ; +- GList *l = sorted_names; +- while (l) +- { +- const char *name = l->data; +- l = l->next; +- struct problem_item *item = g_hash_table_lookup(pd, name); +- if (!item) +- continue; /* paranoia, won't happen */ +- +- if (!(item->flags & CD_FLAG_TXT)) +- continue; +- +- if (is_explicit_or_forbidden(name, comment_fmt_spec)) +- continue; +- +- char *formatted = problem_item_format(item); +- char *content = formatted ? formatted : item->content; +- char *eol = strchrnul(content, '\n'); +- bool is_oneline = (eol[0] == '\0' || eol[1] == '\0'); +- if (oneline == is_oneline) +- printed |= append_text(result, name, content, print_item_name); +- free(formatted); +- } +- if (text && oneline) +- { +- /* %text, and we just did %oneline. Repeat as if %multiline */ +- oneline = 0; +- /*multiline = 1; - not checked in fact, so why bother setting? */ +- goto again; +- } +- +- g_list_free(sorted_names); /* names themselves are not freed */ +- +- return printed; +-} +- +-static +-void generate_bz_comment(struct strbuf *result, problem_data_t *pd, GList *comment_fmt_spec) +-{ +- bool last_line_is_empty = true; +- GList *l = comment_fmt_spec; +- while (l) +- { +- section_t *sec = l->data; +- l = l->next; +- +- /* Skip special sections such as "%attach" */ +- if (sec->name[0] == '%') +- continue; +- +- if (sec->items) +- { +- /* "Text: item[,item]..." */ +- struct strbuf *output = strbuf_new(); +- GList *item = sec->items; +- while (item) +- { +- const char *str = item->data; +- item = item->next; +- if (str[0] == '-') /* "-name", ignore it */ +- continue; +- append_item(output, str, pd, comment_fmt_spec); +- } +- +- if (output->len != 0) +- { +- strbuf_append_strf(result, +- sec->name[0] ? "%s:\n%s" : "%s%s", +- sec->name, +- output->buf +- ); +- last_line_is_empty = false; +- } +- strbuf_free(output); +- } +- else +- { +- /* Just "Text" (can be "") */ +- +- /* Filter out consecutive empty lines */ +- if (sec->name[0] != '\0' || !last_line_is_empty) +- strbuf_append_strf(result, "%s\n", sec->name); +- last_line_is_empty = (sec->name[0] == '\0'); +- } +- } +- +- /* Nuke any trailing empty lines */ +- while (result->len >= 1 +- && result->buf[result->len-1] == '\n' +- && (result->len == 1 || result->buf[result->len-2] == '\n') +- ) { +- result->buf[--result->len] = '\0'; +- } +-} +- +- + /* BZ attachments */ + + static +@@ -573,104 +69,6 @@ int attach_file_item(struct abrt_xmlrpc *ax, const char *bug_id, + return (r == 0); + } + +-static +-int attach_item(struct abrt_xmlrpc *ax, const char *bug_id, +- const char *item_name, problem_data_t *pd, GList *comment_fmt_spec) +-{ +- if (item_name[0] != '%') +- { +- struct problem_item *item = problem_data_get_item_or_NULL(pd, item_name); +- if (!item) +- return 0; +- if (item->flags & CD_FLAG_TXT) +- return attach_text_item(ax, bug_id, item_name, item); +- if (item->flags & CD_FLAG_BIN) +- return attach_file_item(ax, bug_id, item_name, item); +- return 0; +- } +- +- /* Special item name */ +- +- /* %oneline,%multiline,%text,%binary */ +- bool oneline = (strcmp(item_name+1, "oneline" ) == 0); +- bool multiline = (strcmp(item_name+1, "multiline") == 0); +- bool text = (strcmp(item_name+1, "text" ) == 0); +- bool binary = (strcmp(item_name+1, "binary" ) == 0); +- if (!oneline && !multiline && !text && !binary) +- { +- log("Unknown or unsupported element specifier '%s'", item_name); +- return 0; +- } +- +- log_debug("Special item_name '%s', iterating for attach...", item_name); +- int done = 0; +- +- /* Iterate over _sorted_ items */ +- GList *sorted_names = g_hash_table_get_keys(pd); +- sorted_names = g_list_sort(sorted_names, (GCompareFunc)strcmp); +- +- GList *l = sorted_names; +- while (l) +- { +- const char *name = l->data; +- l = l->next; +- struct problem_item *item = g_hash_table_lookup(pd, name); +- if (!item) +- continue; /* paranoia, won't happen */ +- +- if (is_explicit_or_forbidden(name, comment_fmt_spec)) +- continue; +- +- if ((item->flags & CD_FLAG_TXT) && !binary) +- { +- char *content = item->content; +- char *eol = strchrnul(content, '\n'); +- bool is_oneline = (eol[0] == '\0' || eol[1] == '\0'); +- if (text || oneline == is_oneline) +- done |= attach_text_item(ax, bug_id, name, item); +- } +- if ((item->flags & CD_FLAG_BIN) && binary) +- done |= attach_file_item(ax, bug_id, name, item); +- } +- +- g_list_free(sorted_names); /* names themselves are not freed */ +- +- +- log_debug("...Done iterating over '%s' for attach", item_name); +- +- return done; +-} +- +-static +-int attach_files(struct abrt_xmlrpc *ax, const char *bug_id, +- problem_data_t *pd, GList *comment_fmt_spec) +-{ +- int done = 0; +- GList *l = comment_fmt_spec; +- while (l) +- { +- section_t *sec = l->data; +- l = l->next; +- +- /* Find %attach" */ +- if (strcmp(sec->name, "%attach") != 0) +- continue; +- +- GList *item = sec->items; +- while (item) +- { +- const char *str = item->data; +- item = item->next; +- if (str[0] == '-') /* "-name", ignore it */ +- continue; +- done |= attach_item(ax, bug_id, str, pd, comment_fmt_spec); +- } +- } +- +- return done; +-} +- +- + /* Main */ + + struct bugzilla_struct { +@@ -1103,18 +501,29 @@ int main(int argc, char **argv) + + if (opts & OPT_D) + { +- GList *comment_fmt_spec = load_bzrep_conf_file(fmt_file); +- struct strbuf *bzcomment_buf = strbuf_new(); +- generate_bz_comment(bzcomment_buf, problem_data, comment_fmt_spec); +- char *bzcomment = strbuf_free_nobuf(bzcomment_buf); +- char *summary = create_summary_string(problem_data, comment_fmt_spec); ++ problem_formatter_t *pf = problem_formatter_new(); ++ ++ if (problem_formatter_load_file(pf, fmt_file)) ++ error_msg_and_die("Invalid format file: %s", fmt_file); ++ ++ problem_report_t *pr = NULL; ++ if (problem_formatter_generate_report(pf, problem_data, &pr)) ++ error_msg_and_die("Failed to format bug report from problem data"); ++ + printf("summary: %s\n" + "\n" + "%s" +- , summary, bzcomment ++ "\n" ++ , problem_report_get_summary(pr) ++ , problem_report_get_description(pr) + ); +- free(bzcomment); +- free(summary); ++ ++ puts("attachments:"); ++ for (GList *a = problem_report_get_attachments(pr); a != NULL; a = g_list_next(a)) ++ printf(" %s\n", (const char *)a->data); ++ ++ problem_report_free(pr); ++ problem_formatter_free(pf); + exit(0); + } + +@@ -1227,22 +636,29 @@ int main(int argc, char **argv) + /* Create new bug */ + log(_("Creating a new bug")); + +- GList *comment_fmt_spec = load_bzrep_conf_file(fmt_file); ++ problem_formatter_t *pf = problem_formatter_new(); ++ ++ if (problem_formatter_load_file(pf, fmt_file)) ++ error_msg_and_die("Invalid format file: %s", fmt_file); ++ ++ problem_report_t *pr = NULL; ++ if (problem_formatter_generate_report(pf, problem_data, &pr)) ++ error_msg_and_die("Failed to format problem data"); + +- struct strbuf *bzcomment_buf = strbuf_new(); +- generate_bz_comment(bzcomment_buf, problem_data, comment_fmt_spec); + if (crossver_id >= 0) +- strbuf_append_strf(bzcomment_buf, "\nPotential duplicate: bug %u\n", crossver_id); +- char *bzcomment = strbuf_free_nobuf(bzcomment_buf); +- char *summary = create_summary_string(problem_data, comment_fmt_spec); ++ problem_report_buffer_printf( ++ problem_report_get_buffer(pr, PR_SEC_DESCRIPTION), ++ "\nPotential duplicate: bug %u\n", crossver_id); ++ ++ problem_formatter_free(pf); ++ + int new_id = rhbz_new_bug(client, + problem_data, rhbz.b_product, rhbz.b_product_version, +- summary, bzcomment, ++ problem_report_get_summary(pr), ++ problem_report_get_description(pr), + rhbz.b_create_private, + rhbz.b_private_groups + ); +- free(bzcomment); +- free(summary); + + if (new_id == -1) + { +@@ -1267,9 +683,17 @@ int main(int argc, char **argv) + char new_id_str[sizeof(int)*3 + 2]; + sprintf(new_id_str, "%i", new_id); + +- attach_files(client, new_id_str, problem_data, comment_fmt_spec); +- +-//TODO: free_comment_fmt_spec(comment_fmt_spec); ++ for (GList *a = problem_report_get_attachments(pr); a != NULL; a = g_list_next(a)) ++ { ++ const char *item_name = (const char *)a->data; ++ struct problem_item *item = problem_data_get_item_or_NULL(problem_data, item_name); ++ if (!item) ++ continue; ++ else if (item->flags & CD_FLAG_TXT) ++ attach_text_item(client, new_id_str, item_name, item); ++ else if (item->flags & CD_FLAG_BIN) ++ attach_file_item(client, new_id_str, item_name, item); ++ } + + bz = new_bug_info(); + bz->bi_status = xstrdup("NEW"); +@@ -1340,18 +764,21 @@ int main(int argc, char **argv) + const char *comment = problem_data_get_content_or_NULL(problem_data, FILENAME_COMMENT); + if (comment && comment[0]) + { +- GList *comment_fmt_spec = load_bzrep_conf_file(fmt_file2); +- struct strbuf *bzcomment_buf = strbuf_new(); +- generate_bz_comment(bzcomment_buf, problem_data, comment_fmt_spec); +- char *bzcomment = strbuf_free_nobuf(bzcomment_buf); +-//TODO: free_comment_fmt_spec(comment_fmt_spec); ++ problem_formatter_t *pf = problem_formatter_new(); ++ if (problem_formatter_load_file(pf, fmt_file2)) ++ error_msg_and_die("Invalid duplicate format file: '%s", fmt_file2); ++ ++ problem_report_t *pr; ++ if (problem_formatter_generate_report(pf, problem_data, &pr)) ++ error_msg_and_die("Failed to format duplicate comment from problem data"); ++ ++ const char *bzcomment = problem_report_get_description(pr); + + int dup_comment = is_comment_dup(bz->bi_comments, bzcomment); + if (!dup_comment) + { + log(_("Adding new comment to bug %d"), bz->bi_id); + rhbz_add_comment(client, bz->bi_id, bzcomment, 0); +- free(bzcomment); + + const char *bt = problem_data_get_content_or_NULL(problem_data, FILENAME_BACKTRACE); + unsigned rating = 0; +@@ -1369,10 +796,10 @@ int main(int argc, char **argv) + } + } + else +- { +- free(bzcomment); + log(_("Found the same comment in the bug history, not adding a new one")); +- } ++ ++ problem_report_free(pr); ++ problem_formatter_free(pf); + } + + log_out: +-- +1.8.3.1 + diff --git a/SOURCES/1001-lib-created-a-new-lib-file-for-reporters.patch b/SOURCES/1001-lib-created-a-new-lib-file-for-reporters.patch new file mode 100644 index 0000000..d0e74f9 --- /dev/null +++ b/SOURCES/1001-lib-created-a-new-lib-file-for-reporters.patch @@ -0,0 +1,405 @@ +From 8d9f7ba8a42ec1cfd39e6c249aef15e9295fe0a1 Mon Sep 17 00:00:00 2001 +From: Matej Habrnal <mhabrnal@redhat.com> +Date: Tue, 13 Jan 2015 19:23:08 -0500 +Subject: [PATCH 1001/1015] lib: created a new lib file for reporters + +Moved some functions from rhbz.c to src/lib/reporters.c and src/lib/strbuf.c. + +Related to #272 + +Signed-off-by: Matej Habrnal <mhabrnal@redhat.com> +--- + po/POTFILES.in | 1 + + src/include/Makefile.am | 3 +- + src/include/internal_libreport.h | 5 +++ + src/include/reporters.h | 36 ++++++++++++++++++ + src/lib/Makefile.am | 3 +- + src/lib/reporters.c | 80 ++++++++++++++++++++++++++++++++++++++++ + src/lib/strbuf.c | 60 ++++++++++++++++++++++++++++++ + src/plugins/rhbz.c | 78 +-------------------------------------- + src/plugins/rhbz.h | 2 - + 9 files changed, 188 insertions(+), 80 deletions(-) + create mode 100644 src/include/reporters.h + create mode 100644 src/lib/reporters.c + +diff --git a/po/POTFILES.in b/po/POTFILES.in +index 4246e06..003e686 100644 +--- a/po/POTFILES.in ++++ b/po/POTFILES.in +@@ -27,6 +27,7 @@ src/lib/parse_options.c + src/lib/problem_data.c + src/lib/problem_report.c + src/lib/reported_to.c ++src/lib/reporters.c + src/lib/run_event.c + src/plugins/abrt_rh_support.c + src/plugins/report_Bugzilla.xml.in +diff --git a/src/include/Makefile.am b/src/include/Makefile.am +index 87e5e60..4d8c6a5 100644 +--- a/src/include/Makefile.am ++++ b/src/include/Makefile.am +@@ -15,7 +15,8 @@ libreport_include_HEADERS = \ + file_obj.h \ + internal_libreport.h \ + internal_abrt_dbus.h \ +- xml_parser.h ++ xml_parser.h \ ++ reporters.h + + if BUILD_UREPORT + libreport_include_HEADERS += ureport.h +diff --git a/src/include/internal_libreport.h b/src/include/internal_libreport.h +index cf5730c..a867649 100644 +--- a/src/include/internal_libreport.h ++++ b/src/include/internal_libreport.h +@@ -98,6 +98,7 @@ int vdprintf(int d, const char *format, va_list ap); + #include "workflow.h" + #include "file_obj.h" + #include "libreport_types.h" ++#include "reporters.h" + + #ifdef __cplusplus + extern "C" { +@@ -107,6 +108,10 @@ extern "C" { + int prefixcmp(const char *str, const char *prefix); + #define suffixcmp libreport_suffixcmp + int suffixcmp(const char *str, const char *suffix); ++#define trim_all_whitespace libreport_trim_all_whitespace ++char *trim_all_whitespace(const char *str); ++#define shorten_string_to_length libreport_shorten_string_to_length ++char *shorten_string_to_length(const char *str, unsigned length); + #define strtrim libreport_strtrim + char *strtrim(char *str); + #define strtrimch libreport_strtrimch +diff --git a/src/include/reporters.h b/src/include/reporters.h +new file mode 100644 +index 0000000..d415b7f +--- /dev/null ++++ b/src/include/reporters.h +@@ -0,0 +1,36 @@ ++/* ++ Copyright (C) 2014 ABRT team ++ Copyright (C) 2014 RedHat Inc ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 2 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License along ++ with this program; if not, write to the Free Software Foundation, Inc., ++ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ++*/ ++ ++#ifndef REPORTERS_H ++#define REPORTERS_H ++ ++#ifdef __cplusplus ++extern "C" { ++#endif ++ ++#define is_comment_dup libreport_is_comment_dup ++int is_comment_dup(GList *comments, const char *comment); ++#define comments_find_best_bt_rating libreport_comments_find_best_bt_rating ++unsigned comments_find_best_bt_rating(GList *comments); ++ ++#ifdef __cplusplus ++} ++#endif ++ ++#endif +diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am +index c11a42d..d41e543 100644 +--- a/src/lib/Makefile.am ++++ b/src/lib/Makefile.am +@@ -58,7 +58,8 @@ libreport_la_SOURCES = \ + xml_parser.c \ + libreport_init.c \ + global_configuration.c \ +- uriparser.c ++ uriparser.c \ ++ reporters.c + + libreport_la_CPPFLAGS = \ + -I$(srcdir)/../include \ +diff --git a/src/lib/reporters.c b/src/lib/reporters.c +new file mode 100644 +index 0000000..e3305ca +--- /dev/null ++++ b/src/lib/reporters.c +@@ -0,0 +1,80 @@ ++/* ++ String buffer implementation ++ ++ Copyright (C) 2015 RedHat inc. ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 2 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License along ++ with this program; if not, write to the Free Software Foundation, Inc., ++ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ++*/ ++ ++#include "internal_libreport.h" ++ ++int ++is_comment_dup(GList *comments, const char *comment) ++{ ++ char * const trim_comment = trim_all_whitespace(comment); ++ bool same_comments = false; ++ ++ for (GList *l = comments; l && !same_comments; l = l->next) ++ { ++ const char * const comment_body = (const char *) l->data; ++ char * const trim_comment_body = trim_all_whitespace(comment_body); ++ same_comments = (strcmp(trim_comment_body, trim_comment) == 0); ++ free(trim_comment_body); ++ } ++ ++ free(trim_comment); ++ return same_comments; ++} ++ ++unsigned ++comments_find_best_bt_rating(GList *comments) ++{ ++ if (comments == NULL) ++ return 0; ++ ++ unsigned best_rating = 0; ++ for (GList *l = comments; l; l = l->next) ++ { ++ char *comment_body = (char *) l->data; ++ ++ char *start_rating_line = strstr(comment_body, FILENAME_RATING": "); ++ if (!start_rating_line) ++ { ++ log_debug(_("Note does not contain rating")); ++ continue; ++ } ++ ++ start_rating_line += strlen(FILENAME_RATING": "); ++ ++ errno = 0; ++ char *e; ++ long rating = strtoul(start_rating_line, &e, 10); ++ /* ++ * Note: we intentionally check for '\n'. Any other terminator ++ * (even '\0') is not ok in this case. ++ */ ++ if (errno || e == start_rating_line || (*e != '\n' && *e != '\r') || (unsigned long)rating > UINT_MAX) ++ { ++ /* error / no digits / illegal trailing chars */ ++ continue; ++ } ++ ++ if (rating > best_rating) ++ best_rating = rating; ++ } ++ ++ return best_rating; ++} ++ +diff --git a/src/lib/strbuf.c b/src/lib/strbuf.c +index ef8bda8..f0cd1b8 100644 +--- a/src/lib/strbuf.c ++++ b/src/lib/strbuf.c +@@ -37,6 +37,66 @@ int suffixcmp(const char *str, const char *suffix) + return strcmp(str + len_minus_suflen, suffix); + } + ++char *trim_all_whitespace(const char *str) ++{ ++ char *trim = xzalloc(sizeof(char) * strlen(str) + 1); ++ int i = 0; ++ while (*str) ++ { ++ if (!isspace(*str)) ++ trim[i++] = *str; ++ str++; ++ } ++ ++ return trim; ++} ++ ++/* If str is longer than max allowed length then ++ * try to find first ' ' from the end of acceptable long str string ++ * ++ * If ' ' is found replace string after that by "..." ++ * ++ * If ' ' is NOT found in maximal allowed range, cut str string on ++ * lenght (MAX_SUMMARY_LENGTH - strlen("...")) and append "..." ++ * ++ * If MAX_LENGTH is 15 and max allowed cut is 5: ++ * ++ * 0123456789ABCDEF -> 0123456789AB... ++ * 0123456789 BCDEF -> 0123456789 ... ++ * 012345 789ABCDEF -> 012345 789AB... ++ */ ++char * ++shorten_string_to_length(const char *str, unsigned length) ++{ ++ char *dup_str = xstrdup(str); ++ if (strlen(str) > length) ++ { ++ char *max_end = dup_str + (length - strlen("...")); ++ ++ /* maximal number of characters to cut due to attempt cut dup_str ++ * string after last ' ' ++ */ ++ int max_cut = 16; ++ ++ /* start looking for ' ' one char before the last possible character */ ++ char *buf = max_end - 1; ++ while (buf[0] != ' ' && max_cut--) ++ --buf; ++ ++ if (buf[0] != ' ') ++ buf = max_end; ++ else ++ ++buf; ++ ++ buf[0] = '.'; ++ buf[1] = '.'; ++ buf[2] = '.'; ++ buf[3] = '\0'; ++ } ++ ++ return dup_str; ++} ++ + /* + * Trims whitespace characters both from left and right side of a string. + * Modifies the string in-place. Returns the trimmed string. +diff --git a/src/plugins/rhbz.c b/src/plugins/rhbz.c +index a227c62..fdcfff9 100644 +--- a/src/plugins/rhbz.c ++++ b/src/plugins/rhbz.c +@@ -133,41 +133,6 @@ static GList *rhbz_comments(struct abrt_xmlrpc *ax, int bug_id) + return g_list_reverse(comments); + } + +-static char *trim_all_whitespace(const char *str) +-{ +- func_entry(); +- +- char *trim = xzalloc(sizeof(char) * strlen(str) + 1); +- int i = 0; +- while (*str) +- { +- if (!isspace(*str)) +- trim[i++] = *str; +- str++; +- } +- +- return trim; +-} +- +-int is_comment_dup(GList *comments, const char *comment) +-{ +- func_entry(); +- +- char * const trim_comment = trim_all_whitespace(comment); +- bool same_comments = false; +- +- for (GList *l = comments; l && !same_comments; l = l->next) +- { +- const char * const comment_body = (const char *) l->data; +- char * const trim_comment_body = trim_all_whitespace(comment_body); +- same_comments = (strcmp(trim_comment_body, trim_comment) == 0); +- free(trim_comment_body); +- } +- +- free(trim_comment); +- return same_comments; +-} +- + static unsigned find_best_bt_rating_in_comments(GList *comments) + { + func_entry(); +@@ -553,46 +518,7 @@ int rhbz_new_bug(struct abrt_xmlrpc *ax, + if (!duphash) duphash = problem_data_get_content_or_NULL(problem_data, + "global_uuid"); + +- /* If summary is longer than max allowed summary length then +- * try to find first ' ' from the end of acceptable long summary string +- * +- * If ' ' is found replace string after that by "..." +- * +- * If ' ' is NOT found in maximal allowed range, cut summary string on +- * lenght (MAX_SUMMARY_LENGTH - strlen("...")) and append "..." +- * +- * If MAX_SUMMARY_LENGTH is 15 and max allowed cut is 5: +- * +- * 0123456789ABCDEF -> 0123456789AB... +- * 0123456789 BCDEF -> 0123456789 ... +- * 012345 789ABCDEF -> 012345 789AB... +- */ +- char *summary = NULL; +- if (strlen(bzsummary) > MAX_SUMMARY_LENGTH) +- { +- summary = xstrdup(bzsummary); +- char *max_end = summary + (MAX_SUMMARY_LENGTH - strlen("...")); +- +- /* maximal number of characters to cut due to attempt cut summary +- * string after last ' ' +- */ +- int max_cut = 16; +- +- /* start looking for ' ' one char before the last possible character */ +- char *buf = max_end - 1; +- while (buf[0] != ' ' && max_cut--) +- --buf; +- +- if (buf[0] != ' ') +- buf = max_end; +- else +- ++buf; +- +- buf[0] = '.'; +- buf[1] = '.'; +- buf[2] = '.'; +- buf[3] = '\0'; +- } ++ char *summary = shorten_string_to_length(bzsummary, MAX_SUMMARY_LENGTH); + + char *status_whiteboard = xasprintf("abrt_hash:%s", duphash); + +@@ -604,7 +530,7 @@ int rhbz_new_bug(struct abrt_xmlrpc *ax, + abrt_xmlrpc_params_add_string(&env, params, "product", product); + abrt_xmlrpc_params_add_string(&env, params, "component", component); + abrt_xmlrpc_params_add_string(&env, params, "version", version); +- abrt_xmlrpc_params_add_string(&env, params, "summary", (summary ? summary : bzsummary)); ++ abrt_xmlrpc_params_add_string(&env, params, "summary", summary); + abrt_xmlrpc_params_add_string(&env, params, "description", bzcomment); + abrt_xmlrpc_params_add_string(&env, params, "status_whiteboard", status_whiteboard); + +diff --git a/src/plugins/rhbz.h b/src/plugins/rhbz.h +index 15e7699..86632a3 100644 +--- a/src/plugins/rhbz.h ++++ b/src/plugins/rhbz.h +@@ -105,8 +105,6 @@ int rhbz_attach_blob(struct abrt_xmlrpc *ax, const char *bug_id, + int rhbz_attach_fd(struct abrt_xmlrpc *ax, const char *bug_id, + const char *att_name, int fd, int flags); + +-int is_comment_dup(GList *comments, const char *comment); +- + GList *rhbz_bug_cc(xmlrpc_value *result_xml); + + struct bug_info *rhbz_bug_info(struct abrt_xmlrpc *ax, int bug_id); +-- +1.8.3.1 + diff --git a/SOURCES/1003-ureport-set-url-to-public-faf-server.patch b/SOURCES/1003-ureport-set-url-to-public-faf-server.patch new file mode 100644 index 0000000..ee9a33c --- /dev/null +++ b/SOURCES/1003-ureport-set-url-to-public-faf-server.patch @@ -0,0 +1,27 @@ +From 2221fee2d3b930f171fa5181439ded9a1748ff00 Mon Sep 17 00:00:00 2001 +From: Matej Habrnal <mhabrnal@redhat.com> +Date: Mon, 2 Feb 2015 16:31:51 +0100 +Subject: [PATCH 1003/1015] ureport: set url to public faf server + +Set url to public faf server because the private one doesn't return URL to +known issues, bthash, possible solution etc. + +Signed-off-by: Matej Habrnal <mhabrnal@redhat.com> +--- + src/plugins/ureport.conf | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/plugins/ureport.conf b/src/plugins/ureport.conf +index 2256a7f..b82b0e1 100644 +--- a/src/plugins/ureport.conf ++++ b/src/plugins/ureport.conf +@@ -1,5 +1,5 @@ + # Base URL to uReport server +-# URL = http://bug-report.itos.redhat.com ++URL = https://retrace.fedoraproject.org/faf + + # no means that ssl certificates will not be checked + # SSLVerify = no +-- +1.8.3.1 + diff --git a/SOURCES/1004-conf-changed-URL-for-sending-uReport.patch b/SOURCES/1004-conf-changed-URL-for-sending-uReport.patch new file mode 100644 index 0000000..f0386e8 --- /dev/null +++ b/SOURCES/1004-conf-changed-URL-for-sending-uReport.patch @@ -0,0 +1,29 @@ +From e8bb90e42d0356cdcf91d22c9deb6635d1d3c376 Mon Sep 17 00:00:00 2001 +From: Matej Habrnal <mhabrnal@redhat.com> +Date: Mon, 2 Feb 2015 21:41:36 +0100 +Subject: [PATCH 1004/1015] conf: changed URL for sending uReport + +Changed faf server url in report_uReport.xml.in. +uReports are sending to https://retrace.fedoraproject.org/faf by default. + +Signed-off-by: Matej Habrnal <mhabrnal@redhat.com> +--- + src/plugins/report_uReport.xml.in | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/plugins/report_uReport.xml.in b/src/plugins/report_uReport.xml.in +index b997851..eca30e0 100644 +--- a/src/plugins/report_uReport.xml.in ++++ b/src/plugins/report_uReport.xml.in +@@ -12,7 +12,7 @@ + <_label>uReport Server URL</_label> + <allow-empty>no</allow-empty> + <_description>Address of uReport webservice</_description> +- <default-value>http://bug-report.itos.redhat.com</default-value> ++ <default-value>https://retrace.fedoraproject.org/faf</default-value> + </option> + <option type="text" name="uReport_ContactEmail"> + <_label>Contact email address</_label> +-- +1.8.3.1 + diff --git a/SOURCES/1005-reporter-mantisbt-first-version-of-the-reporter-mant.patch b/SOURCES/1005-reporter-mantisbt-first-version-of-the-reporter-mant.patch new file mode 100644 index 0000000..b267fe4 --- /dev/null +++ b/SOURCES/1005-reporter-mantisbt-first-version-of-the-reporter-mant.patch @@ -0,0 +1,2659 @@ +From be6691dc649962cad24ad7d7a89451b2c43de606 Mon Sep 17 00:00:00 2001 +From: Matej Habrnal <mhabrnal@redhat.com> +Date: Tue, 13 Jan 2015 19:30:27 -0500 +Subject: [PATCH 1005/1015] reporter-mantisbt: first version of the + reporter-mantisbt + +Related to #272 + +Signed-off-by: Matej Habrnal <mhabrnal@redhat.com> +--- + configure.ac | 33 + + po/POTFILES.in | 11 + + src/plugins/Makefile.am | 43 +- + src/plugins/centos_report_event.conf | 37 + + src/plugins/mantisbt.c | 1111 ++++++++++++++++++++++++ + src/plugins/mantisbt.conf | 8 + + src/plugins/mantisbt.h | 140 +++ + src/plugins/mantisbt_format.conf | 59 ++ + src/plugins/mantisbt_formatdup.conf | 65 ++ + src/plugins/report_CentOSBugTracker.conf | 4 + + src/plugins/report_CentOSBugTracker.xml.in | 65 ++ + src/plugins/reporter-mantisbt.c | 696 +++++++++++++++ + src/workflows/Makefile.am | 15 +- + src/workflows/report_centos.conf | 31 + + src/workflows/workflow_CentOSCCpp.xml.in | 12 + + src/workflows/workflow_CentOSJava.xml.in | 11 + + src/workflows/workflow_CentOSKerneloops.xml.in | 11 + + src/workflows/workflow_CentOSLibreport.xml.in | 9 + + src/workflows/workflow_CentOSPython.xml.in | 11 + + src/workflows/workflow_CentOSPython3.xml.in | 11 + + src/workflows/workflow_CentOSVmcore.xml.in | 12 + + src/workflows/workflow_CentOSXorg.xml.in | 9 + + 22 files changed, 2402 insertions(+), 2 deletions(-) + create mode 100644 src/plugins/centos_report_event.conf + create mode 100644 src/plugins/mantisbt.c + create mode 100644 src/plugins/mantisbt.conf + create mode 100644 src/plugins/mantisbt.h + create mode 100644 src/plugins/mantisbt_format.conf + create mode 100644 src/plugins/mantisbt_formatdup.conf + create mode 100644 src/plugins/report_CentOSBugTracker.conf + create mode 100644 src/plugins/report_CentOSBugTracker.xml.in + create mode 100644 src/plugins/reporter-mantisbt.c + create mode 100644 src/workflows/report_centos.conf + create mode 100644 src/workflows/workflow_CentOSCCpp.xml.in + create mode 100644 src/workflows/workflow_CentOSJava.xml.in + create mode 100644 src/workflows/workflow_CentOSKerneloops.xml.in + create mode 100644 src/workflows/workflow_CentOSLibreport.xml.in + create mode 100644 src/workflows/workflow_CentOSPython.xml.in + create mode 100644 src/workflows/workflow_CentOSPython3.xml.in + create mode 100644 src/workflows/workflow_CentOSVmcore.xml.in + create mode 100644 src/workflows/workflow_CentOSXorg.xml.in + +diff --git a/configure.ac b/configure.ac +index a7f67c9..83c1a52 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -144,6 +144,39 @@ do + done + fi dnl end NO_BUGZILLA + ++AC_ARG_WITH(mantisbt, ++AS_HELP_STRING([--with-mantisbt],[use MantisBT plugin (default is YES)]), ++LIBREPORT_PARSE_WITH([mantisbt])) ++ ++if test -z "$NO_MANTISBT"; then ++AM_CONDITIONAL(BUILD_MANTISBT, true) ++ ++# enable mantisbt & deps translations ++for FILE in `grep -e "#.*antisbt.*" -e "#.*naconda.*" po/POTFILES.in` ++do ++ sed -ie "s,$FILE,${FILE:1}," po/POTFILES.in ++ sed -ie "\,^${FILE:1}$,d" po/POTFILES.skip ++done ++else ++AM_CONDITIONAL(BUILD_MANTISBT, false) ++ ++# disablie mantisbt & deps translations ++for FILE in `grep -e "antisbt" -e "naconda" po/POTFILES.in` ++do ++ if test "${FILE:0:1}" = "#" ++ then ++ continue ++ fi ++ ++ sed -ie "s,$FILE,#$FILE," po/POTFILES.in ++ grep "$FILE" po/POTFILES.skip > /dev/null 2>&1 ++ if test $? ++ then ++ echo "$FILE" >> po/POTFILES.skip ++ fi ++done ++fi dnl end NO_MANTISBT ++ + AC_PATH_PROG([PYTHON_CONFIG], [python-config], [no]) + [if test "$PYTHON_CONFIG" = "no"] + [then] +diff --git a/po/POTFILES.in b/po/POTFILES.in +index 003e686..9cf8f72 100644 +--- a/po/POTFILES.in ++++ b/po/POTFILES.in +@@ -39,6 +39,8 @@ src/plugins/reporter-print.c + src/plugins/reporter-rhtsupport.c + src/plugins/reporter-rhtsupport-parse.c + src/plugins/reporter-upload.c ++src/plugins/reporter-mantisbt.c ++src/plugins/report_CentOSBugTracker.xml.in + src/plugins/report_Kerneloops.xml.in + src/plugins/report_Logger.xml.in + src/plugins/report_Mailx.xml.in +@@ -47,12 +49,21 @@ src/plugins/report_Uploader.xml.in + src/plugins/report_uReport.xml.in + src/plugins/report_EmergencyAnalysis.xml.in + src/plugins/rhbz.c ++src/plugins/mantisbt.c + src/plugins/reporter-ureport.c + src/report-newt/report-newt.c + src/workflows/workflow_AnacondaFedora.xml.in + src/workflows/workflow_AnacondaRHEL.xml.in + src/workflows/workflow_AnacondaRHELBugzilla.xml.in + src/workflows/workflow_AnacondaUpload.xml.in ++src/workflows/workflow_CentOSCCpp.xml.in ++src/workflows/workflow_CentOSJava.xml.in ++src/workflows/workflow_CentOSKerneloops.xml.in ++src/workflows/workflow_CentOSLibreport.xml.in ++src/workflows/workflow_CentOSPython.xml.in ++src/workflows/workflow_CentOSPython3.xml.in ++src/workflows/workflow_CentOSVmcore.xml.in ++src/workflows/workflow_CentOSXorg.xml.in + src/workflows/workflow_FedoraCCpp.xml.in + src/workflows/workflow_FedoraKerneloops.xml.in + src/workflows/workflow_FedoraPython.xml.in +diff --git a/src/plugins/Makefile.am b/src/plugins/Makefile.am +index 8e1a166..fd3f477 100644 +--- a/src/plugins/Makefile.am ++++ b/src/plugins/Makefile.am +@@ -7,6 +7,11 @@ reporters_bin += \ + report + endif + ++if BUILD_MANTISBT ++reporters_bin += \ ++ reporter-mantisbt ++endif ++ + if BUILD_UREPORT + reporters_bin += reporter-ureport + endif +@@ -34,6 +39,12 @@ reporters_plugin_format_conf += bugzilla_format.conf \ + bugzilla_formatdup_anaconda.conf + endif + ++if BUILD_MANTISBT ++reporters_plugin_conf += mantisbt.conf ++reporters_plugin_format_conf += mantisbt_format.conf \ ++ mantisbt_formatdup.conf ++endif ++ + defaultreportpluginsconfdir = $(DEFAULT_REPORT_PLUGINS_CONF_DIR) + dist_defaultreportpluginsconf_DATA = $(reporters_plugin_conf) \ + rhtsupport.conf \ +@@ -54,6 +65,12 @@ reporters_events += report_Bugzilla.xml + reporters_events_conf += report_Bugzilla.conf + endif + ++if BUILD_MANTISBT ++reporters_events += report_CentOSBugTracker.xml ++ ++reporters_events_conf += report_CentOSBugTracker.conf ++endif ++ + if BUILD_UREPORT + reporters_events += report_uReport.xml + endif +@@ -79,7 +96,8 @@ dist_eventsdef_DATA = \ + print_event.conf \ + rhtsupport_event.conf \ + uploader_event.conf \ +- emergencyanalysis_event.conf ++ emergencyanalysis_event.conf \ ++ centos_report_event.conf + + reporters_extra_dist = + if BUILD_BUGZILLA +@@ -127,6 +145,29 @@ reporter_bugzilla_LDADD = \ + ../lib/libreport.la + endif + ++if BUILD_MANTISBT ++reporter_mantisbt_SOURCES = \ ++ reporter-mantisbt.c mantisbt.c mantisbt.h ++reporter_mantisbt_CPPFLAGS = \ ++ -I$(srcdir)/../include \ ++ -I$(srcdir)/../lib \ ++ -DBIN_DIR=\"$(bindir)\" \ ++ -DCONF_DIR=\"$(CONF_DIR)\" \ ++ -DLOCALSTATEDIR='"$(localstatedir)"' \ ++ -DDEBUG_DUMPS_DIR=\"$(DEBUG_DUMPS_DIR)\" \ ++ -DDEBUG_INFO_DIR=\"$(DEBUG_INFO_DIR)\" \ ++ -DPLUGINS_LIB_DIR=\"$(PLUGINS_LIB_DIR)\" \ ++ -DPLUGINS_CONF_DIR=\"$(REPORT_PLUGINS_CONF_DIR)\" \ ++ $(GLIB_CFLAGS) \ ++ $(LIBREPORT_CFLAGS) \ ++ $(LIBXML_CFLAGS) \ ++ -D_GNU_SOURCE ++reporter_mantisbt_LDADD = \ ++ $(GLIB_LIBS) \ ++ ../lib/libreport-web.la \ ++ ../lib/libreport.la ++endif ++ + reporter_rhtsupport_SOURCES = \ + abrt_rh_support.h abrt_rh_support.c \ + reporter-rhtsupport.h \ +diff --git a/src/plugins/centos_report_event.conf b/src/plugins/centos_report_event.conf +new file mode 100644 +index 0000000..53f12d8 +--- /dev/null ++++ b/src/plugins/centos_report_event.conf +@@ -0,0 +1,37 @@ ++EVENT=report_CentOSBugTracker analyzer=xorg ++ reporter-mantisbt ++ ++EVENT=report_CentOSBugTracker analyzer=Kerneloops ++ reporter-mantisbt ++ ++EVENT=report_CentOSBugTracker analyzer=vmcore ++ reporter-mantisbt ++ ++EVENT=report_CentOSBugTracker analyzer=Python component!=anaconda ++ test -f component || abrt-action-save-package-data ++ reporter-mantisbt \ ++ -c /etc/libreport/plugins/mantisbt.conf \ ++ -F /etc/libreport/plugins/mantisbt_format.conf \ ++ -A /etc/libreport/plugins/mantisbt_formatdup.conf ++ ++EVENT=report_CentOSBugTracker analyzer=Python3 component!=anaconda ++ test -f component || abrt-action-save-package-data ++ reporter-mantisbt \ ++ -c /etc/libreport/plugins/mantisbt.conf \ ++ -F /etc/libreport/plugins/mantisbt_format.conf \ ++ -A /etc/libreport/plugins/mantisbt_formatdup.conf ++ ++EVENT=report_CentOSBugTracker analyzer=CCpp duphash!= ++ test -f component || abrt-action-save-package-data ++ component="`cat component`" ++ format="mantisbt_format.conf" ++ test -f "/etc/libreport/plugins/mantisbt_format_$component.conf" \ ++ && format="mantisbt_format_$component.conf" ++ formatdup="mantisbt_formatdup.conf" ++ test -f "/etc/libreport/plugins/mantisbt_formatdup_$component.conf" \ ++ && formatdup="mantisbt_formatdup_$component.conf" ++ reporter-mantisbt \ ++ -c /etc/libreport/plugins/mantisbt.conf \ ++ -F "/etc/libreport/plugins/$format" \ ++ -A "/etc/libreport/plugins/$formatdup" ++ +diff --git a/src/plugins/mantisbt.c b/src/plugins/mantisbt.c +new file mode 100644 +index 0000000..1c496b4 +--- /dev/null ++++ b/src/plugins/mantisbt.c +@@ -0,0 +1,1111 @@ ++/* ++ Copyright (C) 2014 ABRT team ++ Copyright (C) 2014 RedHat Inc ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 2 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License along ++ with this program; if not, write to the Free Software Foundation, Inc., ++ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ++*/ ++ ++#include <curl/curl.h> ++ ++#include <libxml/xmlreader.h> ++ ++#include "internal_libreport.h" ++#include "libreport_curl.h" ++#include "mantisbt.h" ++#include "client.h" ++ ++/* ++ * SOAP ++*/ ++ ++#define XML_VERSION "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" ++ ++/* fprint string */ ++#define SOAP_TEMPLATE \ ++ "<SOAP-ENV:Envelope xmlns:ns3=\"http://schemas.xmlsoap.org/soap/encoding/\" " \ ++ "xmlns:SOAP-ENC=\"http://schemas.xmlsoap.org/soap/encoding/\" " \ ++ "xmlns:ns0=\"http://schemas.xmlsoap.org/soap/encoding/\" " \ ++ "xmlns:ns1=\"http://schemas.xmlsoap.org/soap/envelope/\" " \ ++ "xmlns:ns2=\"http://www.w3.org/2001/XMLSchema\" " \ ++ "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " \ ++ "xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" " \ ++ "SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" \ ++ "<SOAP-ENV:Header/>" \ ++ "<ns1:Body>" \ ++ "<ns3:%s>" \ ++ "</ns3:%s>" \ ++ "</ns1:Body>" \ ++ "</SOAP-ENV:Envelope>" ++ ++#define MAX_SUMMARY_LENGTH 128 ++#define CUSTOMFIELD_DUPHASH "abrt_hash" ++#define CUSTOMFIELD_URL "URL" ++#define MAX_HOPS 5 ++ ++/* MantisBT limit is 2MB by default ++ */ ++#define MANTISBT_MAX_FILE_UPLOAD_SIZE (2 * 1024 * 1024) ++ ++typedef struct mantisbt_custom_fields ++{ ++ char *cf_abrt_hash_id; ++ char *cf_url_id; ++} mantisbt_custom_fields_t; ++ ++/* ++ * MantisBT settings issue info ++ */ ++void ++mantisbt_settings_free(mantisbt_settings_t *s) ++{ ++ if (s == NULL) ++ return; ++ ++ free(s->m_login); ++ free(s->m_password); ++ free(s->m_project); ++ free(s->m_project_id); ++ free(s->m_project_version); ++} ++ ++/* ++ * MantisBT issue info ++ */ ++mantisbt_issue_info_t * ++mantisbt_issue_info_new() ++{ ++ mantisbt_issue_info_t *info = xzalloc(sizeof(mantisbt_issue_info_t)); ++ info->mii_id = -1; ++ info->mii_dup_id = -1; ++ ++ return info; ++} ++ ++void ++mantisbt_issue_info_free(mantisbt_issue_info_t *info) ++{ ++ if (info == NULL) ++ return; ++ ++ free(info->mii_status); ++ free(info->mii_resolution); ++ free(info->mii_reporter); ++ free(info->mii_project); ++ ++ list_free_with_free(info->mii_notes); ++ list_free_with_free(info->mii_attachments); ++ ++ free(info); ++} ++ ++mantisbt_issue_info_t * ++mantisbt_find_origin_bug_closed_duplicate(mantisbt_settings_t *settings, mantisbt_issue_info_t *info) ++{ ++ mantisbt_issue_info_t *info_tmp = mantisbt_issue_info_new(); ++ info_tmp->mii_id = info->mii_id; ++ info_tmp->mii_dup_id = info->mii_dup_id; ++ ++ for (int ii = 0; ii <= MAX_HOPS; ii++) ++ { ++ if (ii == MAX_HOPS) ++ error_msg_and_die(_("MantisBT couldn't find parent of issue %d"), info->mii_id); ++ ++ log("Issue %d is a duplicate, using parent issue %d", info_tmp->mii_id, info_tmp->mii_dup_id); ++ int issue_id = info_tmp->mii_dup_id; ++ ++ mantisbt_issue_info_free(info_tmp); ++ info_tmp = mantisbt_get_issue_info(settings, issue_id); ++ ++ // found a issue which is not CLOSED as DUPLICATE ++ if (info_tmp->mii_dup_id == -1) ++ break; ++ } ++ ++ return info_tmp; ++} ++ ++/* ++ * SOAP request ++ */ ++static soap_request_t * ++soap_request_new() ++{ ++ soap_request_t *req = xzalloc(sizeof(*req)); ++ ++ return req; ++} ++ ++void ++soap_request_free(soap_request_t *req) ++{ ++ if (req == NULL) ++ return; ++ ++ if (req->sr_root != NULL) ++ xmlFreeDoc(req->sr_root->doc); ++ ++ free(req); ++ ++ return; ++} ++ ++static xmlNodePtr ++soap_node_get_next_element_node(xmlNodePtr node) ++{ ++ for (; node != NULL; node = node->next) ++ if (node->type == XML_ELEMENT_NODE) ++ break; ++ ++ return node; ++} ++ ++static xmlNodePtr ++soap_node_get_child_element(xmlNodePtr node) ++{ ++ if (node == NULL) ++ error_msg_and_die(_("SOAP: Failed to get child element because of no parent.")); ++ ++ return soap_node_get_next_element_node(node->xmlChildrenNode); ++} ++ ++static xmlNodePtr ++soap_node_get_next_sibling(xmlNodePtr node) ++{ ++ if (node == NULL) ++ error_msg_and_die(_("SOAP: Failed to get next element because of no node.")); ++ ++ return soap_node_get_next_element_node(node->next); ++} ++ ++static xmlNodePtr ++soap_node_get_child_node(xmlNodePtr parent, const char *name) ++{ ++ if (parent == NULL) ++ error_msg_and_die(_("SOAP: Failed to get child node because of no parent.")); ++ ++ xmlNodePtr node; ++ for (node = soap_node_get_child_element(parent); node != NULL; node = soap_node_get_next_sibling(node)) ++ { ++ if (xmlStrcmp(node->name, BAD_CAST name) == 0) ++ return node; ++ } ++ ++ return NULL; ++} ++ ++soap_request_t * ++soap_request_new_for_method(const char *method) ++{ ++ char *xml_str = xasprintf(SOAP_TEMPLATE, method, method); ++ ++ xmlDocPtr doc = xmlParseDoc(BAD_CAST xml_str); ++ free(xml_str); ++ ++ if (doc == NULL) ++ error_msg_and_die(_("SOAP: Failed to parse xml during creating request.")); ++ ++ soap_request_t *req = soap_request_new(); ++ ++ req->sr_root = xmlDocGetRootElement(doc); ++ if (req->sr_root == NULL) ++ { ++ soap_request_free(req); ++ error_msg_and_die(_("SOAP: Failed to get xml root element.")); ++ } ++ ++ req->sr_body = soap_node_get_child_node(req->sr_root, "Body"); ++ req->sr_method = soap_node_get_child_node(req->sr_body, method); ++ ++ return req; ++} ++ ++static xmlNodePtr ++soap_node_add_child_node(xmlNodePtr node, const char *name, const char *type, const char *value) ++{ ++ if (node == NULL || name == NULL) ++ error_msg_and_die(_("SOAP: Failed to add a new child node because of no node or no child name.")); ++ ++ xmlNodePtr new_node = xmlNewTextChild(node, /* namespace */ NULL, BAD_CAST name, BAD_CAST value); ++ ++ if (new_node == NULL) ++ error_msg_and_die(_("SOAP: Failed to create a new xml child item.")); ++ ++ if (type != NULL) ++ { ++ if (xmlNewProp(new_node, BAD_CAST "xsi:type", BAD_CAST type) == NULL) ++ error_msg_and_die(_("SOAP: Failed to create a new property.")); ++ } ++ ++ return new_node; ++} ++ ++void ++soap_request_add_method_parameter(soap_request_t *req, const char *name, const char *type, const char *value) ++{ ++ if (req == NULL || req->sr_method == NULL) ++ error_msg_and_die(_("SOAP: Failed to add method parametr.")); ++ ++ soap_node_add_child_node(req->sr_method, name, type, value); ++ return; ++} ++ ++void ++soap_request_add_credentials_parameter(soap_request_t *req, const mantisbt_settings_t *settings) ++{ ++ soap_request_add_method_parameter(req, "username", SOAP_STRING, settings->m_login); ++ soap_request_add_method_parameter(req, "password", SOAP_STRING, settings->m_password); ++ ++ return; ++} ++ ++static void ++soap_add_new_issue_parameters(soap_request_t *req, ++ const char *project, ++ const char *version, ++ const char *category, ++ const char *summary, ++ const char *description, ++ const char *additional_information, ++ bool private, ++ mantisbt_custom_fields_t *fields, ++ const char *duphash, ++ const char *tracker_url) ++{ ++ if (req == NULL || req->sr_method == NULL) ++ error_msg_and_die(_("SOAP: Failed to add new issue parametrs.")); ++ ++ if (project == NULL || category == NULL || summary == NULL || description == NULL) ++ error_msg_and_die(_("SOAP: Failed to add new issue parameters because the required items are missing.")); ++ ++ xmlNodePtr issue_node = soap_node_add_child_node(req->sr_method, "issue", SOAP_ISSUEDATA, /* content */ NULL); ++ ++ // project ++ xmlNodePtr project_node = soap_node_add_child_node(issue_node, "project", SOAP_OBJECTREF, /* content */ NULL); ++ soap_node_add_child_node(project_node, "name", SOAP_STRING, project); ++ ++ // view status ++ xmlNodePtr view_node = soap_node_add_child_node(issue_node, "view_state", SOAP_OBJECTREF, /* content */ NULL); ++ soap_node_add_child_node(view_node, "name", SOAP_STRING, (private) ? "private" : "public"); ++ ++ /* if any custom field exists */ ++ int custom_fields_count = 0; ++ xmlNodePtr duphash_node; ++ if (fields->cf_abrt_hash_id != NULL || fields->cf_url_id != NULL) ++ duphash_node = soap_node_add_child_node(issue_node, "custom_fields", SOAP_CUSTOMFIELD_ARRAY, /* content */ NULL); ++ ++ // custom fields (duphash and URL to tracker) ++ xmlNodePtr item_node, field_node; ++ if (fields->cf_abrt_hash_id != NULL) ++ { ++ item_node = soap_node_add_child_node(duphash_node, "item", SOAP_CUSTOMFIELD, /* content */ NULL); ++ field_node = soap_node_add_child_node(item_node, "field", SOAP_OBJECTREF, /* content */ NULL); ++ soap_node_add_child_node(field_node, "id", SOAP_INTEGER, /* custom_field id */ fields->cf_abrt_hash_id); ++ soap_node_add_child_node(item_node, "value", SOAP_STRING, duphash); ++ ++custom_fields_count; ++ } ++ ++ // if tracker url exists, attach it to the issue ++ if (tracker_url != NULL && fields->cf_url_id != NULL) ++ { ++ item_node = soap_node_add_child_node(duphash_node, "item", SOAP_CUSTOMFIELD, /* content */ NULL); ++ field_node = soap_node_add_child_node(item_node, "field", SOAP_OBJECTREF, /* content */ NULL); ++ soap_node_add_child_node(field_node, "id", SOAP_INTEGER, /* custom_field */ fields->cf_url_id); ++ soap_node_add_child_node(item_node, "value", SOAP_STRING, tracker_url); ++ ++custom_fields_count; ++ } ++ ++ if (custom_fields_count > 0) ++ { ++ char *type = xasprintf("%s[%i]", SOAP_CUSTOMFIELD, custom_fields_count); ++ ++ if (xmlNewProp(duphash_node, BAD_CAST "ns3:arrayType", BAD_CAST type) == NULL) ++ error_msg_and_die(_("SOAP: Failed to create a new property in custom fields.")); ++ ++ free(type); ++ } ++ ++ soap_node_add_child_node(issue_node, "os_build", SOAP_STRING, version); ++ soap_node_add_child_node(issue_node, "category", SOAP_STRING, category); ++ soap_node_add_child_node(issue_node, "summary", SOAP_STRING, summary); ++ soap_node_add_child_node(issue_node, "description", SOAP_STRING, description); ++ soap_node_add_child_node(issue_node, "additional_information", SOAP_STRING, additional_information); ++ ++ return; ++} ++ ++char * ++soap_request_to_str(const soap_request_t *req) ++{ ++ if (req == NULL || req->sr_root == NULL || req->sr_root->doc == NULL) ++ error_msg_and_die(_("SOAP: Failed to create SOAP string because of invalid function arguments.")); ++ ++ xmlBufferPtr buffer = xmlBufferCreate(); ++ int err = xmlNodeDump(buffer, req->sr_root->doc, req->sr_root, 1, /* formatting */ 0); ++ if (err == -1) ++ { ++ xmlBufferFree(buffer); ++ error_msg_and_die(_("SOAP: Failed to dump xml node.")); ++ } ++ ++ char *ret = xasprintf("%s%s", XML_VERSION, (const char *) xmlBufferContent(buffer)); ++ xmlBufferFree(buffer); ++ ++ return ret; ++} ++ ++#if 0 ++void ++soap_request_print(soap_request_t *req) ++{ ++ if (req == NULL || req->sr_root == NULL || req->sr_root->doc == NULL) ++ error_msg_and_die(_("SOAP: Failed to print SOAP string.")); ++ ++ xmlBufferPtr buffer = xmlBufferCreate(); ++ int err = xmlNodeDump(buffer, req->sr_root->doc, req->sr_root, 1, /* formatting */ 0); ++ if (err == -1) ++ { ++ xmlBufferFree(buffer); ++ error_msg_and_die(_("Failed to dump xml node.")); ++ } ++ ++ puts((const char *) xmlBufferContent(buffer)); ++ ++ xmlBufferFree(buffer); ++ return; ++} ++#endif ++ ++static bool ++reader_move_reader_if_node_type_is_element_with_name_and_verify_its_value(xmlTextReaderPtr reader, const char *name) ++{ ++ /* is not element node */ ++ if (xmlTextReaderNodeType(reader) != XML_ELEMENT_NODE) ++ return false; ++ ++ /* is not required name */ ++ if (xmlStrcmp(xmlTextReaderConstName(reader), BAD_CAST name) != 0) ++ return false; ++ ++ /* read next node */ ++ if (xmlTextReaderRead(reader) != 1) ++ return false; ++ ++ /* no value node */ ++ if (xmlTextReaderHasValue(reader) == 0) ++ return false; ++ ++ /* no text node */ ++ if (xmlTextReaderNodeType(reader) != XML_TEXT_NODE) ++ return false; ++ ++ return true; ++} ++ ++static void ++reader_find_element_by_name(xmlTextReaderPtr reader, const char *name) ++{ ++ while (xmlTextReaderRead(reader) == 1) ++ { ++ /* is not element node */ ++ if (xmlTextReaderNodeType(reader) != XML_ELEMENT_NODE) ++ continue; ++ ++ /* is not required name */ ++ if (xmlStrcmp(xmlTextReaderConstName(reader), BAD_CAST name) != 0) ++ continue; ++ ++ break; ++ } ++ ++ return; ++} ++ ++/* It is not possible to search only by name because the response contains ++ * different node with the same name. (e.g. id - user id, project id, issue id etc.) ++ * We are interested in only about issues id which is located at a different depth than others. ++ * ... ++ * <item xsi:type="ns1:IssueData"> ++ * <id xsi:type="xsd:integer">10</id> <-- This is issue ID (required) ++ * <view_state xsi:type="ns1:ObjectRef"> ++ * <id xsi:type="xsd:integer">10</id> <-- This is view_state ID (not required) ++ * <name xsi:type="xsd:string">public</name> ++ * </view_state> ++ * <project xsi:type="ns1:ObjectRef"> ++ * <id xsi:type="xsd:integer">1</id> <-- This is project ID (not required) ++ * <name xsi:type="xsd:string">test</name> ++ * </project> ++ * ... ++ */ ++static GList * ++response_values_at_depth_by_name(const char *xml, const char *name, int depth) ++{ ++ xmlDocPtr doc = xmlParseDoc(BAD_CAST xml); ++ if (doc == NULL) ++ error_msg_and_die(_("SOAP: Failed to parse xml (searching value at depth by name).")); ++ ++ xmlTextReaderPtr reader = xmlReaderWalker(doc); ++ if (reader == NULL) ++ error_msg_and_die(_("SOAP: Failed to create xml text reader.")); ++ ++ GList *result = NULL; ++ ++ const xmlChar *value; ++ while (xmlTextReaderRead(reader) == 1) ++ { ++ /* is not right depth */ ++ if (depth != -1 && xmlTextReaderDepth(reader) != depth) ++ continue; ++ ++ if (reader_move_reader_if_node_type_is_element_with_name_and_verify_its_value(reader, name) == false) ++ continue; ++ ++ if ((value = xmlTextReaderConstValue(reader)) != NULL) ++ result = g_list_append(result, xstrdup((const char *) value)); ++ } ++ xmlFreeTextReader(reader); ++ ++ return result; ++} ++ ++/* ++ * Finds an element named 'elem' and returns a text of a child named 'name' ++ * ++ * Example: ++ * For ++ * <elem> ++ * <id>1</id> ++ * <name>foo</name> ++ * </elem> ++ * ++ * returns "foo" ++ */ ++static char * ++response_get_name_value_of_element(const char *xml, const char *element) ++{ ++ xmlDocPtr doc = xmlParseDoc(BAD_CAST xml); ++ if (doc == NULL) ++ error_msg_and_die(_("SOAP: Failed to parse xml.")); ++ ++ xmlTextReaderPtr reader = xmlReaderWalker(doc); ++ if (reader == NULL) ++ error_msg_and_die(_("SOAP: Failed to create xml text reader.")); ++ ++ const xmlChar *value = NULL; ++ ++ reader_find_element_by_name(reader, element); ++ ++ /* find 'name' element and return its text */ ++ while (xmlTextReaderRead(reader) == 1) ++ { ++ if (reader_move_reader_if_node_type_is_element_with_name_and_verify_its_value(reader, "name") == false) ++ continue; ++ ++ if ((value = xmlTextReaderConstValue(reader)) != NULL) ++ break; ++ } ++ xmlFreeTextReader(reader); ++ ++ return (char *) value; ++} ++ ++static int ++response_get_id_of_relatedto_issue(const char *xml) ++{ ++ xmlDocPtr doc = xmlParseDoc(BAD_CAST xml); ++ if (doc == NULL) ++ error_msg_and_die(_("SOAP: Failed to parse xml (get related to issue).")); ++ ++ xmlTextReaderPtr reader = xmlReaderWalker(doc); ++ if (reader == NULL) ++ error_msg_and_die(_("SOAP: Failed to create xml text reader.")); ++ ++ const xmlChar *value = NULL; ++ const xmlChar *id = NULL; ++ ++ /* find relationships section */ ++ reader_find_element_by_name(reader, "relationships"); ++ ++ /* find "name" value of 'name' element */ ++ while (xmlTextReaderRead(reader) == 1) ++ { ++ /* find type of relattionship */ ++ if (reader_move_reader_if_node_type_is_element_with_name_and_verify_its_value(reader, "name") == false) ++ continue; ++ ++ if ((value = xmlTextReaderConstValue(reader)) == NULL) ++ continue; ++ ++ /* we need 'duplicate of' realtionship type */ ++ if (xmlStrcmp(value, BAD_CAST "duplicate of") != 0) ++ continue; ++ ++ /* find id of duplicate issues */ ++ reader_find_element_by_name(reader, "target_id"); ++ ++ /* verify target_id node */ ++ if (reader_move_reader_if_node_type_is_element_with_name_and_verify_its_value(reader, "target_id") == false) ++ continue; ++ ++ /* get its value */ ++ if ((id = xmlTextReaderConstValue(reader)) != NULL) ++ break; ++ } ++ xmlFreeTextReader(reader); ++ ++ return (id == NULL) ? -1 : atoi((const char *) id); ++} ++ ++GList * ++response_get_main_ids_list(const char *xml) ++{ ++ return response_values_at_depth_by_name(xml, "id", 5); ++} ++ ++int ++response_get_main_id(const char *xml) ++{ ++ GList *l = response_values_at_depth_by_name(xml, "id", 5); ++ return (l != NULL) ? atoi(l->data) : -1; ++} ++ ++static int ++response_get_return_value(const char *xml) ++{ ++ GList *l = response_values_at_depth_by_name(xml, "return", 3); ++ return (l != NULL) ? atoi(l->data) : -1; ++} ++ ++static char* ++response_get_return_value_as_string(const char *xml) ++{ ++ GList *l = response_values_at_depth_by_name(xml, "return", 3); ++ return (l != NULL) ? l->data : NULL; ++} ++ ++static char * ++response_get_error_msg(const char *xml) ++{ ++ GList *l = response_values_at_depth_by_name(xml, "faultstring", 3); ++ return (l != NULL) ? xstrdup(l->data) : NULL; ++} ++ ++static char * ++response_get_additioanl_information(const char *xml) ++{ ++ GList *l = response_values_at_depth_by_name(xml, "additional_information", -1); ++ return (l != NULL) ? xstrdup(l->data) : NULL; ++} ++ ++void ++response_values_free(GList *values) ++{ ++ g_list_free_full(values, free); ++} ++ ++/* ++ * POST ++ */ ++ ++void ++mantisbt_result_free(mantisbt_result_t *result) ++{ ++ if (result == NULL) ++ return; ++ ++ free(result->mr_url); ++ free(result->mr_msg); ++ free(result->mr_body); ++ free(result); ++} ++ ++mantisbt_result_t * ++mantisbt_soap_call(const mantisbt_settings_t *settings, const soap_request_t *req) ++{ ++ char *request = soap_request_to_str(req); ++ ++ const char *url = settings->m_mantisbt_soap_url; ++ ++ mantisbt_result_t *result = xzalloc(sizeof(*result)); ++ ++ if (url == NULL || request == NULL) ++ { ++ result->mr_error = -2; ++ result->mr_msg = xasprintf(_("Url or request isn't specified.")); ++ free(request); ++ ++ return result; ++ } ++ ++ char *url_copy = NULL; ++ ++ int redirect_count = 0; ++ char *errmsg; ++ post_state_t *post_state; ++ ++redirect: ++ post_state = new_post_state(0 ++ + POST_WANT_HEADERS ++ + POST_WANT_BODY ++ + POST_WANT_ERROR_MSG ++ + (settings->m_ssl_verify ? POST_WANT_SSL_VERIFY : 0) ++ ); ++ ++ post_string(post_state, settings->m_mantisbt_soap_url, "text/xml", NULL, request); ++ ++ char *location = find_header_in_post_state(post_state, "Location:"); ++ ++ switch (post_state->http_resp_code) ++ { ++ case 404: ++ result->mr_error = -1; ++ result->mr_msg = xasprintf(_("Error in HTTP POST, " ++ "HTTP code: 404 (Not found), URL:'%s'"), url); ++ break; ++ case 500: ++ result->mr_error = -1; ++ result->mr_msg = response_get_error_msg(post_state->body); ++ ++ break; ++ case 301: /* "301 Moved Permanently" (for example, used to move http:// to https://) */ ++ case 302: /* "302 Found" (just in case) */ ++ case 305: /* "305 Use Proxy" */ ++ if (++redirect_count < 10 && location) ++ { ++ free(url_copy); ++ url = url_copy = xstrdup(location); ++ free_post_state(post_state); ++ goto redirect; ++ } ++ /* fall through */ ++ ++ default: ++ result->mr_error = -1; ++ errmsg = post_state->curl_error_msg; ++ if (errmsg && errmsg[0]) ++ result->mr_msg = xasprintf(_("Error in MantisBT request at '%s': %s"), url, errmsg); ++ else ++ result->mr_msg = xasprintf(_("Error in MantisBT request at '%s'"), url); ++ break; ++ ++ case 200: ++ case 201: ++ /* sent successfully */ ++ result->mr_url = xstrdup(location); /* note: xstrdup(NULL) returns NULL */ ++ } /* switch (HTTP code) */ ++ ++ result->mr_http_resp_code = post_state->http_resp_code; ++ result->mr_body = post_state->body; ++ post_state->body = NULL; ++ ++ free_post_state(post_state); ++ free(url_copy); ++ free(request); ++ ++ return result; ++} ++ ++int ++mantisbt_attach_data(const mantisbt_settings_t *settings, const char *bug_id, ++ const char *att_name, const char *data, int size) ++{ ++ soap_request_t *req = soap_request_new_for_method("mc_issue_attachment_add"); ++ soap_request_add_credentials_parameter(req, settings); ++ ++ soap_request_add_method_parameter(req, "issue_id", SOAP_INTEGER, bug_id); ++ soap_request_add_method_parameter(req, "name", SOAP_STRING, att_name); ++ ++ soap_request_add_method_parameter(req, "file_type", SOAP_STRING, "text"); ++ soap_request_add_method_parameter(req, "content", SOAP_BASE64, encode_base64(data, size)); ++ ++ mantisbt_result_t *result = mantisbt_soap_call(settings, req); ++ soap_request_free(req); ++ ++ if (result->mr_http_resp_code != 200) ++ { ++ int ret = -1; ++ if (strcmp(result->mr_msg, "Duplicate filename.") == 0) ++ ret = -2; ++ ++ error_msg(_("Failed to attach file: '%s'"), result->mr_msg); ++ mantisbt_result_free(result); ++ return ret; ++ } ++ ++ int id = response_get_return_value(result->mr_body); ++ ++ mantisbt_result_free(result); ++ ++ return id; ++} ++ ++static int ++mantisbt_attach_fd(const mantisbt_settings_t *settings, const char *bug_id, ++ const char *att_name, int fd) ++{ ++ off_t size = lseek(fd, 0, SEEK_END); ++ if (size < 0) ++ { ++ perror_msg(_("Can't lseek '%s'"), att_name); ++ return -1; ++ } ++ ++ if (size >= MANTISBT_MAX_FILE_UPLOAD_SIZE) ++ { ++ error_msg(_("Can't upload '%s', it's too large (%llu bytes)"), att_name, (long long)size); ++ return -1; ++ } ++ lseek(fd, 0, SEEK_SET); ++ ++ char *data = xmalloc(size + 1); ++ ssize_t r = full_read(fd, data, size); ++ if (r < 0) ++ { ++ free(data); ++ perror_msg(_("Can't read '%s'"), att_name); ++ return -1; ++ } ++ ++ int res = mantisbt_attach_data(settings, bug_id, att_name, data, size); ++ free(data); ++ return res; ++} ++ ++int ++mantisbt_attach_file(const mantisbt_settings_t *settings, const char *bug_id, ++ const char *att_name, const char *path) ++{ ++ int fd = open(path, O_RDONLY); ++ if (fd < 0) ++ { ++ perror_msg(_("Can't open '%s'"), path); ++ return 0; ++ } ++ errno = 0; ++ struct stat st; ++ if (fstat(fd, &st) != 0 || !S_ISREG(st.st_mode)) ++ { ++ perror_msg("'%s': not a regular file", path); ++ close(fd); ++ return 0; ++ } ++ log_debug("attaching '%s' as file", att_name); ++ int ret = mantisbt_attach_fd(settings, bug_id, att_name, fd); ++ close(fd); ++ return ret; ++} ++ ++static void ++soap_filter_add_new_array_parameter(xmlNodePtr filter_node, const char *name, const char *type, const char *value) ++{ ++ const char *array_type = NULL; ++ if( strcmp(type, SOAP_INTEGER) == 0 ) ++ array_type = SOAP_INTEGERARRAY; ++ else ++ array_type = SOAP_STRINGARRAY; ++ ++ xmlNodePtr filter_item = soap_node_add_child_node(filter_node, name, array_type, /* content */ NULL); ++ soap_node_add_child_node(filter_item, "item", type, value); ++} ++ ++static void ++soap_filter_custom_fields_add_new_item(xmlNodePtr filter_node, const char *custom_field_name, const char *value) ++{ ++ xmlNodePtr item_node = soap_node_add_child_node(filter_node, "item", SOAP_FILTER_CUSTOMFIELD, /* content */ NULL); ++ ++ xmlNodePtr field_node = soap_node_add_child_node(item_node, "field", SOAP_OBJECTREF, /* content */ NULL); ++ soap_node_add_child_node(field_node, "name", SOAP_STRING, custom_field_name); ++ ++ xmlNodePtr value_node = soap_node_add_child_node(item_node, "value", SOAP_STRINGARRAY, /* content */ NULL); ++ soap_node_add_child_node(value_node, "item", SOAP_STRING, value); ++} ++ ++GList * ++mantisbt_search_by_abrt_hash(mantisbt_settings_t *settings, const char *abrt_hash) ++{ ++ soap_request_t *req = soap_request_new_for_method("mc_filter_search_issues"); ++ soap_request_add_credentials_parameter(req, settings); ++ ++ xmlNodePtr filter_node = soap_node_add_child_node(req->sr_method, "filter", SOAP_FILTER_SEARCH_DATA, /* content */ NULL); ++ ++ /* 'hide_status_is : -2' means, searching within all status */ ++ soap_filter_add_new_array_parameter(filter_node, "hide_status_id", SOAP_INTEGERARRAY, "-2"); ++ ++ // custom fields ++ xmlNodePtr custom_fields_node = soap_node_add_child_node(filter_node, "custom_fields", SOAP_FILTER_CUSTOMFIELD_ARRAY, /* content */ NULL); ++ ++ // custom field 'abrt_hash' ++ soap_filter_custom_fields_add_new_item(custom_fields_node, "abrt_hash", abrt_hash); ++ ++ soap_request_add_method_parameter(req, "page_number", SOAP_INTEGER, "1"); ++ soap_request_add_method_parameter(req, "per_page", SOAP_INTEGER, /* -1 means get all issues */ "-1"); ++ ++ mantisbt_result_t *result = mantisbt_soap_call(settings, req); ++ soap_request_free(req); ++ ++ if (result->mr_error == -1) ++ { ++ error_msg(_("Failed to search MantisBT issue by duphash: '%s'"), result->mr_msg); ++ mantisbt_result_free(result); ++ return NULL; ++ } ++ ++ GList *ids = response_get_main_ids_list(result->mr_body); ++ ++ return ids; ++} ++ ++GList * ++mantisbt_search_duplicate_issues(mantisbt_settings_t *settings, const char *category, ++ const char *version, const char *abrt_hash) ++{ ++ soap_request_t *req = soap_request_new_for_method("mc_filter_search_issues"); ++ soap_request_add_credentials_parameter(req, settings); ++ ++ xmlNodePtr filter_node = soap_node_add_child_node(req->sr_method, "filter", SOAP_FILTER_SEARCH_DATA, /* content */ NULL); ++ ++ soap_filter_add_new_array_parameter(filter_node, "project_id", SOAP_INTEGERARRAY, settings->m_project_id); ++ ++ /* 'hide_status_is : -2' means, searching within all status */ ++ soap_filter_add_new_array_parameter(filter_node, "hide_status_id", SOAP_INTEGERARRAY, "-2"); ++ ++ soap_filter_add_new_array_parameter(filter_node, "category", SOAP_STRINGARRAY, category); ++ ++ // custom fields ++ xmlNodePtr custom_fields_node = soap_node_add_child_node(filter_node, "custom_fields", SOAP_FILTER_CUSTOMFIELD_ARRAY, /* content */ NULL); ++ ++ // custom field 'abrt_hash' ++ soap_filter_custom_fields_add_new_item(custom_fields_node, "abrt_hash", abrt_hash); ++ ++ // version ++ if (version != NULL) ++ soap_filter_add_new_array_parameter(filter_node, "os_build", SOAP_STRINGARRAY, version); ++ ++ soap_request_add_method_parameter(req, "page_number", SOAP_INTEGER, "1"); ++ soap_request_add_method_parameter(req, "per_page", SOAP_INTEGER, /* -1 means get all issues */ "-1"); ++ ++ mantisbt_result_t *result = mantisbt_soap_call(settings, req); ++ soap_request_free(req); ++ ++ if (result->mr_error == -1) ++ { ++ error_msg(_("Failed to search MantisBT duplicate issue: '%s'"), result->mr_msg); ++ mantisbt_result_free(result); ++ return NULL; ++ } ++ ++ GList *ids = response_get_main_ids_list(result->mr_body); ++ ++ return ids; ++} ++ ++static char * ++custom_field_get_id_from_name(GList *ids, GList *names, const char *name) ++{ ++ GList *i = ids; ++ GList *n = names; ++ for (; i != NULL; i = i->next, n = n->next) ++ { ++ if (strcmp(n->data, name) == 0) ++ return i->data; ++ } ++ ++ return NULL; ++} ++ ++static void ++custom_field_ask(const char *name) ++{ ++ char *msg = xasprintf(_("CentOS Bug Tracker doesn't contain custom field '%s', which is required for full functionality of the reporter. Do you still want to create a new issue?"), name); ++ int yes = ask_yes_no(msg); ++ free(msg); ++ ++ if (!yes) ++ { ++ set_xfunc_error_retval(EXIT_CANCEL_BY_USER); ++ xfunc_die(); ++ } ++ ++ return; ++} ++ ++static void ++mantisbt_get_custom_fields(const mantisbt_settings_t *settings, mantisbt_custom_fields_t *fields, const char *project_id) ++{ ++ soap_request_t *req = soap_request_new_for_method("mc_project_get_custom_fields"); ++ soap_request_add_credentials_parameter(req, settings); ++ soap_request_add_method_parameter(req, "project_id", SOAP_INTEGER, project_id); ++ ++ mantisbt_result_t *result = mantisbt_soap_call(settings, req); ++ soap_request_free(req); ++ ++ if (result->mr_http_resp_code != 200) ++ error_msg_and_die(_("Failed to get custom fields for '%s' project"), settings->m_project); ++ ++ GList *ids = response_values_at_depth_by_name(result->mr_body, "id", -1); ++ GList *names = response_values_at_depth_by_name(result->mr_body, "name", -1); ++ ++ mantisbt_result_free(result); ++ ++ if ((fields->cf_abrt_hash_id = custom_field_get_id_from_name(ids, names, CUSTOMFIELD_DUPHASH)) == NULL) ++ custom_field_ask(CUSTOMFIELD_DUPHASH); ++ ++ if ((fields->cf_url_id = custom_field_get_id_from_name(ids, names, CUSTOMFIELD_URL)) == NULL) ++ custom_field_ask(CUSTOMFIELD_URL); ++ ++ return; ++} ++ ++int ++mantisbt_create_new_issue(const mantisbt_settings_t *settings, ++ problem_data_t *problem_data, ++ const problem_report_t *pr, ++ const char *tracker_url) ++{ ++ ++ const char *category = problem_data_get_content_or_NULL(problem_data, FILENAME_COMPONENT); ++ const char *duphash = problem_data_get_content_or_NULL(problem_data, FILENAME_DUPHASH); ++ ++ char *summary = shorten_string_to_length(problem_report_get_summary(pr), MAX_SUMMARY_LENGTH); ++ ++ const char *description = problem_report_get_description(pr); ++ const char *additional_information = problem_report_get_section(pr, PR_SEC_ADDITIONAL_INFO); ++ ++ mantisbt_custom_fields_t fields; ++ mantisbt_get_custom_fields(settings, &fields, settings->m_project_id); ++ ++ soap_request_t *req = soap_request_new_for_method("mc_issue_add"); ++ soap_request_add_credentials_parameter(req, settings); ++ soap_add_new_issue_parameters(req, settings->m_project, settings->m_project_version, category, summary, description, additional_information, settings->m_create_private, &fields, duphash, tracker_url); ++ ++ mantisbt_result_t *result = mantisbt_soap_call(settings, req); ++ soap_request_free(req); ++ free(summary); ++ ++ if (result->mr_error == -1) ++ { ++ error_msg(_("Failed to create a new issue: '%s'"), result->mr_msg); ++ mantisbt_result_free(result); ++ return -1; ++ } ++ ++ int id = response_get_return_value(result->mr_body); ++ ++ mantisbt_result_free(result); ++ return id; ++} ++ ++mantisbt_issue_info_t * ++mantisbt_get_issue_info(const mantisbt_settings_t *settings, int issue_id) ++{ ++ soap_request_t *req = soap_request_new_for_method("mc_issue_get"); ++ soap_request_add_credentials_parameter(req, settings); ++ ++ char *issue_id_str = xasprintf("%d", issue_id); ++ soap_request_add_method_parameter(req, "issue_id", SOAP_INTEGER, issue_id_str); ++ free(issue_id_str); ++ ++ mantisbt_result_t *result = mantisbt_soap_call(settings, req); ++ soap_request_free(req); ++ ++ if (result->mr_error == -1) ++ { ++ error_msg(_("Failed to get MantisBT issue: '%s'"), result->mr_msg); ++ mantisbt_result_free(result); ++ return NULL; ++ } ++ ++ mantisbt_issue_info_t *issue_info = mantisbt_issue_info_new(); ++ ++ issue_info->mii_id = issue_id; ++ issue_info->mii_status = response_get_name_value_of_element(result->mr_body, "status"); ++ issue_info->mii_resolution = response_get_name_value_of_element(result->mr_body, "resolution"); ++ issue_info->mii_reporter = response_get_name_value_of_element(result->mr_body, "reporter"); ++ issue_info->mii_project = response_get_name_value_of_element(result->mr_body, "project"); ++ ++ if (strcmp(issue_info->mii_status, "closed") == 0 && !issue_info->mii_resolution) ++ error_msg(_("Issue %i is CLOSED, but it has no RESOLUTION"), issue_info->mii_id); ++ ++ issue_info->mii_dup_id = response_get_id_of_relatedto_issue(result->mr_body); ++ ++ if (strcmp(issue_info->mii_status, "closed") == 0 ++ && strcmp(issue_info->mii_resolution, "duplicate") == 0 ++ && issue_info->mii_dup_id == -1 ) ++ { ++ error_msg(_("Issue %i is CLOSED as DUPLICATE, but it has no DUPLICATE_ID"), ++ issue_info->mii_id); ++ } ++ ++ /* notes are stored in <text> element */ ++ issue_info->mii_notes = response_values_at_depth_by_name(result->mr_body, "text", -1); ++ ++ /* looking for bt rating in additional information too */ ++ char *add_info = response_get_additioanl_information(result->mr_body); ++ if (add_info != NULL) ++ issue_info->mii_notes = g_list_append (issue_info->mii_notes, add_info); ++ issue_info->mii_attachments = response_values_at_depth_by_name(result->mr_body, "filename", -1); ++ issue_info->mii_best_bt_rating = comments_find_best_bt_rating(issue_info->mii_notes); ++ ++ mantisbt_result_free(result); ++ return issue_info; ++} ++ ++int ++mantisbt_add_issue_note(const mantisbt_settings_t *settings, int issue_id, const char *note) ++{ ++ soap_request_t *req = soap_request_new_for_method("mc_issue_note_add"); ++ soap_request_add_credentials_parameter(req, settings); ++ ++ char *issue_id_str = xasprintf("%i", issue_id); ++ soap_node_add_child_node(req->sr_method, "issue_id", SOAP_INTEGER, issue_id_str); ++ ++ xmlNodePtr note_node = soap_node_add_child_node(req->sr_method, "note", SOAP_ISSUENOTE, /* content */ NULL); ++ soap_node_add_child_node(note_node, "text", SOAP_STRING, note); ++ ++ mantisbt_result_t *result = mantisbt_soap_call(settings, req); ++ ++ free(issue_id_str); ++ soap_request_free(req); ++ ++ if (result->mr_error == -1) ++ { ++ error_msg(_("Failed to add MantisBT issue note: '%s'"), result->mr_msg); ++ mantisbt_result_free(result); ++ return -1; ++ } ++ int id = response_get_return_value(result->mr_body); ++ ++ mantisbt_result_free(result); ++ return id; ++} ++ ++void ++mantisbt_get_project_id_from_name(mantisbt_settings_t *settings) ++{ ++ if (settings->m_project == NULL) ++ error_msg_and_die(_("The MantisBT project has not been deretmined.")); ++ ++ soap_request_t *req = soap_request_new_for_method("mc_project_get_id_from_name"); ++ soap_request_add_credentials_parameter(req, settings); ++ soap_node_add_child_node(req->sr_method, "project_name", SOAP_STRING, settings->m_project); ++ ++ mantisbt_result_t *result = mantisbt_soap_call(settings, req); ++ ++ if (result->mr_http_resp_code != 200) ++ error_msg_and_die(_("Failed to get project id from name")); ++ ++ settings->m_project_id = response_get_return_value_as_string(result->mr_body); ++ ++ return; ++} +diff --git a/src/plugins/mantisbt.conf b/src/plugins/mantisbt.conf +new file mode 100644 +index 0000000..15a1065 +--- /dev/null ++++ b/src/plugins/mantisbt.conf +@@ -0,0 +1,8 @@ ++# MantisBT URL ++MantisbtURL = https://bugs.centos.org/ ++# yes means that ssl certificates will be checked ++SSLVerify = yes ++# your login has to exist, if you don have any, please create one ++Login = ++# your password ++Password = +diff --git a/src/plugins/mantisbt.h b/src/plugins/mantisbt.h +new file mode 100644 +index 0000000..c31c174 +--- /dev/null ++++ b/src/plugins/mantisbt.h +@@ -0,0 +1,140 @@ ++/* ++ Copyright (C) 2014 ABRT team ++ Copyright (C) 2014 RedHat Inc ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 2 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License along ++ with this program; if not, write to the Free Software Foundation, Inc., ++ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ++*/ ++ ++#ifndef MANTISBT_H ++#define MANTISBT_H ++ ++#ifdef __cplusplus ++extern "C" { ++#endif ++ ++#include <libxml/encoding.h> ++#include "problem_report.h" ++ ++#define SOAP_STRING "ns2:string" ++#define SOAP_INTEGER "ns2:integer" ++#define SOAP_INTEGERARRAY "ns2:IntegerArray" ++#define SOAP_STRINGARRAY "ns2:StringArray" ++#define SOAP_ISSUEDATA "ns3:IssueData" ++#define SOAP_OBJECTREF "ns3:ObjectRef" ++#define SOAP_CUSTOMFIELD_ARRAY "ns2:CustomFieldValueForIssueDataArray" ++#define SOAP_FILTER_CUSTOMFIELD "ns2:FilterCustomField" ++#define SOAP_FILTER_CUSTOMFIELD_ARRAY "ns2:FilterCustomFieldArray" ++#define SOAP_FILTER_SEARCH_DATA "ns2:FilterSearchData" ++#define SOAP_CUSTOMFIELD "ns2:CustomFieldValueForIssueData" ++#define SOAP_BASE64 "SOAP-ENC:base64" ++#define SOAP_ISSUENOTE "ns3:IssueNoteData" ++ ++#define PR_SEC_ADDITIONAL_INFO "Additional info" ++ ++typedef struct soap_request ++{ ++ xmlNodePtr sr_root; ++ xmlNodePtr sr_body; ++ xmlNodePtr sr_method; ++} soap_request_t; ++ ++typedef struct mantisbt_settings ++{ ++ char *m_login; ++ char *m_password; ++ const char *m_mantisbt_url; ++ const char *m_mantisbt_soap_url; ++ char *m_project; ++ char *m_project_id; ++ char *m_project_version; ++ const char *m_DontMatchComponents; ++ int m_ssl_verify; ++ int m_create_private; ++} mantisbt_settings_t; ++ ++typedef struct mantisbt_result ++{ ++ int mr_http_resp_code; ++ int mr_error; ++ char *mr_msg; ++ char *mr_url; ++ char *mr_body; ++} mantisbt_result_t; ++ ++typedef struct mantisbt_issue_info ++{ ++ int mii_id; ++ int mii_dup_id; ++ unsigned mii_best_bt_rating; ++ ++ char *mii_status; ++ char *mii_resolution; ++ char *mii_reporter; ++ char *mii_project; ++ ++ GList *mii_notes; ++ GList *mii_attachments; ++} mantisbt_issue_info_t; ++ ++void mantisbt_settings_free(mantisbt_settings_t *settings); ++ ++mantisbt_issue_info_t * mantisbt_issue_info_new(); ++void mantisbt_issue_info_free(mantisbt_issue_info_t *info); ++mantisbt_issue_info_t * mantisbt_find_origin_bug_closed_duplicate(mantisbt_settings_t *settings, mantisbt_issue_info_t *info); ++ ++void soap_request_free(soap_request_t *req); ++ ++soap_request_t *soap_request_new_for_method(const char *method); ++ ++void soap_request_add_method_parameter(soap_request_t *req, const char *name, const char *type, const char *value); ++void soap_request_add_credentials_parameter(soap_request_t *req, const mantisbt_settings_t *settings); ++ ++char *soap_request_to_str(const soap_request_t *req); ++ ++#if 0 ++void soap_request_print(soap_request_t *req); ++#endif ++ ++GList * response_get_main_ids_list(const char *xml); ++int response_get_main_id(const char *xml); ++void response_values_free(GList *values); ++ ++void mantisbt_result_free(mantisbt_result_t *result); ++mantisbt_result_t *mantisbt_soap_call(const mantisbt_settings_t *settings, const soap_request_t *req); ++ ++int mantisbt_attach_data(const mantisbt_settings_t *settings, const char *bug_id, ++ const char *att_name, const char *data, int size); ++ ++int mantisbt_attach_file(const mantisbt_settings_t *settings, const char *bug_id, ++ const char *att_name, const char *data); ++ ++GList * mantisbt_search_by_abrt_hash(mantisbt_settings_t *settings, const char *abrt_hash); ++GList * mantisbt_search_duplicate_issues(mantisbt_settings_t *settings, const char *category, ++ const char *version, const char *abrt_hash); ++ ++int mantisbt_create_new_issue(const mantisbt_settings_t *settings, problem_data_t *problem_data, ++ const problem_report_t *pr, const char *tracker_url); ++ ++mantisbt_issue_info_t * mantisbt_get_issue_info(const mantisbt_settings_t *settings, int issue_id); ++int mantisbt_add_issue_note(const mantisbt_settings_t *settings, int issue_id, const char *note); ++ ++void mantisbt_get_project_id_from_name(mantisbt_settings_t *settings); ++ ++#ifdef __cplusplus ++} ++#endif ++ ++#endif ++ +diff --git a/src/plugins/mantisbt_format.conf b/src/plugins/mantisbt_format.conf +new file mode 100644 +index 0000000..da08aa6 +--- /dev/null ++++ b/src/plugins/mantisbt_format.conf +@@ -0,0 +1,59 @@ ++# Lines starting with # are ignored. ++# Lines can be continued on the next line using trailing backslash. ++# ++# Format: ++# %summary:: summary format ++# section:: element1[,element2]... ++# The literal text line to be added to Bugzilla comment. Can be empty. ++# (IOW: empty lines are NOT ignored!) ++# ++# Summary format is a line of text, where %element% is replaced by ++# text element's content, and [[...%element%...]] block is used only if ++# %element% exists. [[...]] blocks can nest. ++# ++# Sections can be: ++# - %summary: bug summary format string. ++# - %attach: a list of elements to attach. ++# - text, double colon (::) and the list of comma-separated elements. ++# Text can be empty (":: elem1, elem2, elem3" works), ++# in this case "Text:" header line will be omitted. ++# ++# Elements can be: ++# - problem directory element names, which get formatted as ++# <element_name>: <contents> ++# or ++# <element_name>: ++# :<contents> ++# :<contents> ++# :<contents> ++# - problem directory element names prefixed by "%bare_", ++# which is formatted as-is, without "<element_name>:" and colons ++# - %oneline, %multiline, %text wildcards, which select all corresponding ++# elements for output or attachment ++# - %binary wildcard, valid only for %attach section, instructs to attach ++# binary elements ++# - problem directory element names prefixed by "-", ++# which excludes given element from all wildcards ++# ++# Nonexistent elements are silently ignored. ++# If none of elements exists, the section will not be created. ++ ++%summary:: [abrt] %pkg_name%[[: %crash_function%()]][[: %reason%]][[: TAINTED %tainted_short%]] ++ ++Description of problem:: %bare_comment ++ ++Version-Release number of selected component:: %bare_package ++ ++Truncated backtrace:: %bare_%short_backtrace ++ ++%Additional info:: ++:: -pkg_arch,-pkg_epoch,-pkg_name,-pkg_release,-pkg_version,\ ++ -component,-architecture,\ ++ -analyzer,-count,-duphash,-uuid,-abrt_version,\ ++ -username,-hostname,-os_release,-os_info,\ ++ -time,-pid,-pwd,-last_occurrence,-ureports_counter,\ ++ %reporter,\ ++ %oneline ++ ++%attach:: -comment,-reason,-reported_to,-event_log,%multiline,\ ++ -coredump,%binary +diff --git a/src/plugins/mantisbt_formatdup.conf b/src/plugins/mantisbt_formatdup.conf +new file mode 100644 +index 0000000..b1552ac +--- /dev/null ++++ b/src/plugins/mantisbt_formatdup.conf +@@ -0,0 +1,65 @@ ++# Lines starting with # are ignored. ++# Lines can be continued on the next line using trailing backslash. ++# ++# Format: ++# %summary:: summary format ++# section:: element1[,element2]... ++# The literal text line to be added to Bugzilla comment. Can be empty. ++# (IOW: empty lines are NOT ignored!) ++# ++# Summary format is a line of text, where %element% is replaced by ++# text element's content, and [[...%element%...]] block is used only if ++# %element% exists. [[...]] blocks can nest. ++# ++# Sections can be: ++# - %summary: bug summary format string. ++# - %attach: a list of elements to attach. ++# - text, double colon (::) and the list of comma-separated elements. ++# Text can be empty (":: elem1, elem2, elem3" works), ++# in this case "Text:" header line will be omitted. ++# ++# Elements can be: ++# - problem directory element names, which get formatted as ++# <element_name>: <contents> ++# or ++# <element_name>: ++# :<contents> ++# :<contents> ++# :<contents> ++# - problem directory element names prefixed by "%bare_", ++# which is formatted as-is, without "<element_name>:" and colons ++# - %oneline, %multiline, %text wildcards, which select all corresponding ++# elements for output or attachment ++# - %binary wildcard, valid only for %attach section, instructs to attach ++# binary elements ++# - problem directory element names prefixed by "-", ++# which excludes given element from all wildcards ++# ++# Nonexistent elements are silently ignored. ++# If none of elements exists, the section will not be created. ++ ++# When we add a comment to an existing BZ, %summary is ignored ++# (it specifies *new bug* summary field): ++# %summary:: blah blah ++ ++# When dup is detected, BZ reporter adds a comment to it. ++# This comment may interrupt an ongoing conversation in the BZ. ++# (Three people independently filed a bug against abrt about this). ++# Need to clearly explain what this comment is, to prevent confusion. ++# Hopefully, this line would suffice: ++Another user experienced a similar problem: ++ ++# If user filled out comment field, show it: ++:: %bare_comment ++ ++# var_log_messages has too much variance (time/date), ++# we exclude it from message so that dup message elimination has more chances to work ++:: \ ++ -pkg_arch,-pkg_epoch,-pkg_name,-pkg_release,-pkg_version,\ ++ -component,-architecture,\ ++ -analyzer,-count,-duphash,-uuid,-abrt_version,\ ++ -username,-hostname,-os_release,-os_info,\ ++ -time,-pid,-pwd,-last_occurrence,-ureports_counter,\ ++ -var_log_messages,\ ++ %reporter,\ ++ %oneline +diff --git a/src/plugins/report_CentOSBugTracker.conf b/src/plugins/report_CentOSBugTracker.conf +new file mode 100644 +index 0000000..04cab13 +--- /dev/null ++++ b/src/plugins/report_CentOSBugTracker.conf +@@ -0,0 +1,4 @@ ++Mantisbt_MantisbtURL = https://bugs.centos.org ++Mantisbt_Login = ++Mantisbt_Password ++Mantisbt_SSLVerify = yes +diff --git a/src/plugins/report_CentOSBugTracker.xml.in b/src/plugins/report_CentOSBugTracker.xml.in +new file mode 100644 +index 0000000..81f909b +--- /dev/null ++++ b/src/plugins/report_CentOSBugTracker.xml.in +@@ -0,0 +1,65 @@ ++<?xml version="1.0" encoding="UTF-8" ?> ++<event> ++ <_name>CentOS Bug Tracker</_name> ++ <_description>Report to CentOS Bug Tracker</_description> ++ ++ <requires-items>component,duphash,os_release</requires-items> ++ <exclude-items-by-default>coredump,count,event_log,reported_to,vmcore</exclude-items-by-default> ++ <exclude-items-always></exclude-items-always> ++ <exclude-binary-items>no</exclude-binary-items> ++ <include-items-by-default></include-items-by-default> ++ <minimal-rating>3</minimal-rating> ++ <gui-review-elements>yes</gui-review-elements> ++ ++ <options> ++ <option type="text" name="Mantisbt_Login"> ++ <_label>User name</_label> ++ <allow-empty>no</allow-empty> ++ <_description>CentOS Bug Tracker account user name</_description> ++ <_note-html>You can create bugs.centos.org account <a href="https://bugs.centos.org/signup_page.php">here</a></_note-html> ++ </option> ++ <option type="password" name="Mantisbt_Password"> ++ <_label>Password</_label> ++ <allow-empty>no</allow-empty> ++ <_description>CentOS Bug Tracker account password</_description> ++ </option> ++ <advanced-options> ++ <option type="text" name="Mantisbt_MantisbtURL"> ++ <_label>CentOS Bug Tracker URL</_label> ++ <allow-empty>no</allow-empty> ++ <_note-html>Address of CentOS Bug Tracker server</_note-html> ++ <default-value>https://bugs.centos.org</default-value> ++ </option> ++ <option type="bool" name="Mantisbt_SSLVerify"> ++ <_label>Verify SSL</_label> ++ <_note-html>Check SSL key validity</_note-html> ++ <default-value>yes</default-value> ++ </option> ++ <option type="text" name="Mantisbt_Project"> ++ <_label>CentOS Bug Tracker project</_label> ++ <allow-empty>yes</allow-empty> ++ <_note-html>Specify this only if you needed different project than specified in /etc/os-release</_note-html> ++ </option> ++ <option type="text" name="Mantisbt_ProjectVersion"> ++ <_label>CentOS Bug Tracker project version</_label> ++ <allow-empty>yes</allow-empty> ++ <_note-html>Specify this only if you needed different project version than specified in /etc/os-release</_note-html> ++ </option> ++ <option type="text" name="http_proxy"> ++ <_label>HTTP Proxy</_label> ++ <allow-empty>yes</allow-empty> ++ <_note-html>Sets the proxy server to use for HTTP</_note-html> ++ </option> ++ <option type="text" name="HTTPS_PROXY"> ++ <_label>HTTPS Proxy</_label> ++ <allow-empty>yes</allow-empty> ++ <_note-html>Sets the proxy server to use for HTTPS</_note-html> ++ </option> ++ <option type="bool" name="Mantisbt_CreatePrivate"> ++ <_label>Restrict access</_label> ++ <_note-html>Restrict access to the created CentOS Bug Tracker issue allowing only users from specified groups to view it (see advanced settings for more details)</_note-html> ++ <default-value>no</default-value> ++ </option> ++ </advanced-options> ++ </options> ++</event> +diff --git a/src/plugins/reporter-mantisbt.c b/src/plugins/reporter-mantisbt.c +new file mode 100644 +index 0000000..d281cdb +--- /dev/null ++++ b/src/plugins/reporter-mantisbt.c +@@ -0,0 +1,696 @@ ++/* ++ Copyright (C) 2014 ABRT team ++ Copyright (C) 2014 RedHat Inc ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 2 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License along ++ with this program; if not, write to the Free Software Foundation, Inc., 51 ++ Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ++*/ ++ ++#include "internal_libreport.h" ++#include "client.h" ++#include "mantisbt.h" ++#include "problem_report.h" ++ ++static void ++parse_osinfo_for_mantisbt(map_string_t *osinfo, char** project, char** version) ++{ ++ const char *name = get_map_string_item_or_NULL(osinfo, "CENTOS_MANTISBT_PROJECT"); ++ if (!name) ++ name = get_map_string_item_or_NULL(osinfo, OSINFO_NAME); ++ ++ const char *version_id = get_map_string_item_or_NULL(osinfo, "CENTOS_MANTISBT_PROJECT_VERSION"); ++ if (!version_id) ++ version_id = get_map_string_item_or_NULL(osinfo, OSINFO_VERSION_ID); ++ ++ if (name && version_id) ++ { ++ *project = xstrdup(name); ++ *version = xstrdup(version_id); ++ return; ++ } ++ ++ /* something bad happend */ ++ *project = NULL; ++ *version = NULL; ++} ++ ++static char * ++ask_mantisbt_login(const char *message) ++{ ++ char *login = ask(message); ++ if (login == NULL || login[0] == '\0') ++ { ++ set_xfunc_error_retval(EXIT_CANCEL_BY_USER); ++ error_msg_and_die(_("Can't continue without login")); ++ } ++ ++ return login; ++} ++ ++static char * ++ask_mantisbt_password(const char *message) ++{ ++ char *password = ask_password(message); ++ /* TODO: this should be fixed in ask_password() as other tools have the same problem */ ++ putchar('\n'); ++ if (password == NULL || password[0] == '\0') ++ { ++ set_xfunc_error_retval(EXIT_CANCEL_BY_USER); ++ error_msg_and_die(_("Can't continue without password")); ++ } ++ ++ return password; ++} ++ ++static void ++ask_mantisbt_credentials(mantisbt_settings_t *settings, const char *pre_message) ++{ ++ free(settings->m_login); ++ free(settings->m_password); ++ ++ char *question = xasprintf("%s %s", pre_message, _("Please enter your CentOS Bug Tracker login:")); ++ settings->m_login = ask_mantisbt_login(question); ++ free(question); ++ ++ question = xasprintf("%s %s '%s':", pre_message, _("Please enter the password for"), settings->m_login); ++ settings->m_password = ask_mantisbt_password(question); ++ free(question); ++ ++ return; ++} ++ ++static void ++verify_credentials(mantisbt_settings_t *settings) ++{ ++ if (settings->m_login[0] == '\0' || settings->m_password[0] == '\0') ++ ask_mantisbt_credentials(settings, _("Credentials are not provided by configuration.")); ++ ++ while (true) ++ { ++ soap_request_t *req = soap_request_new_for_method("mc_login"); ++ soap_request_add_credentials_parameter(req, settings); ++ ++ mantisbt_result_t *result = mantisbt_soap_call(settings, req); ++ soap_request_free(req); ++ ++ if (g_verbose > 2) ++ { ++ GList *ids = response_get_main_ids_list(result->mr_body); ++ if (ids != NULL) ++ log("%s", (char *)ids->data); ++ response_values_free(ids); ++ } ++ ++ int result_val = result->mr_http_resp_code; ++ mantisbt_result_free(result); ++ ++ if (result_val == 200) ++ return; ++ ++ ask_mantisbt_credentials(settings, _("Invalid password or login.")); ++ } ++} ++ ++static void ++set_settings(mantisbt_settings_t *m, map_string_t *settings, struct dump_dir *dd) ++{ ++ const char *environ; ++ ++ environ = getenv("Mantisbt_Login"); ++ m->m_login = xstrdup(environ ? environ : get_map_string_item_or_empty(settings, "Login")); ++ ++ environ = getenv("Mantisbt_Password"); ++ m->m_password = xstrdup(environ ? environ : get_map_string_item_or_empty(settings, "Password")); ++ ++ environ = getenv("Mantisbt_MantisbtURL"); ++ m->m_mantisbt_url = environ ? environ : get_map_string_item_or_empty(settings, "MantisbtURL"); ++ if (!m->m_mantisbt_url[0]) ++ m->m_mantisbt_url = "https://bugs.centos.org/"; ++ else ++ { ++ /* We don't want trailing '/': "https://host/dir/" -> "https://host/dir" */ ++ char *last_slash = strrchr(m->m_mantisbt_url, '/'); ++ if (last_slash && last_slash[1] == '\0') ++ *last_slash = '\0'; ++ } ++ m->m_mantisbt_soap_url = concat_path_file(m->m_mantisbt_url, "api/soap/mantisconnect.php"); ++ ++ environ = getenv("Mantisbt_Project"); ++ if (environ) ++ { ++ m->m_project = xstrdup(environ); ++ environ = getenv("Mantisbt_ProjectVersion"); ++ if (environ) ++ m->m_project_version = xstrdup(environ); ++ } ++ else ++ { ++ const char *option = get_map_string_item_or_NULL(settings, "Project"); ++ if (option) ++ m->m_project = xstrdup(option); ++ option = get_map_string_item_or_NULL(settings, "ProjectVersion"); ++ if (option) ++ m->m_project_version = xstrdup(option); ++ } ++ ++ if (!m->m_project || !*m->m_project) /* if not overridden or empty... */ ++ { ++ free(m->m_project); ++ free(m->m_project_version); ++ ++ if (dd != NULL) ++ { ++ map_string_t *osinfo = new_map_string(); ++ ++ char *os_info_data = dd_load_text(dd, FILENAME_OS_INFO); ++ parse_osinfo(os_info_data, osinfo); ++ free(os_info_data); ++ ++ parse_osinfo_for_mantisbt(osinfo, &m->m_project, &m->m_project_version); ++ free_map_string(osinfo); ++ } ++ } ++ ++ environ = getenv("Mantisbt_SSLVerify"); ++ m->m_ssl_verify = string_to_bool(environ ? environ : get_map_string_item_or_empty(settings, "SSLVerify")); ++ ++ environ = getenv("Mantisbt_DontMatchComponents"); ++ m->m_DontMatchComponents = environ ? environ : get_map_string_item_or_empty(settings, "DontMatchComponents"); ++ ++ environ = getenv(CREATE_PRIVATE_TICKET); ++ if (environ) ++ m->m_create_private = string_to_bool(environ); ++ ++ if (!m->m_create_private) ++ { ++ environ = getenv("Mantisbt_CreatePrivate"); ++ m->m_create_private = string_to_bool(environ ? environ : get_map_string_item_or_empty(settings, "CreatePrivate")); ++ } ++ log_notice("create private CentOS Bug Tracker ticket: '%s'", m->m_create_private ? "YES": "NO"); ++} ++ ++int main(int argc, char **argv) ++{ ++ abrt_init(argv); ++ ++ /* I18n */ ++ setlocale(LC_ALL, ""); ++#if ENABLE_NLS ++ bindtextdomain(PACKAGE, LOCALEDIR); ++ textdomain(PACKAGE); ++#endif ++ ++ const char *program_usage_string = _( ++ "\n& [-vf] [-c CONFFILE]... [-F FMTFILE] [-A FMTFILE2] -d DIR" ++ "\nor:" ++ "\n& [-v] [-c CONFFILE]... [-d DIR] -t[ID] FILE..." ++ "\nor:" ++ "\n& [-v] [-c CONFFILE]... [-d DIR] -t[ID] -w" ++ "\nor:" ++ "\n& [-v] [-c CONFFILE]... -h DUPHASH" ++ "\n" ++ "\nReports problem to CentOS Bug Tracker." ++ "\n" ++ "\nThe tool reads DIR. Then it tries to find an issue" ++ "\nwith the same abrt_hash in custom field 'abrt_hash'." ++ "\n" ++ "\nIf such issue is not found, then a new issue is created. Elements of DIR" ++ "\nare stored in the issue as part of issue description or as attachments," ++ "\ndepending on their type and size." ++ "\n" ++ "\nOtherwise, if such issue is found and it is marked as CLOSED DUPLICATE," ++ "\nthe tool follows the chain of duplicates until it finds a non-DUPLICATE issue." ++ "\nThe tool adds a new comment to found issue." ++ "\n" ++ "\nThe URL to new or modified issue is printed to stdout and recorded in" ++ "\n'reported_to' element." ++ "\n" ++ "\nOption -t uploads FILEs to the already created issue on CentOS Bug Tracker site." ++ "\nThe issue ID is retrieved from directory specified by -d DIR." ++ "\nIf problem data in DIR was never reported to CentOS Bug Tracker, upload will fail." ++ "\n" ++ "\nOption -tID uploads FILEs to the issue with specified ID on CentOS Bug Tracker site." ++ "\n-d DIR is ignored." ++ "\n" ++ "\nOption -r sets the last url from reporter_to element which is prefixed with" ++ "\nTRACKER_NAME to URL field. This option is applied only when a new issue is to be" ++ "\nfiled. The default value is 'ABRT Server'" ++ "\n" ++ "\nIf not specified, CONFFILE defaults to "CONF_DIR"/plugins/mantisb.conf" ++ "\nIts lines should have 'PARAM = VALUE' format." ++ "\nRecognized string parameters: MantisbtURL, Login, Password, Project, ProjectVersion." ++ "\nRecognized boolean parameter (VALUE should be 1/0, yes/no): SSLVerify, CreatePrivate." ++ "\nParameters can be overridden via $Mantisbt_PARAM environment variables." ++ "\n" ++ "\nFMTFILE and FMTFILE2 default to "CONF_DIR"/plugins/mantisbt_format.conf" ++ ); ++ ++ enum { ++ OPT_v = 1 << 0, ++ OPT_d = 1 << 1, ++ OPT_c = 1 << 2, ++ OPT_F = 1 << 3, ++ OPT_A = 1 << 4, ++ OPT_t = 1 << 5, ++ OPT_f = 1 << 6, ++ OPT_h = 1 << 7, ++ OPT_r = 1 << 8, ++ OPT_D = 1 << 9, ++ }; ++ ++ const char *dump_dir_name = "."; ++ GList *conf_file = NULL; ++ const char *fmt_file = CONF_DIR"/plugins/mantisbt_format.conf"; ++ const char *fmt_file2 = fmt_file; ++ char *abrt_hash = NULL; ++ char *ticket_no = NULL; ++ const char *tracker_str = "ABRT Server"; ++ char *debug_str = NULL; ++ mantisbt_settings_t mbt_settings = { 0 }; ++ /* Keep enum above and order of options below in sync! */ ++ struct options program_options[] = { ++ OPT__VERBOSE(&g_verbose), ++ OPT_STRING( 'd', NULL, &dump_dir_name , "DIR" , _("Problem directory")), ++ OPT_LIST( 'c', NULL, &conf_file , "FILE" , _("Configuration file (may be given many times)")), ++ OPT_STRING( 'F', NULL, &fmt_file , "FILE" , _("Formatting file for initial comment")), ++ OPT_STRING( 'A', NULL, &fmt_file2 , "FILE" , _("Formatting file for duplicates")), ++ OPT_OPTSTRING('t', "ticket", &ticket_no , "ID" , _("Attach FILEs [to issue with this ID]")), ++ OPT_BOOL( 'f', NULL, NULL, _("Force reporting even if this problem is already reported")), ++ OPT_STRING( 'h', "duphash", &abrt_hash, "DUPHASH", _("Print BUG_ID which has given DUPHASH")), ++ OPT_STRING( 'r', "tracker", &tracker_str, "TRACKER_NAME", _("A name of bug tracker for an additional URL from 'reported_to'")), ++ ++ OPT_OPTSTRING('D', "debug", &debug_str , "STR" , _("Debug")), ++ OPT_END() ++ }; ++ ++ unsigned opts = parse_opts(argc, argv, program_options, program_usage_string); ++ argv += optind; ++ ++ export_abrt_envvars(0); ++ ++ map_string_t *settings = new_map_string(); ++ ++ { ++ if (!conf_file) ++ conf_file = g_list_append(conf_file, (char*) CONF_DIR"/plugins/mantisbt.conf"); ++ while (conf_file) ++ { ++ char *fn = (char *)conf_file->data; ++ log_notice("Loading settings from '%s'", fn); ++ load_conf_file(fn, settings, /*skip key w/o values:*/ false); ++ log_debug("Loaded '%s'", fn); ++ conf_file = g_list_delete_link(conf_file, conf_file); ++ } ++ ++ struct dump_dir *dd = NULL; ++ if (abrt_hash == NULL) ++ { ++ dd = dd_opendir(dump_dir_name, /*flags:*/ 0); ++ if (!dd) ++ error_msg_and_die(_("Can't open problem dir '%s'."), dump_dir_name); ++ } ++ ++ set_settings(&mbt_settings, settings, dd); ++ dd_close(dd); ++ /* WRONG! set_settings() does not copy the strings, it merely sets up pointers ++ * to settings[] dictionary: ++ */ ++ /*free_map_string(settings);*/ ++ } ++ ++ /* No connection is opened between client and server. Users authentication ++ * is performed on every SOAP method call. In the first step we verify the ++ * credentials by calling 'mc_login' method. In the case the credentials are ++ * correctly applies the reporter uses them in the next requests. It is not ++ * necessary to call 'mc_login' method because the method provides only ++ * verification of credentials. ++ */ ++ verify_credentials(&mbt_settings); ++ ++ if (abrt_hash) ++ { ++ log(_("Looking for similar problems in CentOS Bug Tracker")); ++ GList *ids = mantisbt_search_by_abrt_hash(&mbt_settings, abrt_hash); ++ mantisbt_settings_free(&mbt_settings); ++ ++ if (ids == NULL) ++ return EXIT_FAILURE; ++ ++ puts(ids->data); ++ response_values_free(ids); ++ return EXIT_SUCCESS; ++ } ++ ++ mantisbt_get_project_id_from_name(&mbt_settings); ++ ++ if (opts & OPT_t) ++ { ++ if (!argv[0]) ++ show_usage_and_die(program_usage_string, program_options); ++ ++ if (!ticket_no) ++ { ++ struct dump_dir *dd = dd_opendir(dump_dir_name, /*flags:*/ 0); ++ if (!dd) ++ xfunc_die(); ++ report_result_t *reported_to = find_in_reported_to(dd, "CentOS Bug Tracker"); ++ dd_close(dd); ++ ++ if (!reported_to || !reported_to->url) ++ error_msg_and_die(_("Can't get MantisBT ID because this problem has not yet been reported to MantisBT.")); ++ ++ char *url = reported_to->url; ++ reported_to->url = NULL; ++ free_report_result(reported_to); ++ ++ if (prefixcmp(url, mbt_settings.m_mantisbt_url) != 0) ++ error_msg_and_die(_("This problem has been reported to MantisBT '%s' which differs from the configured MantisBT '%s'."), url, mbt_settings.m_mantisbt_url); ++ ++ ticket_no = strrchr(url, '='); ++ if (!ticket_no) ++ error_msg_and_die(_("Malformed url to MantisBT '%s'."), url); ++ ++ /* won't ever call free on it - it simplifies the code a lot */ ++ ticket_no = xstrdup(ticket_no + 1); ++ log(_("Using CentOS Bug Tracker ID '%s'"), ticket_no); ++ } ++ ++ /* Attach files to existing MantisBT issues */ ++ while (*argv) ++ { ++ const char *path = *argv++; ++ char *filename = basename(path); ++ log(_("Attaching file '%s' to issue %s"), filename, ticket_no); ++ mantisbt_attach_file(&mbt_settings, ticket_no, filename, path); ++ } ++ ++ return 0; ++ } ++ ++ /* Create new issue in MantisBT */ ++ ++ if (!(opts & OPT_f)) ++ { ++ struct dump_dir *dd = dd_opendir(dump_dir_name, /*flags:*/ 0); ++ if (!dd) ++ xfunc_die(); ++ report_result_t *reported_to = find_in_reported_to(dd, "CentOS Bug Tracker"); ++ dd_close(dd); ++ ++ if (reported_to && reported_to->url) ++ { ++ char *msg = xasprintf(_("This problem was already reported to CentOS Bug Tracker (see '%s')." ++ " Do you still want to create a new issue?"), ++ reported_to->url); ++ int yes = ask_yes_no(msg); ++ free(msg); ++ if (!yes) ++ return 0; ++ } ++ free_report_result(reported_to); ++ } ++ ++ problem_data_t *problem_data = create_problem_data_for_reporting(dump_dir_name); ++ if (!problem_data) ++ xfunc_die(); /* create_problem_data_for_reporting already emitted error msg */ ++ ++ const char *category = problem_data_get_content_or_die(problem_data, FILENAME_COMPONENT); ++ const char *duphash = problem_data_get_content_or_die(problem_data, FILENAME_DUPHASH); ++ ++ if (opts & OPT_D) ++ { ++ problem_formatter_t *pf = problem_formatter_new(); ++ problem_formatter_add_section(pf, PR_SEC_ADDITIONAL_INFO, /* optional section */ 0); ++ ++ if (problem_formatter_load_file(pf, fmt_file)) ++ error_msg_and_die("Invalid format file: %s", fmt_file); ++ ++ problem_report_t *pr = NULL; ++ if (problem_formatter_generate_report(pf, problem_data, &pr)) ++ error_msg_and_die("Failed to format issue report from problem data"); ++ ++ printf("summary: %s\n" ++ "\n" ++ "Description:\n%s\n" ++ "Additional info:\n%s\n" ++ , problem_report_get_summary(pr) ++ , problem_report_get_description(pr) ++ , problem_report_get_section(pr, PR_SEC_ADDITIONAL_INFO) ++ ); ++ ++ puts("attachments:"); ++ for (GList *a = problem_report_get_attachments(pr); a != NULL; a = g_list_next(a)) ++ printf(" %s\n", (const char *)a->data); ++ ++ problem_report_free(pr); ++ problem_formatter_free(pf); ++ exit(0); ++ } ++ ++ int bug_id = 0; ++ ++ /* If REMOTE_RESULT contains "DUPLICATE 12345", we consider it a dup of 12345 ++ * and won't search on MantisBT server. ++ */ ++ char *remote_result; ++ remote_result = problem_data_get_content_or_NULL(problem_data, FILENAME_REMOTE_RESULT); ++ if (remote_result) ++ { ++ char *cmd = strtok(remote_result, " \n"); ++ char *id = strtok(NULL, " \n"); ++ ++ if (!prefixcmp(cmd, "DUPLICATE")) ++ { ++ errno = 0; ++ char *e; ++ bug_id = strtoul(id, &e, 10); ++ if (errno || id == e || *e != '\0' || bug_id > INT_MAX) ++ { ++ /* error / no digits / illegal trailing chars / too big a number */ ++ bug_id = 0; ++ } ++ } ++ } ++ ++ mantisbt_issue_info_t *ii; ++ if (!bug_id) ++ { ++ log(_("Checking for duplicates")); ++ ++ int existing_id = -1; ++ int crossver_id = -1; ++ { ++ /* Figure out whether we want to match category ++ * when doing dup search. ++ */ ++ const char *category_substitute = is_in_comma_separated_list(category, mbt_settings.m_DontMatchComponents) ? NULL : category; ++ ++ /* We don't do dup detection across versions (see below why), ++ * but we do add a note if cross-version potential dup exists. ++ * For that, we search for cross version dups first: ++ */ ++ // SOAP API searching method is not in the final version, it's possible the project will be string ++ GList *crossver_bugs_ids = mantisbt_search_duplicate_issues(&mbt_settings, category_substitute, /*version*/ NULL, duphash); ++ ++ unsigned crossver_bugs_count = g_list_length(crossver_bugs_ids); ++ log_debug("CentOS Bug Tracker has %i reports with duphash '%s' including cross-version ones", ++ crossver_bugs_count, duphash); ++ if (crossver_bugs_count > 0) ++ crossver_id = atoi(g_list_first(crossver_bugs_ids)->data); ++ ++ if (crossver_bugs_count > 0) ++ { ++ // SOAP API searching method is not in the final version, it's possible the project will be string ++ GList *dup_bugs_ids = mantisbt_search_duplicate_issues(&mbt_settings, category_substitute, mbt_settings.m_project_version, duphash); ++ ++ unsigned dup_bugs_count = g_list_length(dup_bugs_ids); ++ log_debug("CentOS Bug Tracker has %i reports with duphash '%s'", ++ dup_bugs_count, duphash); ++ if (dup_bugs_count > 0) ++ existing_id = atoi(g_list_first(dup_bugs_ids)->data); ++ } ++ } ++ ++ if (existing_id < 0) ++ { ++ /* Create new issue */ ++ log(_("Creating a new issue")); ++ problem_formatter_t *pf = problem_formatter_new(); ++ problem_formatter_add_section(pf, PR_SEC_ADDITIONAL_INFO, 0); ++ ++ if (problem_formatter_load_file(pf, fmt_file)) ++ error_msg_and_die(_("Invalid format file: %s"), fmt_file); ++ ++ problem_report_t *pr = NULL; ++ if (problem_formatter_generate_report(pf, problem_data, &pr)) ++ error_msg_and_die(_("Failed to format problem data")); ++ ++ if (crossver_id >= 0) ++ problem_report_buffer_printf( ++ problem_report_get_buffer(pr, PR_SEC_DESCRIPTION), ++ "\nPotential duplicate: issue %u\n", crossver_id); ++ ++ problem_formatter_free(pf); ++ ++ /* get tracker URL if exists */ ++ struct dump_dir *dd = dd_opendir(dump_dir_name, 0); ++ char *tracker_url = NULL; ++ if (dd) ++ { ++ report_result_t *reported_to = find_in_reported_to(dd, tracker_str); ++ dd_close(dd); ++ ++ if (reported_to && reported_to->url) ++ { ++ log(_("Adding External URL to issue")); ++ tracker_url = xstrdup(reported_to->url); ++ free_report_result(reported_to); ++ } ++ } ++ ++ int new_id = mantisbt_create_new_issue(&mbt_settings, problem_data, pr, tracker_url); ++ ++ free(tracker_url); ++ ++ if (new_id == -1) ++ return EXIT_FAILURE; ++ ++ log(_("Adding attachments to issue %i"), new_id); ++ char *new_id_str = xasprintf("%u", new_id); ++ ++ for (GList *a = problem_report_get_attachments(pr); a != NULL; a = g_list_next(a)) ++ { ++ const char *item_name = (const char *)a->data; ++ struct problem_item *item = problem_data_get_item_or_NULL(problem_data, item_name); ++ if (!item) ++ continue; ++ else if (item->flags & CD_FLAG_TXT) ++ mantisbt_attach_data(&mbt_settings, new_id_str, item_name, item->content, strlen(item->content)); ++ else if (item->flags & CD_FLAG_BIN) ++ mantisbt_attach_file(&mbt_settings, new_id_str, item_name, item->content); ++ } ++ ++ free(new_id_str); ++ problem_report_free(pr); ++ ii = mantisbt_issue_info_new(); ++ ii->mii_id = new_id; ++ ii->mii_status = xstrdup("new"); ++ ++ goto finish; ++ } ++ ++ bug_id = existing_id; ++ } ++ ++ ii = mantisbt_get_issue_info(&mbt_settings, bug_id); ++ ++ log(_("Bug is already reported: %i"), ii->mii_id); ++ ++ /* Follow duplicates */ ++ if ((strcmp(ii->mii_status, "closed") == 0) ++ && (strcmp(ii->mii_resolution, "duplicate") == 0) ++ ) { ++ mantisbt_issue_info_t *origin = mantisbt_find_origin_bug_closed_duplicate(&mbt_settings, ii); ++ if (origin) ++ { ++ mantisbt_issue_info_free(ii); ++ ii = origin; ++ } ++ } ++ ++ /* TODO CC list ++ * Is no MantisBT SOAP API method which allows adding users to CC list ++ * without updating issue. ++ */ ++ ++ /* Add comment and bt */ ++ const char *comment = problem_data_get_content_or_NULL(problem_data, FILENAME_COMMENT); ++ if (comment && comment[0]) ++ { ++ problem_formatter_t *pf = problem_formatter_new(); ++ ++ if (problem_formatter_load_file(pf, fmt_file2)) ++ error_msg_and_die(_("Invalid duplicate format file: '%s"), fmt_file2); ++ ++ problem_report_t *pr; ++ if (problem_formatter_generate_report(pf, problem_data, &pr)) ++ error_msg_and_die(_("Failed to format duplicate comment from problem data")); ++ ++ const char *mbtcomment = problem_report_get_description(pr); ++ ++ int dup_comment = is_comment_dup(ii->mii_notes, mbtcomment); ++ if (!dup_comment) ++ { ++ log(_("Adding new comment to issue %d"), ii->mii_id); ++ mantisbt_add_issue_note(&mbt_settings, ii->mii_id, mbtcomment); ++ ++ const char *bt = problem_data_get_content_or_NULL(problem_data, FILENAME_BACKTRACE); ++ unsigned rating = 0; ++ const char *rating_str = problem_data_get_content_or_NULL(problem_data, FILENAME_RATING); ++ /* python doesn't have rating file */ ++ if (rating_str) ++ rating = xatou(rating_str); ++ if (bt && rating > ii->mii_best_bt_rating) ++ { ++ char *bug_id_str = xasprintf("%i", ii->mii_id); ++ ++ log(_("Attaching better backtrace")); ++ ++ // find unique filename of attachment ++ char *name = NULL; ++ for (int i = 0;; ++i) ++ { ++ if (i == 0) ++ name = xasprintf("%s", FILENAME_BACKTRACE); ++ else ++ name = xasprintf("%s%d", FILENAME_BACKTRACE, i); ++ ++ if (g_list_find_custom(ii->mii_attachments, name, (GCompareFunc) strcmp) == NULL) ++ break; ++ ++ free(name); ++ } ++ mantisbt_attach_data(&mbt_settings, bug_id_str, name, bt, strlen(bt)); ++ ++ free(name); ++ free(bug_id_str); ++ } ++ } ++ else ++ log(_("Found the same comment in the issue history, not adding a new one")); ++ ++ problem_report_free(pr); ++ problem_formatter_free(pf); ++ } ++ ++finish: ++ log(_("Status: %s%s%s %s/view.php?id=%u"), ++ ii->mii_status, ++ ii->mii_resolution ? " " : "", ++ ii->mii_resolution ? ii->mii_resolution : "", ++ mbt_settings.m_mantisbt_url, ++ ii->mii_id); ++ ++ struct dump_dir *dd = dd_opendir(dump_dir_name, /*flags:*/ 0); ++ if (dd) ++ { ++ char *msg = xasprintf("CentOS Bug Tracker: URL=%s/view.php?id=%u", mbt_settings.m_mantisbt_url, ii->mii_id); ++ add_reported_to(dd, msg); ++ free(msg); ++ dd_close(dd); ++ } ++ ++ mantisbt_settings_free(&mbt_settings); ++ return 0; ++} +diff --git a/src/workflows/Makefile.am b/src/workflows/Makefile.am +index 72502ca..7ecb34e 100644 +--- a/src/workflows/Makefile.am ++++ b/src/workflows/Makefile.am +@@ -23,6 +23,18 @@ dist_workflows_DATA = \ + workflow_Logger.xml \ + workflow_LoggerCCpp.xml + ++if BUILD_MANTISBT ++dist_workflows_DATA += \ ++ workflow_CentOSCCpp.xml \ ++ workflow_CentOSKerneloops.xml \ ++ workflow_CentOSPython.xml \ ++ workflow_CentOSPython3.xml \ ++ workflow_CentOSVmcore.xml \ ++ workflow_CentOSXorg.xml \ ++ workflow_CentOSLibreport.xml \ ++ workflow_CentOSJava.xml ++endif ++ + if BUILD_BUGZILLA + dist_workflows_DATA += \ + workflow_AnacondaFedora.xml \ +@@ -46,7 +58,8 @@ dist_workflowsdef_DATA =\ + report_uReport.conf \ + report_mailx.conf \ + report_logger.conf \ +- report_uploader.conf ++ report_uploader.conf \ ++ report_centos.conf + + if BUILD_BUGZILLA + dist_workflowsdef_DATA += \ +diff --git a/src/workflows/report_centos.conf b/src/workflows/report_centos.conf +new file mode 100644 +index 0000000..07b0d40 +--- /dev/null ++++ b/src/workflows/report_centos.conf +@@ -0,0 +1,31 @@ ++EVENT=workflow_CentOSLibreport analyzer=libreport ++# this is just a meta event which consists of other events ++# the list is defined in the xml file ++ ++EVENT=workflow_CentOSCCpp analyzer=CCpp ++# this is just a meta event which consists of other events ++# the list is defined in the xml file ++ ++EVENT=workflow_CentOSPython analyzer=Python component!=anaconda ++# this is just a meta event which consists of other events ++# the list is defined in the xml file ++ ++EVENT=workflow_CentOSPython3 analyzer=Python3 component!=anaconda ++# this is just a meta event which consists of other events ++# the list is defined in the xml file ++ ++EVENT=workflow_CentOSKerneloops analyzer=Kerneloops ++# this is just a meta event which consists of other events ++# the list is defined in the xml file ++ ++EVENT=workflow_CentOSVmcore analyzer=vmcore ++# this is just a meta event which consists of other events ++# the list is defined in the xml file ++ ++EVENT=workflow_CentOSXorg analyzer=xorg ++# this is just a meta event which consists of other events ++# the list is defined in the xml file ++ ++EVENT=workflow_CentOSJava analyzer=Java ++# this is just a meta event which consists of other events ++# the list is defined in the xml file +diff --git a/src/workflows/workflow_CentOSCCpp.xml.in b/src/workflows/workflow_CentOSCCpp.xml.in +new file mode 100644 +index 0000000..ea94d56 +--- /dev/null ++++ b/src/workflows/workflow_CentOSCCpp.xml.in +@@ -0,0 +1,12 @@ ++<?xml version="1.0" encoding="UTF-8" ?> ++<workflow> ++ <_name>Report to CentOS Bug Tracker</_name> ++ <_description>Process the C/C++ crash using the CentOS infrastructure</_description> ++ ++ <events> ++ <event>report_uReport</event> ++ <event>collect_*</event> ++ <event>analyze_CCpp</event> ++ <event>report_CentOSBugTracker</event> ++ </events> ++</workflow> +diff --git a/src/workflows/workflow_CentOSJava.xml.in b/src/workflows/workflow_CentOSJava.xml.in +new file mode 100644 +index 0000000..89f9295 +--- /dev/null ++++ b/src/workflows/workflow_CentOSJava.xml.in +@@ -0,0 +1,11 @@ ++<?xml version="1.0" encoding="UTF-8" ?> ++<workflow> ++ <_name>Report to CentOS Bug Tracker</_name> ++ <_description>Process the Java exception using the CentOS infrastructure</_description> ++ ++ <events> ++ <event>report_uReport</event> ++ <event>collect_*</event> ++ <event>report_CentOSBugTracker</event> ++ </events> ++</workflow> +diff --git a/src/workflows/workflow_CentOSKerneloops.xml.in b/src/workflows/workflow_CentOSKerneloops.xml.in +new file mode 100644 +index 0000000..c43c681 +--- /dev/null ++++ b/src/workflows/workflow_CentOSKerneloops.xml.in +@@ -0,0 +1,11 @@ ++<?xml version="1.0" encoding="UTF-8" ?> ++<workflow> ++ <_name>Report to CentOS Bug Tracker</_name> ++ <_description>Process the kerneloops using the CentOS infrastructure</_description> ++ ++ <events> ++ <event>report_uReport</event> ++ <event>collect_*</event> ++ <event>report_CentOSBugTracker</event> ++ </events> ++</workflow> +diff --git a/src/workflows/workflow_CentOSLibreport.xml.in b/src/workflows/workflow_CentOSLibreport.xml.in +new file mode 100644 +index 0000000..2f6ed82 +--- /dev/null ++++ b/src/workflows/workflow_CentOSLibreport.xml.in +@@ -0,0 +1,9 @@ ++<?xml version="1.0" encoding="UTF-8" ?> ++<workflow> ++ <_name>Report to CentOS Bug Tracker</_name> ++ <_description>Process the problem using the CentOS infrastructure</_description> ++ ++ <events> ++ <event>report_CentOSBugTracker</event> ++ </events> ++</workflow> +diff --git a/src/workflows/workflow_CentOSPython.xml.in b/src/workflows/workflow_CentOSPython.xml.in +new file mode 100644 +index 0000000..7e26c6c +--- /dev/null ++++ b/src/workflows/workflow_CentOSPython.xml.in +@@ -0,0 +1,11 @@ ++<?xml version="1.0" encoding="UTF-8" ?> ++<workflow> ++ <_name>Report to CentOS Bug Tracker</_name> ++ <_description>Process the python exception using the CentOS infrastructure</_description> ++ ++ <events> ++ <event>report_uReport</event> ++ <event>collect_*</event> ++ <event>report_CentOSBugTracker</event> ++ </events> ++</workflow> +diff --git a/src/workflows/workflow_CentOSPython3.xml.in b/src/workflows/workflow_CentOSPython3.xml.in +new file mode 100644 +index 0000000..f1dd8a9 +--- /dev/null ++++ b/src/workflows/workflow_CentOSPython3.xml.in +@@ -0,0 +1,11 @@ ++<?xml version="1.0" encoding="UTF-8" ?> ++<workflow> ++ <_name>Report to CentOS Bug Tracker</_name> ++ <_description>Process the python 3 exception using the CentOS infrastructure</_description> ++ ++ <events> ++ <event>report_uReport</event> ++ <event>collect_*</event> ++ <event>report_CentOSBugTracker</event> ++ </events> ++</workflow> +diff --git a/src/workflows/workflow_CentOSVmcore.xml.in b/src/workflows/workflow_CentOSVmcore.xml.in +new file mode 100644 +index 0000000..c876594 +--- /dev/null ++++ b/src/workflows/workflow_CentOSVmcore.xml.in +@@ -0,0 +1,12 @@ ++<?xml version="1.0" encoding="UTF-8" ?> ++<workflow> ++ <_name>Report to CentOS Bug Tracker</_name> ++ <_description>Process the kernel crash using the CentOS infrastructure</_description> ++ ++ <events> ++ <event>analyze_VMcore</event> ++ <event>report_uReport</event> ++ <event>collect_*</event> ++ <event>report_CentOSBugTracker</event> ++ </events> ++</workflow> +diff --git a/src/workflows/workflow_CentOSXorg.xml.in b/src/workflows/workflow_CentOSXorg.xml.in +new file mode 100644 +index 0000000..bf52221 +--- /dev/null ++++ b/src/workflows/workflow_CentOSXorg.xml.in +@@ -0,0 +1,9 @@ ++<?xml version="1.0" encoding="UTF-8" ?> ++<workflow> ++ <_name>Report to CentOS Bug Tracker</_name> ++ <_description>Process the X Server problem using the CentOS infrastructure</_description> ++ ++ <events> ++ <event>report_CentOSBugTracker</event> ++ </events> ++</workflow> +-- +1.8.3.1 + diff --git a/SOURCES/1007-reporter-mantisbt-change-default-formating-file-for-.patch b/SOURCES/1007-reporter-mantisbt-change-default-formating-file-for-.patch new file mode 100644 index 0000000..6f1777a --- /dev/null +++ b/SOURCES/1007-reporter-mantisbt-change-default-formating-file-for-.patch @@ -0,0 +1,43 @@ +From 8d54f840cdb094ca8e32f02e967393d7498ee26d Mon Sep 17 00:00:00 2001 +From: Matej Habrnal <mhabrnal@redhat.com> +Date: Fri, 20 Feb 2015 00:27:05 +0100 +Subject: [PATCH 1007/1015] reporter-mantisbt: change default formating file + for duplicate issues + +reporter-mantisbt doesn't work well with mantisbt_format.conf as a default +format conf file for creating duplicate issues because the note which is added +doesn't contain an 'Additional information' section. + +Default formating file for duplicate is mantisbt_formatdup.conf + +Signed-off-by: Matej Habrnal <mhabrnal@redhat.com> +--- + src/plugins/reporter-mantisbt.c | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +diff --git a/src/plugins/reporter-mantisbt.c b/src/plugins/reporter-mantisbt.c +index d281cdb..dc9968f 100644 +--- a/src/plugins/reporter-mantisbt.c ++++ b/src/plugins/reporter-mantisbt.c +@@ -253,7 +253,8 @@ int main(int argc, char **argv) + "\nRecognized boolean parameter (VALUE should be 1/0, yes/no): SSLVerify, CreatePrivate." + "\nParameters can be overridden via $Mantisbt_PARAM environment variables." + "\n" +- "\nFMTFILE and FMTFILE2 default to "CONF_DIR"/plugins/mantisbt_format.conf" ++ "\nFMTFILE default to "CONF_DIR"/plugins/mantisbt_format.conf." ++ "\nFMTFILE2 default to "CONF_DIR"/plugins/mantisbt_formatdup.conf." + ); + + enum { +@@ -272,7 +273,7 @@ int main(int argc, char **argv) + const char *dump_dir_name = "."; + GList *conf_file = NULL; + const char *fmt_file = CONF_DIR"/plugins/mantisbt_format.conf"; +- const char *fmt_file2 = fmt_file; ++ const char *fmt_file2 = CONF_DIR"/plugins/mantisbt_formatdup.conf"; + char *abrt_hash = NULL; + char *ticket_no = NULL; + const char *tracker_str = "ABRT Server"; +-- +1.8.3.1 + diff --git a/SOURCES/1009-reporter-mantisbt-adds-man-pages-for-reporter-mantis.patch b/SOURCES/1009-reporter-mantisbt-adds-man-pages-for-reporter-mantis.patch new file mode 100644 index 0000000..efb3a9f --- /dev/null +++ b/SOURCES/1009-reporter-mantisbt-adds-man-pages-for-reporter-mantis.patch @@ -0,0 +1,528 @@ +From 9c819b8c77ea5c05e3a37117a2174162160b1c57 Mon Sep 17 00:00:00 2001 +From: Matej Habrnal <mhabrnal@redhat.com> +Date: Fri, 20 Feb 2015 03:14:54 +0100 +Subject: [PATCH 1009/1015] reporter-mantisbt: adds man pages for + reporter-mantisbt + +Signed-off-by: Matej Habrnal <mhabrnal@redhat.com> + +Conflicts: + doc/Makefile.am +--- + doc/Makefile.am | 13 ++- + doc/centos_report_event.conf.5 | 1 + + doc/mantisbt.conf.txt | 18 +++ + doc/mantisbt_format.conf.txt | 18 +++ + doc/mantisbt_formatdup.conf.txt | 18 +++ + doc/report_CentOSBugTracker.conf.txt | 45 +++++++ + doc/report_centos.conf.txt | 41 +++++++ + doc/reporter-mantisbt.txt | 219 +++++++++++++++++++++++++++++++++++ + src/plugins/Makefile.am | 4 + + src/workflows/Makefile.am | 12 ++ + 10 files changed, 387 insertions(+), 2 deletions(-) + create mode 100644 doc/centos_report_event.conf.5 + create mode 100644 doc/mantisbt.conf.txt + create mode 100644 doc/mantisbt_format.conf.txt + create mode 100644 doc/mantisbt_formatdup.conf.txt + create mode 100644 doc/report_CentOSBugTracker.conf.txt + create mode 100644 doc/report_centos.conf.txt + create mode 100644 doc/reporter-mantisbt.txt + +diff --git a/doc/Makefile.am b/doc/Makefile.am +index 83a41d9..e437388 100644 +--- a/doc/Makefile.am ++++ b/doc/Makefile.am +@@ -25,6 +25,7 @@ MAN1_TXT += reporter-print.txt + MAN1_TXT += reporter-rhtsupport.txt + MAN1_TXT += reporter-upload.txt + MAN1_TXT += reporter-ureport.txt ++MAN1_TXT += reporter-mantisbt.txt + + MAN5_TXT = + MAN5_TXT += anaconda_event.conf.txt +@@ -37,6 +38,9 @@ MAN5_TXT += bugzilla_formatdup_anaconda.conf.txt + MAN5_TXT += bugzilla_formatdup.conf.txt + MAN5_TXT += bugzilla_format_kernel.conf.txt + MAN5_TXT += bugzilla_format_libreport.conf.txt ++MAN5_TXT += mantisbt.conf.txt ++MAN5_TXT += mantisbt_format.conf.txt ++MAN5_TXT += mantisbt_formatdup.conf.txt + MAN5_TXT += emergencyanalysis_event.conf.txt + MAN5_TXT += forbidden_words.conf.txt + MAN5_TXT += mailx.conf.txt +@@ -45,6 +49,7 @@ MAN5_TXT += print_event.conf.txt + MAN5_TXT += report_Bugzilla.conf.txt + MAN5_TXT += report_event.conf.txt + MAN5_TXT += report_fedora.conf.txt ++MAN5_TXT += report_centos.conf.txt + MAN5_TXT += report_Logger.conf.txt + MAN5_TXT += report_rhel.conf.txt + MAN5_TXT += report_rhel_bugzilla.conf.txt +@@ -53,15 +58,19 @@ MAN5_TXT += report_logger.conf.txt + MAN5_TXT += report_mailx.conf.txt + MAN5_TXT += report_uploader.conf.txt + MAN5_TXT += report_Uploader.conf.txt ++MAN5_TXT += report_CentOSBugTracker.conf.txt + MAN5_TXT += rhtsupport.conf.txt + MAN5_TXT += rhtsupport_event.conf.txt + MAN5_TXT += uploader_event.conf.txt + MAN5_TXT += ureport.conf.txt + MAN5_TXT += upload.conf.txt + ++MAN5_PREFORMATTED = ++MAN5_PREFORMATTED += centos_report_event.conf.5 ++ + # Manual pages are generated from .txt via Docbook + man1_MANS = ${MAN1_TXT:%.txt=%.1} +-man5_MANS = ${MAN5_TXT:%.txt=%.5} ++man5_MANS = ${MAN5_TXT:%.txt=%.5} ${MAN5_PREFORMATTED} + + SUFFIXES = .1 .5 + +@@ -76,5 +85,5 @@ SUFFIXES = .1 .5 + --conf-file ../asciidoc.conf \ + -alibreport_version=$(PACKAGE_VERSION) -o $@ $< + +-EXTRA_DIST = $(MAN1_TXT) $(MAN5_TXT) ++EXTRA_DIST = $(MAN1_TXT) $(MAN5_TXT) $(MAN5_PREFORMATTED) + CLEANFILES = $(man1_MANS) +diff --git a/doc/centos_report_event.conf.5 b/doc/centos_report_event.conf.5 +new file mode 100644 +index 0000000..71c3fcb +--- /dev/null ++++ b/doc/centos_report_event.conf.5 +@@ -0,0 +1 @@ ++.so man5/report_event.conf.5 +diff --git a/doc/mantisbt.conf.txt b/doc/mantisbt.conf.txt +new file mode 100644 +index 0000000..d4ba605 +--- /dev/null ++++ b/doc/mantisbt.conf.txt +@@ -0,0 +1,18 @@ ++mantisbt.conf(5) ++=============== ++ ++NAME ++---- ++mantisbt.conf - configuration file for libreport. ++ ++DESCRIPTION ++----------- ++This configuration file provides default configuration for 'reporter-mantisbt'. ++ ++SEE ALSO ++-------- ++reporter-mantisbt(1) ++ ++AUTHOR ++------ ++* ABRT team +diff --git a/doc/mantisbt_format.conf.txt b/doc/mantisbt_format.conf.txt +new file mode 100644 +index 0000000..860d911 +--- /dev/null ++++ b/doc/mantisbt_format.conf.txt +@@ -0,0 +1,18 @@ ++mantisbt_format.conf(5) ++======================= ++ ++NAME ++---- ++mantisbt_format.conf - configuration file for libreport. ++ ++DESCRIPTION ++----------- ++This configuration file provides definition of general formatting for new MantisBT issues. ++ ++SEE ALSO ++-------- ++reporter-mantisbt(1) ++ ++AUTHOR ++------ ++* ABRT Team +diff --git a/doc/mantisbt_formatdup.conf.txt b/doc/mantisbt_formatdup.conf.txt +new file mode 100644 +index 0000000..a617226 +--- /dev/null ++++ b/doc/mantisbt_formatdup.conf.txt +@@ -0,0 +1,18 @@ ++mantisbt_formatdup.conf(5) ++========================== ++ ++NAME ++---- ++mantisbt_formatdup.conf - configuration file for libreport. ++ ++DESCRIPTION ++----------- ++This configuration file provides definition of general formatting for duplicate MantisBT issues. ++ ++SEE ALSO ++-------- ++reporter-mantisbt(1) ++ ++AUTHOR ++------ ++* ABRT Team +diff --git a/doc/report_CentOSBugTracker.conf.txt b/doc/report_CentOSBugTracker.conf.txt +new file mode 100644 +index 0000000..6ba35d3 +--- /dev/null ++++ b/doc/report_CentOSBugTracker.conf.txt +@@ -0,0 +1,45 @@ ++report_CentOSBugTracker.conf(5) ++=============================== ++ ++NAME ++---- ++report_CentOSBugTracker.conf - libreport's configuration file for 'report_CentOSBugTracker' events. ++ ++DESCRIPTION ++----------- ++This configuration file contains values for options defined in ++/usr/share/libreport/events/report_CentOSBugTracker.xml ++ ++Configuration file lines should have 'PARAM = VALUE' format. The parameters are: ++ ++'Mantisbt_Login':: ++ Login to MantisBT account. ++ ++'Mantisbt_Password':: ++ Password to MantisBT account. ++ ++'Mantisbt_MantisbtURL':: ++ MantisBT HTTP(S) address. (default: https://bugs.centos.org) ++ ++'Mantisbt_SSLVerify':: ++ Use yes/true/on/1 to verify server's SSL certificate. (default: yes) ++ ++'Mantisbt_Project':: ++ Project issue field value. Useful if you needed different project than specified in /etc/os-release ++ ++'Mantisbt_ProjectVersion':: ++ Version issue field value. Useful if you needed different project version than specified in /etc/os-release ++ ++'http_proxy':: ++ the proxy server to use for HTTP ++ ++'HTTPS_PROXY':: ++ the proxy server to use for HTTPS ++ ++SEE ALSO ++-------- ++report_event.conf(5), reporter-mantisbt(1) ++ ++AUTHOR ++------ ++* ABRT team +diff --git a/doc/report_centos.conf.txt b/doc/report_centos.conf.txt +new file mode 100644 +index 0000000..23a5fde +--- /dev/null ++++ b/doc/report_centos.conf.txt +@@ -0,0 +1,41 @@ ++report_centos.conf(5) ++===================== ++ ++NAME ++---- ++report_centos.conf - configuration file for libreport. ++ ++DESCRIPTION ++----------- ++This configuration file specifies which of the reporting work flow definitions ++are applicable for all problems types on CentOS. ++ ++All applicable work flows are presented to users in User Interface as ++possibilities for processing of any problems. A particular work flow becomes ++applicable if its conditions are satisfied. ++ ++This configuration file consists from one condition per line. ++ ++Each condition line must start with EVENT=workflow_NAME where "workflow_" is ++constant prefix and "workflow_NAME" is base name of path to reporting work flow ++configuration file. ++ ++The rest of condition line has form VAR=VAL, VAR!=VAL or VAL~=REGEX, where VAR ++is a name of problem directory element to be checked (for example, ++"executable", "package", hostname" etc). The condition may consists ++from as many element checks as it is necessary. ++ ++EXAMPLES ++-------- ++Condition line:: ++ EVENT=workflow_CentOSCCpp analyzer=CCpp ++ ++The condition line above expects existence of /usr/share/libreport/workflows/workflow_CentOSCCpp.xml ++ ++SEE ALSO ++-------- ++report-gtk(1) ++ ++AUTHOR ++------ ++* ABRT team +diff --git a/doc/reporter-mantisbt.txt b/doc/reporter-mantisbt.txt +new file mode 100644 +index 0000000..92255b0 +--- /dev/null ++++ b/doc/reporter-mantisbt.txt +@@ -0,0 +1,219 @@ ++reporter-mantisbt(1) ++==================== ++ ++NAME ++---- ++reporter-mantisbt - Reports problem to Mantis Bug Tracker. ++ ++SYNOPSIS ++-------- ++'reporter-mantisbt' [-vrf] [-c CONFFILE]... [-F FMTFILE] [-A FMTFILE2] -d DIR ++ ++Or: ++ ++'reporter-mantisbt' [-v] [-c CONFFILE]... [-d DIR] -t[ID] FILE... ++ ++Or: ++ ++'reporter-mantisbt' [-v] [-c CONFFILE]... -h DUPHASH ++ ++DESCRIPTION ++----------- ++The tool reads problem directory DIR. Then it logs in to MantisBT ++and tries to find an issue with the same duphash HEXSTRING in 'abrt_hash' field. ++ ++If such issue is not found, then a new issue is created. Elements of DIR ++are stored in the issue as part of issue description or as attachments, ++depending on their type and size. ++ ++Otherwise, if such issue is found and it is marked as CLOSED DUPLICATE, ++the tool follows the chain of duplicates until it finds a non-DUPLICATE issue. ++The tool adds a new note to found issue. ++ ++The URL to new or modified issue is printed to stdout and recorded in ++'reported_to' element in DIR. ++ ++Option -t uploads FILEs to the already created issue on MantisBT site. ++The issue ID is retrieved from directory specified by -d DIR. ++If problem data in DIR was never reported to MantisBT, upload will fail. ++ ++Option -tID uploads FILEs to the issue with specified ID on MantisBT site. ++-d DIR is ignored. ++ ++Option -r sets the last url from reporter_to element which is prefixed with ++TRACKER_NAME to URL field. This option is applied only when a new issue is to be ++filed. The default value is 'ABRT Server'" ++ ++Configuration file ++~~~~~~~~~~~~~~~~~~ ++If not specified, CONFFILE defaults to /etc/libreport/plugins/mantisbt.conf. ++Configuration file lines should have 'PARAM = VALUE' format. The parameters are: ++ ++'Login':: ++ Login to MantisBT account. ++ ++'Password':: ++ Password to MantisBT account. ++ ++'MantisbtURL':: ++ MantisBT HTTP(S) address. (default: http://localhost/mantisbt) ++ ++'SSLVerify':: ++ Use yes/true/on/1 to verify server's SSL certificate. (default: no) ++ ++'Project':: ++ Project issue field value. Useful if you needed different project than specified in /etc/os-release ++ ++'ProjectVersion':: ++ Version issue field value. Useful if you needed different project version than specified in /etc/os-release ++ ++'CreatePrivate':: ++ Create private MantisBT issue. (default: no) ++ ++Parameters can be overridden via $Mantisbt_PARAM environment variables. ++ ++Formatting configuration files ++~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ++Lines starting with # are ignored. ++ ++Lines can be continued on the next line using trailing backslash. ++ ++Format: ++ ++ "%summary:: summary format" ++ "section:: element1[,element2]..." ++ The literal text line to be added to MantisBT Description or Additional information. Can be empty. ++ (Empty lines are NOT ignored!) ++ ++ Summary format is a line of text, where %element% is replaced by ++ text element's content, and [[...%element%...]] block is used only if ++ %element% exists. [[...]] blocks can nest. ++ ++ Sections can be: ++ - %summary: issue Summary format string. ++ - %attach: a list of elements to attach. ++ - %Additional info: issue Additional Information content. ++ - text, double colon (::) and the list of comma-separated elements. ++ ++ Description and Additional information MantisBT's fields: ++ All text, double colons (::) and lists of comma-separated elements which ++ are placed above the section '%Additional info::' in the configuration file are ++ stored in the 'Description' field in MantisBT. All text etc. which are placed ++ under the '%Additional info::' are stored in the 'Additional information' field. ++ ++ For example: ++ |:: comment | (Description) ++ | | (Description) ++ |Package:: package | (Description) ++ | | (Description) ++ |%Additional_info:: | ++ |%reporter% | (Additional info) ++ |User:: user_name,uid | (Additional info) ++ | | (Additional info) ++ |Directories:: root,cwd | (Additional info) ++ ++ Elements can be: ++ - problem directory element names, which get formatted as ++ <element_name>: <contents> ++ or ++ <element_name>: ++ :<contents> ++ :<contents> ++ :<contents> ++ - problem directory element names prefixed by "%bare_", ++ which is formatted as-is, without "<element_name>:" and colons ++ - %oneline, %multiline, %text wildcards, which select all corresponding ++ elements for output or attachment ++ - %binary wildcard, valid only for %attach section, instructs to attach ++ binary elements ++ - problem directory element names prefixed by "-", ++ which excludes given element from all wildcards ++ ++ Nonexistent elements are silently ignored. ++ If none of elements exists, the section will not be created. ++ ++Integration with ABRT events ++~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ++'reporter-mantisbt' can be used as an ABRT reporter. Example ++fragment for /etc/libreport/report_event.conf: ++ ++------------ ++# Report Python crashes ++EVENT=report_CentOSBugTracker analyzer=Python ++ reporter-mantisbt -d . -c /etc/libreport/plugins/mantisbt.conf ++------------ ++ ++OPTIONS ++------- ++-d DIR:: ++ Path to problem directory. ++ ++-c CONFFILE:: ++ Path to configuration file. ++ ++-f:: ++ Force reporting even if this problem is already reported. ++ ++-F CONF_FORMAT_FILE:: ++ Formatting file for new issues. Default: /etc/libreport/plugins/mantisbt_format.conf ++ ++-A CONF_FORMAT_FILE:: ++ Formatting file for duplicates. Default: /etc/libreport/plugins/mantisbt_formatdup.conf ++ ++-t[ID]:: ++ Upload FILEs to the already created issue on MantisBT site. ++ ++-h:: ++--duphash DUPHASH:: ++ Search in MantisBT by abrt's DUPHASH and print ISSUE_ID. ++ ++-r TRACKER_NAME:: ++ Set the last url from reporter_to element which is prefixed with TRACKER_NAME to URL field in MantisBT. ++ ++ENVIRONMENT VARIABLES ++--------------------- ++Environment variables take precedence over values provided in ++the configuration file. ++ ++'Mantisbt_Login':: ++ Login to MantisBT account. ++ ++'Mantisbt_Password':: ++ Password to MantisBT account. ++ ++'Mantisbt_MantisbtURL':: ++ MantisBT HTTP(S) address. (default: http://localhost/mantisbt) ++ ++'Mantisbt_SSLVerify':: ++ Use yes/true/on/1 to verify server's SSL certificate. (default: no) ++ ++'Mantisbt_Project':: ++ Project issue field value. Useful if you needed different project than specified in /etc/os-release ++ ++'Mantisbt_ProjectVersion':: ++ Version issue field value. Useful if you needed different project version than specified in /etc/os-release ++ ++'Mantisbt_CreatePrivate':: ++ Create private MantisBT issue. (default: no) ++ ++FILES ++----- ++/usr/share/libreport/conf.d/plugins/mantisbt.conf:: ++ Readonly default configuration files. ++ ++/etc/libreport/plugins/mantisbt.conf:: ++ Configuration file. ++ ++/etc/libreport/plugins/mantisbt_format.conf:: ++ Configure formating for reporting. ++ ++/etc/libreport/plugins/mantisbt_formatdup.conf:: ++ Configure formating for reporting duplicates. ++ ++SEE ALSO ++-------- ++report_event.conf(5), mantisbt_format.conf(5), mantisbt_formatdup.conf(5) ++ ++AUTHORS ++------- ++* ABRT team +diff --git a/src/plugins/Makefile.am b/src/plugins/Makefile.am +index fd3f477..27194a4 100644 +--- a/src/plugins/Makefile.am ++++ b/src/plugins/Makefile.am +@@ -109,6 +109,10 @@ if BUILD_UREPORT + reporters_extra_dist += report_uReport.xml.in + endif + ++if BUILD_MANTISBT ++reporters_extra_dist += report_CentOSBugTracker.xml.in ++endif ++ + EXTRA_DIST = $(reporters_extra_dist) \ + report_Logger.conf \ + report_Logger.xml.in \ +diff --git a/src/workflows/Makefile.am b/src/workflows/Makefile.am +index 7ecb34e..17127a0 100644 +--- a/src/workflows/Makefile.am ++++ b/src/workflows/Makefile.am +@@ -107,3 +107,15 @@ EXTRA_DIST += \ + workflow_RHELBugzillaLibreport.xml.in \ + workflow_RHELBugzillaJava.xml.in + endif ++ ++if BUILD_MANTISBT ++EXTRA_DIST += \ ++ workflow_CentOSCCpp.xml.in \ ++ workflow_CentOSKerneloops.xml.in \ ++ workflow_CentOSPython.xml.in \ ++ workflow_CentOSPython3.xml.in \ ++ workflow_CentOSVmcore.xml.in \ ++ workflow_CentOSXorg.xml.in \ ++ workflow_CentOSLibreport.xml.in \ ++ workflow_CentOSJava.xml.in ++endif +-- +1.8.3.1 + diff --git a/SOURCES/1010-move-problem_report-to-plugins.patch b/SOURCES/1010-move-problem_report-to-plugins.patch new file mode 100644 index 0000000..9ba00e7 --- /dev/null +++ b/SOURCES/1010-move-problem_report-to-plugins.patch @@ -0,0 +1,3284 @@ +From a24be1f0915646dd0390884ceb4ee1bfae7fbe0c Mon Sep 17 00:00:00 2001 +From: Jakub Filak <jfilak@redhat.com> +Date: Wed, 25 Mar 2015 16:43:19 +0100 +Subject: [PATCH 1010/1015] move problem_report to plugins + +Get rid of satyr from libreport. + +Signed-off-by: Jakub Filak <jfilak@redhat.com> +--- + po/POTFILES.in | 2 +- + src/include/Makefile.am | 1 - + src/include/problem_report.h | 334 ------------ + src/lib/Makefile.am | 7 +- + src/lib/problem_report.c | 1210 ------------------------------------------ + src/plugins/Makefile.am | 24 +- + src/plugins/problem_report.c | 1210 ++++++++++++++++++++++++++++++++++++++++++ + src/plugins/problem_report.h | 334 ++++++++++++ + tests/testsuite.at | 2 +- + 9 files changed, 1568 insertions(+), 1556 deletions(-) + delete mode 100644 src/include/problem_report.h + delete mode 100644 src/lib/problem_report.c + create mode 100644 src/plugins/problem_report.c + create mode 100644 src/plugins/problem_report.h + +diff --git a/po/POTFILES.in b/po/POTFILES.in +index 9cf8f72..8b62b59 100644 +--- a/po/POTFILES.in ++++ b/po/POTFILES.in +@@ -25,11 +25,11 @@ src/lib/ureport.c + src/lib/make_descr.c + src/lib/parse_options.c + src/lib/problem_data.c +-src/lib/problem_report.c + src/lib/reported_to.c + src/lib/reporters.c + src/lib/run_event.c + src/plugins/abrt_rh_support.c ++src/plugins/problem_report.c + src/plugins/report_Bugzilla.xml.in + src/plugins/report.c + src/plugins/reporter-bugzilla.c +diff --git a/src/include/Makefile.am b/src/include/Makefile.am +index 4d8c6a5..7a76cf4 100644 +--- a/src/include/Makefile.am ++++ b/src/include/Makefile.am +@@ -5,7 +5,6 @@ libreport_include_HEADERS = \ + dump_dir.h \ + event_config.h \ + problem_data.h \ +- problem_report.h \ + report.h \ + run_event.h \ + libreport_curl.h \ +diff --git a/src/include/problem_report.h b/src/include/problem_report.h +deleted file mode 100644 +index f2d41d8..0000000 +--- a/src/include/problem_report.h ++++ /dev/null +@@ -1,334 +0,0 @@ +-/* +- Copyright (C) 2014 ABRT team +- Copyright (C) 2014 RedHat Inc +- +- This program is free software; you can redistribute it and/or modify +- it under the terms of the GNU General Public License as published by +- the Free Software Foundation; either version 2 of the License, or +- (at your option) any later version. +- +- This program is distributed in the hope that it will be useful, +- but WITHOUT ANY WARRANTY; without even the implied warranty of +- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +- GNU General Public License for more details. +- +- You should have received a copy of the GNU General Public License along +- with this program; if not, write to the Free Software Foundation, Inc., +- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +- +- @brief API for formating of problem data +- +- These functions can be used to convert a problem data to its string +- representation. +- +- The output format can be parsed from a string: +- +- problem_formatter_t *formatter = problem_formatter_new(); +- problem_formatter_load_string(formatter, MY_FORMAT_STRING); +- +- or loaded from a file: +- +- problem_formatter_t *formatter = problem_formatter_new(); +- problem_formatter_load_file(formatter, MY_FORMAT_FILE); +- +- Once you have configured your formatter you can convert problem_data to +- problem_report by calling: +- +- problem_report_t *report; +- if (problem_formatter_generate_report(formatter, data, &report) != 0) +- errx(EXIT_FAILURE, "Problem data cannot be converted to problem report."); +- +- Now you can print the report: +- +- printf("Problem: %s\n", problem_report_get_summary()); +- printf("%s\n", problem_report_get_description()); +- +- puts("Problem attachments:"); +- for (GList *a = problem_report_get_attachments(pr); a != NULL; a = g_list_next(a)) +- printf(" %s\n", a->data); +- +- Format description: +- +- ---- +- %summary:: summary format +- %attach:: elemnt1[,element2]... +- section:: element1[,element2]... +- The literal text line to be added to report. +- ---- +- +- Summary format is a line of text, where %element% is replaced by +- text element's content, and [[...%element%...]] block is used only if +- %element% exists. [[...]] blocks can nest. +- +- Sections can be: +- - %summary: bug summary format string. +- +- - %attach: a list of elements to attach. +- +- - text, double colon (::) and the list of comma-separated elements. +- Text can be empty (":: elem1, elem2, elem3" works), +- in this case "Text:" header line will be omitted. +- +- - %description: this section is implicit and contains all text +- sections unless another section was specified (%summary and %attach +- are ignored when determining text section's placement) +- +- - every text element belongs to the last specified section (%summary +- and %attach sections are ignored). If no section was specified, +- the text element belogns to %description. +- +- - If none of elements exists, the section will not be created. +- +- - Empty lines are NOT ignored. +- +- Elements can be: +- - problem directory element names, which get formatted as +- <element_name>: <contents> +- or +- <element_name>: +- :<contents> +- :<contents> +- :<contents> +- +- - problem directory element names prefixed by "%bare_", +- which is formatted as-is, without "<element_name>:" and colons +- +- - %oneline, %multiline, %text wildcards, which select all corresponding +- elements for output or attachment +- +- - %binary wildcard, valid only for %attach section, instructs to attach +- binary elements +- +- - problem directory element names prefixed by "-", +- which excludes given element from all wildcards +- +- - Nonexistent elements are silently ignored. +- +- You can add your own section: +- +- problem_formatter_t *formatter = problem_formatter_new(); +- problem_formatter_add_section(formatter, "additional_info", PFFF_REQUIRED); +- +- and then you can use the section in the formatting string: +- +- problem_formatter_load_string(formatter, +- "::comment\n" +- "%additional_info:: maps"); +- problem_formatter_generate_report(formatter, data, &report); +- +- printf("Problem: %s\n", problem_report_get_summary()); +- printf("%s\n", problem_report_get_description()); +- printf("Additional info: %s\n", problem_report_get_section(report, "additiona_info")); +- +- The lines above are equivalent to the following lines: +- +- printf("Problem: %s\n", problem_data_get_content_or_NULL(data, "reason")); +- printf("%s\n", problem_data_get_content_or_NULL(data, "comment")); +- printf("Additional info: %s\n", problem_data_get_content_or_NULL(data, "maps")); +-*/ +-#ifndef LIBREPORT_PROBLEM_REPORT_H +-#define LIBREPORT_PROBLEM_REPORT_H +- +-#include <glib.h> +-#include <stdio.h> +-#include "problem_data.h" +- +-#ifdef __cplusplus +-extern "C" { +-#endif +- +-#define PR_SEC_SUMMARY "summary" +-#define PR_SEC_DESCRIPTION "description" +- +-/* +- * The problem report structure represents a problem data formatted according +- * to a format string. +- * +- * A problem report is composed of well-known sections: +- * - summary +- * - descritpion +- * - attach +- * +- * and custom sections accessed by: +- * problem_report_get_section(); +- */ +-struct problem_report; +-typedef struct problem_report problem_report_t; +- +-/* +- * Helpers for easily switching between FILE and struct strbuf +- */ +- +-/* +- * Type of buffer used by Problem report +- */ +-typedef FILE problem_report_buffer; +- +-/* +- * Wrapper for the proble buffer's formated output function. +- */ +-#define problem_report_buffer_printf(buf, fmt, ...)\ +- fprintf((buf), (fmt), ##__VA_ARGS__) +- +- +-/* +- * Get a section buffer +- * +- * Use this function if you need to amend something to a formatted section. +- * +- * @param self Problem report +- * @param section_name Name of required section +- * @return Always valid pointer to a section buffer +- */ +-problem_report_buffer *problem_report_get_buffer(const problem_report_t *self, +- const char *section_name); +- +-/* +- * Get Summary string +- * +- * The returned pointer is valid as long as you perform no further output to +- * the summary buffer. +- * +- * @param self Problem report +- * @return Non-NULL pointer to summary data +- */ +-const char *problem_report_get_summary(const problem_report_t *self); +- +-/* +- * Get Description string +- * +- * The returned pointer is valid as long as you perform no further output to +- * the description buffer. +- * +- * @param self Problem report +- * @return Non-NULL pointer to description data +- */ +-const char *problem_report_get_description(const problem_report_t *self); +- +-/* +- * Get Section's string +- * +- * The returned pointer is valid as long as you perform no further output to +- * the section's buffer. +- * +- * @param self Problem report +- * @param section_name Name of the required section +- * @return Non-NULL pointer to description data +- */ +-const char *problem_report_get_section(const problem_report_t *self, +- const char *section_name); +- +-/* +- * Get GList of the problem data items that are to be attached +- * +- * @param self Problem report +- * @return A pointer to GList (NULL means empty list) +- */ +-GList *problem_report_get_attachments(const problem_report_t *self); +- +-/* +- * Releases all resources allocated by a problem report +- * +- * @param self Problem report +- */ +-void problem_report_free(problem_report_t *self); +- +- +-/* +- * An enum of Extra section flags +- */ +-enum problem_formatter_section_flags { +- PFFF_REQUIRED = 1 << 0, ///< section must be present in the format spec +-}; +- +-/* +- * The problem formatter structure formats a problem data according to a format +- * string and stores result a problem report. +- * +- * The problem formatter uses '%reason%' as %summary section format string, if +- * %summary is not provided by a format string. +- */ +-struct problem_formatter; +-typedef struct problem_formatter problem_formatter_t; +- +-/* +- * Constructs a new problem formatter. +- * +- * @return Non-NULL pointer to the new problem formatter +- */ +-problem_formatter_t *problem_formatter_new(void); +- +-/* +- * Releases all resources allocated by a problem formatter +- * +- * @param self Problem formatter +- */ +-void problem_formatter_free(problem_formatter_t *self); +- +-/* +- * Adds a new recognized section +- * +- * The problem formatter ignores a section in the format spec if the section is +- * not one of the default nor added by this function. +- * +- * How the problem formatter handles these extra sections: +- * +- * A custom section is something like %description section. %description is the +- * default section where all text (sub)sections are stored. If the formatter +- * finds the custom section in format string, then starts storing text +- * (sub)sections in the custom section. +- * +- * (%description) |:: comment +- * (%description) | +- * (%description) |Package:: package +- * (%description) | +- * (%additiona_info) |%additional_info:: +- * (%additiona_info) |%reporter% +- * (%additiona_info) |User:: user_name,uid +- * (%additiona_info) | +- * (%additiona_info) |Directories:: root,cwd +- * +- * +- * @param self Problem formatter +- * @param name Name of the added section +- * @param flags Info about the added section +- * @return Zero on success. -EEXIST if the name is already known by the formatter +- */ +-int problem_formatter_add_section(problem_formatter_t *self, const char *name, int flags); +- +-/* +- * Loads a problem format from a string. +- * +- * @param self Problem formatter +- * @param fmt Format +- * @return Zero on success or number of warnings (e.g. missing section, +- * unrecognized section). +- */ +-int problem_formatter_load_string(problem_formatter_t* self, const char *fmt); +- +-/* +- * Loads a problem format from a file. +- * +- * @param self Problem formatter +- * @param pat Path to the format file +- * @return Zero on success or number of warnings (e.g. missing section, +- * unrecognized section). +- */ +-int problem_formatter_load_file(problem_formatter_t* self, const char *path); +- +-/* +- * Creates a new problem report, formats the data according to the loaded +- * format string and stores output in the report. +- * +- * @param self Problem formatter +- * @param data Problem data to format +- * @param report Pointer where the created problem report is to be stored +- * @return Zero on success, otherwise non-zero value. +- */ +-int problem_formatter_generate_report(const problem_formatter_t *self, problem_data_t *data, problem_report_t **report); +- +-#ifdef __cplusplus +-} +-#endif +- +-#endif // LIBREPORT_PROBLEM_REPORT_H +diff --git a/src/lib/Makefile.am b/src/lib/Makefile.am +index d41e543..80a7c4d 100644 +--- a/src/lib/Makefile.am ++++ b/src/lib/Makefile.am +@@ -38,7 +38,6 @@ libreport_la_SOURCES = \ + make_descr.c \ + run_event.c \ + problem_data.c \ +- problem_report.c \ + create_dump_dir.c \ + abrt_types.c \ + parse_release.c \ +@@ -80,7 +79,6 @@ libreport_la_CPPFLAGS = \ + $(GLIB_CFLAGS) \ + $(GOBJECT_CFLAGS) \ + $(AUGEAS_CFLAGS) \ +- $(SATYR_CFLAGS) \ + -D_GNU_SOURCE + libreport_la_LDFLAGS = \ + -ltar \ +@@ -90,8 +88,7 @@ libreport_la_LIBADD = \ + $(GLIB_LIBS) \ + $(JOURNAL_LIBS) \ + $(GOBJECT_LIBS) \ +- $(AUGEAS_LIBS) \ +- $(SATYR_LIBS) ++ $(AUGEAS_LIBS) + + libreportconfdir = $(CONF_DIR) + dist_libreportconf_DATA = \ +@@ -152,8 +149,8 @@ libreport_web_la_LIBADD = \ + $(PROXY_LIBS) \ + $(LIBXML_LIBS) \ + $(JSON_C_LIBS) \ +- $(SATYR_LIBS) \ + $(XMLRPC_LIBS) $(XMLRPC_CLIENT_LIBS) \ ++ $(SATYR_LIBS) \ + libreport.la + + DEFS = -DLOCALEDIR=\"$(localedir)\" @DEFS@ +diff --git a/src/lib/problem_report.c b/src/lib/problem_report.c +deleted file mode 100644 +index 2bf5530..0000000 +--- a/src/lib/problem_report.c ++++ /dev/null +@@ -1,1210 +0,0 @@ +-/* +- Copyright (C) 2014 ABRT team +- Copyright (C) 2014 RedHat Inc +- +- This program is free software; you can redistribute it and/or modify +- it under the terms of the GNU General Public License as published by +- the Free Software Foundation; either version 2 of the License, or +- (at your option) any later version. +- +- This program is distributed in the hope that it will be useful, +- but WITHOUT ANY WARRANTY; without even the implied warranty of +- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +- GNU General Public License for more details. +- +- You should have received a copy of the GNU General Public License along +- with this program; if not, write to the Free Software Foundation, Inc., +- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +-*/ +- +-#include "problem_report.h" +-#include "internal_libreport.h" +- +-#include <satyr/stacktrace.h> +-#include <satyr/abrt.h> +- +-#include <assert.h> +- +-#define DESTROYED_POINTER (void *)0xdeadbeef +- +-/* FORMAT: +- * |%summary:: Hello, world +- * |Problem description:: %bare_comment +- * | +- * |Package:: package +- * | +- * |%attach: %binary, backtrace +- * | +- * |%additional_info:: +- * |%reporter% +- * |User:: user_name,uid +- * | +- * |Directories:: root,cwd +- * +- * PARSED DATA (list of struct section_t): +- * { +- * section_t { +- * .name = '%summary'; +- * .items = { 'Hello, world' }; +- * .children = NULL; +- * }, +- * section_t { +- * .name = '%attach' +- * .items = { '%binary', 'backtrace' }; +- * .children = NULL; +- * }, +- * section_t { +- * .name = '%description' +- * .items = NULL; +- * .children = { +- * section_t { +- * .name = 'Problem description:'; +- * .items = { '%bare_comment' }; +- * .children = NULL; +- * }, +- * section_t { +- * .name = ''; +- * .items = NULL; +- * .children = NULL; +- * }, +- * section_t { +- * .name = 'Package:'; +- * .items = { 'package' }; +- * .children = NULL; +- * }, +- * } +- * }, +- * section_t { +- * .name = '%additional_info' +- * .items = { '%reporter%' }; +- * .children = { +- * section_t { +- * .name = 'User:'; +- * .items = { 'user_name', 'uid' }; +- * .children = NULL; +- * }, +- * section_t { +- * .name = ''; +- * .items = NULL; +- * .children = NULL; +- * }, +- * section_t { +- * .name = 'Directories:'; +- * .items = { 'root', 'cwd' }; +- * .children = NULL; +- * }, +- * } +- * } +- * } +- */ +-struct section_t { +- char *name; ///< name or output text (%summar, 'Package version:'); +- GList *items; ///< list of file names and special items (%reporter, %binar, ...) +- GList *children; ///< list of sub sections (struct section_t) +-}; +- +-typedef struct section_t section_t; +- +-static section_t * +-section_new(const char *name) +-{ +- section_t *self = xmalloc(sizeof(*self)); +- self->name = xstrdup(name); +- self->items = NULL; +- self->children = NULL; +- +- return self; +-} +- +-static void +-section_free(section_t *self) +-{ +- if (self == NULL) +- return; +- +- free(self->name); +- g_list_free_full(self->items, free); +- g_list_free_full(self->children, (GDestroyNotify)section_free); +- +- free(self); +-} +- +-static int +-section_name_cmp(section_t *lhs, const char *rhs) +-{ +- return strcmp((lhs->name + 1), rhs); +-} +- +-/* Utility functions */ +- +-static GList* +-split_string_on_char(const char *str, char ch) +-{ +- GList *list = NULL; +- for (;;) +- { +- const char *delim = strchrnul(str, ch); +- list = g_list_prepend(list, xstrndup(str, delim - str)); +- if (*delim == '\0') +- break; +- str = delim + 1; +- } +- return g_list_reverse(list); +-} +- +-static int +-compare_item_name(const char *lookup, const char *name) +-{ +- if (lookup[0] == '-') +- lookup++; +- else if (strncmp(lookup, "%bare_", 6) == 0) +- lookup += 6; +- return strcmp(lookup, name); +-} +- +-static int +-is_item_name_in_section(const section_t *lookup, const char *name) +-{ +- if (g_list_find_custom(lookup->items, name, (GCompareFunc)compare_item_name)) +- return 0; /* "found it!" */ +- return 1; +-} +- +-static bool is_explicit_or_forbidden(const char *name, GList *comment_fmt_spec); +- +-static int +-is_explicit_or_forbidden_child(const section_t *master_section, const char *name) +-{ +- if (is_explicit_or_forbidden(name, master_section->children)) +- return 0; /* "found it!" */ +- return 1; +-} +- +-/* For example: 'package' belongs to '%oneline', but 'package' is used in +- * 'Version of component', so it is not very helpful to include that file once +- * more in another section +- */ +-static bool +-is_explicit_or_forbidden(const char *name, GList *comment_fmt_spec) +-{ +- return g_list_find_custom(comment_fmt_spec, name, (GCompareFunc)is_item_name_in_section) +- || g_list_find_custom(comment_fmt_spec, name, (GCompareFunc)is_explicit_or_forbidden_child); +-} +- +-static GList* +-load_stream(FILE *fp) +-{ +- assert(fp); +- +- GList *sections = NULL; +- section_t *master = section_new("%description"); +- section_t *sec = NULL; +- +- sections = g_list_append(sections, master); +- +- char *line; +- while ((line = xmalloc_fgetline(fp)) != NULL) +- { +- /* Skip comments */ +- char first = *skip_whitespace(line); +- if (first == '#') +- goto free_line; +- +- /* Handle trailing backslash continuation */ +- check_continuation: ; +- unsigned len = strlen(line); +- if (len && line[len-1] == '\\') +- { +- line[len-1] = '\0'; +- char *next_line = xmalloc_fgetline(fp); +- if (next_line) +- { +- line = append_to_malloced_string(line, next_line); +- free(next_line); +- goto check_continuation; +- } +- } +- +- /* We are reusing line buffer to form temporary +- * "key\0values\0..." in its beginning +- */ +- bool summary_line = false; +- char *value = NULL; +- char *src; +- char *dst; +- for (src = dst = line; *src; src++) +- { +- char c = *src; +- /* did we reach the value list? */ +- if (!value && c == ':' && src[1] == ':') +- { +- *dst++ = '\0'; /* terminate key */ +- src += 1; +- value = dst; /* remember where value starts */ +- summary_line = (strcmp(line, "%summary") == 0); +- if (summary_line) +- { +- value = (src + 1); +- break; +- } +- continue; +- } +- /* skip whitespace in value list */ +- if (value && isspace(c)) +- continue; +- *dst++ = c; /* store next key or value char */ +- } +- +- GList *item_list = NULL; +- if (summary_line) +- { +- /* %summary is special */ +- item_list = g_list_append(NULL, xstrdup(skip_whitespace(value))); +- } +- else +- { +- *dst = '\0'; /* terminate value (or key) */ +- if (value) +- item_list = split_string_on_char(value, ','); +- } +- +- sec = section_new(line); +- sec->items = item_list; +- +- if (sec->name[0] == '%') +- { +- if (!summary_line && strcmp(sec->name, "%attach") != 0) +- { +- master->children = g_list_reverse(master->children); +- master = sec; +- } +- +- sections = g_list_prepend(sections, sec); +- } +- else +- master->children = g_list_prepend(master->children, sec); +- +- free_line: +- free(line); +- } +- +- /* If master equals sec, then master's children list was not yet reversed. +- * +- * %description is the default section (i.e is not explicitly mentioned) +- * and %summary nor %attach cause its children list to reverse. +- */ +- if (master == sec || strcmp(master->name, "%description") == 0) +- master->children = g_list_reverse(master->children); +- +- return sections; +-} +- +- +-/* Summary generation */ +- +-#define MAX_OPT_DEPTH 10 +-static int +-format_percented_string(const char *str, problem_data_t *pd, FILE *result) +-{ +- long old_pos[MAX_OPT_DEPTH] = { 0 }; +- int okay[MAX_OPT_DEPTH] = { 1 }; +- long len = 0; +- int opt_depth = 1; +- +- while (*str) { +- switch (*str) { +- default: +- putc(*str, result); +- len++; +- str++; +- break; +- case '\\': +- if (str[1]) +- str++; +- putc(*str, result); +- len++; +- str++; +- break; +- case '[': +- if (str[1] == '[' && opt_depth < MAX_OPT_DEPTH) +- { +- old_pos[opt_depth] = len; +- okay[opt_depth] = 1; +- opt_depth++; +- str += 2; +- } else { +- putc(*str, result); +- len++; +- str++; +- } +- break; +- case ']': +- if (str[1] == ']' && opt_depth > 1) +- { +- opt_depth--; +- if (!okay[opt_depth]) +- { +- if (fseek(result, old_pos[opt_depth], SEEK_SET) < 0) +- perror_msg_and_die("fseek"); +- len = old_pos[opt_depth]; +- } +- str += 2; +- } else { +- putc(*str, result); +- len++; +- str++; +- } +- break; +- case '%': ; +- char *nextpercent = strchr(++str, '%'); +- if (!nextpercent) +- { +- error_msg_and_die("Unterminated %%element%%: '%s'", str - 1); +- } +- +- *nextpercent = '\0'; +- const problem_item *item = problem_data_get_item_or_NULL(pd, str); +- *nextpercent = '%'; +- +- if (item && (item->flags & CD_FLAG_TXT)) +- { +- fputs(item->content, result); +- len += strlen(item->content); +- } +- else +- okay[opt_depth - 1] = 0; +- str = nextpercent + 1; +- break; +- } +- } +- +- if (opt_depth > 1) +- { +- error_msg_and_die("Unbalanced [[ ]] bracket"); +- } +- +- if (!okay[0]) +- { +- error_msg("Undefined variable outside of [[ ]] bracket"); +- } +- +- return 0; +-} +- +-/* BZ comment generation */ +- +-static int +-append_text(struct strbuf *result, const char *item_name, const char *content, bool print_item_name) +-{ +- char *eol = strchrnul(content, '\n'); +- if (eol[0] == '\0' || eol[1] == '\0') +- { +- /* one-liner */ +- int pad = 16 - (strlen(item_name) + 2); +- if (pad < 0) +- pad = 0; +- if (print_item_name) +- strbuf_append_strf(result, +- eol[0] == '\0' ? "%s: %*s%s\n" : "%s: %*s%s", +- item_name, pad, "", content +- ); +- else +- strbuf_append_strf(result, +- eol[0] == '\0' ? "%s\n" : "%s", +- content +- ); +- } +- else +- { +- /* multi-line item */ +- if (print_item_name) +- strbuf_append_strf(result, "%s:\n", item_name); +- for (;;) +- { +- eol = strchrnul(content, '\n'); +- strbuf_append_strf(result, +- /* For %bare_multiline_item, we don't want to print colons */ +- (print_item_name ? ":%.*s\n" : "%.*s\n"), +- (int)(eol - content), content +- ); +- if (eol[0] == '\0' || eol[1] == '\0') +- break; +- content = eol + 1; +- } +- } +- return 1; +-} +- +-static int +-append_short_backtrace(struct strbuf *result, problem_data_t *problem_data, size_t max_text_size, bool print_item_name) +-{ +- const problem_item *item = problem_data_get_item_or_NULL(problem_data, +- FILENAME_BACKTRACE); +- if (!item) +- return 0; /* "I did not print anything" */ +- if (!(item->flags & CD_FLAG_TXT)) +- return 0; /* "I did not print anything" */ +- +- char *truncated = NULL; +- +- if (strlen(item->content) >= max_text_size) +- { +- log_debug("'backtrace' exceeds the text file size, going to append its short version"); +- +- char *error_msg = NULL; +- const char *type = problem_data_get_content_or_NULL(problem_data, FILENAME_TYPE); +- if (!type) +- { +- log_debug("Problem data does not contain '"FILENAME_TYPE"' file"); +- return 0; +- } +- +- /* For CCpp crashes, use the GDB-produced backtrace which should be +- * available by now. sr_abrt_type_from_analyzer returns SR_REPORT_CORE +- * by default for CCpp crashes. +- */ +- enum sr_report_type report_type = sr_abrt_type_from_analyzer(type); +- if (strcmp(type, "CCpp") == 0) +- { +- log_debug("Successfully identified 'CCpp' abrt type"); +- report_type = SR_REPORT_GDB; +- } +- +- struct sr_stacktrace *backtrace = sr_stacktrace_parse(report_type, +- item->content, &error_msg); +- +- if (!backtrace) +- { +- log(_("Can't parse backtrace: %s"), error_msg); +- free(error_msg); +- return 0; +- } +- +- /* Get optimized thread stack trace for 10 top most frames */ +- truncated = sr_stacktrace_to_short_text(backtrace, 10); +- sr_stacktrace_free(backtrace); +- +- if (!truncated) +- { +- log(_("Can't generate stacktrace description (no crash thread?)")); +- return 0; +- } +- } +- else +- { +- log_debug("'backtrace' is small enough to be included as is"); +- } +- +- append_text(result, +- /*item_name:*/ truncated ? "truncated_backtrace" : FILENAME_BACKTRACE, +- /*content:*/ truncated ? truncated : item->content, +- print_item_name +- ); +- free(truncated); +- return 1; +-} +- +-static int +-append_item(struct strbuf *result, const char *item_name, problem_data_t *pd, GList *comment_fmt_spec) +-{ +- bool print_item_name = (strncmp(item_name, "%bare_", strlen("%bare_")) != 0); +- if (!print_item_name) +- item_name += strlen("%bare_"); +- +- if (item_name[0] != '%') +- { +- struct problem_item *item = problem_data_get_item_or_NULL(pd, item_name); +- if (!item) +- return 0; /* "I did not print anything" */ +- if (!(item->flags & CD_FLAG_TXT)) +- return 0; /* "I did not print anything" */ +- +- char *formatted = problem_item_format(item); +- char *content = formatted ? formatted : item->content; +- append_text(result, item_name, content, print_item_name); +- free(formatted); +- return 1; /* "I printed something" */ +- } +- +- /* Special item name */ +- +- /* Compat with previously-existed ad-hockery: %short_backtrace */ +- if (strcmp(item_name, "%short_backtrace") == 0) +- return append_short_backtrace(result, pd, CD_TEXT_ATT_SIZE_BZ, print_item_name); +- +- /* Compat with previously-existed ad-hockery: %reporter */ +- if (strcmp(item_name, "%reporter") == 0) +- return append_text(result, "reporter", PACKAGE"-"VERSION, print_item_name); +- +- /* %oneline,%multiline,%text */ +- bool oneline = (strcmp(item_name+1, "oneline" ) == 0); +- bool multiline = (strcmp(item_name+1, "multiline") == 0); +- bool text = (strcmp(item_name+1, "text" ) == 0); +- if (!oneline && !multiline && !text) +- { +- log("Unknown or unsupported element specifier '%s'", item_name); +- return 0; /* "I did not print anything" */ +- } +- +- int printed = 0; +- +- /* Iterate over _sorted_ items */ +- GList *sorted_names = g_hash_table_get_keys(pd); +- sorted_names = g_list_sort(sorted_names, (GCompareFunc)strcmp); +- +- /* %text => do as if %oneline, then repeat as if %multiline */ +- if (text) +- oneline = 1; +- +- again: ; +- GList *l = sorted_names; +- while (l) +- { +- const char *name = l->data; +- l = l->next; +- struct problem_item *item = g_hash_table_lookup(pd, name); +- if (!item) +- continue; /* paranoia, won't happen */ +- +- if (!(item->flags & CD_FLAG_TXT)) +- continue; +- +- if (is_explicit_or_forbidden(name, comment_fmt_spec)) +- continue; +- +- char *formatted = problem_item_format(item); +- char *content = formatted ? formatted : item->content; +- char *eol = strchrnul(content, '\n'); +- bool is_oneline = (eol[0] == '\0' || eol[1] == '\0'); +- if (oneline == is_oneline) +- printed |= append_text(result, name, content, print_item_name); +- free(formatted); +- } +- if (text && oneline) +- { +- /* %text, and we just did %oneline. Repeat as if %multiline */ +- oneline = 0; +- /*multiline = 1; - not checked in fact, so why bother setting? */ +- goto again; +- } +- +- g_list_free(sorted_names); /* names themselves are not freed */ +- +- return printed; +-} +- +-#define add_to_section_output(format, ...) \ +- do { \ +- for (; empty_lines > 0; --empty_lines) fputc('\n', result); \ +- empty_lines = 0; \ +- fprintf(result, format, __VA_ARGS__); \ +- } while (0) +- +-static void +-format_section(section_t *section, problem_data_t *pd, GList *comment_fmt_spec, FILE *result) +-{ +- int empty_lines = -1; +- +- for (GList *iter = section->children; iter; iter = g_list_next(iter)) +- { +- section_t *child = (section_t *)iter->data; +- if (child->items) +- { +- /* "Text: item[,item]..." */ +- struct strbuf *output = strbuf_new(); +- GList *item = child->items; +- while (item) +- { +- const char *str = item->data; +- item = item->next; +- if (str[0] == '-') /* "-name", ignore it */ +- continue; +- append_item(output, str, pd, comment_fmt_spec); +- } +- +- if (output->len != 0) +- add_to_section_output((child->name[0] ? "%s:\n%s" : "%s%s"), +- child->name, output->buf); +- +- strbuf_free(output); +- } +- else +- { +- /* Just "Text" (can be "") */ +- +- /* Filter out trailint empty lines */ +- if (child->name[0] != '\0') +- add_to_section_output("%s\n", child->name); +- /* Do not count empty lines, if output wasn't yet produced */ +- else if (empty_lines >= 0) +- ++empty_lines; +- } +- } +-} +- +-static GList * +-get_special_items(const char *item_name, problem_data_t *pd, GList *comment_fmt_spec) +-{ +- /* %oneline,%multiline,%text,%binary */ +- bool oneline = (strcmp(item_name+1, "oneline" ) == 0); +- bool multiline = (strcmp(item_name+1, "multiline") == 0); +- bool text = (strcmp(item_name+1, "text" ) == 0); +- bool binary = (strcmp(item_name+1, "binary" ) == 0); +- if (!oneline && !multiline && !text && !binary) +- { +- log("Unknown or unsupported element specifier '%s'", item_name); +- return NULL; +- } +- +- log_debug("Special item_name '%s', iterating for attach...", item_name); +- GList *result = 0; +- +- /* Iterate over _sorted_ items */ +- GList *sorted_names = g_hash_table_get_keys(pd); +- sorted_names = g_list_sort(sorted_names, (GCompareFunc)strcmp); +- +- GList *l = sorted_names; +- while (l) +- { +- const char *name = l->data; +- l = l->next; +- struct problem_item *item = g_hash_table_lookup(pd, name); +- if (!item) +- continue; /* paranoia, won't happen */ +- +- if (is_explicit_or_forbidden(name, comment_fmt_spec)) +- continue; +- +- if ((item->flags & CD_FLAG_TXT) && !binary) +- { +- char *content = item->content; +- char *eol = strchrnul(content, '\n'); +- bool is_oneline = (eol[0] == '\0' || eol[1] == '\0'); +- if (text || oneline == is_oneline) +- result = g_list_append(result, xstrdup(name)); +- } +- else if ((item->flags & CD_FLAG_BIN) && binary) +- result = g_list_append(result, xstrdup(name)); +- } +- +- g_list_free(sorted_names); /* names themselves are not freed */ +- +- +- log_debug("...Done iterating over '%s' for attach", item_name); +- +- return result; +-} +- +-static GList * +-get_attached_files(problem_data_t *pd, GList *items, GList *comment_fmt_spec) +-{ +- GList *result = NULL; +- GList *item = items; +- while (item != NULL) +- { +- const char *item_name = item->data; +- item = item->next; +- if (item_name[0] == '-') /* "-name", ignore it */ +- continue; +- +- if (item_name[0] != '%') +- { +- result = g_list_append(result, xstrdup(item_name)); +- continue; +- } +- +- GList *special = get_special_items(item_name, pd, comment_fmt_spec); +- if (special == NULL) +- { +- log_notice("No attachment found for '%s'", item_name); +- continue; +- } +- +- result = g_list_concat(result, special); +- } +- +- return result; +-} +- +-/* +- * Problem Report - memor stream +- * +- * A wrapper for POSIX memory stream. +- * +- * A memory stream is presented as FILE *. +- * +- * A memory stream is associated with a pointer to written data and a pointer +- * to size of the written data. +- * +- * This structure holds all of the used pointers. +- */ +-struct memstream_buffer +-{ +- char *msb_buffer; +- size_t msb_size; +- FILE *msb_stream; +-}; +- +-static struct memstream_buffer * +-memstream_buffer_new() +-{ +- struct memstream_buffer *self = xmalloc(sizeof(*self)); +- +- self->msb_buffer = NULL; +- self->msb_stream = open_memstream(&(self->msb_buffer), &(self->msb_size)); +- +- return self; +-} +- +-static void +-memstream_buffer_free(struct memstream_buffer *self) +-{ +- if (self == NULL) +- return; +- +- fclose(self->msb_stream); +- self->msb_stream = DESTROYED_POINTER; +- +- free(self->msb_buffer); +- self->msb_buffer = DESTROYED_POINTER; +- +- free(self); +-} +- +-static FILE * +-memstream_get_stream(struct memstream_buffer *self) +-{ +- assert(self != NULL); +- +- return self->msb_stream; +-} +- +-static const char * +-memstream_get_string(struct memstream_buffer *self) +-{ +- assert(self != NULL); +- assert(self->msb_stream != NULL); +- +- fflush(self->msb_stream); +- +- return self->msb_buffer; +-} +- +- +-/* +- * Problem Report +- * +- * The formated strings are internaly stored in "buffer"s. If a programer wants +- * to get a formated section data, a getter function extracts those data from +- * the apropriate buffer and returns them in form of null-terminated string. +- * +- * Each section has own buffer. +- * +- * There are three common sections that are always present: +- * 1. summary +- * 2. description +- * 3. attach +- * Buffers of these sections has own structure member for the sake of +- * efficiency. +- * +- * The custom sections hash their buffers stored in a map where key is a +- * section's name and value is a section's buffer. +- * +- * Problem report provides the programers with the possibility to ammend +- * formated output to any section buffer. +- */ +-struct problem_report +-{ +- struct memstream_buffer *pr_sec_summ; ///< %summary buffer +- struct memstream_buffer *pr_sec_desc; ///< %description buffer +- GList *pr_attachments; ///< %attach - list of file names +- GHashTable *pr_sec_custom; ///< map : %(custom section) -> buffer +-}; +- +-static problem_report_t * +-problem_report_new() +-{ +- problem_report_t *self = xmalloc(sizeof(*self)); +- +- self->pr_sec_summ = memstream_buffer_new(); +- self->pr_sec_desc = memstream_buffer_new(); +- self->pr_attachments = NULL; +- self->pr_sec_custom = NULL; +- +- return self; +-} +- +-static void +-problem_report_initialize_custom_sections(problem_report_t *self) +-{ +- assert(self != NULL); +- assert(self->pr_sec_custom == NULL); +- +- self->pr_sec_custom = g_hash_table_new_full(g_str_hash, g_str_equal, free, +- (GDestroyNotify)memstream_buffer_free); +-} +- +-static void +-problem_report_destroy_custom_sections(problem_report_t *self) +-{ +- assert(self != NULL); +- assert(self->pr_sec_custom != NULL); +- +- g_hash_table_destroy(self->pr_sec_custom); +-} +- +-static int +-problem_report_add_custom_section(problem_report_t *self, const char *name) +-{ +- assert(self != NULL); +- +- if (self->pr_sec_custom == NULL) +- { +- problem_report_initialize_custom_sections(self); +- } +- +- if (problem_report_get_buffer(self, name)) +- { +- log_warning("Custom section already exists : '%s'", name); +- return -EEXIST; +- } +- +- log_debug("Problem report enriched with section : '%s'", name); +- g_hash_table_insert(self->pr_sec_custom, xstrdup(name), memstream_buffer_new()); +- return 0; +-} +- +-static struct memstream_buffer * +-problem_report_get_section_buffer(const problem_report_t *self, const char *section_name) +-{ +- if (self->pr_sec_custom == NULL) +- { +- log_debug("Couldn't find section '%s': no custom section added", section_name); +- return NULL; +- } +- +- return (struct memstream_buffer *)g_hash_table_lookup(self->pr_sec_custom, section_name); +-} +- +-problem_report_buffer * +-problem_report_get_buffer(const problem_report_t *self, const char *section_name) +-{ +- assert(self != NULL); +- assert(section_name != NULL); +- +- if (strcmp(PR_SEC_SUMMARY, section_name) == 0) +- return memstream_get_stream(self->pr_sec_summ); +- +- if (strcmp(PR_SEC_DESCRIPTION, section_name) == 0) +- return memstream_get_stream(self->pr_sec_desc); +- +- struct memstream_buffer *buf = problem_report_get_section_buffer(self, section_name); +- return buf == NULL ? NULL : memstream_get_stream(buf); +-} +- +-const char * +-problem_report_get_summary(const problem_report_t *self) +-{ +- assert(self != NULL); +- +- return memstream_get_string(self->pr_sec_summ); +-} +- +-const char * +-problem_report_get_description(const problem_report_t *self) +-{ +- assert(self != NULL); +- +- return memstream_get_string(self->pr_sec_desc); +-} +- +-const char * +-problem_report_get_section(const problem_report_t *self, const char *section_name) +-{ +- assert(self != NULL); +- assert(section_name); +- +- struct memstream_buffer *buf = problem_report_get_section_buffer(self, section_name); +- +- if (buf == NULL) +- return NULL; +- +- return memstream_get_string(buf); +-} +- +-static void +-problem_report_set_attachments(problem_report_t *self, GList *attachments) +-{ +- assert(self != NULL); +- assert(self->pr_attachments == NULL); +- +- self->pr_attachments = attachments; +-} +- +-GList * +-problem_report_get_attachments(const problem_report_t *self) +-{ +- assert(self != NULL); +- +- return self->pr_attachments; +-} +- +-void +-problem_report_free(problem_report_t *self) +-{ +- if (self == NULL) +- return; +- +- memstream_buffer_free(self->pr_sec_summ); +- self->pr_sec_summ = DESTROYED_POINTER; +- +- memstream_buffer_free(self->pr_sec_desc); +- self->pr_sec_desc = DESTROYED_POINTER; +- +- g_list_free_full(self->pr_attachments, free); +- self->pr_attachments = DESTROYED_POINTER; +- +- if (self->pr_sec_custom) +- { +- problem_report_destroy_custom_sections(self); +- self->pr_sec_custom = DESTROYED_POINTER; +- } +- +- free(self); +-} +- +-/* +- * Problem Formatter - extra section +- */ +-struct extra_section +-{ +- char *pfes_name; ///< name with % prefix +- int pfes_flags; ///< whether is required or not +-}; +- +-static struct extra_section * +-extra_section_new(const char *name, int flags) +-{ +- struct extra_section *self = xmalloc(sizeof(*self)); +- +- self->pfes_name = xstrdup(name); +- self->pfes_flags = flags; +- +- return self; +-} +- +-static void +-extra_section_free(struct extra_section *self) +-{ +- if (self == NULL) +- return; +- +- free(self->pfes_name); +- self->pfes_name = DESTROYED_POINTER; +- +- free(self); +-} +- +-static int +-extra_section_name_cmp(struct extra_section *lhs, const char *rhs) +-{ +- return strcmp(lhs->pfes_name, rhs); +-} +- +-/* +- * Problem Formatter +- * +- * Holds parsed sections lists. +- */ +-struct problem_formatter +-{ +- GList *pf_sections; ///< parsed sections (struct section_t) +- GList *pf_extra_sections; ///< user configured sections (struct extra_section) +- char *pf_default_summary; ///< default summary format +-}; +- +-problem_formatter_t * +-problem_formatter_new(void) +-{ +- problem_formatter_t *self = xzalloc(sizeof(*self)); +- +- self->pf_default_summary = xstrdup("%reason%"); +- +- return self; +-} +- +-void +-problem_formatter_free(problem_formatter_t *self) +-{ +- if (self == NULL) +- return; +- +- g_list_free_full(self->pf_sections, (GDestroyNotify)section_free); +- self->pf_sections = DESTROYED_POINTER; +- +- g_list_free_full(self->pf_extra_sections, (GDestroyNotify)extra_section_free); +- self->pf_extra_sections = DESTROYED_POINTER; +- +- free(self->pf_default_summary); +- self->pf_default_summary = DESTROYED_POINTER; +- +- free(self); +-} +- +-static int +-problem_formatter_is_section_known(problem_formatter_t *self, const char *name) +-{ +- return strcmp(name, "summary") == 0 +- || strcmp(name, "attach") == 0 +- || strcmp(name, "description") == 0 +- || NULL != g_list_find_custom(self->pf_extra_sections, name, (GCompareFunc)extra_section_name_cmp); +-} +- +-// i.e additional_info -> no flags +-int +-problem_formatter_add_section(problem_formatter_t *self, const char *name, int flags) +-{ +- /* Do not add already added sections */ +- if (problem_formatter_is_section_known(self, name)) +- { +- log_debug("Extra section already exists : '%s' ", name); +- return -EEXIST; +- } +- +- self->pf_extra_sections = g_list_prepend(self->pf_extra_sections, +- extra_section_new(name, flags)); +- +- return 0; +-} +- +-// check format validity and produce warnings +-static int +-problem_formatter_validate(problem_formatter_t *self) +-{ +- int retval = 0; +- +- /* Go through all (struct extra_section)s and check whete those having flag +- * PFFF_REQUIRED are present in the parsed (struct section_t)s. +- */ +- for (GList *iter = self->pf_extra_sections; iter; iter = g_list_next(iter)) +- { +- struct extra_section *section = (struct extra_section *)iter->data; +- +- log_debug("Validating extra section : '%s'", section->pfes_name); +- +- if ( (PFFF_REQUIRED & section->pfes_flags) +- && NULL == g_list_find_custom(self->pf_sections, section->pfes_name, (GCompareFunc)section_name_cmp)) +- { +- log_warning("Problem format misses required section : '%s'", section->pfes_name); +- ++retval; +- } +- } +- +- /* Go through all the parsed (struct section_t)s check whether are all +- * known, i.e. each section is either one of the common sections (summary, +- * description, attach) or is present in the (struct extra_section)s. +- */ +- for (GList *iter = self->pf_sections; iter; iter = g_list_next(iter)) +- { +- section_t *section = (section_t *)iter->data; +- +- if (!problem_formatter_is_section_known(self, (section->name + 1))) +- { +- log_warning("Problem format contains unrecognized section : '%s'", section->name); +- ++retval; +- } +- } +- +- return retval; +-} +- +-int +-problem_formatter_load_string(problem_formatter_t *self, const char *fmt) +-{ +- const size_t len = strlen(fmt); +- if (len != 0) +- { +- FILE *fp = fmemopen((void *)fmt, len, "r"); +- if (fp == NULL) +- { +- error_msg("Not enough memory to open a stream for reading format string."); +- return -ENOMEM; +- } +- +- self->pf_sections = load_stream(fp); +- fclose(fp); +- } +- +- return problem_formatter_validate(self); +-} +- +-int +-problem_formatter_load_file(problem_formatter_t *self, const char *path) +-{ +- FILE *fp = stdin; +- if (strcmp(path, "-") != 0) +- { +- fp = fopen(path, "r"); +- if (!fp) +- return -ENOENT; +- } +- +- self->pf_sections = load_stream(fp); +- +- if (fp != stdin) +- fclose(fp); +- +- return problem_formatter_validate(self); +-} +- +-// generates report +-int +-problem_formatter_generate_report(const problem_formatter_t *self, problem_data_t *data, problem_report_t **report) +-{ +- problem_report_t *pr = problem_report_new(); +- +- for (GList *iter = self->pf_extra_sections; iter; iter = g_list_next(iter)) +- problem_report_add_custom_section(pr, ((struct extra_section *)iter->data)->pfes_name); +- +- bool has_summary = false; +- for (GList *iter = self->pf_sections; iter; iter = g_list_next(iter)) +- { +- section_t *section = (section_t *)iter->data; +- +- /* %summary is something special */ +- if (strcmp(section->name, "%summary") == 0) +- { +- has_summary = true; +- format_percented_string((const char *)section->items->data, data, +- problem_report_get_buffer(pr, PR_SEC_SUMMARY)); +- } +- /* %attach as well */ +- else if (strcmp(section->name, "%attach") == 0) +- { +- problem_report_set_attachments(pr, get_attached_files(data, section->items, self->pf_sections)); +- } +- else /* %description or a custom section (e.g. %additional_info) */ +- { +- FILE *buffer = problem_report_get_buffer(pr, section->name + 1); +- +- if (buffer != NULL) +- { +- log_debug("Formatting section : '%s'", section->name); +- format_section(section, data, self->pf_sections, buffer); +- } +- else +- log_warning("Unsupported section '%s'", section->name); +- } +- } +- +- if (!has_summary) { +- log_debug("Problem format misses section '%%summary'. Using the default one : '%s'.", +- self->pf_default_summary); +- +- format_percented_string(self->pf_default_summary, +- data, problem_report_get_buffer(pr, PR_SEC_SUMMARY)); +- } +- +- *report = pr; +- return 0; +-} +diff --git a/src/plugins/Makefile.am b/src/plugins/Makefile.am +index 27194a4..f4f94ff 100644 +--- a/src/plugins/Makefile.am ++++ b/src/plugins/Makefile.am +@@ -125,6 +125,17 @@ EXTRA_DIST = $(reporters_extra_dist) \ + $(DESTDIR)/$(DEBUG_INFO_DIR): + $(mkdir_p) '$@' + ++noinst_LIBRARIES = libreport-problem-report.a ++libreport_problem_report_a_SOURCES = \ ++ problem_report.c \ ++ problem_report.h ++libreport_problem_report_a_CFLAGS = \ ++ -I$(srcdir)/../include \ ++ $(LIBREPORT_CFLAGS) \ ++ $(GLIB_CFLAGS) \ ++ $(SATYR_CFLAGS) \ ++ -D_GNU_SOURCE ++ + if BUILD_BUGZILLA + reporter_bugzilla_SOURCES = \ + reporter-bugzilla.c rhbz.c rhbz.h +@@ -146,7 +157,8 @@ reporter_bugzilla_LDADD = \ + $(GLIB_LIBS) \ + $(XMLRPC_LIBS) $(XMLRPC_CLIENT_LIBS) \ + ../lib/libreport-web.la \ +- ../lib/libreport.la ++ ../lib/libreport.la \ ++ libreport-problem-report.a + endif + + if BUILD_MANTISBT +@@ -169,7 +181,8 @@ reporter_mantisbt_CPPFLAGS = \ + reporter_mantisbt_LDADD = \ + $(GLIB_LIBS) \ + ../lib/libreport-web.la \ +- ../lib/libreport.la ++ ../lib/libreport.la \ ++ libreport-problem-report.a + endif + + reporter_rhtsupport_SOURCES = \ +@@ -196,7 +209,8 @@ reporter_rhtsupport_LDADD = \ + $(GLIB_LIBS) \ + $(LIBXML_LIBS) \ + ../lib/libreport-web.la \ +- ../lib/libreport.la ++ ../lib/libreport.la \ ++ libreport-problem-report.a + + reporter_upload_SOURCES = \ + reporter-upload.c +@@ -255,7 +269,9 @@ reporter_mailx_CPPFLAGS = \ + $(LIBREPORT_CFLAGS) \ + -D_GNU_SOURCE + reporter_mailx_LDADD = \ +- ../lib/libreport.la ++ ../lib/libreport.la \ ++ $(SATYR_LIBS) \ ++ libreport-problem-report.a + + reporter_print_SOURCES = \ + reporter-print.c +diff --git a/src/plugins/problem_report.c b/src/plugins/problem_report.c +new file mode 100644 +index 0000000..2bf5530 +--- /dev/null ++++ b/src/plugins/problem_report.c +@@ -0,0 +1,1210 @@ ++/* ++ Copyright (C) 2014 ABRT team ++ Copyright (C) 2014 RedHat Inc ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 2 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License along ++ with this program; if not, write to the Free Software Foundation, Inc., ++ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ++*/ ++ ++#include "problem_report.h" ++#include "internal_libreport.h" ++ ++#include <satyr/stacktrace.h> ++#include <satyr/abrt.h> ++ ++#include <assert.h> ++ ++#define DESTROYED_POINTER (void *)0xdeadbeef ++ ++/* FORMAT: ++ * |%summary:: Hello, world ++ * |Problem description:: %bare_comment ++ * | ++ * |Package:: package ++ * | ++ * |%attach: %binary, backtrace ++ * | ++ * |%additional_info:: ++ * |%reporter% ++ * |User:: user_name,uid ++ * | ++ * |Directories:: root,cwd ++ * ++ * PARSED DATA (list of struct section_t): ++ * { ++ * section_t { ++ * .name = '%summary'; ++ * .items = { 'Hello, world' }; ++ * .children = NULL; ++ * }, ++ * section_t { ++ * .name = '%attach' ++ * .items = { '%binary', 'backtrace' }; ++ * .children = NULL; ++ * }, ++ * section_t { ++ * .name = '%description' ++ * .items = NULL; ++ * .children = { ++ * section_t { ++ * .name = 'Problem description:'; ++ * .items = { '%bare_comment' }; ++ * .children = NULL; ++ * }, ++ * section_t { ++ * .name = ''; ++ * .items = NULL; ++ * .children = NULL; ++ * }, ++ * section_t { ++ * .name = 'Package:'; ++ * .items = { 'package' }; ++ * .children = NULL; ++ * }, ++ * } ++ * }, ++ * section_t { ++ * .name = '%additional_info' ++ * .items = { '%reporter%' }; ++ * .children = { ++ * section_t { ++ * .name = 'User:'; ++ * .items = { 'user_name', 'uid' }; ++ * .children = NULL; ++ * }, ++ * section_t { ++ * .name = ''; ++ * .items = NULL; ++ * .children = NULL; ++ * }, ++ * section_t { ++ * .name = 'Directories:'; ++ * .items = { 'root', 'cwd' }; ++ * .children = NULL; ++ * }, ++ * } ++ * } ++ * } ++ */ ++struct section_t { ++ char *name; ///< name or output text (%summar, 'Package version:'); ++ GList *items; ///< list of file names and special items (%reporter, %binar, ...) ++ GList *children; ///< list of sub sections (struct section_t) ++}; ++ ++typedef struct section_t section_t; ++ ++static section_t * ++section_new(const char *name) ++{ ++ section_t *self = xmalloc(sizeof(*self)); ++ self->name = xstrdup(name); ++ self->items = NULL; ++ self->children = NULL; ++ ++ return self; ++} ++ ++static void ++section_free(section_t *self) ++{ ++ if (self == NULL) ++ return; ++ ++ free(self->name); ++ g_list_free_full(self->items, free); ++ g_list_free_full(self->children, (GDestroyNotify)section_free); ++ ++ free(self); ++} ++ ++static int ++section_name_cmp(section_t *lhs, const char *rhs) ++{ ++ return strcmp((lhs->name + 1), rhs); ++} ++ ++/* Utility functions */ ++ ++static GList* ++split_string_on_char(const char *str, char ch) ++{ ++ GList *list = NULL; ++ for (;;) ++ { ++ const char *delim = strchrnul(str, ch); ++ list = g_list_prepend(list, xstrndup(str, delim - str)); ++ if (*delim == '\0') ++ break; ++ str = delim + 1; ++ } ++ return g_list_reverse(list); ++} ++ ++static int ++compare_item_name(const char *lookup, const char *name) ++{ ++ if (lookup[0] == '-') ++ lookup++; ++ else if (strncmp(lookup, "%bare_", 6) == 0) ++ lookup += 6; ++ return strcmp(lookup, name); ++} ++ ++static int ++is_item_name_in_section(const section_t *lookup, const char *name) ++{ ++ if (g_list_find_custom(lookup->items, name, (GCompareFunc)compare_item_name)) ++ return 0; /* "found it!" */ ++ return 1; ++} ++ ++static bool is_explicit_or_forbidden(const char *name, GList *comment_fmt_spec); ++ ++static int ++is_explicit_or_forbidden_child(const section_t *master_section, const char *name) ++{ ++ if (is_explicit_or_forbidden(name, master_section->children)) ++ return 0; /* "found it!" */ ++ return 1; ++} ++ ++/* For example: 'package' belongs to '%oneline', but 'package' is used in ++ * 'Version of component', so it is not very helpful to include that file once ++ * more in another section ++ */ ++static bool ++is_explicit_or_forbidden(const char *name, GList *comment_fmt_spec) ++{ ++ return g_list_find_custom(comment_fmt_spec, name, (GCompareFunc)is_item_name_in_section) ++ || g_list_find_custom(comment_fmt_spec, name, (GCompareFunc)is_explicit_or_forbidden_child); ++} ++ ++static GList* ++load_stream(FILE *fp) ++{ ++ assert(fp); ++ ++ GList *sections = NULL; ++ section_t *master = section_new("%description"); ++ section_t *sec = NULL; ++ ++ sections = g_list_append(sections, master); ++ ++ char *line; ++ while ((line = xmalloc_fgetline(fp)) != NULL) ++ { ++ /* Skip comments */ ++ char first = *skip_whitespace(line); ++ if (first == '#') ++ goto free_line; ++ ++ /* Handle trailing backslash continuation */ ++ check_continuation: ; ++ unsigned len = strlen(line); ++ if (len && line[len-1] == '\\') ++ { ++ line[len-1] = '\0'; ++ char *next_line = xmalloc_fgetline(fp); ++ if (next_line) ++ { ++ line = append_to_malloced_string(line, next_line); ++ free(next_line); ++ goto check_continuation; ++ } ++ } ++ ++ /* We are reusing line buffer to form temporary ++ * "key\0values\0..." in its beginning ++ */ ++ bool summary_line = false; ++ char *value = NULL; ++ char *src; ++ char *dst; ++ for (src = dst = line; *src; src++) ++ { ++ char c = *src; ++ /* did we reach the value list? */ ++ if (!value && c == ':' && src[1] == ':') ++ { ++ *dst++ = '\0'; /* terminate key */ ++ src += 1; ++ value = dst; /* remember where value starts */ ++ summary_line = (strcmp(line, "%summary") == 0); ++ if (summary_line) ++ { ++ value = (src + 1); ++ break; ++ } ++ continue; ++ } ++ /* skip whitespace in value list */ ++ if (value && isspace(c)) ++ continue; ++ *dst++ = c; /* store next key or value char */ ++ } ++ ++ GList *item_list = NULL; ++ if (summary_line) ++ { ++ /* %summary is special */ ++ item_list = g_list_append(NULL, xstrdup(skip_whitespace(value))); ++ } ++ else ++ { ++ *dst = '\0'; /* terminate value (or key) */ ++ if (value) ++ item_list = split_string_on_char(value, ','); ++ } ++ ++ sec = section_new(line); ++ sec->items = item_list; ++ ++ if (sec->name[0] == '%') ++ { ++ if (!summary_line && strcmp(sec->name, "%attach") != 0) ++ { ++ master->children = g_list_reverse(master->children); ++ master = sec; ++ } ++ ++ sections = g_list_prepend(sections, sec); ++ } ++ else ++ master->children = g_list_prepend(master->children, sec); ++ ++ free_line: ++ free(line); ++ } ++ ++ /* If master equals sec, then master's children list was not yet reversed. ++ * ++ * %description is the default section (i.e is not explicitly mentioned) ++ * and %summary nor %attach cause its children list to reverse. ++ */ ++ if (master == sec || strcmp(master->name, "%description") == 0) ++ master->children = g_list_reverse(master->children); ++ ++ return sections; ++} ++ ++ ++/* Summary generation */ ++ ++#define MAX_OPT_DEPTH 10 ++static int ++format_percented_string(const char *str, problem_data_t *pd, FILE *result) ++{ ++ long old_pos[MAX_OPT_DEPTH] = { 0 }; ++ int okay[MAX_OPT_DEPTH] = { 1 }; ++ long len = 0; ++ int opt_depth = 1; ++ ++ while (*str) { ++ switch (*str) { ++ default: ++ putc(*str, result); ++ len++; ++ str++; ++ break; ++ case '\\': ++ if (str[1]) ++ str++; ++ putc(*str, result); ++ len++; ++ str++; ++ break; ++ case '[': ++ if (str[1] == '[' && opt_depth < MAX_OPT_DEPTH) ++ { ++ old_pos[opt_depth] = len; ++ okay[opt_depth] = 1; ++ opt_depth++; ++ str += 2; ++ } else { ++ putc(*str, result); ++ len++; ++ str++; ++ } ++ break; ++ case ']': ++ if (str[1] == ']' && opt_depth > 1) ++ { ++ opt_depth--; ++ if (!okay[opt_depth]) ++ { ++ if (fseek(result, old_pos[opt_depth], SEEK_SET) < 0) ++ perror_msg_and_die("fseek"); ++ len = old_pos[opt_depth]; ++ } ++ str += 2; ++ } else { ++ putc(*str, result); ++ len++; ++ str++; ++ } ++ break; ++ case '%': ; ++ char *nextpercent = strchr(++str, '%'); ++ if (!nextpercent) ++ { ++ error_msg_and_die("Unterminated %%element%%: '%s'", str - 1); ++ } ++ ++ *nextpercent = '\0'; ++ const problem_item *item = problem_data_get_item_or_NULL(pd, str); ++ *nextpercent = '%'; ++ ++ if (item && (item->flags & CD_FLAG_TXT)) ++ { ++ fputs(item->content, result); ++ len += strlen(item->content); ++ } ++ else ++ okay[opt_depth - 1] = 0; ++ str = nextpercent + 1; ++ break; ++ } ++ } ++ ++ if (opt_depth > 1) ++ { ++ error_msg_and_die("Unbalanced [[ ]] bracket"); ++ } ++ ++ if (!okay[0]) ++ { ++ error_msg("Undefined variable outside of [[ ]] bracket"); ++ } ++ ++ return 0; ++} ++ ++/* BZ comment generation */ ++ ++static int ++append_text(struct strbuf *result, const char *item_name, const char *content, bool print_item_name) ++{ ++ char *eol = strchrnul(content, '\n'); ++ if (eol[0] == '\0' || eol[1] == '\0') ++ { ++ /* one-liner */ ++ int pad = 16 - (strlen(item_name) + 2); ++ if (pad < 0) ++ pad = 0; ++ if (print_item_name) ++ strbuf_append_strf(result, ++ eol[0] == '\0' ? "%s: %*s%s\n" : "%s: %*s%s", ++ item_name, pad, "", content ++ ); ++ else ++ strbuf_append_strf(result, ++ eol[0] == '\0' ? "%s\n" : "%s", ++ content ++ ); ++ } ++ else ++ { ++ /* multi-line item */ ++ if (print_item_name) ++ strbuf_append_strf(result, "%s:\n", item_name); ++ for (;;) ++ { ++ eol = strchrnul(content, '\n'); ++ strbuf_append_strf(result, ++ /* For %bare_multiline_item, we don't want to print colons */ ++ (print_item_name ? ":%.*s\n" : "%.*s\n"), ++ (int)(eol - content), content ++ ); ++ if (eol[0] == '\0' || eol[1] == '\0') ++ break; ++ content = eol + 1; ++ } ++ } ++ return 1; ++} ++ ++static int ++append_short_backtrace(struct strbuf *result, problem_data_t *problem_data, size_t max_text_size, bool print_item_name) ++{ ++ const problem_item *item = problem_data_get_item_or_NULL(problem_data, ++ FILENAME_BACKTRACE); ++ if (!item) ++ return 0; /* "I did not print anything" */ ++ if (!(item->flags & CD_FLAG_TXT)) ++ return 0; /* "I did not print anything" */ ++ ++ char *truncated = NULL; ++ ++ if (strlen(item->content) >= max_text_size) ++ { ++ log_debug("'backtrace' exceeds the text file size, going to append its short version"); ++ ++ char *error_msg = NULL; ++ const char *type = problem_data_get_content_or_NULL(problem_data, FILENAME_TYPE); ++ if (!type) ++ { ++ log_debug("Problem data does not contain '"FILENAME_TYPE"' file"); ++ return 0; ++ } ++ ++ /* For CCpp crashes, use the GDB-produced backtrace which should be ++ * available by now. sr_abrt_type_from_analyzer returns SR_REPORT_CORE ++ * by default for CCpp crashes. ++ */ ++ enum sr_report_type report_type = sr_abrt_type_from_analyzer(type); ++ if (strcmp(type, "CCpp") == 0) ++ { ++ log_debug("Successfully identified 'CCpp' abrt type"); ++ report_type = SR_REPORT_GDB; ++ } ++ ++ struct sr_stacktrace *backtrace = sr_stacktrace_parse(report_type, ++ item->content, &error_msg); ++ ++ if (!backtrace) ++ { ++ log(_("Can't parse backtrace: %s"), error_msg); ++ free(error_msg); ++ return 0; ++ } ++ ++ /* Get optimized thread stack trace for 10 top most frames */ ++ truncated = sr_stacktrace_to_short_text(backtrace, 10); ++ sr_stacktrace_free(backtrace); ++ ++ if (!truncated) ++ { ++ log(_("Can't generate stacktrace description (no crash thread?)")); ++ return 0; ++ } ++ } ++ else ++ { ++ log_debug("'backtrace' is small enough to be included as is"); ++ } ++ ++ append_text(result, ++ /*item_name:*/ truncated ? "truncated_backtrace" : FILENAME_BACKTRACE, ++ /*content:*/ truncated ? truncated : item->content, ++ print_item_name ++ ); ++ free(truncated); ++ return 1; ++} ++ ++static int ++append_item(struct strbuf *result, const char *item_name, problem_data_t *pd, GList *comment_fmt_spec) ++{ ++ bool print_item_name = (strncmp(item_name, "%bare_", strlen("%bare_")) != 0); ++ if (!print_item_name) ++ item_name += strlen("%bare_"); ++ ++ if (item_name[0] != '%') ++ { ++ struct problem_item *item = problem_data_get_item_or_NULL(pd, item_name); ++ if (!item) ++ return 0; /* "I did not print anything" */ ++ if (!(item->flags & CD_FLAG_TXT)) ++ return 0; /* "I did not print anything" */ ++ ++ char *formatted = problem_item_format(item); ++ char *content = formatted ? formatted : item->content; ++ append_text(result, item_name, content, print_item_name); ++ free(formatted); ++ return 1; /* "I printed something" */ ++ } ++ ++ /* Special item name */ ++ ++ /* Compat with previously-existed ad-hockery: %short_backtrace */ ++ if (strcmp(item_name, "%short_backtrace") == 0) ++ return append_short_backtrace(result, pd, CD_TEXT_ATT_SIZE_BZ, print_item_name); ++ ++ /* Compat with previously-existed ad-hockery: %reporter */ ++ if (strcmp(item_name, "%reporter") == 0) ++ return append_text(result, "reporter", PACKAGE"-"VERSION, print_item_name); ++ ++ /* %oneline,%multiline,%text */ ++ bool oneline = (strcmp(item_name+1, "oneline" ) == 0); ++ bool multiline = (strcmp(item_name+1, "multiline") == 0); ++ bool text = (strcmp(item_name+1, "text" ) == 0); ++ if (!oneline && !multiline && !text) ++ { ++ log("Unknown or unsupported element specifier '%s'", item_name); ++ return 0; /* "I did not print anything" */ ++ } ++ ++ int printed = 0; ++ ++ /* Iterate over _sorted_ items */ ++ GList *sorted_names = g_hash_table_get_keys(pd); ++ sorted_names = g_list_sort(sorted_names, (GCompareFunc)strcmp); ++ ++ /* %text => do as if %oneline, then repeat as if %multiline */ ++ if (text) ++ oneline = 1; ++ ++ again: ; ++ GList *l = sorted_names; ++ while (l) ++ { ++ const char *name = l->data; ++ l = l->next; ++ struct problem_item *item = g_hash_table_lookup(pd, name); ++ if (!item) ++ continue; /* paranoia, won't happen */ ++ ++ if (!(item->flags & CD_FLAG_TXT)) ++ continue; ++ ++ if (is_explicit_or_forbidden(name, comment_fmt_spec)) ++ continue; ++ ++ char *formatted = problem_item_format(item); ++ char *content = formatted ? formatted : item->content; ++ char *eol = strchrnul(content, '\n'); ++ bool is_oneline = (eol[0] == '\0' || eol[1] == '\0'); ++ if (oneline == is_oneline) ++ printed |= append_text(result, name, content, print_item_name); ++ free(formatted); ++ } ++ if (text && oneline) ++ { ++ /* %text, and we just did %oneline. Repeat as if %multiline */ ++ oneline = 0; ++ /*multiline = 1; - not checked in fact, so why bother setting? */ ++ goto again; ++ } ++ ++ g_list_free(sorted_names); /* names themselves are not freed */ ++ ++ return printed; ++} ++ ++#define add_to_section_output(format, ...) \ ++ do { \ ++ for (; empty_lines > 0; --empty_lines) fputc('\n', result); \ ++ empty_lines = 0; \ ++ fprintf(result, format, __VA_ARGS__); \ ++ } while (0) ++ ++static void ++format_section(section_t *section, problem_data_t *pd, GList *comment_fmt_spec, FILE *result) ++{ ++ int empty_lines = -1; ++ ++ for (GList *iter = section->children; iter; iter = g_list_next(iter)) ++ { ++ section_t *child = (section_t *)iter->data; ++ if (child->items) ++ { ++ /* "Text: item[,item]..." */ ++ struct strbuf *output = strbuf_new(); ++ GList *item = child->items; ++ while (item) ++ { ++ const char *str = item->data; ++ item = item->next; ++ if (str[0] == '-') /* "-name", ignore it */ ++ continue; ++ append_item(output, str, pd, comment_fmt_spec); ++ } ++ ++ if (output->len != 0) ++ add_to_section_output((child->name[0] ? "%s:\n%s" : "%s%s"), ++ child->name, output->buf); ++ ++ strbuf_free(output); ++ } ++ else ++ { ++ /* Just "Text" (can be "") */ ++ ++ /* Filter out trailint empty lines */ ++ if (child->name[0] != '\0') ++ add_to_section_output("%s\n", child->name); ++ /* Do not count empty lines, if output wasn't yet produced */ ++ else if (empty_lines >= 0) ++ ++empty_lines; ++ } ++ } ++} ++ ++static GList * ++get_special_items(const char *item_name, problem_data_t *pd, GList *comment_fmt_spec) ++{ ++ /* %oneline,%multiline,%text,%binary */ ++ bool oneline = (strcmp(item_name+1, "oneline" ) == 0); ++ bool multiline = (strcmp(item_name+1, "multiline") == 0); ++ bool text = (strcmp(item_name+1, "text" ) == 0); ++ bool binary = (strcmp(item_name+1, "binary" ) == 0); ++ if (!oneline && !multiline && !text && !binary) ++ { ++ log("Unknown or unsupported element specifier '%s'", item_name); ++ return NULL; ++ } ++ ++ log_debug("Special item_name '%s', iterating for attach...", item_name); ++ GList *result = 0; ++ ++ /* Iterate over _sorted_ items */ ++ GList *sorted_names = g_hash_table_get_keys(pd); ++ sorted_names = g_list_sort(sorted_names, (GCompareFunc)strcmp); ++ ++ GList *l = sorted_names; ++ while (l) ++ { ++ const char *name = l->data; ++ l = l->next; ++ struct problem_item *item = g_hash_table_lookup(pd, name); ++ if (!item) ++ continue; /* paranoia, won't happen */ ++ ++ if (is_explicit_or_forbidden(name, comment_fmt_spec)) ++ continue; ++ ++ if ((item->flags & CD_FLAG_TXT) && !binary) ++ { ++ char *content = item->content; ++ char *eol = strchrnul(content, '\n'); ++ bool is_oneline = (eol[0] == '\0' || eol[1] == '\0'); ++ if (text || oneline == is_oneline) ++ result = g_list_append(result, xstrdup(name)); ++ } ++ else if ((item->flags & CD_FLAG_BIN) && binary) ++ result = g_list_append(result, xstrdup(name)); ++ } ++ ++ g_list_free(sorted_names); /* names themselves are not freed */ ++ ++ ++ log_debug("...Done iterating over '%s' for attach", item_name); ++ ++ return result; ++} ++ ++static GList * ++get_attached_files(problem_data_t *pd, GList *items, GList *comment_fmt_spec) ++{ ++ GList *result = NULL; ++ GList *item = items; ++ while (item != NULL) ++ { ++ const char *item_name = item->data; ++ item = item->next; ++ if (item_name[0] == '-') /* "-name", ignore it */ ++ continue; ++ ++ if (item_name[0] != '%') ++ { ++ result = g_list_append(result, xstrdup(item_name)); ++ continue; ++ } ++ ++ GList *special = get_special_items(item_name, pd, comment_fmt_spec); ++ if (special == NULL) ++ { ++ log_notice("No attachment found for '%s'", item_name); ++ continue; ++ } ++ ++ result = g_list_concat(result, special); ++ } ++ ++ return result; ++} ++ ++/* ++ * Problem Report - memor stream ++ * ++ * A wrapper for POSIX memory stream. ++ * ++ * A memory stream is presented as FILE *. ++ * ++ * A memory stream is associated with a pointer to written data and a pointer ++ * to size of the written data. ++ * ++ * This structure holds all of the used pointers. ++ */ ++struct memstream_buffer ++{ ++ char *msb_buffer; ++ size_t msb_size; ++ FILE *msb_stream; ++}; ++ ++static struct memstream_buffer * ++memstream_buffer_new() ++{ ++ struct memstream_buffer *self = xmalloc(sizeof(*self)); ++ ++ self->msb_buffer = NULL; ++ self->msb_stream = open_memstream(&(self->msb_buffer), &(self->msb_size)); ++ ++ return self; ++} ++ ++static void ++memstream_buffer_free(struct memstream_buffer *self) ++{ ++ if (self == NULL) ++ return; ++ ++ fclose(self->msb_stream); ++ self->msb_stream = DESTROYED_POINTER; ++ ++ free(self->msb_buffer); ++ self->msb_buffer = DESTROYED_POINTER; ++ ++ free(self); ++} ++ ++static FILE * ++memstream_get_stream(struct memstream_buffer *self) ++{ ++ assert(self != NULL); ++ ++ return self->msb_stream; ++} ++ ++static const char * ++memstream_get_string(struct memstream_buffer *self) ++{ ++ assert(self != NULL); ++ assert(self->msb_stream != NULL); ++ ++ fflush(self->msb_stream); ++ ++ return self->msb_buffer; ++} ++ ++ ++/* ++ * Problem Report ++ * ++ * The formated strings are internaly stored in "buffer"s. If a programer wants ++ * to get a formated section data, a getter function extracts those data from ++ * the apropriate buffer and returns them in form of null-terminated string. ++ * ++ * Each section has own buffer. ++ * ++ * There are three common sections that are always present: ++ * 1. summary ++ * 2. description ++ * 3. attach ++ * Buffers of these sections has own structure member for the sake of ++ * efficiency. ++ * ++ * The custom sections hash their buffers stored in a map where key is a ++ * section's name and value is a section's buffer. ++ * ++ * Problem report provides the programers with the possibility to ammend ++ * formated output to any section buffer. ++ */ ++struct problem_report ++{ ++ struct memstream_buffer *pr_sec_summ; ///< %summary buffer ++ struct memstream_buffer *pr_sec_desc; ///< %description buffer ++ GList *pr_attachments; ///< %attach - list of file names ++ GHashTable *pr_sec_custom; ///< map : %(custom section) -> buffer ++}; ++ ++static problem_report_t * ++problem_report_new() ++{ ++ problem_report_t *self = xmalloc(sizeof(*self)); ++ ++ self->pr_sec_summ = memstream_buffer_new(); ++ self->pr_sec_desc = memstream_buffer_new(); ++ self->pr_attachments = NULL; ++ self->pr_sec_custom = NULL; ++ ++ return self; ++} ++ ++static void ++problem_report_initialize_custom_sections(problem_report_t *self) ++{ ++ assert(self != NULL); ++ assert(self->pr_sec_custom == NULL); ++ ++ self->pr_sec_custom = g_hash_table_new_full(g_str_hash, g_str_equal, free, ++ (GDestroyNotify)memstream_buffer_free); ++} ++ ++static void ++problem_report_destroy_custom_sections(problem_report_t *self) ++{ ++ assert(self != NULL); ++ assert(self->pr_sec_custom != NULL); ++ ++ g_hash_table_destroy(self->pr_sec_custom); ++} ++ ++static int ++problem_report_add_custom_section(problem_report_t *self, const char *name) ++{ ++ assert(self != NULL); ++ ++ if (self->pr_sec_custom == NULL) ++ { ++ problem_report_initialize_custom_sections(self); ++ } ++ ++ if (problem_report_get_buffer(self, name)) ++ { ++ log_warning("Custom section already exists : '%s'", name); ++ return -EEXIST; ++ } ++ ++ log_debug("Problem report enriched with section : '%s'", name); ++ g_hash_table_insert(self->pr_sec_custom, xstrdup(name), memstream_buffer_new()); ++ return 0; ++} ++ ++static struct memstream_buffer * ++problem_report_get_section_buffer(const problem_report_t *self, const char *section_name) ++{ ++ if (self->pr_sec_custom == NULL) ++ { ++ log_debug("Couldn't find section '%s': no custom section added", section_name); ++ return NULL; ++ } ++ ++ return (struct memstream_buffer *)g_hash_table_lookup(self->pr_sec_custom, section_name); ++} ++ ++problem_report_buffer * ++problem_report_get_buffer(const problem_report_t *self, const char *section_name) ++{ ++ assert(self != NULL); ++ assert(section_name != NULL); ++ ++ if (strcmp(PR_SEC_SUMMARY, section_name) == 0) ++ return memstream_get_stream(self->pr_sec_summ); ++ ++ if (strcmp(PR_SEC_DESCRIPTION, section_name) == 0) ++ return memstream_get_stream(self->pr_sec_desc); ++ ++ struct memstream_buffer *buf = problem_report_get_section_buffer(self, section_name); ++ return buf == NULL ? NULL : memstream_get_stream(buf); ++} ++ ++const char * ++problem_report_get_summary(const problem_report_t *self) ++{ ++ assert(self != NULL); ++ ++ return memstream_get_string(self->pr_sec_summ); ++} ++ ++const char * ++problem_report_get_description(const problem_report_t *self) ++{ ++ assert(self != NULL); ++ ++ return memstream_get_string(self->pr_sec_desc); ++} ++ ++const char * ++problem_report_get_section(const problem_report_t *self, const char *section_name) ++{ ++ assert(self != NULL); ++ assert(section_name); ++ ++ struct memstream_buffer *buf = problem_report_get_section_buffer(self, section_name); ++ ++ if (buf == NULL) ++ return NULL; ++ ++ return memstream_get_string(buf); ++} ++ ++static void ++problem_report_set_attachments(problem_report_t *self, GList *attachments) ++{ ++ assert(self != NULL); ++ assert(self->pr_attachments == NULL); ++ ++ self->pr_attachments = attachments; ++} ++ ++GList * ++problem_report_get_attachments(const problem_report_t *self) ++{ ++ assert(self != NULL); ++ ++ return self->pr_attachments; ++} ++ ++void ++problem_report_free(problem_report_t *self) ++{ ++ if (self == NULL) ++ return; ++ ++ memstream_buffer_free(self->pr_sec_summ); ++ self->pr_sec_summ = DESTROYED_POINTER; ++ ++ memstream_buffer_free(self->pr_sec_desc); ++ self->pr_sec_desc = DESTROYED_POINTER; ++ ++ g_list_free_full(self->pr_attachments, free); ++ self->pr_attachments = DESTROYED_POINTER; ++ ++ if (self->pr_sec_custom) ++ { ++ problem_report_destroy_custom_sections(self); ++ self->pr_sec_custom = DESTROYED_POINTER; ++ } ++ ++ free(self); ++} ++ ++/* ++ * Problem Formatter - extra section ++ */ ++struct extra_section ++{ ++ char *pfes_name; ///< name with % prefix ++ int pfes_flags; ///< whether is required or not ++}; ++ ++static struct extra_section * ++extra_section_new(const char *name, int flags) ++{ ++ struct extra_section *self = xmalloc(sizeof(*self)); ++ ++ self->pfes_name = xstrdup(name); ++ self->pfes_flags = flags; ++ ++ return self; ++} ++ ++static void ++extra_section_free(struct extra_section *self) ++{ ++ if (self == NULL) ++ return; ++ ++ free(self->pfes_name); ++ self->pfes_name = DESTROYED_POINTER; ++ ++ free(self); ++} ++ ++static int ++extra_section_name_cmp(struct extra_section *lhs, const char *rhs) ++{ ++ return strcmp(lhs->pfes_name, rhs); ++} ++ ++/* ++ * Problem Formatter ++ * ++ * Holds parsed sections lists. ++ */ ++struct problem_formatter ++{ ++ GList *pf_sections; ///< parsed sections (struct section_t) ++ GList *pf_extra_sections; ///< user configured sections (struct extra_section) ++ char *pf_default_summary; ///< default summary format ++}; ++ ++problem_formatter_t * ++problem_formatter_new(void) ++{ ++ problem_formatter_t *self = xzalloc(sizeof(*self)); ++ ++ self->pf_default_summary = xstrdup("%reason%"); ++ ++ return self; ++} ++ ++void ++problem_formatter_free(problem_formatter_t *self) ++{ ++ if (self == NULL) ++ return; ++ ++ g_list_free_full(self->pf_sections, (GDestroyNotify)section_free); ++ self->pf_sections = DESTROYED_POINTER; ++ ++ g_list_free_full(self->pf_extra_sections, (GDestroyNotify)extra_section_free); ++ self->pf_extra_sections = DESTROYED_POINTER; ++ ++ free(self->pf_default_summary); ++ self->pf_default_summary = DESTROYED_POINTER; ++ ++ free(self); ++} ++ ++static int ++problem_formatter_is_section_known(problem_formatter_t *self, const char *name) ++{ ++ return strcmp(name, "summary") == 0 ++ || strcmp(name, "attach") == 0 ++ || strcmp(name, "description") == 0 ++ || NULL != g_list_find_custom(self->pf_extra_sections, name, (GCompareFunc)extra_section_name_cmp); ++} ++ ++// i.e additional_info -> no flags ++int ++problem_formatter_add_section(problem_formatter_t *self, const char *name, int flags) ++{ ++ /* Do not add already added sections */ ++ if (problem_formatter_is_section_known(self, name)) ++ { ++ log_debug("Extra section already exists : '%s' ", name); ++ return -EEXIST; ++ } ++ ++ self->pf_extra_sections = g_list_prepend(self->pf_extra_sections, ++ extra_section_new(name, flags)); ++ ++ return 0; ++} ++ ++// check format validity and produce warnings ++static int ++problem_formatter_validate(problem_formatter_t *self) ++{ ++ int retval = 0; ++ ++ /* Go through all (struct extra_section)s and check whete those having flag ++ * PFFF_REQUIRED are present in the parsed (struct section_t)s. ++ */ ++ for (GList *iter = self->pf_extra_sections; iter; iter = g_list_next(iter)) ++ { ++ struct extra_section *section = (struct extra_section *)iter->data; ++ ++ log_debug("Validating extra section : '%s'", section->pfes_name); ++ ++ if ( (PFFF_REQUIRED & section->pfes_flags) ++ && NULL == g_list_find_custom(self->pf_sections, section->pfes_name, (GCompareFunc)section_name_cmp)) ++ { ++ log_warning("Problem format misses required section : '%s'", section->pfes_name); ++ ++retval; ++ } ++ } ++ ++ /* Go through all the parsed (struct section_t)s check whether are all ++ * known, i.e. each section is either one of the common sections (summary, ++ * description, attach) or is present in the (struct extra_section)s. ++ */ ++ for (GList *iter = self->pf_sections; iter; iter = g_list_next(iter)) ++ { ++ section_t *section = (section_t *)iter->data; ++ ++ if (!problem_formatter_is_section_known(self, (section->name + 1))) ++ { ++ log_warning("Problem format contains unrecognized section : '%s'", section->name); ++ ++retval; ++ } ++ } ++ ++ return retval; ++} ++ ++int ++problem_formatter_load_string(problem_formatter_t *self, const char *fmt) ++{ ++ const size_t len = strlen(fmt); ++ if (len != 0) ++ { ++ FILE *fp = fmemopen((void *)fmt, len, "r"); ++ if (fp == NULL) ++ { ++ error_msg("Not enough memory to open a stream for reading format string."); ++ return -ENOMEM; ++ } ++ ++ self->pf_sections = load_stream(fp); ++ fclose(fp); ++ } ++ ++ return problem_formatter_validate(self); ++} ++ ++int ++problem_formatter_load_file(problem_formatter_t *self, const char *path) ++{ ++ FILE *fp = stdin; ++ if (strcmp(path, "-") != 0) ++ { ++ fp = fopen(path, "r"); ++ if (!fp) ++ return -ENOENT; ++ } ++ ++ self->pf_sections = load_stream(fp); ++ ++ if (fp != stdin) ++ fclose(fp); ++ ++ return problem_formatter_validate(self); ++} ++ ++// generates report ++int ++problem_formatter_generate_report(const problem_formatter_t *self, problem_data_t *data, problem_report_t **report) ++{ ++ problem_report_t *pr = problem_report_new(); ++ ++ for (GList *iter = self->pf_extra_sections; iter; iter = g_list_next(iter)) ++ problem_report_add_custom_section(pr, ((struct extra_section *)iter->data)->pfes_name); ++ ++ bool has_summary = false; ++ for (GList *iter = self->pf_sections; iter; iter = g_list_next(iter)) ++ { ++ section_t *section = (section_t *)iter->data; ++ ++ /* %summary is something special */ ++ if (strcmp(section->name, "%summary") == 0) ++ { ++ has_summary = true; ++ format_percented_string((const char *)section->items->data, data, ++ problem_report_get_buffer(pr, PR_SEC_SUMMARY)); ++ } ++ /* %attach as well */ ++ else if (strcmp(section->name, "%attach") == 0) ++ { ++ problem_report_set_attachments(pr, get_attached_files(data, section->items, self->pf_sections)); ++ } ++ else /* %description or a custom section (e.g. %additional_info) */ ++ { ++ FILE *buffer = problem_report_get_buffer(pr, section->name + 1); ++ ++ if (buffer != NULL) ++ { ++ log_debug("Formatting section : '%s'", section->name); ++ format_section(section, data, self->pf_sections, buffer); ++ } ++ else ++ log_warning("Unsupported section '%s'", section->name); ++ } ++ } ++ ++ if (!has_summary) { ++ log_debug("Problem format misses section '%%summary'. Using the default one : '%s'.", ++ self->pf_default_summary); ++ ++ format_percented_string(self->pf_default_summary, ++ data, problem_report_get_buffer(pr, PR_SEC_SUMMARY)); ++ } ++ ++ *report = pr; ++ return 0; ++} +diff --git a/src/plugins/problem_report.h b/src/plugins/problem_report.h +new file mode 100644 +index 0000000..f2d41d8 +--- /dev/null ++++ b/src/plugins/problem_report.h +@@ -0,0 +1,334 @@ ++/* ++ Copyright (C) 2014 ABRT team ++ Copyright (C) 2014 RedHat Inc ++ ++ This program is free software; you can redistribute it and/or modify ++ it under the terms of the GNU General Public License as published by ++ the Free Software Foundation; either version 2 of the License, or ++ (at your option) any later version. ++ ++ This program is distributed in the hope that it will be useful, ++ but WITHOUT ANY WARRANTY; without even the implied warranty of ++ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ GNU General Public License for more details. ++ ++ You should have received a copy of the GNU General Public License along ++ with this program; if not, write to the Free Software Foundation, Inc., ++ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ++ ++ @brief API for formating of problem data ++ ++ These functions can be used to convert a problem data to its string ++ representation. ++ ++ The output format can be parsed from a string: ++ ++ problem_formatter_t *formatter = problem_formatter_new(); ++ problem_formatter_load_string(formatter, MY_FORMAT_STRING); ++ ++ or loaded from a file: ++ ++ problem_formatter_t *formatter = problem_formatter_new(); ++ problem_formatter_load_file(formatter, MY_FORMAT_FILE); ++ ++ Once you have configured your formatter you can convert problem_data to ++ problem_report by calling: ++ ++ problem_report_t *report; ++ if (problem_formatter_generate_report(formatter, data, &report) != 0) ++ errx(EXIT_FAILURE, "Problem data cannot be converted to problem report."); ++ ++ Now you can print the report: ++ ++ printf("Problem: %s\n", problem_report_get_summary()); ++ printf("%s\n", problem_report_get_description()); ++ ++ puts("Problem attachments:"); ++ for (GList *a = problem_report_get_attachments(pr); a != NULL; a = g_list_next(a)) ++ printf(" %s\n", a->data); ++ ++ Format description: ++ ++ ---- ++ %summary:: summary format ++ %attach:: elemnt1[,element2]... ++ section:: element1[,element2]... ++ The literal text line to be added to report. ++ ---- ++ ++ Summary format is a line of text, where %element% is replaced by ++ text element's content, and [[...%element%...]] block is used only if ++ %element% exists. [[...]] blocks can nest. ++ ++ Sections can be: ++ - %summary: bug summary format string. ++ ++ - %attach: a list of elements to attach. ++ ++ - text, double colon (::) and the list of comma-separated elements. ++ Text can be empty (":: elem1, elem2, elem3" works), ++ in this case "Text:" header line will be omitted. ++ ++ - %description: this section is implicit and contains all text ++ sections unless another section was specified (%summary and %attach ++ are ignored when determining text section's placement) ++ ++ - every text element belongs to the last specified section (%summary ++ and %attach sections are ignored). If no section was specified, ++ the text element belogns to %description. ++ ++ - If none of elements exists, the section will not be created. ++ ++ - Empty lines are NOT ignored. ++ ++ Elements can be: ++ - problem directory element names, which get formatted as ++ <element_name>: <contents> ++ or ++ <element_name>: ++ :<contents> ++ :<contents> ++ :<contents> ++ ++ - problem directory element names prefixed by "%bare_", ++ which is formatted as-is, without "<element_name>:" and colons ++ ++ - %oneline, %multiline, %text wildcards, which select all corresponding ++ elements for output or attachment ++ ++ - %binary wildcard, valid only for %attach section, instructs to attach ++ binary elements ++ ++ - problem directory element names prefixed by "-", ++ which excludes given element from all wildcards ++ ++ - Nonexistent elements are silently ignored. ++ ++ You can add your own section: ++ ++ problem_formatter_t *formatter = problem_formatter_new(); ++ problem_formatter_add_section(formatter, "additional_info", PFFF_REQUIRED); ++ ++ and then you can use the section in the formatting string: ++ ++ problem_formatter_load_string(formatter, ++ "::comment\n" ++ "%additional_info:: maps"); ++ problem_formatter_generate_report(formatter, data, &report); ++ ++ printf("Problem: %s\n", problem_report_get_summary()); ++ printf("%s\n", problem_report_get_description()); ++ printf("Additional info: %s\n", problem_report_get_section(report, "additiona_info")); ++ ++ The lines above are equivalent to the following lines: ++ ++ printf("Problem: %s\n", problem_data_get_content_or_NULL(data, "reason")); ++ printf("%s\n", problem_data_get_content_or_NULL(data, "comment")); ++ printf("Additional info: %s\n", problem_data_get_content_or_NULL(data, "maps")); ++*/ ++#ifndef LIBREPORT_PROBLEM_REPORT_H ++#define LIBREPORT_PROBLEM_REPORT_H ++ ++#include <glib.h> ++#include <stdio.h> ++#include "problem_data.h" ++ ++#ifdef __cplusplus ++extern "C" { ++#endif ++ ++#define PR_SEC_SUMMARY "summary" ++#define PR_SEC_DESCRIPTION "description" ++ ++/* ++ * The problem report structure represents a problem data formatted according ++ * to a format string. ++ * ++ * A problem report is composed of well-known sections: ++ * - summary ++ * - descritpion ++ * - attach ++ * ++ * and custom sections accessed by: ++ * problem_report_get_section(); ++ */ ++struct problem_report; ++typedef struct problem_report problem_report_t; ++ ++/* ++ * Helpers for easily switching between FILE and struct strbuf ++ */ ++ ++/* ++ * Type of buffer used by Problem report ++ */ ++typedef FILE problem_report_buffer; ++ ++/* ++ * Wrapper for the proble buffer's formated output function. ++ */ ++#define problem_report_buffer_printf(buf, fmt, ...)\ ++ fprintf((buf), (fmt), ##__VA_ARGS__) ++ ++ ++/* ++ * Get a section buffer ++ * ++ * Use this function if you need to amend something to a formatted section. ++ * ++ * @param self Problem report ++ * @param section_name Name of required section ++ * @return Always valid pointer to a section buffer ++ */ ++problem_report_buffer *problem_report_get_buffer(const problem_report_t *self, ++ const char *section_name); ++ ++/* ++ * Get Summary string ++ * ++ * The returned pointer is valid as long as you perform no further output to ++ * the summary buffer. ++ * ++ * @param self Problem report ++ * @return Non-NULL pointer to summary data ++ */ ++const char *problem_report_get_summary(const problem_report_t *self); ++ ++/* ++ * Get Description string ++ * ++ * The returned pointer is valid as long as you perform no further output to ++ * the description buffer. ++ * ++ * @param self Problem report ++ * @return Non-NULL pointer to description data ++ */ ++const char *problem_report_get_description(const problem_report_t *self); ++ ++/* ++ * Get Section's string ++ * ++ * The returned pointer is valid as long as you perform no further output to ++ * the section's buffer. ++ * ++ * @param self Problem report ++ * @param section_name Name of the required section ++ * @return Non-NULL pointer to description data ++ */ ++const char *problem_report_get_section(const problem_report_t *self, ++ const char *section_name); ++ ++/* ++ * Get GList of the problem data items that are to be attached ++ * ++ * @param self Problem report ++ * @return A pointer to GList (NULL means empty list) ++ */ ++GList *problem_report_get_attachments(const problem_report_t *self); ++ ++/* ++ * Releases all resources allocated by a problem report ++ * ++ * @param self Problem report ++ */ ++void problem_report_free(problem_report_t *self); ++ ++ ++/* ++ * An enum of Extra section flags ++ */ ++enum problem_formatter_section_flags { ++ PFFF_REQUIRED = 1 << 0, ///< section must be present in the format spec ++}; ++ ++/* ++ * The problem formatter structure formats a problem data according to a format ++ * string and stores result a problem report. ++ * ++ * The problem formatter uses '%reason%' as %summary section format string, if ++ * %summary is not provided by a format string. ++ */ ++struct problem_formatter; ++typedef struct problem_formatter problem_formatter_t; ++ ++/* ++ * Constructs a new problem formatter. ++ * ++ * @return Non-NULL pointer to the new problem formatter ++ */ ++problem_formatter_t *problem_formatter_new(void); ++ ++/* ++ * Releases all resources allocated by a problem formatter ++ * ++ * @param self Problem formatter ++ */ ++void problem_formatter_free(problem_formatter_t *self); ++ ++/* ++ * Adds a new recognized section ++ * ++ * The problem formatter ignores a section in the format spec if the section is ++ * not one of the default nor added by this function. ++ * ++ * How the problem formatter handles these extra sections: ++ * ++ * A custom section is something like %description section. %description is the ++ * default section where all text (sub)sections are stored. If the formatter ++ * finds the custom section in format string, then starts storing text ++ * (sub)sections in the custom section. ++ * ++ * (%description) |:: comment ++ * (%description) | ++ * (%description) |Package:: package ++ * (%description) | ++ * (%additiona_info) |%additional_info:: ++ * (%additiona_info) |%reporter% ++ * (%additiona_info) |User:: user_name,uid ++ * (%additiona_info) | ++ * (%additiona_info) |Directories:: root,cwd ++ * ++ * ++ * @param self Problem formatter ++ * @param name Name of the added section ++ * @param flags Info about the added section ++ * @return Zero on success. -EEXIST if the name is already known by the formatter ++ */ ++int problem_formatter_add_section(problem_formatter_t *self, const char *name, int flags); ++ ++/* ++ * Loads a problem format from a string. ++ * ++ * @param self Problem formatter ++ * @param fmt Format ++ * @return Zero on success or number of warnings (e.g. missing section, ++ * unrecognized section). ++ */ ++int problem_formatter_load_string(problem_formatter_t* self, const char *fmt); ++ ++/* ++ * Loads a problem format from a file. ++ * ++ * @param self Problem formatter ++ * @param pat Path to the format file ++ * @return Zero on success or number of warnings (e.g. missing section, ++ * unrecognized section). ++ */ ++int problem_formatter_load_file(problem_formatter_t* self, const char *path); ++ ++/* ++ * Creates a new problem report, formats the data according to the loaded ++ * format string and stores output in the report. ++ * ++ * @param self Problem formatter ++ * @param data Problem data to format ++ * @param report Pointer where the created problem report is to be stored ++ * @return Zero on success, otherwise non-zero value. ++ */ ++int problem_formatter_generate_report(const problem_formatter_t *self, problem_data_t *data, problem_report_t **report); ++ ++#ifdef __cplusplus ++} ++#endif ++ ++#endif // LIBREPORT_PROBLEM_REPORT_H +diff --git a/tests/testsuite.at b/tests/testsuite.at +index ccb37d1..d4648d4 100644 +--- a/tests/testsuite.at ++++ b/tests/testsuite.at +@@ -17,7 +17,7 @@ m4_include([xml_definition.at]) + m4_include([report_python.at]) + m4_include([string_list.at]) + m4_include([ureport.at]) +-m4_include([problem_report.at]) ++# m4_include([problem_report.at]) + m4_include([dump_dir.at]) + m4_include([global_config.at]) + m4_include([iso_date.at]) +-- +1.8.3.1 + diff --git a/SOURCES/1012-reporter-mantisbt-add-event-for-reporting-AVCs.patch b/SOURCES/1012-reporter-mantisbt-add-event-for-reporting-AVCs.patch new file mode 100644 index 0000000..84924bd --- /dev/null +++ b/SOURCES/1012-reporter-mantisbt-add-event-for-reporting-AVCs.patch @@ -0,0 +1,245 @@ +From 9a7a376a236e24c448eb650328d0d4841026568b Mon Sep 17 00:00:00 2001 +From: Matej Habrnal <mhabrnal@redhat.com> +Date: Wed, 13 May 2015 16:37:19 +0200 +Subject: [PATCH 1012/1015] reporter-mantisbt: add event for reporting AVCs + +Without this commit is not possible to report AVCs because there are not event +for 'report_CentOSBugTracker' with analyzer=libreport which is used for +reporting AVCs. + +Related to bugs.centos#8422 and libreport#348 + +Signed-off-by: Matej Habrnal <mhabrnal@redhat.com> +--- + doc/Makefile.am | 2 + + doc/mantisbt_format_analyzer_libreport.conf.txt | 18 +++++++ + doc/mantisbt_formatdup_analyzer_libreport.conf.txt | 18 +++++++ + src/plugins/Makefile.am | 4 +- + src/plugins/centos_report_event.conf | 5 ++ + .../mantisbt_format_analyzer_libreport.conf | 59 ++++++++++++++++++++++ + .../mantisbt_formatdup_analyzer_libreport.conf | 56 ++++++++++++++++++++ + 7 files changed, 161 insertions(+), 1 deletion(-) + create mode 100644 doc/mantisbt_format_analyzer_libreport.conf.txt + create mode 100644 doc/mantisbt_formatdup_analyzer_libreport.conf.txt + create mode 100644 src/plugins/mantisbt_format_analyzer_libreport.conf + create mode 100644 src/plugins/mantisbt_formatdup_analyzer_libreport.conf + +diff --git a/doc/Makefile.am b/doc/Makefile.am +index e437388..c10deb4 100644 +--- a/doc/Makefile.am ++++ b/doc/Makefile.am +@@ -41,6 +41,8 @@ MAN5_TXT += bugzilla_format_libreport.conf.txt + MAN5_TXT += mantisbt.conf.txt + MAN5_TXT += mantisbt_format.conf.txt + MAN5_TXT += mantisbt_formatdup.conf.txt ++MAN5_TXT += mantisbt_format_analyzer_libreport.conf.txt ++MAN5_TXT += mantisbt_formatdup_analyzer_libreport.conf.txt + MAN5_TXT += emergencyanalysis_event.conf.txt + MAN5_TXT += forbidden_words.conf.txt + MAN5_TXT += mailx.conf.txt +diff --git a/doc/mantisbt_format_analyzer_libreport.conf.txt b/doc/mantisbt_format_analyzer_libreport.conf.txt +new file mode 100644 +index 0000000..8cbd327 +--- /dev/null ++++ b/doc/mantisbt_format_analyzer_libreport.conf.txt +@@ -0,0 +1,18 @@ ++mantisbt_format_analyzer_libreport.conf(5) ++========================================== ++ ++NAME ++---- ++mantisbt_format_analyzer_libreport.conf - configuration file for libreport. ++ ++DESCRIPTION ++----------- ++This configuration file provides definition of general formatting for duplicate MantisBT issues. ++ ++SEE ALSO ++-------- ++reporter-mantisbt(1) ++ ++AUTHOR ++------ ++* ABRT Team +diff --git a/doc/mantisbt_formatdup_analyzer_libreport.conf.txt b/doc/mantisbt_formatdup_analyzer_libreport.conf.txt +new file mode 100644 +index 0000000..cd082de +--- /dev/null ++++ b/doc/mantisbt_formatdup_analyzer_libreport.conf.txt +@@ -0,0 +1,18 @@ ++mantisbt_formatdup_analyzer_libreport.conf(5) ++============================================= ++ ++NAME ++---- ++mantisbt_formatdup_analyzer_libreport.conf - configuration file for libreport. ++ ++DESCRIPTION ++----------- ++This configuration file provides definition of general formatting for duplicate MantisBT issues. ++ ++SEE ALSO ++-------- ++reporter-mantisbt(1) ++ ++AUTHOR ++------ ++* ABRT Team +diff --git a/src/plugins/Makefile.am b/src/plugins/Makefile.am +index f4f94ff..3c1dfff 100644 +--- a/src/plugins/Makefile.am ++++ b/src/plugins/Makefile.am +@@ -42,7 +42,9 @@ endif + if BUILD_MANTISBT + reporters_plugin_conf += mantisbt.conf + reporters_plugin_format_conf += mantisbt_format.conf \ +- mantisbt_formatdup.conf ++ mantisbt_formatdup.conf \ ++ mantisbt_format_analyzer_libreport.conf \ ++ mantisbt_formatdup_analyzer_libreport.conf + endif + + defaultreportpluginsconfdir = $(DEFAULT_REPORT_PLUGINS_CONF_DIR) +diff --git a/src/plugins/centos_report_event.conf b/src/plugins/centos_report_event.conf +index 53f12d8..adbca93 100644 +--- a/src/plugins/centos_report_event.conf ++++ b/src/plugins/centos_report_event.conf +@@ -35,3 +35,8 @@ EVENT=report_CentOSBugTracker analyzer=CCpp duphash!= + -F "/etc/libreport/plugins/$format" \ + -A "/etc/libreport/plugins/$formatdup" + ++EVENT=report_CentOSBugTracker analyzer=libreport ++ reporter-mantisbt \ ++ -c /etc/libreport/plugins/mantisbt.conf \ ++ -F /etc/libreport/plugins/mantisbt_format_analyzer_libreport.conf \ ++ -A /etc/libreport/plugins/mantisbt_formatdup_analyzer_libreport.conf +diff --git a/src/plugins/mantisbt_format_analyzer_libreport.conf b/src/plugins/mantisbt_format_analyzer_libreport.conf +new file mode 100644 +index 0000000..a514e38 +--- /dev/null ++++ b/src/plugins/mantisbt_format_analyzer_libreport.conf +@@ -0,0 +1,59 @@ ++# Lines starting with # are ignored. ++# Lines can be continued on the next line using trailing backslash. ++# ++# Format: ++# %summary:: summary format ++# section:: element1[,element2]... ++# The literal text line to be added to Bugzilla comment. Can be empty. ++# (IOW: empty lines are NOT ignored!) ++# ++# Summary format is a line of text, where %element% is replaced by ++# text element's content, and [[...%element%...]] block is used only if ++# %element% exists. [[...]] blocks can nest. ++# ++# Sections can be: ++# - %summary: bug summary format string. ++# - %attach: a list of elements to attach. ++# - text, double colon (::) and the list of comma-separated elements. ++# Text can be empty (":: elem1, elem2, elem3" works), ++# in this case "Text:" header line will be omitted. ++# ++# Elements can be: ++# - problem directory element names, which get formatted as ++# <element_name>: <contents> ++# or ++# <element_name>: ++# :<contents> ++# :<contents> ++# :<contents> ++# - problem directory element names prefixed by "%bare_", ++# which is formatted as-is, without "<element_name>:" and colons ++# - %oneline, %multiline, %text wildcards, which select all corresponding ++# elements for output or attachment ++# - %binary wildcard, valid only for %attach section, instructs to attach ++# binary elements ++# - problem directory element names prefixed by "-", ++# which excludes given element from all wildcards ++# ++# Nonexistent elements are silently ignored. ++# If none of elements exists, the section will not be created. ++ ++%summary:: %reason% ++ ++Description of problem:: %bare_comment, %bare_description ++ ++Version-Release number of selected component:: %bare_package ++ ++Truncated backtrace:: %bare_%short_backtrace ++ ++%Additional info:: ++:: -pkg_arch,-pkg_epoch,-pkg_name,-pkg_release,-pkg_version,\ ++ -component,-architecture,\ ++ -analyzer,-count,-duphash,-uuid,-abrt_version,\ ++ -username,-hostname,-os_release,-os_info,\ ++ -time,-pid,-pwd,-last_occurrence,-ureports_counter,\ ++ %reporter,\ ++ %oneline ++ ++%attach:: -reported_to,-comment,-reason,-event_log,%multiline,\ ++ -coredump,%binary +diff --git a/src/plugins/mantisbt_formatdup_analyzer_libreport.conf b/src/plugins/mantisbt_formatdup_analyzer_libreport.conf +new file mode 100644 +index 0000000..d9ab0e3 +--- /dev/null ++++ b/src/plugins/mantisbt_formatdup_analyzer_libreport.conf +@@ -0,0 +1,56 @@ ++# Lines starting with # are ignored. ++# Lines can be continued on the next line using trailing backslash. ++# ++# Format: ++# %summary:: summary format ++# section:: element1[,element2]... ++# The literal text line to be added to Bugzilla comment. Can be empty. ++# (IOW: empty lines are NOT ignored!) ++# ++# Summary format is a line of text, where %element% is replaced by ++# text element's content, and [[...%element%...]] block is used only if ++# %element% exists. [[...]] blocks can nest. ++# ++# Sections can be: ++# - %summary: bug summary format string. ++# - %attach: a list of elements to attach. ++# - text, double colon (::) and the list of comma-separated elements. ++# Text can be empty (":: elem1, elem2, elem3" works), ++# in this case "Text:" header line will be omitted. ++# ++# Elements can be: ++# - problem directory element names, which get formatted as ++# <element_name>: <contents> ++# or ++# <element_name>: ++# :<contents> ++# :<contents> ++# :<contents> ++# - problem directory element names prefixed by "%bare_", ++# which is formatted as-is, without "<element_name>:" and colons ++# - %oneline, %multiline, %text wildcards, which select all corresponding ++# elements for output or attachment ++# - %binary wildcard, valid only for %attach section, instructs to attach ++# binary elements ++# - problem directory element names prefixed by "-", ++# which excludes given element from all wildcards ++# ++# Nonexistent elements are silently ignored. ++# If none of elements exists, the section will not be created. ++ ++Another user experienced a similar problem: ++ ++# If user filled out comment field, show it: ++:: %bare_comment ++ ++# var_log_messages has too much variance (time/date), ++# we exclude it from message so that dup message elimination has more chances to work ++:: \ ++ -pkg_arch,-pkg_epoch,-pkg_name,-pkg_release,-pkg_version,\ ++ -component,-architecture,\ ++ -analyzer,-count,-duphash,-uuid,-abrt_version,\ ++ -username,-hostname,-os_release,-os_info,\ ++ -time,-pid,-pwd,-last_occurrence,-ureports_counter,\ ++ -var_log_messages,\ ++ %reporter,\ ++ %oneline +-- +1.8.3.1 + diff --git a/SOURCES/1014-event-disable-report_RHTSupport-event-and-change-URL.patch b/SOURCES/1014-event-disable-report_RHTSupport-event-and-change-URL.patch new file mode 100644 index 0000000..3765c62 --- /dev/null +++ b/SOURCES/1014-event-disable-report_RHTSupport-event-and-change-URL.patch @@ -0,0 +1,39 @@ +From 93cfff1cd5ca8f418709e4fa0e1b1c9d5b15ddcf Mon Sep 17 00:00:00 2001 +From: Matej Habrnal <mhabrnal@redhat.com> +Date: Fri, 12 Jun 2015 07:51:50 +0200 +Subject: [PATCH 1014/1015] event: disable report_RHTSupport event and change + URL for bugzilla + +Set the URL for bugzilla to non-exist one because we do not want the centos +users report to bugzilla. + +Signed-off-by: Matej Habrnal <mhabrnal@redhat.com> +--- + src/plugins/bugzilla.conf | 2 +- + src/plugins/rhtsupport_event.conf | 4 ++-- + 2 files changed, 3 insertions(+), 3 deletions(-) + +diff --git a/src/plugins/bugzilla.conf b/src/plugins/bugzilla.conf +index 51648de..159b188 100644 +--- a/src/plugins/bugzilla.conf ++++ b/src/plugins/bugzilla.conf +@@ -1,5 +1,5 @@ + # Bugzilla URL +-BugzillaURL = https://bugzilla.redhat.com/ ++BugzillaURL = https://bugzilla.example.com/ + # yes means that ssl certificates will be checked + SSLVerify = yes + # your login has to exist, if you don have any, please create one +diff --git a/src/plugins/rhtsupport_event.conf b/src/plugins/rhtsupport_event.conf +index f6a9d47..4ccf9f5 100644 +--- a/src/plugins/rhtsupport_event.conf ++++ b/src/plugins/rhtsupport_event.conf +@@ -1,3 +1,3 @@ +-EVENT=report_RHTSupport ++#EVENT=report_RHTSupport + # Submit an uReport and create a case in Red Hat Customer Portal +- reporter-rhtsupport -u ++ # reporter-rhtsupport -u +-- +1.8.3.1 + diff --git a/SPECS/libreport.spec b/SPECS/libreport.spec index 5a8e49a..d1b71a2 100644 --- a/SPECS/libreport.spec +++ b/SPECS/libreport.spec @@ -238,6 +238,23 @@ Patch210: 0210-RHTSupport-include-count-in-Support-cases.patch Patch211: 0211-configure-set-version-to-2.1.11.1.patch Patch212: 0212-Translation-updates.patch +Patch1000: 1000-bugzilla-port-to-Problem-Format-API.patch +Patch1001: 1001-lib-created-a-new-lib-file-for-reporters.patch +#Patch1002: 1002-spec-changed-spec-file-to-work-with-last-commit.patch +Patch1003: 1003-ureport-set-url-to-public-faf-server.patch +Patch1004: 1004-conf-changed-URL-for-sending-uReport.patch +Patch1005: 1005-reporter-mantisbt-first-version-of-the-reporter-mant.patch +#Patch1006: 1006-spec-changed-spec-file-to-work-with-reporter-mantisb.patch +Patch1007: 1007-reporter-mantisbt-change-default-formating-file-for-.patch +#Patch1008: 1008-spec-change-spec-file-to-work-with-last-commit.patch +Patch1009: 1009-reporter-mantisbt-adds-man-pages-for-reporter-mantis.patch +Patch1010: 1010-move-problem_report-to-plugins.patch +#Patch1011: 1011-spec-change-related-to-moving-problem_report-to-plug.patch +Patch1012: 1012-reporter-mantisbt-add-event-for-reporting-AVCs.patch +#Patch1013: 1013-spec-add-files-related-to-reporting-AVCs-by-reporter.patch +Patch1014: 1014-event-disable-report_RHTSupport-event-and-change-URL.patch +#Patch1015: 1015-spec-remove-dependecy-on-redhat-access-insights.patch + # git is need for '%%autosetup -S git' which automatically applies all the # patches above. Please, be aware that the patches must be generated # by 'git format-patch' @@ -426,12 +443,32 @@ Uploads micro-report to abrt server %description plugin-bugzilla Plugin to report bugs into the bugzilla. +%package plugin-mantisbt +Summary: %{name}'s mantisbt plugin +Group: System Environment/Libraries +Requires: %{name} = %{version}-%{release} +Requires: libreport-web = %{version}-%{release} + +%description plugin-mantisbt +Plugin to report bugs into the mantisbt. + +%package centos +Summary: %{name}'s CentOS Bug Tracker workflow +Group: System Environment/Libraries +Requires: %{name} = %{version}-%{release} +Requires: libreport-web = %{version}-%{release} +Requires: libreport-plugin-mantisbt = %{version}-%{release} + +%description centos +Workflows to report issues into the CentOS Bug Tracker. + %package plugin-rhtsupport Summary: %{name}'s RHTSupport plugin Group: System Environment/Libraries Requires: %{name} = %{version}-%{release} Requires: libreport-web = %{version}-%{release} -Requires: redhat-access-insights +# This feature is only for RHEL so we do not need another dependency +# Requires: redhat-access-insights %description plugin-rhtsupport Plugin to report bugs into RH support system. @@ -664,7 +701,6 @@ gtk-update-icon-cache %{_datadir}/icons/hicolor &>/dev/null || : %{_includedir}/libreport/dump_dir.h %{_includedir}/libreport/event_config.h %{_includedir}/libreport/problem_data.h -%{_includedir}/libreport/problem_report.h %{_includedir}/libreport/report.h %{_includedir}/libreport/run_event.h %{_includedir}/libreport/file_obj.h @@ -672,6 +708,7 @@ gtk-update-icon-cache %{_datadir}/icons/hicolor &>/dev/null || : %{_includedir}/libreport/workflow.h %{_includedir}/libreport/ureport.h %{_includedir}/libreport/global_configuration.h +%{_includedir}/libreport/reporters.h # Private api headers: %{_includedir}/libreport/internal_abrt_dbus.h %{_includedir}/libreport/internal_libreport.h @@ -791,6 +828,40 @@ gtk-update-icon-cache %{_datadir}/icons/hicolor &>/dev/null || : %{_mandir}/man5/bugzilla_format_kernel.conf.5.* %{_bindir}/reporter-bugzilla +%files plugin-mantisbt +%defattr(-,root,root,-) +%config(noreplace) %{_sysconfdir}/libreport/plugins/mantisbt.conf +%{_datadir}/%{name}/conf.d/plugins/mantisbt.conf +%config(noreplace) %{_sysconfdir}/libreport/plugins/mantisbt_format.conf +%config(noreplace) %{_sysconfdir}/libreport/plugins/mantisbt_formatdup.conf +%config(noreplace) %{_sysconfdir}/libreport/plugins/mantisbt_format_analyzer_libreport.conf +%config(noreplace) %{_sysconfdir}/libreport/plugins/mantisbt_formatdup_analyzer_libreport.conf +%{_bindir}/reporter-mantisbt +%{_mandir}/man1/reporter-mantisbt.1.gz +%{_mandir}/man5/mantisbt.conf.5.* +%{_mandir}/man5/mantisbt_format.conf.5.* +%{_mandir}/man5/mantisbt_formatdup.conf.5.* +%{_mandir}/man5/mantisbt_format_analyzer_libreport.conf.5.* +%{_mandir}/man5/mantisbt_formatdup_analyzer_libreport.conf.5.* + +%files centos +%{_datadir}/%{name}/workflows/workflow_CentOSCCpp.xml +%{_datadir}/%{name}/workflows/workflow_CentOSKerneloops.xml +%{_datadir}/%{name}/workflows/workflow_CentOSPython.xml +%{_datadir}/%{name}/workflows/workflow_CentOSPython3.xml +%{_datadir}/%{name}/workflows/workflow_CentOSVmcore.xml +%{_datadir}/%{name}/workflows/workflow_CentOSXorg.xml +%{_datadir}/%{name}/workflows/workflow_CentOSLibreport.xml +%{_datadir}/%{name}/workflows/workflow_CentOSJava.xml +%config(noreplace) %{_sysconfdir}/libreport/workflows.d/report_centos.conf +%{_mandir}/man5/report_centos.conf.5.* +%{_datadir}/%{name}/events/report_CentOSBugTracker.xml +%config(noreplace) %{_sysconfdir}/libreport/events/report_CentOSBugTracker.conf +%{_mandir}/man5/report_CentOSBugTracker.conf.5.* +# report_CentOSBugTracker events are shipped by libreport package +%config(noreplace) %{_sysconfdir}/libreport/events.d/centos_report_event.conf +%{_mandir}/man5/centos_report_event.conf.5.gz + %files plugin-rhtsupport %defattr(-,root,root,-) %config(noreplace) %{_sysconfdir}/libreport/plugins/rhtsupport.conf @@ -892,7 +963,7 @@ gtk-update-icon-cache %{_datadir}/icons/hicolor &>/dev/null || : %changelog -* Thu Sep 1 2016 Matej Habrnal <mhabrnal@redhat.com> - 2.1.11-35 +* Thu Sep 1 2016 Matej Habrnal <mhabrnal@redhat.com> - 2.1.11-35.el7.centos - Translation updates - Related: #1304240