From 1d67b0488f33aedb88191433a8383a061cd486c5 Mon Sep 17 00:00:00 2001 From: =?utf8?q?J=C3=A1n=20Kupec?= Date: Wed, 15 Jul 2009 08:30:49 +0200 Subject: [PATCH] Manage updates for bugzilla and CVE issues (fate #305503). - list-updates: --bugzilla, --cve, --issues, --all options added - patch: --bugzilla, --cve options added --- src/Zypper.cc | 40 ++++++++- src/update.cc | 267 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++- src/update.h | 20 ++++- 3 files changed, 318 insertions(+), 9 deletions(-) diff --git a/src/Zypper.cc b/src/Zypper.cc index 37736cd..9f2ee60 100644 --- a/src/Zypper.cc +++ b/src/Zypper.cc @@ -1566,6 +1566,9 @@ void Zypper::processCommandOptions() {"no-recommends", no_argument, 0, 0 }, {"recommends", no_argument, 0, 0 }, {"dry-run", no_argument, 0, 'D'}, + {"bz", required_argument, 0, 'b'}, + {"bugzilla", required_argument, 0, 'b'}, + {"cve", required_argument, 0, 0 }, {"help", no_argument, 0, 'h'}, {0, 0, 0, 0} }; @@ -1577,16 +1580,18 @@ void Zypper::processCommandOptions() "\n" " Command options:\n" "\n" - "-r, --repo Load only the specified repository.\n" " --skip-interactive Skip interactive patches.\n" "-l, --auto-agree-with-licenses\n" " Automatically say 'yes' to third party license\n" " confirmation prompt.\n" " See man zypper for more details.\n" + "-b, --bugzilla # Install patch fixing the specified bugzilla issue.\n" + " --cve # Install patch fixing the specified CVE issue.\n" " --debug-solver Create solver test case for debugging.\n" " --no-recommends Do not install recommended packages, only required.\n" " --recommends Install also recommended packages in addition\n" " to the required.\n" + "-r, --repo Load only the specified repository.\n" "-D, --dry-run Test the update, do not actually update.\n" ); break; @@ -1596,6 +1601,11 @@ void Zypper::processCommandOptions() { static struct option list_updates_options[] = { {"repo", required_argument, 0, 'r'}, + {"bz", optional_argument, 0, 'b'}, + {"bugzilla", optional_argument, 0, 'b'}, + {"cve", optional_argument, 0, 0 }, + {"issues", optional_argument, 0, 0 }, + {"all", no_argument, 0, 'a'}, {"help", no_argument, 0, 'h'}, {0, 0, 0, 0} }; @@ -1606,7 +1616,11 @@ void Zypper::processCommandOptions() "List all available needed patches.\n" "\n" " Command options:\n" - "-r, --repo List only patches from the specified repository.\n" + "-b, --bugzilla[=#] List needed patches for Bugzilla issues.\n" + " --cve[=#] List needed patches for CVE issues.\n" + " --issues[=string] Look for issues matching the specified string.\n" + "-a, --all List all patches, not only the needed ones.\n" + "-r, --repo List only patches from the specified repository.\n" ); break; } @@ -3591,6 +3605,8 @@ void Zypper::doCommand() else kinds.insert(ResKind::package); + //! \todo drop this option - it's the default for packages now, irrelevant + //! for patches; just test with products and patterns bool best_effort = copts.count( "best-effort" ); if (globalOpts().is_rug_compatible && best_effort) @@ -3602,6 +3618,15 @@ void Zypper::doCommand() Out::HIGH); } + if ((copts.count("bugzilla") || copts.count("bz") || copts.count("cve")) && + copts.count("issues")) + { + out().error(str::form( + _("Cannot use %s together with %s."), "--issues", "--bz, --cve")); + setExitCode(ZYPPER_EXIT_ERR_INVALID_ARGS); + return; + } + initRepoManager(); init_target(*this); init_repos(*this); @@ -3610,7 +3635,11 @@ void Zypper::doCommand() load_resolvables(*this); resolve(*this); - list_updates(*this, kinds, best_effort); + if (copts.count("bugzilla") || copts.count("bz") + || copts.count("cve") || copts.count("issues")) + list_patches_by_issue(*this); + else + list_updates(*this, kinds, best_effort); break; } @@ -3698,7 +3727,10 @@ void Zypper::doCommand() bool skip_interactive = copts.count("skip-interactive") || globalOpts().non_interactive; - mark_updates(*this, kinds, skip_interactive, best_effort); + if (copts.count("bugzilla") || copts.count("bz") || copts.count("cve")) + mark_updates_by_issue(*this); + else + mark_updates(*this, kinds, skip_interactive, best_effort); solve_and_commit(*this); diff --git a/src/update.cc b/src/update.cc index 4b95f8c..91925d8 100755 --- a/src/update.cc +++ b/src/update.cc @@ -11,6 +11,7 @@ #include "Table.h" #include "update.h" +#include "main.h" using namespace std; using namespace zypp; @@ -181,6 +182,8 @@ static void xml_list_updates(const ResKindSet & kinds) static bool list_patch_updates(Zypper & zypper) { + bool all = zypper.cOpts().count("all"); + Table tbl; Table pm_tbl; // only those that affect packagemanager (restartSuggested()), they have priority TableHeader th; @@ -200,7 +203,7 @@ static bool list_patch_updates(Zypper & zypper) { ResObject::constPtr res = it->resolvable(); - if ( it->isRelevant() && ! it->isSatisfied() ) + if (all || it->isBroken()) { Patch::constPtr patch = asKind(res); @@ -209,9 +212,9 @@ static bool list_patch_updates(Zypper & zypper) tr << patch->repoInfo().name(); tr << res->name () << res->edition ().asString(); tr << patch->category(); - tr << _("Needed"); + tr << (it->isBroken() ? _("needed") : _("not needed")); - if (patch->restartSuggested ()) + if (!all && patch->restartSuggested ()) pm_tbl << tr; else tbl << tr; @@ -646,6 +649,8 @@ mark_patch_update(ui::Selectable & s, bool skip_interactive, bool ignore_affects_pm) { Patch::constPtr patch = asKind(s.candidateObj()); + XXX << "candidate patch " << patch->name() << " " << ignore_affects_pm << ", " + << patch->restartSuggested() << endl; if (s.isBroken()) // bnc #506860 { DBG << "candidate patch " << patch->name() << " " << ignore_affects_pm << ", " @@ -825,3 +830,259 @@ void mark_updates(Zypper & zypper, const ResKindSet & kinds, bool skip_interacti } } } + +// ---------------------------------------------------------------------------- + +void list_patches_by_issue(Zypper & zypper) +{ + // lu --issues - list all issues which need to be fixed + // lu --issues=foo - look for foo in issue # or description + // lu --bz - list all bugzilla issues + // lu --cve - list all CVE issues + // lu --bz=foo --cve=foo - look for foo in bugzillas or CVEs + // --all - list all, not only needed patches + + // --bz, --cve can't be used together with --issue; this case is ruled out + // in the initial arguments validation in Zypper.cc + + typedef set > Issues; + bool only_needed = !zypper.cOpts().count("all"); + bool specific = false; // whether specific issue numbers were given + + Table t; + TableHeader th; + th << _("Issue") << _("No.") << _("Patch") << _("Category"); + t << th; + +#define FILL_ISSUES(TYPE, ID) \ + it = zypper.cOpts().find(TYPE); \ + if (it != zypper.cOpts().end()) \ + for_(i, it->second.begin(), it->second.end()) \ + { \ + issues.insert(pair(ID, *i)); \ + if (!i->empty()) \ + specific = true; \ + } \ + + // make a set of unique issues + Issues issues; + parsed_opts::const_iterator it; + FILL_ISSUES("bugzilla", "bugzilla"); + FILL_ISSUES("bz", "bugzilla"); + FILL_ISSUES("cve", "cve"); + FILL_ISSUES("issues", "issues"); + + // remove those without arguments if there are any with arguments + // (will show only the specified ones) + if (specific) + for (Issues::const_iterator i = issues.begin(); i != issues.end(); ) + { + if (i->second.empty()) + { + zypper.out().warning(str::form(_( + "Ignoring %s without argument because similar option with" + " an argument has been specified."), + ("--" + i->first).c_str())); + issues.erase(i++); + } + else + ++i; + } + + // construct a PoolQuery for each argument separately and add the results + // to the Table. + string issuesstr; + for_(issue, issues.begin(), issues.end()) + { + PoolQuery q; + q.setMatchSubstring(); + q.setCaseSensitive(false); + q.addKind(ResKind::patch); // is this unnecessary? only patches should have updateReference* attributes + + // specific bugzilla or CVE + if (specific) + { + if (issue->first == "bugzilla" || issue->first == "cve") + q.addAttribute(sat::SolvAttr::updateReferenceId, issue->second); + } + // all bugzillas or CVEs + else + { + if (issue->first == "bugzilla") + q.addAttribute(sat::SolvAttr::updateReferenceType, "bugzilla"); + else if (issue->first == "cve") + q.addAttribute(sat::SolvAttr::updateReferenceType, "cve"); + } + // look for substring in description and reference ID (issue number) + if (issue->first == "issues") + { + // whether argument was given or not, it does not matter; without + // argument, all issues will be found + q.addAttribute(sat::SolvAttr::updateReferenceId, issue->second); + issuesstr = issue->second; + } + + cout << "*****" << endl << q << endl << "*****" << endl; + + for_(it, q.begin(), q.end()) + { + PoolItem pi(*it); + if (only_needed && !pi.status().isBroken()) + continue; + Patch::constPtr patch = asKind(pi.resolvable()); + + // Print details about each match in that solvable: + for_( d, it.matchesBegin(), it.matchesEnd() ) + { + string itype = + d->subFind(sat::SolvAttr::updateReferenceType).asString(); + if (itype != issue->first) + continue; + + TableRow tr; + tr << itype; + tr << d->subFind(sat::SolvAttr::updateReferenceId).asString(); + tr << (patch->name() + "-" + patch->edition().asString()); + tr << patch->category(); + t << tr; + } + } + } + + // look for matches in patch descriptions + Table t1; + TableHeader th1; + th1 << _("Name") << _("Version") << _("Category") << _("Summary"); + t1 << th1; + if (!issuesstr.empty()) + { + PoolQuery q; + q.setMatchSubstring(); + q.setCaseSensitive(false); + q.addKind(ResKind::patch); + q.addAttribute(sat::SolvAttr::summary, issuesstr); + q.addAttribute(sat::SolvAttr::description, issuesstr); + + for_(it, q.begin(), q.end()) + { + PoolItem pi(*it); + if (only_needed && !pi.status().isBroken()) + continue; + Patch::constPtr patch = asKind(pi.resolvable()); + + TableRow tr; + tr << patch->name() << patch->edition().asString() << patch->category(); + //! \todo could show a highlighted match with a portion of surrounding + //! text. Needs case-insensitive find. + tr << patch->summary(); + t1 << tr; + } + } + + if (!zypper.globalOpts().no_abbrev) + t1.allowAbbrev(3); + t.sort(3); + t1.sort(0); + + if (t.empty() && t1.empty()) + zypper.out().info(_("No matching issues found.")); + else + { + if (!t.empty()) + { + if (!issuesstr.empty()) + { + cout << endl; + zypper.out().info(_( + "The following matches in issue numbers have been found:")); + } + + cout << endl << t; + } + + if (!t1.empty()) + { + if (!t.empty()) + cout << endl; + zypper.out().info(_( + "Matches in patch descriptions of the following patches have been" + " found:")); + cout << endl << t1; + } + } +} + +// ---------------------------------------------------------------------------- + +void mark_updates_by_issue(Zypper & zypper) +{ + typedef set > Issues; + Issues issues; + parsed_opts::const_iterator it = zypper.cOpts().find("bugzilla"); + if (it != zypper.cOpts().end()) + for_(i, it->second.begin(), it->second.end()) + issues.insert(pair("b", *i)); + it = zypper.cOpts().find("bz"); + if (it != zypper.cOpts().end()) + for_(i, it->second.begin(), it->second.end()) + issues.insert(pair("b", *i)); + it = zypper.cOpts().find("cve"); + if (it != zypper.cOpts().end()) + for_(i, it->second.begin(), it->second.end()) + issues.insert(pair("c", *i)); + + for_(issue, issues.begin(), issues.end()) + { + PoolQuery q; + q.setMatchExact(); + q.setCaseSensitive(false); + q.addKind(ResKind::patch); // is this unnecessary? + if (issue->first == "b") + q.addAttribute(sat::SolvAttr::updateReferenceId, issue->second); + else if (issue->first == "c") + q.addAttribute(sat::SolvAttr::updateReferenceId, issue->second); + + bool found = false; + for_(sit, q.begin(), q.end()) + { + if (!PoolItem(*sit).status().isBroken()) // not needed + continue; + + for_( d, sit.matchesBegin(), sit.matchesEnd() ) + { + if (issue->first == "b" && + d->subFind(sat::SolvAttr::updateReferenceType).asString() == "bugzilla") + { + if (mark_patch_update(*God->pool().proxy().lookup(*sit), + zypper.cOpts().count("skip-interactive"), true)) + found = true; + else + DBG << str::form("fix for bugzilla issue number %s was not marked.", + issue->second.c_str()); + } + else if (issue->first == "b" && + d->subFind(sat::SolvAttr::updateReferenceType).asString() == "bugzilla") + { + if (mark_patch_update(*God->pool().proxy().lookup(*sit), + zypper.cOpts().count("skip-interactive"), true)) + found = true; + else + DBG << str::form("fix for CVE issue number %s was not marked.", + issue->second.c_str()); + } + } + } + if (!found) + { + if (issue->first == "b") + zypper.out().info(str::form(_( + "Fix for bugzilla issue number %s was not found or is not needed."), + issue->second.c_str())); + else if (issue->first == "c") + zypper.out().info(str::form(_( + "Fix for CVE issue number %s was not found or is not needed."), + issue->second.c_str())); + zypper.setExitCode(ZYPPER_EXIT_INF_CAP_NOT_FOUND); + } + } // next issue from --bz --cve +} diff --git a/src/update.h b/src/update.h index a7c96c9..2f4c8b1 100755 --- a/src/update.h +++ b/src/update.h @@ -23,9 +23,19 @@ void list_updates(Zypper & zypper, bool best_effort); /** - * \param kind resolvable type - * \param skip_interactive whether to skip updates that need user interaction + * List available fixes to all issues or issues specified in --bugzilla + * or --cve options, or look for --issues[=str[ in numbers and descriptions + */ +void list_patches_by_issue(Zypper & zypper); + +/** + * Mark patches for installation or package for update. + * + * \param kinds set of resolvable types + * \param skip_interactive + * whether to skip updates that need user interaction * \param best_effort + * only require the resolvable name, let the solver choose */ void mark_updates(Zypper & zypper, const ResKindSet & kinds, @@ -33,6 +43,12 @@ void mark_updates(Zypper & zypper, bool best_effort); /** + * Mark patches for installation according to bugzilla or CVE number specified + * in --cve or --bugzilla or --bz. + */ +void mark_updates_by_issue(Zypper & zypper); + +/** * Find best (according to edition) uninstalled item * with same kind/name/arch as \a item. * -- 2.7.4