/*
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.
*/
#include <json.h>
#include <rpm/rpmts.h>
#include <rpm/rpmcli.h>
#include <rpm/rpmdb.h>
#include <rpm/rpmpgp.h>
#include <libreport/internal_libreport.h>
#include <libreport/libreport_curl.h>
#include <libreport/client.h>
#include "libabrt.h"
//699198,705037,705036
/* bodhi returns json structure
{
"rows_per_page": 20,
"total": 1,
"chrome": true,
"display_user": true,
"pages": 1,
"updates": [
{
"close_bugs": true,
"old_updateid": "FEDORA-2015-13720",
"pushed": true,
"require_testcases": false,
"critpath": false,
"date_approved": null,
"stable_karma": null,
"date_pushed": "2015-08-19 04:49:00",
"requirements": null,
"severity": "unspecified",
"title": "hwloc-1.11.0-3.fc22",
"suggest": "unspecified",
"require_bugs": false,
"comments": [
{
"bug_feedback": [],
"user_id": 91,
"text": "This update has been submitted for testing by jhladky. ",
"testcase_feedback": [],
"karma_critpath": 0,
"update": 21885,
"update_id": 21885,
"karma": 0,
"anonymous": false,
"timestamp": "2015-08-18 13:38:35",
"id": 166016,
"user": {"stacks": [],
"name": "bodhi",
"avatar": "https://apps.fedoraproject.org/img/icons/bodhi-24.png"}
},
{
...
}
],
"updateid": "FEDORA-2015-13720",
"cves": [],
"type": "bugfix",
"status": "testing",
"date_submitted": "2015-08-18 13:37:26",
"unstable_karma": null,
"submitter": "jhladky",
"user":
{
"stacks": [],
"buildroot_overrides": [],
"name": "jhladky",
"avatar": "https://seccdn.libravatar.org/avatar/b838b78fcf707a13cdaeb1c846d514e614d617cbf2c106718e71cb397607f59b?s=24&d=retro"
},
"locked": false,
"builds": [{"override": null,
"nvr": "hwloc-1.11.0-3.fc22"}],
"date_modified": null,
"test_cases": [],
"notes": "Fix for BZ1253977",
"request": null,
"bugs": [
{
"bug_id": 1253977,
"security": false,
"feedback": [],
"parent": false,
"title": "conflict between hwloc-libs-1.11.0-1.fc22.i686 and hwloc-libs-1.11.0-1.fc22.x86_64"
}
],
"alias": "FEDORA-2015-13720",
"karma": 0,
"release":
{
"dist_tag": "f22",
"name": "F22",
"testing_tag": "f22-updates-testing",
"pending_stable_tag": "f22-updates-pending",
"long_name": "Fedora 22",
"state": "current",
"version": "22",
"override_tag": "f22-override",
"branch": "f22",
"id_prefix": "FEDORA",
"pending_testing_tag": "f22-updates-testing-pending",
"stable_tag": "f22-updates",
"candidate_tag": "f22-updates-candidate"
}
}
],
"display_request": true,
"page": 1
}
*/
static const char *bodhi_url = "https://bodhi.fedoraproject.org/updates";
struct bodhi {
char *nvr;
#if 0
char *date_pushed;
char *status;
char *dist_tag;
GList *bz_ids;
#endif
};
enum {
BODHI_READ_STR,
BODHI_READ_INT,
BODHI_READ_JSON_OBJ,
};
static void free_bodhi_item(struct bodhi *b)
{
if (!b)
return;
free(b->nvr);
#if 0
list_free_with_free(b->bz_ids);
free(b->date_pushed);
free(b->status);
free(b->dist_tag);
#endif
free(b);
}
static void bodhi_read_value(json_object *json, const char *item_name,
void *value, int flags)
{
json_object *j = NULL;
if (!json_object_object_get_ex(json, item_name, &j))
{
error_msg("'%s' section is not available", item_name);
return;
}
switch (flags) {
case BODHI_READ_INT:
*(int *) value = json_object_get_int(j);
break;
case BODHI_READ_STR:
*(char **) value = (char *) strtrimch(xstrdup(json_object_to_json_string(j)), '"');
break;
case BODHI_READ_JSON_OBJ:
*(json_object **) value = (json_object *) j;
break;
};
}
#if 0
static void print_bodhi(struct bodhi *b)
{
for (GList *l = b->nvr; l; l = l->next)
printf("'%s' ", (char *)l->data);
for (GList *l = b->name; l; l = l->next)
printf("'%s' ", (char *)l->data);
if (b->date_pushed)
printf(" '%s'", b->date_pushed);
if (b->status)
printf(" '%s'", b->status);
if (b->dist_tag)
printf(" '%s'", b->dist_tag);
printf(" %i", b->karma);
/*
for (GList *li = b->bz_ids; li; li = li->next)
printf(" %i", *(int*) li->data);
*/
puts("");
}
#endif
/* bodhi returns following json structure in case of error
{
"status": "error",
"errors":
[
{
"location": "querystring",
"name": "releases",
"description": "Invalid releases specified: Rawhide"
}
]
}
*/
static void bodhi_print_errors_from_json(json_object *json)
{
json_object *errors_array = NULL;
bodhi_read_value(json, "errors", &errors_array, BODHI_READ_JSON_OBJ);
if (!errors_array)
{
error_msg("Error: unable to read 'errors' array from json");
return;
}
int errors_len = json_object_array_length(errors_array);
for (int i = 0; i < errors_len; ++i)
{
json_object *error = json_object_array_get_idx(errors_array, i);
if (!error)
{
error_msg("Error: unable to get 'error[%d]'", i);
json_object_put(errors_array);
return;
}
char *desc_item = NULL;
bodhi_read_value(error, "description", &desc_item, BODHI_READ_STR);
if (!desc_item)
{
error_msg("Error: unable to get 'description' from 'error[%d]'", i);
continue;
}
error_msg("Error: %s", desc_item);
json_object_put(error);
free(desc_item);
}
json_object_put(errors_array);
return;
}
/**
* Parses only name from nvr
* nvr is RPM packages naming convention format: name-version-release
*
* for example: meanwhile3.34.3-3.34-3.fc666
* ^name ^ver.^release
*/
static int parse_nvr_name(const char *nvr, char **name)
{
const int len = strlen(nvr);
if (len <= 0)
return EINVAL;
const char *c = nvr + len - 1;
/* skip release */
for (; *c != '-'; --c)
{
if (c <= nvr)
return EINVAL;
}
--c;
/* skip version */
for (; *c != '-'; --c)
{
if (c <= nvr)
return EINVAL;
}
if (c <= nvr)
return EINVAL;
*name = xstrndup(nvr, (c - nvr));
return 0;
}
static GHashTable *bodhi_parse_json(json_object *json, const char *release)
{
int num_items = 0;
bodhi_read_value(json, "total", &num_items, BODHI_READ_INT);
if (num_items <= 0)
return NULL;
json_object *updates = NULL;
bodhi_read_value(json, "updates", &updates, BODHI_READ_JSON_OBJ);
if (!updates)
return NULL;
int updates_len = json_object_array_length(updates);
GHashTable *bodhi_table = g_hash_table_new_full(g_str_hash, g_str_equal, free,
(GDestroyNotify) free_bodhi_item);
for (int i = 0; i < updates_len; ++i)
{
json_object *updates_item = json_object_array_get_idx(updates, i);
/* some of item are null */
if (!updates_item)
continue;
json_object *builds_item = NULL;
bodhi_read_value(updates_item, "builds", &builds_item, BODHI_READ_JSON_OBJ);
if (!builds_item) /* broken json */
continue;
int karma, unstable_karma;
bodhi_read_value(updates_item, "karma", &karma, BODHI_READ_INT);
bodhi_read_value(updates_item, "unstable_karma", &unstable_karma, BODHI_READ_INT);
if (karma <= unstable_karma)
continue;
struct bodhi *b = NULL;
int builds_len = json_object_array_length(builds_item);
for (int k = 0; k < builds_len; ++k)
{
b = xzalloc(sizeof(struct bodhi));
char *name = NULL;
json_object *build = json_object_array_get_idx(builds_item, k);
bodhi_read_value(build, "nvr", &b->nvr, BODHI_READ_STR);
if (parse_nvr_name(b->nvr, &name))
error_msg_and_die("failed to parse package name from nvr: '%s'", b->nvr);
log_info("Found package: %s\n", name);
struct bodhi *bodhi_tbl_item = g_hash_table_lookup(bodhi_table, name);
if (bodhi_tbl_item && rpmvercmp(bodhi_tbl_item->nvr, b->nvr) > 0)
{
free_bodhi_item(b);
continue;
}
g_hash_table_replace(bodhi_table, name, b);
}
#if 0
bodhi_read_value(updates_item, "date_pushed", &b->date_pushed, BODHI_READ_STR);
bodhi_read_value(updates_item, "status", &b->status, BODHI_READ_STR);
json_object *release_item = NULL;
bodhi_read_value(updates_item, "release", &release_item, BODHI_READ_JSON_OBJ);
if (release_item)
bodhi_read_value(release_item, "dist_tag", &b->dist_tag, BODHI_READ_STR);
json_object *bugs = NULL;
bodhi_read_value(updates_item, "bugs", &release_item, BODHI_READ_JSON_OBJ);
if (bugs)
{
for (int j = 0; j < json_object_array_length(bugs); ++j)
{
int *bz_id = xmalloc(sizeof(int));
json_object *bug_item = json_object_array_get_idx(bugs, j);
bodhi_read_value(bug_item, "bz_id", bz_id, BODHI_READ_INT);
b->bz_ids = g_list_append(b->bz_ids, bz_id);
}
}
#endif
}
return bodhi_table;
}
static GHashTable *bodhi_query_list(const char *query, const char *release)
{
char *bodhi_url_bugs = xasprintf("%s/?%s", bodhi_url, query);
post_state_t *post_state = new_post_state(POST_WANT_BODY
| POST_WANT_SSL_VERIFY
| POST_WANT_ERROR_MSG);
const char *headers[] = {
"Accept: application/json",
NULL
};
get(post_state, bodhi_url_bugs, "application/x-www-form-urlencoded",
headers);
if (post_state->http_resp_code != 200 && post_state->http_resp_code != 400)
{
char *errmsg = post_state->curl_error_msg;
if (errmsg && errmsg[0])
error_msg_and_die("%s '%s'", errmsg, bodhi_url_bugs);
}
free(bodhi_url_bugs);
// log_warning("%s", post_state->body);
json_object *json = json_tokener_parse(post_state->body);
if (json == NULL)
error_msg_and_die("fatal: unable parse response from bodhi server");
/* we must check the http_resp_code because only error responses contain
* 'status' item. 'bodhi_read_value' function prints an error message in
* the case it did not found the item */
if (post_state->http_resp_code != 200)
{
char *status_item = NULL;
bodhi_read_value(json, "status", &status_item, BODHI_READ_STR);
if (status_item != NULL && strcmp(status_item, "error") == 0)
{
free(status_item);
bodhi_print_errors_from_json(json);
json_object_put(json);
xfunc_die(); // error_msg are printed in bodhi_print_errors_from_json
}
}
GHashTable *bodhi_table = bodhi_parse_json(json, release);
json_object_put(json);
free_post_state(post_state);
return bodhi_table;
}
static char *rpm_get_nvr_by_pkg_name(const char *pkg_name)
{
int status = rpmReadConfigFiles((const char *) NULL, (const char *) NULL);
if (status)
error_msg_and_die("error reading RPM rc files");
char *nvr = NULL;
rpmts ts = rpmtsCreate();
rpmdbMatchIterator iter = rpmtsInitIterator(ts, RPMTAG_NAME, pkg_name, 0);
Header header = rpmdbNextIterator(iter);
if (!header)
goto error;
const char *errmsg = NULL;
nvr = headerFormat(header, "%{name}-%{version}-%{release}", &errmsg);
if (!nvr && errmsg)
error_msg("cannot get nvr. reason: %s", errmsg);
error:
rpmdbFreeIterator(iter);
rpmtsFree(ts);
rpmFreeRpmrc();
rpmFreeCrypto();
rpmFreeMacros(NULL);
return nvr;
}
int main(int argc, char **argv)
{
abrt_init(argv);
enum {
OPT_v = 1 << 0,
OPT_d = 1 << 1,
OPT_g = 1 << 2,
OPT_b = 1 << 3,
OPT_u = 1 << 4,
OPT_r = 1 << 5,
};
const char *bugs = NULL, *release = NULL, *dump_dir_path = ".";
/* Keep enum above and order of options below in sync! */
struct options program_options[] = {
OPT__VERBOSE(&g_verbose),
OPT__DUMP_DIR(&dump_dir_path),
OPT_GROUP(""),
OPT_STRING('b', "bugs", &bugs, "ID1[,ID2,...]" , _("List of bug ids")),
OPT_STRING('u', "url", &bodhi_url, "URL", _("Specify a bodhi server url")),
OPT_OPTSTRING('r', "release", &release, "RELEASE", _("Specify a release")),
OPT_END()
};
const char *program_usage_string = _(
"& [-v] [-r[RELEASE]] (-b ID1[,ID2,...] | PKG-NAME) [PKG-NAME]... \n"
"\n"
"Search for updates on bodhi server"
);
unsigned opts = parse_opts(argc, argv, program_options, program_usage_string);
if (!bugs && !argv[optind])
show_usage_and_die(program_usage_string, program_options);
struct strbuf *query = strbuf_new();
if (bugs)
query = strbuf_append_strf(query, "bugs=%s&", bugs);
if (opts & OPT_r)
{
if (release)
{
/* There are no bodhi updates for Rawhide */
if (strcasecmp(release, "rawhide") == 0)
error_msg_and_die("Release \"%s\" is not supported",release);
query = strbuf_append_strf(query, "releases=%s&", release);
}
else
{
struct dump_dir *dd = dd_opendir(dump_dir_path, DD_OPEN_READONLY);
if (!dd)
xfunc_die();
problem_data_t *problem_data = create_problem_data_from_dump_dir(dd);
dd_close(dd);
if (!problem_data)
xfunc_die(); /* create_problem_data_for_reporting already emitted error msg */
char *product, *version;
map_string_t *osinfo = new_map_string();
problem_data_get_osinfo(problem_data, osinfo);
parse_osinfo_for_rhts(osinfo, &product, &version);
/* There are no bodhi updates for Rawhide */
bool rawhide = strcasecmp(version, "rawhide") == 0;
if (!rawhide)
query = strbuf_append_strf(query, "releases=f%s&", version);
free(product);
free(version);
free_map_string(osinfo);
if (rawhide)
{
strbuf_free(query);
error_msg_and_die("Release \"Rawhide\" is not supported");
}
}
}
if (argv[optind])
{
char *escaped = g_uri_escape_string(argv[optind], NULL, 0);
query = strbuf_append_strf(query, "packages=%s&", escaped);
free(escaped);
}
if (query->buf[query->len - 1] == '&')
query->buf[query->len - 1] = '\0';
log_warning(_("Searching for updates"));
GHashTable *update_hash_tbl = bodhi_query_list(query->buf, release);
strbuf_free(query);
if (!update_hash_tbl || !g_hash_table_size(update_hash_tbl))
{
log_warning(_("No updates for this package found"));
/*if (update_hash_tbl) g_hash_table_unref(update_hash_tbl);*/
return 0;
}
GHashTableIter iter;
char *name;
struct bodhi *b;
struct strbuf *q = strbuf_new();
g_hash_table_iter_init(&iter, update_hash_tbl);
while (g_hash_table_iter_next(&iter, (void **) &name, (void **) &b))
{
char *installed_pkg_nvr = rpm_get_nvr_by_pkg_name(name);
if (installed_pkg_nvr && rpmvercmp(installed_pkg_nvr, b->nvr) >= 0)
{
log_info("Update %s is older or same as local version %s, skipping", b->nvr, installed_pkg_nvr);
free(installed_pkg_nvr);
continue;
}
free(installed_pkg_nvr);
strbuf_append_strf(q, " %s", b->nvr);
}
/*g_hash_table_unref(update_hash_tbl);*/
if (!q->len)
{
/*strbuf_free(q);*/
log_warning(_("Local version of the package is newer than available updates"));
return 0;
}
/* Message is split into text and command in order to make
* translator's job easier
*/
/* We suggest the command which is most likely to exist on user's system,
* and which is familiar to the largest population of users.
* There are other tools (pkcon et al) which might be somewhat more
* convenient (for example, they might be usable from non-root), but they
* might be not present on the system, may evolve or be superseded,
* as it did happen to yum.
*/
map_string_t *settings = new_map_string();
load_abrt_plugin_conf_file("CCpp.conf", settings);
const char *value;
strbuf_prepend_str(q, " update --enablerepo=fedora --enablerepo=updates --enablerepo=updates-testing");
value = get_map_string_item_or_NULL(settings, "PackageManager");
if (value)
strbuf_prepend_str(q, value);
else
strbuf_prepend_str(q, DEFAULT_PACKAGE_MANAGER);
free_map_string(settings);
char *msg = xasprintf(_("An update exists which might fix your problem. "
"You can install it by running: %s. "
"Do you want to continue with reporting the bug?"),
q->buf
);
/*strbuf_free(q);*/
return ask_yes_no(msg) ? 0 : EXIT_STOP_EVENT_RUN;
}