static void report_to_bugzilla(const char *dump_dir_name, map_string_h *settings) { struct dump_dir *dd = dd_opendir(dump_dir_name, /*flags:*/ 0); if (!dd) xfunc_die(); /* dd_opendir already emitted error msg */ problem_data_t *problem_data = create_problem_data_from_dump_dir(dd); dd_close(dd); const char *env; const char *login; const char *password; const char *bugzilla_xmlrpc; const char *bugzilla_url; bool ssl_verify; env = getenv("Bugzilla_Login"); login = env ? env : get_map_string_item_or_empty(settings, "Login"); env = getenv("Bugzilla_Password"); password = env ? env : get_map_string_item_or_empty(settings, "Password"); if (!login[0] || !password[0]) error_msg_and_die(_("Empty login or password, please check your configuration")); env = getenv("Bugzilla_BugzillaURL"); bugzilla_url = env ? env : get_map_string_item_or_empty(settings, "BugzillaURL"); if (!bugzilla_url[0]) bugzilla_url = "https://bugzilla.redhat.com"; bugzilla_xmlrpc = xasprintf("%s"XML_RPC_SUFFIX, bugzilla_url); env = getenv("Bugzilla_SSLVerify"); ssl_verify = string_to_bool(env ? env : get_map_string_item_or_empty(settings, "SSLVerify")); const char *component = get_problem_item_content_or_NULL(problem_data, FILENAME_COMPONENT); const char *duphash = get_problem_item_content_or_NULL(problem_data, FILENAME_DUPHASH); if (!duphash) error_msg_and_die(_("Essential file '%s' is missing, can't continue.."), FILENAME_DUPHASH); if (!*duphash) error_msg_and_die(_("Essential file '%s' is empty, can't continue.."), FILENAME_DUPHASH); const char *release = get_problem_item_content_or_NULL(problem_data, FILENAME_OS_RELEASE); if (!release) /* Old dump dir format compat. Remove in abrt-2.1 */ release = get_problem_item_content_or_NULL(problem_data, "release"); struct abrt_xmlrpc *client = abrt_xmlrpc_new_client(bugzilla_xmlrpc, ssl_verify); log(_("Logging into Bugzilla at %s"), bugzilla_url); rhbz_login(client, login, password); log(_("Checking for duplicates")); char *product = NULL; char *version = NULL; parse_release_for_bz(release, &product, &version); free(version); xmlrpc_value *result; if (strcmp(product, "Fedora") == 0) result = rhbz_search_duphash(client, component, product, duphash); else result = rhbz_search_duphash(client, component, NULL, duphash); xmlrpc_value *all_bugs = rhbz_get_member("bugs", result); xmlrpc_DECREF(result); if (!all_bugs) error_msg_and_die(_("Missing mandatory member 'bugs'")); int all_bugs_size = rhbz_array_size(all_bugs); // When someone clones bug it has same duphash, so we can find more than 1. // Need to be checked if component is same. VERB3 log("Bugzilla has %i reports with same duphash '%s'", all_bugs_size, duphash); int bug_id = -1, dependent_bug = -1; struct bug_info *bz = NULL; if (all_bugs_size > 0) { bug_id = rhbz_bug_id(all_bugs); xmlrpc_DECREF(all_bugs); bz = rhbz_bug_info(client, bug_id); if (strcmp(bz->bi_product, product) != 0) { dependent_bug = bug_id; /* found something, but its a different product */ free_bug_info(bz); xmlrpc_value *result = rhbz_search_duphash(client, component, product, duphash); xmlrpc_value *all_bugs = rhbz_get_member("bugs", result); xmlrpc_DECREF(result); all_bugs_size = rhbz_array_size(all_bugs); if (all_bugs_size > 0) { bug_id = rhbz_bug_id(all_bugs); bz = rhbz_bug_info(client, bug_id); } xmlrpc_DECREF(all_bugs); } } free(product); if (all_bugs_size == 0) // Create new bug { log(_("Creating a new bug")); bug_id = rhbz_new_bug(client, problem_data, bug_id); log("Adding attachments to bug %i", bug_id); char bug_id_str[sizeof(int)*3 + 2]; sprintf(bug_id_str, "%i", bug_id); rhbz_attachments(client, bug_id_str, problem_data); log(_("Logging out")); rhbz_logout(client); log("Status: NEW %s/show_bug.cgi?id=%u", bugzilla_url, bug_id); abrt_xmlrpc_free_client(client); return; } // decision based on state log(_("Bug is already reported: %i"), bz->bi_id); if ((strcmp(bz->bi_status, "CLOSED") == 0) && (strcmp(bz->bi_resolution, "DUPLICATE") == 0)) { struct bug_info *origin; origin = rhbz_find_origin_bug_closed_duplicate(client, bz); if (origin) { free_bug_info(bz); bz = origin; } } if (strcmp(bz->bi_status, "CLOSED") != 0) { if ((strcmp(bz->bi_reporter, login) != 0) && (!g_list_find_custom(bz->bi_cc_list, login, (GCompareFunc)g_strcmp0))) { log(_("Add %s to CC list"), login); rhbz_mail_to_cc(client, bz->bi_id, login); } char *dsc = make_description_comment(problem_data); if (dsc) { const char *package = get_problem_item_content_or_NULL(problem_data, FILENAME_PACKAGE); const char *release = get_problem_item_content_or_NULL(problem_data, FILENAME_OS_RELEASE); if (!release) /* Old dump dir format compat. Remove in abrt-2.1 */ release = get_problem_item_content_or_NULL(problem_data, "release"); const char *arch = get_problem_item_content_or_NULL(problem_data, FILENAME_ARCHITECTURE); const char *is_private = get_problem_item_content_or_NULL(problem_data, "is_private"); char *full_dsc = xasprintf("Package: %s\n" "Architecture: %s\n" "OS Release: %s\n" "%s", package, arch, release, dsc); log(_("Adding new comment to bug %d"), bz->bi_id); free(dsc); int is_priv = is_private && string_to_bool(is_private); rhbz_add_comment(client, bz->bi_id, full_dsc, is_priv); free(full_dsc); } } log(_("Logging out")); rhbz_logout(client); log("Status: %s%s%s %s/show_bug.cgi?id=%u", bz->bi_status, bz->bi_resolution ? " " : "", bz->bi_resolution ? bz->bi_resolution : "", bugzilla_url, bz->bi_id); dd = dd_opendir(dump_dir_name, /*flags:*/ 0); if (dd) { char *msg = xasprintf("Bugzilla: URL=%s/show_bug.cgi?id=%u", bugzilla_url, bz->bi_id); add_reported_to(dd, msg); free(msg); dd_close(dd); } free_problem_data(problem_data); free_bug_info(bz); abrt_xmlrpc_free_client(client); }
int main(int argc, char **argv) { abrt_init(argv); /* I18n */ setlocale(LC_ALL, ""); #if ENABLE_NLS bindtextdomain(PACKAGE, LOCALEDIR); textdomain(PACKAGE); #endif /* Can't keep these strings/structs static: _() doesn't support that */ const char *program_usage_string = _( "\n& [-vbf] [-g GROUP-NAME]... [-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 Bugzilla." "\n" "\nThe tool reads DIR. Then it logs in to Bugzilla and tries to find a bug" "\nwith the same abrt_hash:HEXSTRING in 'Whiteboard'." "\n" "\nIf such bug is not found, then a new bug is created. Elements of DIR" "\nare stored in the bug as part of bug description or as attachments," "\ndepending on their type and size." "\n" "\nOtherwise, if such bug is found and it is marked as CLOSED DUPLICATE," "\nthe tool follows the chain of duplicates until it finds a non-DUPLICATE bug." "\nThe tool adds a new comment to found bug." "\n" "\nThe URL to new or modified bug is printed to stdout and recorded in" "\n'reported_to' element." "\n" "\nOption -t uploads FILEs to the already created bug on Bugzilla site." "\nThe bug ID is retrieved from directory specified by -d DIR." "\nIf problem data in DIR was never reported to Bugzilla, upload will fail." "\n" "\nOption -tID uploads FILEs to the bug with specified ID on Bugzilla site." "\n-d DIR is ignored." "\n" "\nOption -w adds bugzilla user to bug's CC list." "\n" "\nIf not specified, CONFFILE defaults to "CONF_DIR"/plugins/bugzilla.conf" "\nIts lines should have 'PARAM = VALUE' format." "\nRecognized string parameters: BugzillaURL, Login, Password, OSRelease." "\nRecognized boolean parameter (VALUE should be 1/0, yes/no): SSLVerify." "\nParameters can be overridden via $Bugzilla_PARAM environment variables." "\n" "\nFMTFILE and FMTFILE2 default to "CONF_DIR"/plugins/bugzilla_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_b = 1 << 6, OPT_f = 1 << 7, OPT_w = 1 << 8, OPT_h = 1 << 9, OPT_g = 1 << 10, OPT_D = 1 << 11, }; const char *dump_dir_name = "."; GList *conf_file = NULL; const char *fmt_file = CONF_DIR"/plugins/bugzilla_format.conf"; const char *fmt_file2 = fmt_file; char *abrt_hash = NULL; char *ticket_no = NULL; char *debug_str = NULL; GList *group = NULL; /* 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 bug with this ID]")), OPT_BOOL( 'b', NULL, NULL, _("When creating bug, attach binary files too")), OPT_BOOL( 'f', NULL, NULL, _("Force reporting even if this problem is already reported")), OPT_BOOL( 'w', NULL, NULL, _("Add bugzilla user to CC list [of bug with this ID]")), OPT_STRING( 'h', "duphash", &abrt_hash, "DUPHASH", _("Print BUG_ID which has given DUPHASH")), OPT_LIST( 'g', "group", &group , "GROUP" , _("Restrict access to this group only")), 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(); struct bugzilla_struct rhbz = { 0 }; { if (!conf_file) conf_file = g_list_append(conf_file, (char*) CONF_DIR"/plugins/bugzilla.conf"); while (conf_file) { char *fn = (char *)conf_file->data; VERB1 log("Loading settings from '%s'", fn); load_conf_file(fn, settings, /*skip key w/o values:*/ false); VERB3 log("Loaded '%s'", fn); conf_file = g_list_delete_link(conf_file, conf_file); } set_settings(&rhbz, settings); /* WRONG! set_settings() does not copy the strings, it merely sets up pointers * to settings[] dictionary: */ /*free_map_string(settings);*/ } VERB1 log("Initializing XML-RPC library"); xmlrpc_env env; xmlrpc_env_init(&env); xmlrpc_client_setup_global_const(&env); if (env.fault_occurred) abrt_xmlrpc_die(&env); xmlrpc_env_clean(&env); struct abrt_xmlrpc *client; client = abrt_xmlrpc_new_client(rhbz.b_bugzilla_xmlrpc, rhbz.b_ssl_verify); unsigned rhbz_ver = rhbz_version(client); if (abrt_hash) { log(_("Looking for similar problems in bugzilla")); char *hash; if (prefixcmp(abrt_hash, "abrt_hash:")) hash = xasprintf("abrt_hash:%s", abrt_hash); else hash = xstrdup(abrt_hash); /* it's Fedora specific */ xmlrpc_value *all_bugs = rhbz_search_duphash(client, /*product:*/ "Fedora", /*version:*/ NULL, /*component:*/ NULL, hash); free(hash); unsigned all_bugs_size = rhbz_array_size(all_bugs); if (all_bugs_size > 0) { int bug_id = rhbz_get_bug_id_from_array0(all_bugs, rhbz_ver); printf("%i\n", bug_id); } return EXIT_SUCCESS; } if (rhbz.b_login[0] == '\0') { free(rhbz.b_login); rhbz.b_login = ask_bz_login(_("Login is not provided by configuration. Please enter your BZ login:"******"Password is not provided by configuration. Please enter the password for '%s':"), rhbz.b_login); rhbz.b_password = ask_bz_password(question); free(question); } if (opts & OPT_t) { if ((!argv[0] && !(opts & OPT_w)) || (argv[0] && (opts & OPT_w))) 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, "Bugzilla:"); dd_close(dd); if (!reported_to || !reported_to->url) error_msg_and_die(_("Can't get Bugzilla ID because this problem has not yet been reported to Bugzilla.")); char *url = reported_to->url; reported_to->url = NULL; free_report_result(reported_to); if (prefixcmp(url, rhbz.b_bugzilla_url) != 0) error_msg_and_die(_("This problem has been reported to Bugzilla '%s' which differs from the configured Bugzilla '%s'."), url, rhbz.b_bugzilla_url); ticket_no = strrchr(url, '='); if (!ticket_no) error_msg_and_die(_("Malformed url to Bugzilla '%s'."), url); /* won't ever call free on it - it simplifies the code a lot */ ticket_no = xstrdup(ticket_no + 1); log(_("Using Bugzilla ID '%s'"), ticket_no); } login(client, &rhbz); if (opts & OPT_w) rhbz_mail_to_cc(client, xatoi_positive(ticket_no), rhbz.b_login, /* require mail notify */ 0); else { /* Attach files to existing BZ */ while (*argv) { const char *filename = *argv++; VERB1 log("Attaching file '%s' to bug %s", filename, ticket_no); int fd = open(filename, O_RDONLY); if (fd < 0) { perror_msg("Can't open '%s'", filename); continue; } struct stat st; if (fstat(fd, &st) != 0 || !S_ISREG(st.st_mode)) { error_msg("'%s': not a regular file", filename); close(fd); continue; } rhbz_attach_fd(client, ticket_no, filename, fd, /*flags*/ 0); close(fd); } } log(_("Logging out")); rhbz_logout(client); #if 0 /* enable if you search for leaks (valgrind etc) */ abrt_xmlrpc_free_client(client); #endif return 0; } /* Create new bug in Bugzilla */ 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, "Bugzilla:"); dd_close(dd); if (reported_to && reported_to->url) { char *msg = xasprintf("This problem was already reported to Bugzilla (see '%s')." " Do you still want to create a new bug?", 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 *component = problem_data_get_content_or_die(problem_data, FILENAME_COMPONENT); const char *duphash = problem_data_get_content_or_NULL(problem_data, FILENAME_DUPHASH); //COMPAT, remove after 2.1 release if (!duphash) duphash = problem_data_get_content_or_die(problem_data, "global_uuid"); if (!rhbz.b_product || !*rhbz.b_product) /* if not overridden or empty... */ { free(rhbz.b_product); free(rhbz.b_product_version); map_string_t *osinfo = new_map_string(); problem_data_get_osinfo(problem_data, osinfo); parse_osinfo_for_bz(osinfo, &rhbz.b_product, &rhbz.b_product_version); free_map_string(osinfo); if (!rhbz.b_product) error_msg_and_die(_("Can't determine Bugzilla Product from problem data.")); } 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); printf("summary: %s\n" "\n" "%s" , summary, bzcomment ); free(bzcomment); free(summary); exit(0); } login(client, &rhbz); int bug_id = 0; /* If REMOTE_RESULT contains "DUPLICATE 12345", we consider it a dup of 12345 * and won't search on bz 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; } } } struct bug_info *bz = NULL; if (!bug_id) { log(_("Checking for duplicates")); int existing_id = -1; int crossver_id = -1; { /* Figure out whether we want to match component * when doing dup search. */ const char *component_substitute = is_in_comma_separated_list(component, rhbz.b_DontMatchComponents) ? NULL : component; /* 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: */ xmlrpc_value *crossver_bugs = rhbz_search_duphash(client, rhbz.b_product, /*version:*/ NULL, component_substitute, duphash); unsigned crossver_bugs_count = rhbz_array_size(crossver_bugs); VERB3 log("Bugzilla has %i reports with duphash '%s' including cross-version ones", crossver_bugs_count, duphash); if (crossver_bugs_count > 0) crossver_id = rhbz_get_bug_id_from_array0(crossver_bugs, rhbz_ver); xmlrpc_DECREF(crossver_bugs); if (crossver_bugs_count > 0) { /* In dup detection we require match in product *and version*. * Otherwise we sometimes have bugs in e.g. Fedora 17 * considered to be dups of Fedora 16 bugs. * Imagine that F16 is "end-of-lifed" - allowing cross-version * match will make all newly detected crashes DUPed * to a bug in a dead release. */ xmlrpc_value *dup_bugs = rhbz_search_duphash(client, rhbz.b_product, rhbz.b_product_version, component_substitute, duphash); unsigned dup_bugs_count = rhbz_array_size(dup_bugs); VERB3 log("Bugzilla has %i reports with duphash '%s'", dup_bugs_count, duphash); if (dup_bugs_count > 0) existing_id = rhbz_get_bug_id_from_array0(dup_bugs, rhbz_ver); xmlrpc_DECREF(dup_bugs); } } if (existing_id < 0) { /* Create new bug */ log(_("Creating a new bug")); 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); 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); int new_id = rhbz_new_bug(client, problem_data, rhbz.b_product, rhbz.b_product_version, summary, bzcomment, group ); free(bzcomment); free(summary); if (new_id == -1) { error_msg_and_die(_("Failed to create a new bug.")); } log(_("Adding attachments to bug %i"), new_id); 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); bz = new_bug_info(); bz->bi_status = xstrdup("NEW"); bz->bi_id = new_id; goto log_out; } bug_id = existing_id; } bz = rhbz_bug_info(client, bug_id); log(_("Bug is already reported: %i"), bz->bi_id); /* Follow duplicates */ if ((strcmp(bz->bi_status, "CLOSED") == 0) && (strcmp(bz->bi_resolution, "DUPLICATE") == 0) ) { struct bug_info *origin; origin = rhbz_find_origin_bug_closed_duplicate(client, bz); if (origin) { free_bug_info(bz); bz = origin; } } if (strcmp(bz->bi_status, "CLOSED") != 0) { /* Add user's login to CC if not there already */ if (strcmp(bz->bi_reporter, rhbz.b_login) != 0 && !g_list_find_custom(bz->bi_cc_list, rhbz.b_login, (GCompareFunc)g_strcmp0) ) { log(_("Adding %s to CC list"), rhbz.b_login); rhbz_mail_to_cc(client, bz->bi_id, rhbz.b_login, RHBZ_NOMAIL_NOTIFY); } /* Add comment and bt */ 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); 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; 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 > bz->bi_best_bt_rating) { char bug_id_str[sizeof(int)*3 + 2]; sprintf(bug_id_str, "%i", bz->bi_id); log(_("Attaching better backtrace")); rhbz_attach_blob(client, bug_id_str, FILENAME_BACKTRACE, bt, strlen(bt), RHBZ_NOMAIL_NOTIFY); } } else { free(bzcomment); log(_("Found the same comment in the bug history, not adding a new one")); } } } log_out: log(_("Logging out")); rhbz_logout(client); log(_("Status: %s%s%s %s/show_bug.cgi?id=%u"), bz->bi_status, bz->bi_resolution ? " " : "", bz->bi_resolution ? bz->bi_resolution : "", rhbz.b_bugzilla_url, bz->bi_id); struct dump_dir *dd = dd_opendir(dump_dir_name, /*flags:*/ 0); if (dd) { char *msg = xasprintf("Bugzilla: URL=%s/show_bug.cgi?id=%u", rhbz.b_bugzilla_url, bz->bi_id); add_reported_to(dd, msg); free(msg); dd_close(dd); } #if 0 /* enable if you search for leaks (valgrind etc) */ free(rhbz.b_product); free(rhbz.b_product_version); problem_data_free(problem_data); free_bug_info(bz); abrt_xmlrpc_free_client(client); #endif return 0; }