/*
Copyright (C) 2011 ABRT team
Copyright (C) 2011 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.
*/
/* Bugzilla API doc:
* http://www.bugzilla.org/docs/4.2/en/html/api/Bugzilla/WebService.html
* http://www.bugzilla.org/docs/tip/en/html/api/Bugzilla/WebService.html
*
* To make libxmlrpc print debug info to stdout (stderr?),
* export XMLRPC_TRACE_XML=1
*/
#include "internal_libreport.h"
#include "rhbz.h"
#define MAX_HOPS 5
#define MAX_SUMMARY_LENGTH 255
//#define DEBUG
#ifdef DEBUG
#define func_entry() log_warning("-- %s", __func__)
#define func_entry_str(x) log_warning("-- %s\t%s", __func__, (x))
#else
#define func_entry()
#define func_entry_str(x)
#endif
struct bug_info *new_bug_info()
{
func_entry();
struct bug_info *bi = xzalloc(sizeof(struct bug_info));
bi->bi_dup_id = -1;
return bi;
}
void free_bug_info(struct bug_info *bi)
{
func_entry();
if (!bi)
return;
free(bi->bi_status);
free(bi->bi_resolution);
free(bi->bi_reporter);
free(bi->bi_product);
list_free_with_free(bi->bi_cc_list);
free(bi);
}
static GList *rhbz_comments(struct abrt_xmlrpc *ax, int bug_id)
{
func_entry();
/* http://www.bugzilla.org/docs/4.2/en/html/api/Bugzilla/WebService/Bug.html#comments */
/*
* <methodResponse>
* <params><param>
* <value><struct>
* <member><name>bugs</name>
* <value><struct>
* <member><name>BUG_ID</name>
* <value><struct>
* <member><name>comments</name>
* <value><array>
* ...
*/
xmlrpc_value *xml_response = abrt_xmlrpc_call(ax, "Bug.comments", "{s:(i)}",
"ids", bug_id);
/* bugs
* This is used for bugs specified in ids. This is a hash, where the
* keys are the numeric ids of the bugs, and the value is a hash with a
* single key, comments, which is an array of comments.
*/
xmlrpc_value *bugs_memb = rhbz_get_member("bugs", xml_response);
/* Get hash value assigned to bug_id key */
char *item = xasprintf("%d", bug_id);
xmlrpc_value *item_memb = rhbz_get_member(item, bugs_memb);
free(item);
/* Get array of comments */
xmlrpc_value *comments_memb = rhbz_get_member("comments", item_memb);
xmlrpc_env env;
xmlrpc_env_init(&env);
int comments_memb_size = rhbz_array_size(comments_memb);
GList *comments = NULL;
for (int i = 0; i < comments_memb_size; ++i)
{
xmlrpc_value* item = NULL;
xmlrpc_array_read_item(&env, comments_memb, i, &item);
if (env.fault_occurred)
abrt_xmlrpc_die(&env);
char *comment_body = rhbz_bug_read_item("text", item, RHBZ_READ_STR);
/* attachments are sometimes without comments -- skip them */
if (comment_body)
comments = g_list_prepend(comments, comment_body);
xmlrpc_DECREF(item);
}
xmlrpc_env_clean(&env);
xmlrpc_DECREF(comments_memb);
xmlrpc_DECREF(item_memb);
xmlrpc_DECREF(bugs_memb);
xmlrpc_DECREF(xml_response);
return g_list_reverse(comments);
}
static unsigned find_best_bt_rating_in_comments(GList *comments)
{
func_entry();
if (!comments)
return 0;
int best_bt_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("comment 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' || (unsigned long)rating > UINT_MAX)
{
/* error / no digits / illegal trailing chars */
continue;
}
if (rating > best_bt_rating)
best_bt_rating = rating;
}
return best_bt_rating;
}
bool rhbz_login(struct abrt_xmlrpc *ax, const char *login, const char *password)
{
func_entry();
xmlrpc_env env;
xmlrpc_value *result = abrt_xmlrpc_call_full(&env, ax, "User.login", "{s:s,s:s}",
"login", login, "password", password);
if (env.fault_occurred)
{
/* 300 (Invalid Username or Password) - https://wiki.mozilla.org/Bugzilla:WebServices:Errors */
if (env.fault_code != 300)
abrt_xmlrpc_die(&env);
log_notice("xmlrpc fault: (%d) %s", env.fault_code, env.fault_string);
return false;
}
char *token = rhbz_bug_read_item("token", result, RHBZ_READ_STR);
if (token != NULL)
{
log_debug("Adding session param Bugzilla_token");
abrt_xmlrpc_client_add_session_param_string(&env, ax, "Bugzilla_token", token);
free(token);
}
//TODO: with URL like http://bugzilla.redhat.com (that is, with http: instead of https:)
//we are getting this error:
//Logging into Bugzilla at http://bugzilla.redhat.com
//Can't login. Server said: HTTP response code is 301, not 200
//But this is a 301 redirect! We _can_ follow it if we configure curl to understand that!
xmlrpc_DECREF(result);
return true;
}
xmlrpc_value *rhbz_get_member(const char *member, xmlrpc_value *xml)
{
func_entry_str(member);
xmlrpc_env env;
xmlrpc_env_init(&env);
xmlrpc_value *value = NULL;
/* The xmlrpc_struct_find_value functions consider "not found" to be
* a normal result. If a member of the structure with the specified key
* exists, it returns it as a handle to an xmlrpc_value. If not, it returns
* NULL in place of that handle.
*/
xmlrpc_struct_find_value(&env, xml, member, &value);
if (env.fault_occurred)
abrt_xmlrpc_error(&env);
return value;
}
/* The only way this can fail is if arrayP is not actually an array XML-RPC
* value. So it is usually not worth checking *envP.
* die or return size of array
*/
unsigned rhbz_array_size(xmlrpc_value *xml)
{
func_entry();
xmlrpc_env env;
xmlrpc_env_init(&env);
unsigned size = xmlrpc_array_size(&env, xml);
if (env.fault_occurred)
abrt_xmlrpc_die(&env);
return size;
}
xmlrpc_value *rhbz_array_item_at(xmlrpc_value *xml, int pos)
{
func_entry();
xmlrpc_env env;
xmlrpc_env_init(&env);
xmlrpc_value* item = NULL;
xmlrpc_array_read_item(&env, xml, pos, &item);
if (env.fault_occurred)
abrt_xmlrpc_die(&env);
xmlrpc_env_clean(&env);
return item;
}
unsigned rhbz_version(struct abrt_xmlrpc *ax)
{
func_entry();
xmlrpc_value *result;
result = abrt_xmlrpc_call(ax, "Bugzilla.version", "{}");
char *version = NULL;
if (result)
version = rhbz_bug_read_item("version", result, RHBZ_READ_STR);
if (!result || !version)
error_msg_and_die("Can't determine %s", "Bugzilla.version");
xmlrpc_DECREF(result);
strchrnul(version, '-')[0] = '\0';
/* format must be x.y.z */
char *vp;
int i = 0, v[3] = {0, 0, 0};
for (vp = version; i < 3; vp = NULL, ++i)
{
char *tok = strtok(vp, ".");
if (!tok)
break;
v[i] = strtoul(tok, NULL, 10);
}
free(version);
return BUGZILLA_VERSION(v[0], v[1], v[2]);
}
/* die or return bug id; each bug must have bug id otherwise xml is corrupted */
int rhbz_get_bug_id_from_array0(xmlrpc_value* xml, unsigned ver)
{
func_entry();
xmlrpc_env env;
xmlrpc_env_init(&env);
xmlrpc_value *item = NULL;
xmlrpc_array_read_item(&env, xml, 0, &item);
if (env.fault_occurred)
abrt_xmlrpc_die(&env);
const char *id;
if (ver >= BUGZILLA_VERSION(4,2,0))
id = "id";
else
id = "bug_id";
xmlrpc_value *bug;
bug = rhbz_get_member(id, item);
xmlrpc_DECREF(item);
if (!bug)
error_msg_and_die("Can't get member '%s' from bug data", id);
int bug_id = -1;
xmlrpc_read_int(&env, bug, &bug_id);
xmlrpc_DECREF(bug);
if (env.fault_occurred)
abrt_xmlrpc_die(&env);
log_debug("found bug_id %i", bug_id);
return bug_id;
}
/* die when mandatory value is missing (set flag RHBZ_MANDATORY_MEMB)
* or return appropriate string or NULL when fail;
*/
// TODO: npajkovs: add flag to read xmlrpc_read_array_item first
void *rhbz_bug_read_item(const char *memb, xmlrpc_value *xml, int flags)
{
func_entry();
xmlrpc_env env;
xmlrpc_env_init(&env);
xmlrpc_value *member = rhbz_get_member(memb, xml);
const char *string = NULL;
if (!member)
goto die;
if (IS_READ_STR(flags))
{
xmlrpc_read_string(&env, member, &string);
xmlrpc_DECREF(member);
if (env.fault_occurred)
abrt_xmlrpc_die(&env);
if (!*string)
goto die;
log_debug("found %s: '%s'", memb, string);
return (void*)string;
}
if (IS_READ_INT(flags))
{
int *integer = xmalloc(sizeof(int));
xmlrpc_read_int(&env, member, integer);
xmlrpc_DECREF(member);
if (env.fault_occurred)
abrt_xmlrpc_die(&env);
log_debug("found %s: '%i'", memb, *integer);
return (void*)integer;
}
die:
free((void*)string);
if (IS_MANDATORY(flags))
error_msg_and_die(_("Looks like corrupted xml response, because '%s'"
" member is missing."), memb);
return NULL;
}
GList *rhbz_bug_cc(xmlrpc_value* result_xml)
{
func_entry();
xmlrpc_env env;
xmlrpc_env_init(&env);
xmlrpc_value* cc_member = rhbz_get_member("cc", result_xml);
if (!cc_member)
return NULL;
unsigned array_size = rhbz_array_size(cc_member);
log_debug("count members on cc %i", array_size);
GList *cc_list = NULL;
for (unsigned i = 0; i < array_size; ++i)
{
xmlrpc_value* item = NULL;
xmlrpc_array_read_item(&env, cc_member, i, &item);
if (env.fault_occurred)
abrt_xmlrpc_die(&env);
if (!item)
continue;
const char* cc = NULL;
xmlrpc_read_string(&env, item, &cc);
xmlrpc_DECREF(item);
if (env.fault_occurred)
abrt_xmlrpc_die(&env);
if (*cc != '\0')
{
cc_list = g_list_append(cc_list, (char*)cc);
log_debug("member on cc is %s", cc);
continue;
}
free((char*)cc);
}
xmlrpc_DECREF(cc_member);
return cc_list;
}
struct bug_info *rhbz_bug_info(struct abrt_xmlrpc *ax, int bug_id)
{
func_entry();
struct bug_info *bz = new_bug_info();
/* http://www.bugzilla.org/docs/4.2/en/html/api/Bugzilla/WebService/Bug.html#get
*
* <methodResponse>
* <params>
* <param><value><struct>
* <member><name>faults</name><value><array><data/></array></value></member>
* <member><name>bugs</name>
* <value><array><data>
* ...
*/
xmlrpc_value *xml_bug_response = abrt_xmlrpc_call(ax, "Bug.get", "{s:(i)}",
"ids", bug_id);
xmlrpc_value *bugs_memb = rhbz_get_member("bugs", xml_bug_response);
xmlrpc_value *bug_item = rhbz_array_item_at(bugs_memb, 0);
int *ret = (int*)rhbz_bug_read_item("id", bug_item,
RHBZ_MANDATORY_MEMB | RHBZ_READ_INT);
bz->bi_id = *ret;
free(ret);
bz->bi_product = rhbz_bug_read_item("product", bug_item,
RHBZ_MANDATORY_MEMB | RHBZ_READ_STR);
bz->bi_reporter = rhbz_bug_read_item("creator", bug_item,
RHBZ_MANDATORY_MEMB | RHBZ_READ_STR);
bz->bi_status = rhbz_bug_read_item("status", bug_item,
RHBZ_MANDATORY_MEMB | RHBZ_READ_STR);
bz->bi_resolution = rhbz_bug_read_item("resolution", bug_item,
RHBZ_READ_STR);
bz->bi_platform = rhbz_bug_read_item("platform", bug_item,
RHBZ_READ_STR);
if (strcmp(bz->bi_status, "CLOSED") == 0 && !bz->bi_resolution)
error_msg_and_die(_("Bug %i is CLOSED, but it has no RESOLUTION"), bz->bi_id);
ret = (int*)rhbz_bug_read_item("dupe_of", bug_item,
RHBZ_READ_INT);
if (strcmp(bz->bi_status, "CLOSED") == 0
&& strcmp(bz->bi_resolution, "DUPLICATE") == 0
&& !ret)
{
error_msg_and_die(_("Bug %i is CLOSED as DUPLICATE, but it has no DUP_ID"),
bz->bi_id);
}
bz->bi_dup_id = (ret) ? *ret: -1;
free(ret);
bz->bi_cc_list = rhbz_bug_cc(bug_item);
bz->bi_comments = rhbz_comments(ax, bug_id);
bz->bi_best_bt_rating = find_best_bt_rating_in_comments(bz->bi_comments);
xmlrpc_DECREF(bugs_memb);
xmlrpc_DECREF(bug_item);
xmlrpc_DECREF(xml_bug_response);
return bz;
}
int rhbz_new_bug(struct abrt_xmlrpc *ax,
problem_data_t *problem_data,
const char *product,
const char *version,
const char *bzsummary,
const char *bzcomment,
bool private,
GList *group)
{
func_entry();
if (group)
log_debug("# of groups %d", g_list_length(group));
const char *component = problem_data_get_content_or_NULL(problem_data,
FILENAME_COMPONENT);
const char *arch = problem_data_get_content_or_NULL(problem_data,
FILENAME_ARCHITECTURE);
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_NULL(problem_data,
"global_uuid");
char *summary = shorten_string_to_length(bzsummary, MAX_SUMMARY_LENGTH);
struct strbuf *status_whiteboard = strbuf_new();
strbuf_append_strf(status_whiteboard, "abrt_hash:%s;", duphash);
{ /* Add fields from /etc/os-release to Whiteboard for simple metrics. */
map_string_t *osinfo = new_map_string();
problem_data_get_osinfo(problem_data, osinfo);
/* This is the highest abstraction level I am willing to introduce now.
*
* The lines below can be either reduced to the body of the for loop
* or the opts variable can be dynamically initialized
* or you can simply add an another /etc/os-release option name
* (e.g. BUILD_ID).
*/
const char *const opts[] = { "VARIANT_ID", NULL };
for (const char *const *iter = opts; *iter != NULL; ++iter)
{
const char *v = get_map_string_item_or_NULL(osinfo, *iter);
if (v != NULL)
{
/* semi-colon (;) is the delimiter because /etc/os-release *_ID
* options does not permit the ';' character in values
*/
strbuf_append_strf(status_whiteboard, "%s=%s;", *iter, v);
}
}
free_map_string(osinfo);
}
xmlrpc_env env;
xmlrpc_env_init(&env);
xmlrpc_value *params = abrt_xmlrpc_params_new(&env);
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);
abrt_xmlrpc_params_add_string(&env, params, "description", bzcomment);
abrt_xmlrpc_params_add_string(&env, params, "status_whiteboard", status_whiteboard->buf);
if(arch)
abrt_xmlrpc_params_add_string(&env, params, "platform", arch);
if (private)
{
if (group)
{
xmlrpc_value *xmlrpc_groups = abrt_xmlrpc_array_new(&env);
for (GList *l = group; l; l = l->next)
abrt_xmlrpc_array_append_string(&env, xmlrpc_groups, l->data);
abrt_xmlrpc_params_add_array(&env, params, "groups", xmlrpc_groups);
xmlrpc_DECREF(xmlrpc_groups);
}
else
{
error_msg(_("A private ticket creation has been requested, but no groups were specified, please see https://github.com/abrt/abrt/wiki/FAQ#creating-private-bugzilla-tickets for more info"));
return -1;
}
}
xmlrpc_value* result = abrt_xmlrpc_call_params(&env, ax, "Bug.create", params);
xmlrpc_DECREF(params);
xmlrpc_env_clean(&env);
strbuf_free(status_whiteboard);
free(summary);
if (!result)
return -1;
int *r = rhbz_bug_read_item("id", result, RHBZ_MANDATORY_MEMB | RHBZ_READ_INT);
xmlrpc_DECREF(result);
int new_bug_id = *r;
free(r);
log_warning(_("New bug id: %i"), new_bug_id);
return new_bug_id;
}
/* suppress mail notify by {s:i} (nomail:1) (driven by flag) */
int rhbz_attach_blob(struct abrt_xmlrpc *ax, const char *bug_id,
const char *filename, const char *data, int data_len, int flags)
{
func_entry();
if (0 == data_len)
{
log_notice("not attaching an empty file: '%s'", filename);
/* Return SUCCESS */
return 0;
}
char *fn = xasprintf("File: %s", filename);
xmlrpc_value* result;
int nomail_notify = !!IS_NOMAIL_NOTIFY(flags);
/* http://www.bugzilla.org/docs/4.2/en/html/api/Bugzilla/WebService/Bug.html#add_attachment
*
* XMLRPC format options:
* s -> string, single argument (char* value)
* i -> integer, single argument (int value)
* 6 -> base64, two arguments (char* plain data which will be encoded by xmlrpc-c to base64,
* size_t number of bytes to encode)
*/
result = abrt_xmlrpc_call(ax, "Bug.add_attachment", "{s:(s),s:s,s:s,s:s,s:6,s:i}",
"ids", bug_id,
"summary", fn,
"file_name", filename,
"content_type", (flags & RHBZ_BINARY_ATTACHMENT) ? "application/octet-stream" : "text/plain",
/* base64 type requires two arguments: char* (plain data) and size_t (length)
* ! xmlrpc-c takes care about encoding to base64 !
*/
"data", data, (size_t)data_len,
/* Undocumented argument but it works with Red Hat Bugzilla version 4.2.4-7
* and version 4.4.rc1.b02
*/
"nomail", nomail_notify
);
free(fn);
if (!result)
return -1;
xmlrpc_DECREF(result);
return 0;
}
int rhbz_attach_fd(struct abrt_xmlrpc *ax, const char *bug_id,
const char *att_name, int fd, int flags)
{
func_entry();
off_t size = lseek(fd, 0, SEEK_END);
if (size < 0)
{
perror_msg("Can't lseek '%s'", att_name);
return -1;
}
/* bugzilla limit is 20MB
* attaching more then bugzilla's limit could cause that xmlrpc-c fails
* somewhere inside itself.
* https://bugzilla.redhat.com/show_bug.cgi?id=741980
*/
if (size >= (20 * 1024 * 1024))
{
error_msg("Can't upload '%s', it's too large (%llu bytes)", att_name, (long long)size);
return -1;
}
lseek(fd, 0, SEEK_SET);
//TODO: need to have a method of attaching huge files (IOW: 1Gb read isn't good).
char *data = xmalloc(size);
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 = rhbz_attach_blob(ax, bug_id, att_name, data, size, flags);
free(data);
return res;
}
void rhbz_logout(struct abrt_xmlrpc *ax)
{
func_entry();
xmlrpc_env env;
xmlrpc_value *result = abrt_xmlrpc_call_full(&env, ax, "User.logout", "{}");
if (env.fault_occurred)
log_warning("xmlrpc fault: (%d) %s", env.fault_code, env.fault_string);
if (result)
xmlrpc_DECREF(result);
}
struct bug_info *rhbz_find_origin_bug_closed_duplicate(struct abrt_xmlrpc *ax,
struct bug_info *bi)
{
func_entry();
struct bug_info *bi_tmp = new_bug_info();
bi_tmp->bi_id = bi->bi_id;
bi_tmp->bi_dup_id = bi->bi_dup_id;
for (int ii = 0; ii <= MAX_HOPS; ii++)
{
if (ii == MAX_HOPS)
error_msg_and_die(_("Bugzilla couldn't find parent of bug %d"), bi->bi_id);
log_warning("Bug %d is a duplicate, using parent bug %d", bi_tmp->bi_id, bi_tmp->bi_dup_id);
int bug_id = bi_tmp->bi_dup_id;
free_bug_info(bi_tmp);
bi_tmp = rhbz_bug_info(ax, bug_id);
// found a bug which is not CLOSED as DUPLICATE
if (bi_tmp->bi_dup_id == -1)
break;
}
return bi_tmp;
}
/* suppress mail notify by {s:i} (nomail:1) */
void rhbz_mail_to_cc(struct abrt_xmlrpc *ax, int bug_id, const char *mail, int flags)
{
func_entry();
xmlrpc_value *result;
int nomail_notify = !!IS_NOMAIL_NOTIFY(flags);
#if 0 /* Obsolete API */
result = abrt_xmlrpc_call(ax, "Bug.update", "({s:i,s:{s:(s),s:i}})",
"ids", bug_id,
"updates", "add_cc", mail,
"nomail", nomail_notify
);
#endif
/* Bugzilla 4.0+ uses this API: */
result = abrt_xmlrpc_call(ax, "Bug.update", "{s:i,s:{s:(s),s:i}}",
"ids", bug_id,
"cc", "add", mail,
"nomail", nomail_notify
);
if (result)
xmlrpc_DECREF(result);
/* TODO: check that result does indicate that CC was updated.
* The structure I see from Bugzilla 4.2:
* <struct>
* <member><name>bugs</name><value>
* <array><data><value><struct>
* <member><name>changes</name><value><struct>
* <member><name>cc</name><value><struct>
* <member><name>removed</name><value><string /></value></member>
* <member><name>added</name><value><string>USER@HOST.COM</string></value></member>
* </struct></value></member>
* </struct></value></member>
* <member><name>last_change_time</name><value><dateTime.iso8601>20120828T11:09:04</dateTime.iso8601></value></member>
* <member><name>id</name><value><int>NNNNNNN</int></value></member>
* <member><name>alias</name><value><array><data /></array></value></member>
* </struct></value></data>
* </array></value>
* </member></struct>
*/
}
void rhbz_add_comment(struct abrt_xmlrpc *ax, int bug_id, const char *comment,
int flags)
{
func_entry();
int private = !!IS_PRIVATE(flags);
int nomail_notify = !!IS_NOMAIL_NOTIFY(flags);
xmlrpc_value *result;
result = abrt_xmlrpc_call(ax, "Bug.add_comment", "{s:i,s:s,s:b,s:i}",
"id", bug_id, "comment", comment,
"private", private, "nomail", nomail_notify);
if (result)
xmlrpc_DECREF(result);
}
void rhbz_set_url(struct abrt_xmlrpc *ax, int bug_id, const char *url, int flags)
{
func_entry();
const int nomail_notify = !!IS_NOMAIL_NOTIFY(flags);
xmlrpc_value *result = abrt_xmlrpc_call(ax, "Bug.update", "{s:i,s:s,s:i}",
"ids", bug_id,
"url", url,
/* Undocumented argument but it works with Red Hat Bugzilla version 4.2.4-7
* and version 4.4.rc1.b02
*/
"nomail", nomail_notify
);
if (result)
xmlrpc_DECREF(result);
}
void rhbz_close_as_duplicate(struct abrt_xmlrpc *ax, int bug_id,
int duplicate_bug,
int flags)
{
func_entry();
const int nomail_notify = !!IS_NOMAIL_NOTIFY(flags);
xmlrpc_value *result = abrt_xmlrpc_call(ax, "Bug.update", "{s:i,s:s,s:s,s:i,s:i}",
"ids", bug_id,
"status", "CLOSED",
"resolution", "DUPLICATE",
"dupe_of", duplicate_bug,
/* Undocumented argument but it works with Red Hat Bugzilla version 4.2.4-7
* and version 4.4.rc1.b02
*/
"nomail", nomail_notify
);
if (result)
xmlrpc_DECREF(result);
}
xmlrpc_value *rhbz_search_duphash(struct abrt_xmlrpc *ax,
const char *product,
const char *version,
const char *component,
const char *duphash)
{
struct strbuf *query = strbuf_new();
strbuf_append_strf(query, "ALL whiteboard:\"%s\"", duphash);
if (product)
strbuf_append_strf(query, " product:\"%s\"", product);
if (version)
strbuf_append_strf(query, " version:\"%s\"", version);
if (component)
strbuf_append_strf(query, " component:\"%s\"", component);
char *s = strbuf_free_nobuf(query);
log_debug("search for '%s'", s);
xmlrpc_value *search = abrt_xmlrpc_call(ax, "Bug.search", "{s:s}", "quicksearch", s);
free(s);
xmlrpc_value *bugs = rhbz_get_member("bugs", search);
xmlrpc_DECREF(search);
if (!bugs)
error_msg_and_die(_("Bug.search(quicksearch) return value did not contain member 'bugs'"));
return bugs;
}