1# 2# Copyright (C) 2016 Red Hat, Inc. 3# 4# This copyrighted material is made available to anyone wishing to use, 5# modify, copy, or redistribute it subject to the terms and conditions of 6# the GNU General Public License v.2, or (at your option) any later version. 7# This program is distributed in the hope that it will be useful, but WITHOUT 8# ANY WARRANTY expressed or implied, including the implied warranties of 9# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General 10# Public License for more details. You should have received a copy of the 11# GNU General Public License along with this program; if not, write to the 12# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 13# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the 14# source code or documentation are not subject to the GNU General Public 15# License and may only be used or replicated with the express permission of 16# Red Hat, Inc. 17# 18 19from __future__ import absolute_import 20from __future__ import unicode_literals 21from dnf.i18n import _ 22from dnf.cli import commands 23 24import argparse 25import dnf.exceptions 26 27 28class CheckCommand(commands.Command): 29 """A class containing methods needed by the cli to execute the check 30 command. 31 """ 32 33 aliases = ('check',) 34 summary = _('check for problems in the packagedb') 35 36 @staticmethod 37 def set_argparser(parser): 38 parser.add_argument('--all', dest='check_types', 39 action='append_const', const='all', 40 help=_('show all problems; default')) 41 parser.add_argument('--dependencies', dest='check_types', 42 action='append_const', const='dependencies', 43 help=_('show dependency problems')) 44 parser.add_argument('--duplicates', dest='check_types', 45 action='append_const', const='duplicates', 46 help=_('show duplicate problems')) 47 parser.add_argument('--obsoleted', dest='check_types', 48 action='append_const', const='obsoleted', 49 help=_('show obsoleted packages')) 50 parser.add_argument('--provides', dest='check_types', 51 action='append_const', const='provides', 52 help=_('show problems with provides')) 53 # Add compatibility with yum but invisible in help 54 # In choices [] allows to return empty list if no argument otherwise it fails 55 parser.add_argument('check_yum_types', nargs='*', choices=[ 56 'all', 'dependencies', 'duplicates', 'obsoleted', 'provides', []], 57 help=argparse.SUPPRESS) 58 59 def configure(self): 60 self.cli.demands.sack_activation = True 61 if self.opts.check_yum_types: 62 if self.opts.check_types: 63 self.opts.check_types = self.opts.check_types + \ 64 self.opts.check_yum_types 65 else: 66 self.opts.check_types = self.opts.check_yum_types 67 if not self.opts.check_types: 68 self.opts.check_types = {'all'} 69 else: 70 self.opts.check_types = set(self.opts.check_types) 71 self.base.conf.disable_excludes += ["all"] 72 73 def run(self): 74 output_set = set() 75 q = self.base.sack.query().installed() 76 77 if self.opts.check_types.intersection({'all', 'dependencies'}): 78 sack = None 79 for pkg in q: 80 for require in set(pkg.regular_requires) | set(set(pkg.requires_pre) - set(pkg.prereq_ignoreinst)): 81 if str(require).startswith('rpmlib'): 82 continue 83 if not len(q.filter(provides=[require])): 84 if str(require).startswith('('): 85 # rich deps can be only tested by solver 86 if sack is None: 87 sack = dnf.sack.rpmdb_sack(self.base) 88 selector = dnf.selector.Selector(sack) 89 selector.set(provides=str(require)) 90 goal = dnf.goal.Goal(sack) 91 goal.protect_running_kernel = self.base.conf.protect_running_kernel 92 goal.install(select=selector, optional=False) 93 solved = goal.run() 94 # there ase only @system repo in sack, therefore solved is only in case 95 # when rich deps doesn't require any additional package 96 if solved: 97 continue 98 msg = _("{} has missing requires of {}") 99 output_set.add(msg.format( 100 self.base.output.term.bold(pkg), 101 self.base.output.term.bold(require))) 102 for conflict in pkg.conflicts: 103 conflicted = q.filter(provides=[conflict], 104 name=str(conflict).split()[0]) 105 for conflict_pkg in conflicted: 106 msg = '{} has installed conflict "{}": {}' 107 output_set.add(msg.format( 108 self.base.output.term.bold(pkg), 109 self.base.output.term.bold(conflict), 110 self.base.output.term.bold(conflict_pkg))) 111 112 if self.opts.check_types.intersection({'all', 'duplicates'}): 113 installonly = self.base._get_installonly_query(q) 114 dups = q.duplicated().difference(installonly)._name_dict() 115 for name, pkgs in dups.items(): 116 pkgs.sort() 117 for dup in pkgs[1:]: 118 msg = _("{} is a duplicate with {}").format( 119 self.base.output.term.bold(pkgs[0]), 120 self.base.output.term.bold(dup)) 121 output_set.add(msg) 122 123 if self.opts.check_types.intersection({'all', 'obsoleted'}): 124 for pkg in q: 125 for obsolete in pkg.obsoletes: 126 obsoleted = q.filter(provides=[obsolete], 127 name=str(obsolete).split()[0]) 128 if len(obsoleted): 129 msg = _("{} is obsoleted by {}").format( 130 self.base.output.term.bold(obsoleted[0]), 131 self.base.output.term.bold(pkg)) 132 output_set.add(msg) 133 134 if self.opts.check_types.intersection({'all', 'provides'}): 135 for pkg in q: 136 for provide in pkg.provides: 137 if pkg not in q.filter(provides=[provide]): 138 msg = _("{} provides {} but it cannot be found") 139 output_set.add(msg.format( 140 self.base.output.term.bold(pkg), 141 self.base.output.term.bold(provide))) 142 143 for msg in sorted(output_set): 144 print(msg) 145 146 if output_set: 147 raise dnf.exceptions.Error( 148 'Check discovered {} problem(s)'.format(len(output_set))) 149