Blame plugins/debug.py

Packit 3a9065
#
Packit 3a9065
# Copyright (C) 2015  Red Hat, Inc.
Packit 3a9065
#
Packit 3a9065
# This copyrighted material is made available to anyone wishing to use,
Packit 3a9065
# modify, copy, or redistribute it subject to the terms and conditions of
Packit 3a9065
# the GNU General Public License v.2, or (at your option) any later version.
Packit 3a9065
# This program is distributed in the hope that it will be useful, but WITHOUT
Packit 3a9065
# ANY WARRANTY expressed or implied, including the implied warranties of
Packit 3a9065
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
Packit 3a9065
# Public License for more details.  You should have received a copy of the
Packit 3a9065
# GNU General Public License along with this program; if not, write to the
Packit 3a9065
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
Packit 3a9065
# 02110-1301, USA.  Any Red Hat trademarks that are incorporated in the
Packit 3a9065
# source code or documentation are not subject to the GNU General Public
Packit 3a9065
# License and may only be used or replicated with the express permission of
Packit 3a9065
# Red Hat, Inc.
Packit 3a9065
#
Packit 3a9065
Packit 3a9065
from __future__ import absolute_import
Packit 3a9065
from __future__ import unicode_literals
Packit 3a9065
Packit 3a9065
from dnf.i18n import ucd
Packit 3a9065
from dnfpluginscore import _, logger
Packit 3a9065
Packit 3a9065
import dnf
Packit 3a9065
import dnf.cli
Packit 3a9065
import gzip
Packit 3a9065
import hawkey
Packit 3a9065
import os
Packit 3a9065
import rpm
Packit 3a9065
import sys
Packit 3a9065
import time
Packit 3a9065
Packit 3a9065
DEBUG_VERSION = "dnf-debug-dump version 1\n"
Packit 3a9065
Packit 3a9065
Packit 3a9065
class Debug(dnf.Plugin):
Packit 3a9065
Packit 3a9065
    name = 'debug'
Packit 3a9065
Packit 3a9065
    def __init__(self, base, cli):
Packit 3a9065
        super(Debug, self).__init__(base, cli)
Packit 3a9065
        self.base = base
Packit 3a9065
        self.cli = cli
Packit 3a9065
        if self.cli is not None:
Packit 3a9065
            self.cli.register_command(DebugDumpCommand)
Packit 3a9065
            self.cli.register_command(DebugRestoreCommand)
Packit 3a9065
Packit 3a9065
Packit 3a9065
class DebugDumpCommand(dnf.cli.Command):
Packit 3a9065
Packit 3a9065
    aliases = ("debug-dump",)
Packit 3a9065
    summary = _("dump information about installed rpm packages to file")
Packit 3a9065
Packit 3a9065
    def __init__(self, cli):
Packit 3a9065
        super(DebugDumpCommand, self).__init__(cli)
Packit 3a9065
        self.dump_file = None
Packit 3a9065
Packit 3a9065
    def configure(self):
Packit 3a9065
        self.cli.demands.sack_activation = True
Packit 3a9065
        self.cli.demands.available_repos = True
Packit 3a9065
Packit 3a9065
    @staticmethod
Packit 3a9065
    def set_argparser(parser):
Packit 3a9065
        parser.add_argument(
Packit 3a9065
            "--norepos", action="store_true", default=False,
Packit 3a9065
            help=_("do not attempt to dump the repository contents."))
Packit 3a9065
        parser.add_argument(
Packit 3a9065
            "filename", nargs="?",
Packit 3a9065
            help=_("optional name of dump file"))
Packit 3a9065
Packit 3a9065
    def run(self):
Packit 3a9065
        """create debug txt file and compress it, if no filename specified
Packit 3a9065
           use dnf_debug_dump-<timestamp>.txt.gz by default"""
Packit 3a9065
Packit 3a9065
        filename = self.opts.filename
Packit 3a9065
        if not filename:
Packit 3a9065
            now = time.strftime("%Y-%m-%d_%T", time.localtime(time.time()))
Packit 3a9065
            filename = "dnf_debug_dump-%s-%s.txt.gz" % (os.uname()[1], now)
Packit 3a9065
Packit 3a9065
        filename = os.path.abspath(filename)
Packit 3a9065
        if filename.endswith(".gz"):
Packit 3a9065
            self.dump_file = gzip.GzipFile(filename, "w")
Packit 3a9065
        else:
Packit 3a9065
            self.dump_file = open(filename, "w")
Packit 3a9065
Packit 3a9065
        self.write(DEBUG_VERSION)
Packit 3a9065
        self.dump_system_info()
Packit 3a9065
        self.dump_dnf_config_info()
Packit 3a9065
        self.dump_rpm_problems()
Packit 3a9065
        self.dump_packages(not self.opts.norepos)
Packit 3a9065
        self.dump_rpmdb_versions()
Packit 3a9065
        self.dump_file.close()
Packit 3a9065
Packit 3a9065
        print(_("Output written to: %s") % filename)
Packit 3a9065
Packit 3a9065
    def write(self, msg):
Packit 3a9065
        if dnf.pycomp.PY3 and isinstance(self.dump_file, gzip.GzipFile):
Packit 3a9065
            msg = bytes(msg, "utf8")
Packit 3a9065
        dnf.pycomp.write_to_file(self.dump_file, msg)
Packit 3a9065
Packit 3a9065
    def dump_system_info(self):
Packit 3a9065
        self.write("%%%%SYSTEM INFO\n")
Packit 3a9065
        uname = os.uname()
Packit 3a9065
        self.write("  uname: %s, %s\n" % (uname[2], uname[4]))
Packit 3a9065
        self.write("  rpm ver: %s\n" % rpm.__version__)
Packit 3a9065
        self.write("  python ver: %s\n" % sys.version.replace("\n", ""))
Packit 3a9065
        return
Packit 3a9065
Packit 3a9065
    def dump_dnf_config_info(self):
Packit 3a9065
        var = self.base.conf.substitutions
Packit 3a9065
        plugins = ",".join([p.name for p in self.base._plugins.plugins])
Packit 3a9065
        self.write("%%%%DNF INFO\n")
Packit 3a9065
        self.write("  arch: %s\n" % var["arch"])
Packit 3a9065
        self.write("  basearch: %s\n" % var["basearch"])
Packit 3a9065
        self.write("  releasever: %s\n" % var["releasever"])
Packit 3a9065
        self.write("  dnf ver: %s\n" % dnf.const.VERSION)
Packit 3a9065
        self.write("  enabled plugins: %s\n" % plugins)
Packit 3a9065
        self.write("  global excludes: %s\n" % ",".join(self.base.conf.excludepkgs))
Packit 3a9065
        return
Packit 3a9065
Packit 3a9065
    def dump_rpm_problems(self):
Packit 3a9065
        self.write("%%%%RPMDB PROBLEMS\n")
Packit 3a9065
        (missing, conflicts) = rpm_problems(self.base)
Packit 3a9065
        self.write("".join(["Package %s requires %s\n" % (ucd(pkg), ucd(req))
Packit 3a9065
                            for (req, pkg) in missing]))
Packit 3a9065
        self.write("".join(["Package %s conflicts with %s\n" % (ucd(pkg),
Packit 3a9065
                                                                ucd(conf))
Packit 3a9065
                            for (conf, pkg) in conflicts]))
Packit 3a9065
Packit 3a9065
    def dump_packages(self, load_repos):
Packit 3a9065
        q = self.base.sack.query()
Packit 3a9065
        # packages from rpmdb
Packit 3a9065
        self.write("%%%%RPMDB\n")
Packit 3a9065
        for p in sorted(q.installed()):
Packit 3a9065
            self.write("  %s\n" % pkgspec(p))
Packit 3a9065
Packit 3a9065
        if not load_repos:
Packit 3a9065
            return
Packit 3a9065
Packit 3a9065
        self.write("%%%%REPOS\n")
Packit 3a9065
        available = q.available()
Packit 3a9065
        for repo in sorted(self.base.repos.iter_enabled(), key=lambda x: x.id):
Packit 3a9065
            try:
Packit 3a9065
                url = None
Packit 3a9065
                if repo.metalink is not None:
Packit 3a9065
                    url = repo.metalink
Packit 3a9065
                elif repo.mirrorlist is not None:
Packit 3a9065
                    url = repo.mirrorlist
Packit 3a9065
                elif len(repo.baseurl) > 0:
Packit 3a9065
                    url = repo.baseurl[0]
Packit 3a9065
                self.write("%%%s - %s\n" % (repo.id, url))
Packit 3a9065
                self.write("  excludes: %s\n" % ",".join(repo.excludepkgs))
Packit 3a9065
                for po in sorted(available.filter(reponame=repo.id)):
Packit 3a9065
                    self.write("  %s\n" % pkgspec(po))
Packit 3a9065
Packit 3a9065
            except dnf.exceptions.Error as e:
Packit 3a9065
                self.write("Error accessing repo %s: %s\n" % (repo, str(e)))
Packit 3a9065
                continue
Packit 3a9065
        return
Packit 3a9065
Packit 3a9065
    def dump_rpmdb_versions(self):
Packit 3a9065
        self.write("%%%%RPMDB VERSIONS\n")
Packit 3a9065
        version = self.base.sack._rpmdb_version()
Packit 3a9065
        self.write("  all: %s\n" % version)
Packit 3a9065
        return
Packit 3a9065
Packit 3a9065
Packit 3a9065
class DebugRestoreCommand(dnf.cli.Command):
Packit 3a9065
Packit 3a9065
    aliases = ("debug-restore",)
Packit 3a9065
    summary = _("restore packages recorded in debug-dump file")
Packit 3a9065
Packit 3a9065
    def configure(self):
Packit 3a9065
        self.cli.demands.sack_activation = True
Packit 3a9065
        self.cli.demands.available_repos = True
Packit 3a9065
        self.cli.demands.root_user = True
Packit 3a9065
Packit 3a9065
    @staticmethod
Packit 3a9065
    def set_argparser(parser):
Packit 3a9065
        parser.add_argument(
Packit 3a9065
            "--output", action="store_true",
Packit 3a9065
            help=_("output commands that would be run to stdout."))
Packit 3a9065
        parser.add_argument(
Packit 3a9065
            "--install-latest", action="store_true",
Packit 3a9065
            help=_("Install the latest version of recorded packages."))
Packit 3a9065
        parser.add_argument(
Packit 3a9065
            "--ignore-arch", action="store_true",
Packit 3a9065
            help=_("Ignore architecture and install missing packages matching "
Packit 3a9065
                   "the name, epoch, version and release."))
Packit 3a9065
        parser.add_argument(
Packit 3a9065
            "--filter-types", metavar="[install, remove, replace]",
Packit 3a9065
            default="install, remove, replace",
Packit 3a9065
            help=_("limit to specified type"))
Packit 3a9065
        parser.add_argument(
Packit 3a9065
            "filename", nargs=1, help=_("name of dump file"))
Packit 3a9065
Packit 3a9065
    def run(self):
Packit 3a9065
        """Execute the command action here."""
Packit 3a9065
        if self.opts.filter_types:
Packit 3a9065
            self.opts.filter_types = set(
Packit 3a9065
                self.opts.filter_types.replace(",", " ").split())
Packit 3a9065
Packit Service 4c2e05
        installed = self.base.sack.query().installed()
Packit 3a9065
        dump_pkgs = self.read_dump_file(self.opts.filename[0])
Packit 3a9065
Packit Service 4c2e05
        self.process_installed(installed, dump_pkgs, self.opts)
Packit 3a9065
Packit 3a9065
        self.process_dump(dump_pkgs, self.opts)
Packit 3a9065
Packit Service 4c2e05
        if not self.opts.output:
Packit Service 4c2e05
            self.base.resolve()
Packit Service 4c2e05
            self.base.do_transaction()
Packit Service 4c2e05
Packit Service 4c2e05
    def process_installed(self, installed, dump_pkgs, opts):
Packit Service 4c2e05
        for pkg in sorted(installed):
Packit Service 4c2e05
            filtered = False
Packit 3a9065
            spec = pkgspec(pkg)
Packit Service 4c2e05
            action, dn, da, de, dv, dr = dump_pkgs.get((pkg.name, pkg.arch),
Packit Service 4c2e05
                                                       [None, None, None,
Packit Service 4c2e05
                                                        None, None, None])
Packit Service 4c2e05
            dump_naevr = (dn, da, de, dv, dr)
Packit Service 4c2e05
            if pkg.pkgtup == dump_naevr:
Packit Service 4c2e05
                # package unchanged
Packit Service 4c2e05
                del dump_pkgs[(pkg.name, pkg.arch)]
Packit 3a9065
            else:
Packit Service 4c2e05
                if action == "install":
Packit Service 4c2e05
                    # already have some version
Packit Service 4c2e05
                    dump_pkgs[(pkg.name, pkg.arch)][0] = "replace"
Packit Service 4c2e05
                    if "replace" not in opts.filter_types:
Packit Service 4c2e05
                        filtered = True
Packit Service 4c2e05
                else:
Packit Service 4c2e05
                    if "remove" not in opts.filter_types:
Packit Service 4c2e05
                        filtered = True
Packit Service 4c2e05
                if not filtered:
Packit 6bee63
                    if opts.output:
Packit 6bee63
                        print("remove    %s" % spec)
Packit 6bee63
                    else:
Packit 6bee63
                        self.base.package_remove(pkg)
Packit 581aa8
Packit 581aa8
    def process_dump(self, dump_pkgs, opts):
Packit Service 4c2e05
        for (action, n, a, e, v, r) in sorted(dump_pkgs.values()):
Packit Service 4c2e05
            filtered = False
Packit Service 4c2e05
            if opts.ignore_arch:
Packit Service 4c2e05
                arch = ""
Packit Service 4c2e05
            else:
Packit Service 4c2e05
                arch = "." + a
Packit Service 4c2e05
            if opts.install_latest and action == "install":
Packit Service 4c2e05
                pkg_spec = "%s%s" % (n, arch)
Packit Service 4c2e05
                if "install" not in opts.filter_types:
Packit Service 4c2e05
                    filtered = True
Packit Service 4c2e05
            else:
Packit Service 4c2e05
                pkg_spec = pkgtup2spec(n, arch, e, v, r)
Packit Service 4c2e05
                if (action == "replace" and
Packit Service 4c2e05
                        "replace" not in opts.filter_types):
Packit Service 4c2e05
                    filtered = True
Packit Service 4c2e05
            if not filtered:
Packit Service 4c2e05
                if opts.output:
Packit Service 4c2e05
                    print("install   %s" % pkg_spec)
Packit 581aa8
                else:
Packit Service 4c2e05
                    try:
Packit Service 4c2e05
                        self.base.install(pkg_spec)
Packit Service 4c2e05
                    except dnf.exceptions.MarkingError:
Packit Service 4c2e05
                        logger.error(_("Package %s is not available"), pkg_spec)
Packit 3a9065
Packit 3a9065
    @staticmethod
Packit 3a9065
    def read_dump_file(filename):
Packit 3a9065
        if filename.endswith(".gz"):
Packit 3a9065
            fobj = gzip.GzipFile(filename)
Packit 3a9065
        else:
Packit 3a9065
            fobj = open(filename)
Packit 3a9065
Packit 3a9065
        if ucd(fobj.readline()) != DEBUG_VERSION:
Packit 3a9065
            logger.error(_("Bad dnf debug file: %s"), filename)
Packit 3a9065
            raise dnf.exceptions.Error
Packit 3a9065
Packit 3a9065
        skip = True
Packit 3a9065
        pkgs = {}
Packit 3a9065
        for line in fobj:
Packit 3a9065
            line = ucd(line)
Packit 3a9065
            if skip:
Packit 3a9065
                if line == "%%%%RPMDB\n":
Packit 3a9065
                    skip = False
Packit 3a9065
                continue
Packit 3a9065
Packit 3a9065
            if not line or line[0] != " ":
Packit 3a9065
                break
Packit 3a9065
Packit 3a9065
            pkg_spec = line.strip()
Packit 3a9065
            nevra = hawkey.split_nevra(pkg_spec)
Packit Service 4c2e05
            pkgs[(nevra.name, nevra.arch)] = ["install", ucd(nevra.name),
Packit Service 4c2e05
                                              ucd(nevra.arch),
Packit Service 4c2e05
                                              ucd(nevra.epoch),
Packit Service 4c2e05
                                              ucd(nevra.version),
Packit Service 4c2e05
                                              ucd(nevra.release)]
Packit 3a9065
Packit 3a9065
        return pkgs
Packit 3a9065
Packit 3a9065
Packit 3a9065
def rpm_problems(base):
Packit 3a9065
    rpmdb = dnf.sack._rpmdb_sack(base)
Packit 3a9065
    allpkgs = rpmdb.query().installed()
Packit 3a9065
Packit 3a9065
    requires = set()
Packit 3a9065
    conflicts = set()
Packit 3a9065
    for pkg in allpkgs:
Packit 3a9065
        requires.update([(req, pkg) for req in pkg.requires
Packit 3a9065
                         if not str(req) == "solvable:prereqmarker"
Packit 3a9065
                         and not str(req).startswith("rpmlib(")])
Packit 3a9065
        conflicts.update([(conf, pkg) for conf in pkg.conflicts])
Packit 3a9065
Packit 3a9065
    missing_requires = [(req, pkg) for (req, pkg) in requires
Packit 3a9065
                        if not allpkgs.filter(provides=req)]
Packit 3a9065
    existing_conflicts = [(conf, pkg) for (conf, pkg) in conflicts
Packit 3a9065
                          if allpkgs.filter(provides=conf)]
Packit 3a9065
    return missing_requires, existing_conflicts
Packit 3a9065
Packit 3a9065
Packit 3a9065
def pkgspec(pkg):
Packit 3a9065
    return pkgtup2spec(pkg.name, pkg.arch, pkg.epoch, pkg.version, pkg.release)
Packit 3a9065
Packit 3a9065
Packit 3a9065
def pkgtup2spec(name, arch, epoch, version, release):
Packit Service 4c2e05
    a = "" if not arch else ".%s" % arch
Packit 3a9065
    e = "" if epoch in (None, "") else "%s:" % epoch
Packit 3a9065
    return "%s-%s%s-%s%s" % (name, e, version, release, a)