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 Service 6746a6
        if not self.opts.output:
Packit Service 6746a6
            self.cli.demands.resolving = 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 Service 6746a6
            "--remove-installonly", action="store_true",
Packit Service 6746a6
            help=_('Allow removing of install-only packages. Using this option may '
Packit Service 6746a6
                   'result in an attempt to remove the running kernel.'))
Packit Service 6746a6
        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 3a9065
        dump_pkgs = self.read_dump_file(self.opts.filename[0])
Packit 3a9065
Packit Service 6746a6
        self.process_installed(dump_pkgs, self.opts)
Packit 3a9065
Packit 3a9065
        self.process_dump(dump_pkgs, self.opts)
Packit 3a9065
Packit Service 6746a6
    def process_installed(self, dump_pkgs, opts):
Packit Service 6746a6
        installed = self.base.sack.query().installed()
Packit Service 6746a6
        installonly_pkgs = self.base._get_installonly_query(installed)
Packit Service 6746a6
        for pkg in installed:
Packit Service 6746a6
            pkg_remove = False
Packit 3a9065
            spec = pkgspec(pkg)
Packit Service 6746a6
            dumped_versions = dump_pkgs.get((pkg.name, pkg.arch), None)
Packit Service 6746a6
            if dumped_versions is not None:
Packit Service 6746a6
                evr = (pkg.epoch, pkg.version, pkg.release)
Packit Service 6746a6
                if evr in dumped_versions:
Packit Service 6746a6
                    # the correct version is already installed
Packit Service 6746a6
                    dumped_versions[evr] = 'skip'
Packit Service 4c2e05
                else:
Packit Service 6746a6
                    # other version is currently installed
Packit Service 6746a6
                    if pkg in installonly_pkgs:
Packit Service 6746a6
                        # package is install-only, should be removed
Packit Service 6746a6
                        pkg_remove = True
Packit Service 6746a6
                    else:
Packit Service 6746a6
                        # package should be upgraded / downgraded
Packit Service 6746a6
                        if "replace" in opts.filter_types:
Packit Service 6746a6
                            action = 'replace'
Packit Service 6746a6
                        else:
Packit Service 6746a6
                            action = 'skip'
Packit Service 6746a6
                        for d_evr in dumped_versions.keys():
Packit Service 6746a6
                            dumped_versions[d_evr] = action
Packit Service 6746a6
            else:
Packit Service 6746a6
                # package should not be installed
Packit Service 6746a6
                pkg_remove = True
Packit Service 6746a6
            if pkg_remove and "remove" in opts.filter_types:
Packit Service 6746a6
                if pkg not in installonly_pkgs or opts.remove_installonly:
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 6746a6
        for (n, a) in sorted(dump_pkgs.keys()):
Packit Service 6746a6
            dumped_versions = dump_pkgs[(n, a)]
Packit Service 6746a6
            for (e, v, r) in sorted(dumped_versions.keys()):
Packit Service 6746a6
                action = dumped_versions[(e, v, r)]
Packit Service 6746a6
                if action == 'skip':
Packit Service 6746a6
                    continue
Packit Service 6746a6
                if opts.ignore_arch:
Packit Service 6746a6
                    arch = ""
Packit 581aa8
                else:
Packit Service 6746a6
                    arch = "." + a
Packit Service 6746a6
                if opts.install_latest and action == "install":
Packit Service 6746a6
                    pkg_spec = "%s%s" % (n, arch)
Packit Service 6746a6
                else:
Packit Service 6746a6
                    pkg_spec = pkgtup2spec(n, arch, e, v, r)
Packit Service 6746a6
                if action in opts.filter_types:
Packit Service 6746a6
                    if opts.output:
Packit Service 6746a6
                        print("%s   %s" % (action, pkg_spec))
Packit Service 6746a6
                    else:
Packit Service 6746a6
                        try:
Packit Service 6746a6
                            self.base.install(pkg_spec)
Packit Service 6746a6
                        except dnf.exceptions.MarkingError:
Packit Service 6746a6
                            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 6746a6
            # {(name, arch): {(epoch, version, release): action}}
Packit Service 6746a6
            pkgs.setdefault((nevra.name, nevra.arch), {})[
Packit Service 6746a6
                (nevra.epoch, nevra.version, nevra.release)] = "install"
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 6746a6
    a = "" if not arch else ".%s" % arch.lstrip('.')
Packit 3a9065
    e = "" if epoch in (None, "") else "%s:" % epoch
Packit 3a9065
    return "%s-%s%s-%s%s" % (name, e, version, release, a)