|
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)
|