#!/usr/bin/python -t # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Library General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # Copyright 2006 Duke University # Copyright 2013 Red Hat # Written by Seth Vidal """ Classes for subcommands of the yum command line interface. """ import os import sys import cli import rpm from yum import logginglevels from yum import _, P_ from yum import misc import yum.Errors import operator import locale import fnmatch import time from yum.i18n import utf8_width, utf8_width_fill, to_unicode, exception2msg import tempfile import shutil import distutils.spawn import glob import errno import yum.config from yum import updateinfo from yum.packages import parsePackages from yum.fssnapshots import LibLVMError, lvmerr2str def _err_mini_usage(base, basecmd): if basecmd not in base.yum_cli_commands: base.usage() return cmd = base.yum_cli_commands[basecmd] txt = base.yum_cli_commands["help"]._makeOutput(cmd) base.logger.critical(_(' Mini usage:\n')) base.logger.critical(txt) def checkRootUID(base): """Verify that the program is being run by the root user. :param base: a :class:`yum.Yumbase` object. :raises: :class:`cli.CliError` """ if base.conf.uid != 0: base.logger.critical(_('You need to be root to perform this command.')) raise cli.CliError def checkGPGKey(base): """Verify that there are gpg keys for the enabled repositories in the rpm database. :param base: a :class:`yum.Yumbase` object. :raises: :class:`cli.CliError` """ if base._override_sigchecks: return if not base.gpgKeyCheck(): for repo in base.repos.listEnabled(): if (repo.gpgcheck or repo.repo_gpgcheck) and not repo.gpgkey: msg = _(""" You have enabled checking of packages via GPG keys. This is a good thing. However, you do not have any GPG public keys installed. You need to download the keys for packages you wish to install and install them. You can do that by running the command: rpm --import public.gpg.key Alternatively you can specify the url to the key you would like to use for a repository in the 'gpgkey' option in a repository section and yum will install it for you. For more information contact your distribution or package provider. """) base.logger.critical(msg) base.logger.critical(_("Problem repository: %s"), repo) raise cli.CliError def checkPackageArg(base, basecmd, extcmds): """Verify that *extcmds* contains the name of at least one package for *basecmd* to act on. :param base: a :class:`yum.Yumbase` object. :param basecmd: the name of the command being checked for :param extcmds: a list of arguments passed to *basecmd* :raises: :class:`cli.CliError` """ if len(extcmds) == 0: base.logger.critical( _('Error: Need to pass a list of pkgs to %s') % basecmd) _err_mini_usage(base, basecmd) raise cli.CliError def checkSwapPackageArg(base, basecmd, extcmds): """Verify that *extcmds* contains the name of at least two packages for *basecmd* to act on. :param base: a :class:`yum.Yumbase` object. :param basecmd: the name of the command being checked for :param extcmds: a list of arguments passed to *basecmd* :raises: :class:`cli.CliError` """ min_args = 2 if '--' in extcmds: min_args = 3 if len(extcmds) < min_args: base.logger.critical( _('Error: Need at least two packages to %s') % basecmd) _err_mini_usage(base, basecmd) raise cli.CliError def checkRepoPackageArg(base, basecmd, extcmds): """Verify that *extcmds* contains the name of at least one package for *basecmd* to act on. :param base: a :class:`yum.Yumbase` object. :param basecmd: the name of the command being checked for :param extcmds: a list of arguments passed to *basecmd* :raises: :class:`cli.CliError` """ if len(extcmds) < 2: # install|remove [pkgs] base.logger.critical( _('Error: Need to pass a repoid. and command to %s') % basecmd) _err_mini_usage(base, basecmd) raise cli.CliError repos = base.repos.findRepos(extcmds[0], name_match=True, ignore_case=True) if not repos: base.logger.critical( _('Error: Need to pass a single valid repoid. to %s') % basecmd) _err_mini_usage(base, basecmd) raise cli.CliError if len(repos) > 1: repos = [r for r in repos if r.isEnabled()] if len(repos) > 1: repos = ", ".join([r.ui_id for r in repos]) base.logger.critical( _('Error: Need to pass only a single valid repoid. to %s, passed: %s') % (basecmd, repos)) _err_mini_usage(base, basecmd) raise cli.CliError if not repos[0].isEnabled(): # Might as well just fix this... base.repos.enableRepo(repos[0].id) base.verbose_logger.info( _('Repo %s has been automatically enabled.') % repos[0].ui_id) def checkItemArg(base, basecmd, extcmds): """Verify that *extcmds* contains the name of at least one item for *basecmd* to act on. Generally, the items are command-line arguments that are not the name of a package, such as a file name passed to provides. :param base: a :class:`yum.Yumbase` object. :param basecmd: the name of the command being checked for :param extcmds: a list of arguments passed to *basecmd* :raises: :class:`cli.CliError` """ if len(extcmds) == 0: base.logger.critical(_('Error: Need an item to match')) _err_mini_usage(base, basecmd) raise cli.CliError def checkGroupArg(base, basecmd, extcmds): """Verify that *extcmds* contains the name of at least one group for *basecmd* to act on. :param base: a :class:`yum.Yumbase` object. :param basecmd: the name of the command being checked for :param extcmds: a list of arguments passed to *basecmd* :raises: :class:`cli.CliError` """ if len(extcmds) == 0: base.logger.critical(_('Error: Need a group or list of groups')) _err_mini_usage(base, basecmd) raise cli.CliError def checkCleanArg(base, basecmd, extcmds): """Verify that *extcmds* contains at least one argument, and that all arguments in *extcmds* are valid options for clean. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command being checked for :param extcmds: a list of arguments passed to *basecmd* :raises: :class:`cli.CliError` """ VALID_ARGS = ('headers', 'packages', 'metadata', 'dbcache', 'plugins', 'expire-cache', 'rpmdb', 'all') if len(extcmds) == 0: base.logger.critical(_('Error: clean requires an option: %s') % ( ", ".join(VALID_ARGS))) raise cli.CliError for cmd in extcmds: if cmd not in VALID_ARGS: base.logger.critical(_('Error: invalid clean argument: %r') % cmd) _err_mini_usage(base, basecmd) raise cli.CliError def checkShellArg(base, basecmd, extcmds): """Verify that the arguments given to 'yum shell' are valid. yum shell can be given either no argument, or exactly one argument, which is the name of a file. :param base: a :class:`yum.Yumbase` object. :param basecmd: the name of the command being checked for :param extcmds: a list of arguments passed to *basecmd* :raises: :class:`cli.CliError` """ if len(extcmds) == 0: base.verbose_logger.debug(_("No argument to shell")) elif len(extcmds) == 1: base.verbose_logger.debug(_("Filename passed to shell: %s"), extcmds[0]) if not os.path.isfile(extcmds[0]): base.logger.critical( _("File %s given as argument to shell does not exist."), extcmds[0]) base.usage() raise cli.CliError else: base.logger.critical( _("Error: more than one file given as argument to shell.")) base.usage() raise cli.CliError def checkEnabledRepo(base, possible_local_files=[]): """Verify that there is at least one enabled repo. :param base: a :class:`yum.Yumbase` object. :param basecmd: the name of the command being checked for :param extcmds: a list of arguments passed to *basecmd* :raises: :class:`cli.CliError`: """ if base.repos.listEnabled(): return for lfile in possible_local_files: if lfile.endswith(".rpm") and os.path.exists(lfile): return # runs prereposetup (which "most" plugins currently use to add repos.) base.pkgSack if base.repos.listEnabled(): return msg = _('There are no enabled repos.\n' ' Run "yum repolist all" to see the repos you have.\n' ' To enable Red Hat Subscription Management repositories:\n' ' subscription-manager repos --enable \n' ' To enable custom repositories:\n' ' yum-config-manager --enable ') base.logger.critical(msg) raise cli.CliError class YumCommand: """An abstract base class that defines the methods needed by the cli to execute a specific command. Subclasses must override at least :func:`getUsage` and :func:`getSummary`. """ def __init__(self): self.done_command_once = False self.hidden = False def doneCommand(self, base, msg, *args): """ Output *msg* the first time that this method is called, and do nothing on subsequent calls. This is to prevent duplicate messages from being printed for the same command. :param base: a :class:`yum.Yumbase` object :param msg: the message to be output :param *args: additional arguments associated with the message """ if not self.done_command_once: base.verbose_logger.info(logginglevels.INFO_2, msg, *args) self.done_command_once = True def getNames(self): """Return a list of strings that are the names of the command. The command can be called from the command line by using any of these names. :return: a list containing the names of the command """ return [] def getUsage(self): """Return a usage string for the command, including arguments. :return: a usage string for the command """ raise NotImplementedError def getSummary(self): """Return a one line summary of what the command does. :return: a one line summary of what the command does """ raise NotImplementedError def doCheck(self, base, basecmd, extcmds): """Verify that various conditions are met so that the command can run. :param base: a :class:`yum.Yumbase` object. :param basecmd: the name of the command being checked for :param extcmds: a list of arguments passed to *basecmd* """ pass def doCommand(self, base, basecmd, extcmds): """Execute the command :param base: a :class:`yum.Yumbase` object. :param basecmd: the name of the command being executed :param extcmds: a list of arguments passed to *basecmd* :return: (exit_code, [ errors ]) exit_code is:: 0 = we're done, exit 1 = we've errored, exit with error string 2 = we've got work yet to do, onto the next stage """ return 0, [_('Nothing to do')] def needTs(self, base, basecmd, extcmds): """Return whether a transaction set must be set up before the command can run :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: a list of arguments passed to *basecmd* :return: True if a transaction set is needed, False otherwise """ return True # Some of this is subjective, esp. between past/present, but roughly use: # # write = I'm using package data to alter the rpmdb in anyway. # read-only:future = I'm providing data that is likely to result in a # future write, so we might as well do it now. # Eg. yum check-update && yum update -q -y # read-only:present = I'm providing data about the present state of # packages in the repo. # Eg. yum list yum # read-only:past = I'm providing context data about past writes, or just # anything that is available is good enough for me # (speed is much better than quality). # Eg. yum history info # Eg. TAB completion # # ...default is write, which does the same thing we always did (obey # metadata_expire and live with it). def cacheRequirement(self, base, basecmd, extcmds): """Return the cache requirements for the remote repos. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: a list of arguments passed to *basecmd* :return: Type of requirement: read-only:past, read-only:present, read-only:future, write """ return 'write' class InstallCommand(YumCommand): """A class containing methods needed by the cli to execute the install command. """ def getNames(self): """Return a list containing the names of this command. This command can be called from the command line by using any of these names. :return: a list containing the names of this command """ return ['install', 'install-n', 'install-na', 'install-nevra'] def getUsage(self): """Return a usage string for this command. :return: a usage string for this command """ return _("PACKAGE...") def getSummary(self): """Return a one line summary of this command. :return: a one line summary of this command """ return _("Install a package or packages on your system") def doCheck(self, base, basecmd, extcmds): """Verify that conditions are met so that this command can run. These include that the program is being run by the root user, that there are enabled repositories with gpg keys, and that this command is called with appropriate arguments. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: the command line arguments passed to *basecmd* """ checkRootUID(base) checkGPGKey(base) checkPackageArg(base, basecmd, extcmds) checkEnabledRepo(base, extcmds) def doCommand(self, base, basecmd, extcmds): """Execute this command. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: the command line arguments passed to *basecmd* :return: (exit_code, [ errors ]) exit_code is:: 0 = we're done, exit 1 = we've errored, exit with error string 2 = we've got work yet to do, onto the next stage """ self.doneCommand(base, _("Setting up Install Process")) return base.installPkgs(extcmds, basecmd=basecmd) class UpdateCommand(YumCommand): """A class containing methods needed by the cli to execute the update command. """ def getNames(self): """Return a list containing the names of this command. This command can by called from the command line by using any of these names. :return: a list containing the names of this command """ return ['update', 'update-to'] def getUsage(self): """Return a usage string for this command. :return: a usage string for this command """ return _("[PACKAGE...]") def getSummary(self): """Return a one line summary of this command. :return: a one line summary of this command """ return _("Update a package or packages on your system") def doCheck(self, base, basecmd, extcmds): """Verify that conditions are met so that this command can run. These include that there are enabled repositories with gpg keys, and that this command is being run by the root user. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: the command line arguments passed to *basecmd* """ checkRootUID(base) checkGPGKey(base) checkEnabledRepo(base, extcmds) def doCommand(self, base, basecmd, extcmds): """Execute this command. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: the command line arguments passed to *basecmd* :return: (exit_code, [ errors ]) exit_code is:: 0 = we're done, exit 1 = we've errored, exit with error string 2 = we've got work yet to do, onto the next stage """ self.doneCommand(base, _("Setting up Update Process")) ret = base.updatePkgs(extcmds, update_to=(basecmd == 'update-to')) updateinfo.remove_txmbrs(base) return ret class DistroSyncCommand(YumCommand): """A class containing methods needed by the cli to execute the distro-synch command. """ def getNames(self): """Return a list containing the names of this command. This command can be called from the command line by using any of these names. :return: a list containing the names of this command """ return ['distribution-synchronization', 'distro-sync', 'distupgrade'] def getUsage(self): """Return a usage string for this command. :return: a usage string for this command """ return _("[PACKAGE...]") def getSummary(self): """Return a one line summary of this command. :return: a one line summary of this command """ return _("Synchronize installed packages to the latest available versions") def doCheck(self, base, basecmd, extcmds): """Verify that conditions are met so that this command can run. These include that the program is being run by the root user, and that there are enabled repositories with gpg keys. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: the command line arguments passed to *basecmd* """ checkRootUID(base) checkGPGKey(base) checkEnabledRepo(base, extcmds) def doCommand(self, base, basecmd, extcmds): """Execute this command. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: the command line arguments passed to *basecmd* :return: (exit_code, [ errors ]) exit_code is:: 0 = we're done, exit 1 = we've errored, exit with error string 2 = we've got work yet to do, onto the next stage """ self.doneCommand(base, _("Setting up Distribution Synchronization Process")) base.conf.obsoletes = 1 ret = base.distroSyncPkgs(extcmds) updateinfo.remove_txmbrs(base) return ret def _add_pkg_simple_list_lens(data, pkg, indent=''): """ Get the length of each pkg's column. Add that to data. This "knows" about simpleList and printVer. """ na = len(pkg.name) + 1 + len(pkg.arch) + len(indent) ver = len(pkg.version) + 1 + len(pkg.release) rid = len(pkg.ui_from_repo) if pkg.epoch != '0': ver += len(pkg.epoch) + 1 for (d, v) in (('na', na), ('ver', ver), ('rid', rid)): data[d].setdefault(v, 0) data[d][v] += 1 def _list_cmd_calc_columns(base, ypl): """ Work out the dynamic size of the columns to pass to fmtColumns. """ data = {'na' : {}, 'ver' : {}, 'rid' : {}} for lst in (ypl.installed, ypl.available, ypl.extras, ypl.updates, ypl.recent): for pkg in lst: _add_pkg_simple_list_lens(data, pkg) if len(ypl.obsoletes) > 0: for (npkg, opkg) in ypl.obsoletesTuples: _add_pkg_simple_list_lens(data, npkg) _add_pkg_simple_list_lens(data, opkg, indent=" " * 4) data = [data['na'], data['ver'], data['rid']] columns = base.calcColumns(data, remainder_column=1) return (-columns[0], -columns[1], -columns[2]) def _cmdline_exclude(pkgs, cmdline_excludes): """ Do an extra exclude for installed packages that match the cmd line. """ if not cmdline_excludes: return pkgs e,m,u = parsePackages(pkgs, cmdline_excludes) excluded = set(e + m) return [po for po in pkgs if po not in excluded] class InfoCommand(YumCommand): """A class containing methods needed by the cli to execute the info command. """ def getNames(self): """Return a list containing the names of this command. This command can be called from the command line by using any of these names. :return: a list containing the names of this command """ return ['info'] def getUsage(self): """Return a usage string for this command. :return: a usage string for this command """ return "[PACKAGE|all|available|installed|updates|distro-extras|extras|obsoletes|recent]" def getSummary(self): """Return a one line summary of this command. :return: a one line summary of this command """ return _("Display details about a package or group of packages") def doCommand(self, base, basecmd, extcmds, repoid=None): """Execute this command. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: the command line arguments passed to *basecmd* :return: (exit_code, [ errors ]) exit_code is:: 0 = we're done, exit 1 = we've errored, exit with error string 2 = we've got work yet to do, onto the next stage """ if extcmds and extcmds[0] in ('updates', 'obsoletes'): updateinfo.exclude_updates(base) else: updateinfo.exclude_all(base) if True: # Try, YumBase... highlight = base.term.MODE['bold'] # If we are doing: "yum info installed blah" don't do the highlight # because the usability of not accessing the repos. is still higher # than providing colour for a single line. Usable updatesd/etc. FTW. if basecmd == 'info' and extcmds and extcmds[0] == 'installed': highlight = False ypl = base.returnPkgLists(extcmds, installed_available=highlight, repoid=repoid) update_pkgs = {} inst_pkgs = {} local_pkgs = {} columns = None if basecmd == 'list': # Dynamically size the columns columns = _list_cmd_calc_columns(base, ypl) if highlight and ypl.installed: # If we have installed and available lists, then do the # highlighting for the installed packages so you can see what's # available to update, an extra, or newer than what we have. for pkg in (ypl.hidden_available + ypl.reinstall_available + ypl.old_available): key = (pkg.name, pkg.arch) if key not in update_pkgs or pkg.verGT(update_pkgs[key]): update_pkgs[key] = pkg if highlight and ypl.available: # If we have installed and available lists, then do the # highlighting for the available packages so you can see what's # available to install vs. update vs. old. for pkg in ypl.hidden_installed: key = (pkg.name, pkg.arch) if key not in inst_pkgs or pkg.verGT(inst_pkgs[key]): inst_pkgs[key] = pkg if highlight and ypl.updates: # Do the local/remote split we get in "yum updates" for po in sorted(ypl.updates): if po.repo.id != 'installed' and po.verifyLocalPkg(): local_pkgs[(po.name, po.arch)] = po # Output the packages: kern = base.conf.color_list_installed_running_kernel clio = base.conf.color_list_installed_older clin = base.conf.color_list_installed_newer clir = base.conf.color_list_installed_reinstall clie = base.conf.color_list_installed_extra if base.conf.query_install_excludes: ypl.installed = _cmdline_exclude(ypl.installed, base.cmdline_excludes) rip = base.listPkgs(ypl.installed, _('Installed Packages'), basecmd, highlight_na=update_pkgs, columns=columns, highlight_modes={'>' : clio, '<' : clin, 'kern' : kern, '=' : clir, 'not in' : clie}) kern = base.conf.color_list_available_running_kernel clau = base.conf.color_list_available_upgrade clad = base.conf.color_list_available_downgrade clar = base.conf.color_list_available_reinstall clai = base.conf.color_list_available_install rap = base.listPkgs(ypl.available, _('Available Packages'), basecmd, highlight_na=inst_pkgs, columns=columns, highlight_modes={'<' : clau, '>' : clad, 'kern' : kern, '=' : clar, 'not in' : clai}) rep = base.listPkgs(ypl.extras, _('Extra Packages'), basecmd, columns=columns) cul = base.conf.color_update_local cur = base.conf.color_update_remote rup = base.listPkgs(ypl.updates, _('Updated Packages'), basecmd, highlight_na=local_pkgs, columns=columns, highlight_modes={'=' : cul, 'not in' : cur}) # XXX put this into the ListCommand at some point if len(ypl.obsoletes) > 0 and basecmd == 'list': # if we've looked up obsolete lists and it's a list request rop = [0, ''] print _('Obsoleting Packages') # The tuple is (newPkg, oldPkg) ... so sort by new for obtup in sorted(ypl.obsoletesTuples, key=operator.itemgetter(0)): base.updatesObsoletesList(obtup, 'obsoletes', columns=columns, repoid=repoid) else: rop = base.listPkgs(ypl.obsoletes, _('Obsoleting Packages'), basecmd, columns=columns) rrap = base.listPkgs(ypl.recent, _('Recently Added Packages'), basecmd, columns=columns) # extcmds is pop(0)'d if they pass a "special" param like "updates" # in returnPkgLists(). This allows us to always return "ok" for # things like "yum list updates". if len(extcmds) and \ rrap[0] and rop[0] and rup[0] and rep[0] and rap[0] and rip[0]: return 1, [_('No matching Packages to list')] return 0, [] def needTs(self, base, basecmd, extcmds): """Return whether a transaction set must be set up before this command can run. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: a list of arguments passed to *basecmd* :return: True if a transaction set is needed, False otherwise """ if len(extcmds) and extcmds[0] == 'installed': return False return True def cacheRequirement(self, base, basecmd, extcmds): """Return the cache requirements for the remote repos. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: a list of arguments passed to *basecmd* :return: Type of requirement: read-only:past, read-only:present, read-only:future, write """ if len(extcmds) and extcmds[0] in ('updates', 'obsoletes'): return 'read-only:future' if len(extcmds) and extcmds[0] in ('installed', 'distro-extras', 'extras', 'recent'): return 'read-only:past' # available/all return 'read-only:present' class ListCommand(InfoCommand): """A class containing methods needed by the cli to execute the list command. """ def getNames(self): """Return a list containing the names of this command. This command can be called from the command line by using any of these names. :return: a list containing the names of this command """ return ['list'] def getSummary(self): """Return a one line summary of this command. :return: a one line summary of this command """ return _("List a package or groups of packages") class EraseCommand(YumCommand): """A class containing methods needed by the cli to execute the erase command. """ def getNames(self): """Return a list containing the names of this command. This command can be called from the command line by using any of these names. :return: a list containing the names of this command """ return ['erase', 'remove', 'autoremove', 'erase-n', 'erase-na', 'erase-nevra', 'autoremove-n', 'autoremove-na', 'autoremove-nevra', 'remove-n', 'remove-na', 'remove-nevra'] def getUsage(self): """Return a usage string for this command. :return: a usage string for this command """ return "PACKAGE..." def getSummary(self): """Return a one line summary of this command. :return: a one line summary of this command """ return _("Remove a package or packages from your system") def doCheck(self, base, basecmd, extcmds): """Verify that conditions are met so that this command can run. These include that the program is being run by the root user, and that this command is called with appropriate arguments. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: the command line arguments passed to *basecmd* """ checkRootUID(base) if basecmd == 'autoremove': return checkPackageArg(base, basecmd, extcmds) def doCommand(self, base, basecmd, extcmds): """Execute this command. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: the command line arguments passed to *basecmd* :return: (exit_code, [ errors ]) exit_code is:: 0 = we're done, exit 1 = we've errored, exit with error string 2 = we've got work yet to do, onto the next stage """ pos = False if basecmd.startswith('autoremove'): # We have to alter this, as it's used in resolving stage. Which # sucks. Just be careful in "yum shell". base.conf.clean_requirements_on_remove = True basecmd = basecmd[len('auto'):] # pretend it's just remove... if not extcmds: pos = True extcmds = [] for pkg in sorted(base.rpmdb.returnLeafNodes()): if 'reason' not in pkg.yumdb_info: continue if pkg.yumdb_info.reason != 'dep': continue extcmds.append(pkg) self.doneCommand(base, _("Setting up Remove Process")) ret = base.erasePkgs(extcmds, pos=pos, basecmd=basecmd) return ret def needTs(self, base, basecmd, extcmds): """Return whether a transaction set must be set up before this command can run. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: a list of arguments passed to *basecmd* :return: True if a transaction set is needed, False otherwise """ return False def needTsRemove(self, base, basecmd, extcmds): """Return whether a transaction set for removal only must be set up before this command can run. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: a list of arguments passed to *basecmd* :return: True if a remove-only transaction set is needed, False otherwise """ return True class GroupsCommand(YumCommand): """ Single sub-command interface for most groups interaction. """ direct_commands = {'grouplist' : 'list', 'groupinstall' : 'install', 'groupupdate' : 'update', 'groupremove' : 'remove', 'grouperase' : 'remove', 'groupinfo' : 'info'} def getNames(self): """Return a list containing the names of this command. This command can be called from the command line by using any of these names. :return: a list containing the names of this command """ return ['groups', 'group'] + self.direct_commands.keys() def getUsage(self): """Return a usage string for this command. :return: a usage string for this command """ return "[list|info|summary|install|upgrade|remove|mark] [GROUP]" def getSummary(self): """Return a one line summary of this command. :return: a one line summary of this command """ return _("Display, or use, the groups information") def _grp_setup_doCommand(self, base): self.doneCommand(base, _("Setting up Group Process")) base.doRepoSetup(dosack=0) try: base.doGroupSetup() except yum.Errors.GroupsError: return 1, [_('No Groups on which to run command')] except yum.Errors.YumBaseError, e: raise def _grp_cmd(self, basecmd, extcmds): if basecmd in self.direct_commands: cmd = self.direct_commands[basecmd] elif extcmds: cmd = extcmds[0] extcmds = extcmds[1:] else: cmd = 'summary' if cmd in ('mark', 'unmark') and extcmds: cmd = "%s-%s" % (cmd, extcmds[0]) extcmds = extcmds[1:] remap = {'update' : 'upgrade', 'erase' : 'remove', 'mark-erase' : 'mark-remove', } cmd = remap.get(cmd, cmd) return cmd, extcmds def doCheck(self, base, basecmd, extcmds): """Verify that conditions are met so that this command can run. The exact conditions checked will vary depending on the subcommand that is being called. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: the command line arguments passed to *basecmd* """ cmd, extcmds = self._grp_cmd(basecmd, extcmds) checkEnabledRepo(base) ocmds_all = [] ocmds_arg = [] if base.conf.group_command == 'objects': ocmds_arg = ('mark-install', 'mark-remove', 'mark-blacklist', 'mark-packages', 'mark-packages-force', 'unmark-packages', 'mark-packages-sync', 'mark-packages-sync-force', 'mark-groups', 'mark-groups-force', 'unmark-groups', 'mark-groups-sync', 'mark-groups-sync-force') ocmds_all = ('mark-install', 'mark-remove', 'mark-convert', 'mark-convert-whitelist', 'mark-convert-blacklist', 'mark-blacklist', 'mark-packages', 'mark-packages-force', 'unmark-packages', 'mark-packages-sync', 'mark-packages-sync-force', 'mark-groups', 'mark-groups-force', 'unmark-groups', 'mark-groups-sync', 'mark-groups-sync-force') if cmd in ('install', 'remove', 'info') or cmd in ocmds_arg: checkGroupArg(base, cmd, extcmds) if cmd in ('install', 'remove', 'upgrade') or cmd in ocmds_all: checkRootUID(base) if cmd in ('install', 'upgrade'): checkGPGKey(base) cmds = set(('list', 'info', 'remove', 'install', 'upgrade', 'summary')) if base.conf.group_command == 'objects': cmds.update(ocmds_all) if cmd not in cmds: base.logger.critical(_('Invalid groups sub-command, use: %s.'), ", ".join(cmds)) raise cli.CliError if base.conf.group_command != 'objects': pass elif not os.path.exists(os.path.dirname(base.igroups.filename)): base.logger.critical(_("There is no installed groups file.")) base.logger.critical(_("Maybe run: yum groups mark convert (see man yum)")) elif not os.access(os.path.dirname(base.igroups.filename), os.R_OK): base.logger.critical(_("You don't have access to the groups DBs.")) raise cli.CliError elif not os.path.exists(base.igroups.filename): base.logger.critical(_("There is no installed groups file.")) base.logger.critical(_("Maybe run: yum groups mark convert (see man yum)")) elif not os.access(base.igroups.filename, os.R_OK): base.logger.critical(_("You don't have access to the groups DB.")) raise cli.CliError def doCommand(self, base, basecmd, extcmds): """Execute this command. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: the command line arguments passed to *basecmd* :return: (exit_code, [ errors ]) exit_code is:: 0 = we're done, exit 1 = we've errored, exit with error string 2 = we've got work yet to do, onto the next stage """ cmd, extcmds = self._grp_cmd(basecmd, extcmds) self._grp_setup_doCommand(base) if cmd == 'summary': return base.returnGroupSummary(extcmds) if cmd == 'list': return base.returnGroupLists(extcmds) if True: # Try, YumBase... if cmd == 'info': return base.returnGroupInfo(extcmds) if cmd == 'install': return base.installGroups(extcmds) if cmd == 'upgrade': ret = base.installGroups(extcmds, upgrade=True) updateinfo.remove_txmbrs(base) return ret if cmd == 'remove': return base.removeGroups(extcmds) if cmd == 'mark-install': gRG = base._groupReturnGroups(extcmds,ignore_case=False) igrps, grps, ievgrps, evgrps = gRG for evgrp in evgrps: base.igroups.add_environment(evgrp.environmentid, evgrp.allgroups) for grp in grps: pkg_names = set() # Only see names that are installed. for pkg in base.rpmdb.searchNames(grp.packages): pkg_names.add(pkg.name) base.igroups.add_group(grp.groupid, pkg_names) base.igroups.save() return 0, ['Marked install: ' + ','.join(extcmds)] if cmd == 'mark-blacklist': gRG = base._groupReturnGroups(extcmds,ignore_case=False) igrps, grps, ievgrps, evgrps = gRG for ievgrp in ievgrps: evgrp = base.comps.return_environment(igrp.evgid) if not evgrp: continue base.igroups.changed = True ievgrp.grp_names.update(grp.groups) for igrp in igrps: grp = base.comps.return_group(igrp.gid) if not grp: continue base.igroups.changed = True igrp.pkg_names.update(grp.packages) base.igroups.save() return 0, ['Marked upgrade blacklist: ' + ','.join(extcmds)] if cmd in ('mark-packages', 'mark-packages-force'): if len(extcmds) < 2: return 1, ['No group or package given'] gRG = base._groupReturnGroups([extcmds[0]], ignore_case=False) igrps, grps, ievgrps, evgrps = gRG if igrps is None or len(igrps) != 1: return 1, ['No group matched'] grp = igrps[0] force = cmd == 'mark-packages-force' for pkg in base.rpmdb.returnPackages(patterns=extcmds[1:]): if not force and 'group_member' in pkg.yumdb_info: continue pkg.yumdb_info.group_member = grp.gid grp.pkg_names.add(pkg.name) base.igroups.changed = True base.igroups.save() return 0, ['Marked packages: ' + ','.join(extcmds[1:])] if cmd == 'unmark-packages': for pkg in base.rpmdb.returnPackages(patterns=extcmds): if 'group_member' in pkg.yumdb_info: del pkg.yumdb_info.group_member return 0, ['UnMarked packages: ' + ','.join(extcmds)] if cmd in ('mark-packages-sync', 'mark-packages-sync-force'): gRG = base._groupReturnGroups(extcmds,ignore_case=False) igrps, grps, ievgrps, evgrps = gRG if not igrps: return 1, ['No group matched'] force = cmd == 'mark-packages-sync-force' for grp in igrps: for pkg in base.rpmdb.searchNames(grp.pkg_names): if not force and 'group_member' in pkg.yumdb_info: continue pkg.yumdb_info.group_member = grp.gid if force: return 0, ['Marked packages-sync-force: '+','.join(extcmds)] else: return 0, ['Marked packages-sync: ' + ','.join(extcmds)] if cmd in ('mark-groups', 'mark-groups-force'): if len(extcmds) < 2: return 1, ['No environment or group given'] gRG = base._groupReturnGroups([extcmds[0]], ignore_case=False) igrps, grps, ievgrps, evgrps = gRG if ievgrps is None or len(ievgrps) != 1: return 1, ['No environment matched'] evgrp = ievgrps[0] force = cmd == 'mark-groups-force' gRG = base._groupReturnGroups(extcmds[1:], ignore_case=False) for grp in gRG[1]: # Packages full or empty? self.igroups.add_group(grp.groupid, grp.packages, ievgrp) if force: for grp in gRG[0]: grp.environment = evgrp.evgid base.igroups.changed = True base.igroups.save() return 0, ['Marked groups: ' + ','.join(extcmds[1:])] if cmd == 'unmark-groups': gRG = base._groupReturnGroups([extcmds[0]], ignore_case=False) igrps, grps, ievgrps, evgrps = gRG if igrps is None: return 1, ['No groups matched'] for grp in igrps: grp.environment = None base.igroups.changed = True base.igroups.save() return 0, ['UnMarked groups: ' + ','.join(extcmds)] if cmd in ('mark-groups-sync', 'mark-groups-sync-force'): gRG = base._groupReturnGroups(extcmds,ignore_case=False) igrps, grps, ievgrps, evgrps = gRG if not ievgrps: return 1, ['No environment matched'] force = cmd == 'mark-groups-sync-force' for evgrp in ievgrps: grp_names = ",".join(sorted(evgrp.grp_names)) for grp in base.igroups.return_groups(grp_names): if not force and grp.environment is not None: continue grp.environment = evgrp.evgid base.igroups.changed = True base.igroups.save() if force: return 0, ['Marked groups-sync-force: '+','.join(extcmds)] else: return 0, ['Marked groups-sync: ' + ','.join(extcmds)] # FIXME: This doesn't do environment groups atm. if cmd in ('mark-convert', 'mark-convert-whitelist', 'mark-convert-blacklist'): # Convert old style info. into groups as objects. def _convert_grp(grp): if not grp.installed: return pkg_names = [] for pkg in base.rpmdb.searchNames(grp.packages): if 'group_member' in pkg.yumdb_info: continue pkg.yumdb_info.group_member = grp.groupid pkg_names.append(pkg.name) # We only mark the packages installed as a known part of # the group. This way "group update" will work and install # any remaining packages, as it would before the conversion. if cmd == 'mark-convert-whitelist': base.igroups.add_group(grp.groupid, pkg_names) else: base.igroups.add_group(grp.groupid, grp.packages) # Blank everything. for gid in base.igroups.groups.keys(): base.igroups.del_group(gid) for pkg in base.rpmdb: if 'group_member' in pkg.yumdb_info: del pkg.yumdb_info.group_member # Need to do this by hand, when using objects, to setup the # .installed attribute in comps. base.comps.compile(base.rpmdb.simplePkgList()) # This is kind of a hack, to work around the biggest problem # with having pkgs in more than one group. Treat Fedora/EL/etc. # base/core special. Maybe other groups? # Not 100% we want to force install "core", as that's then # "different", but it is better ... so, meh. special_gids = (('core', True), ('base', False)) for gid, force_installed in special_gids: grp = base.comps.return_group(gid) if grp is None: continue if force_installed: grp.installed = True _convert_grp(grp) for grp in base.comps.get_groups(): if grp.groupid in special_gids: continue _convert_grp(grp) base.igroups.save() return 0, ['Converted old style groups to objects.'] if cmd == 'mark-remove': gRG = base._groupReturnGroups(extcmds,ignore_case=False) igrps, grps, ievgrps, evgrps = gRG for evgrp in ievgrps: base.igroups.del_environment(evgrp.evgid) for grp in igrps: base.igroups.del_group(grp.gid) base.igroups.save() return 0, ['Marked remove: ' + ','.join(extcmds)] def needTs(self, base, basecmd, extcmds): """Return whether a transaction set must be set up before this command can run. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: a list of arguments passed to *basecmd* :return: True if a transaction set is needed, False otherwise """ cmd, extcmds = self._grp_cmd(basecmd, extcmds) if cmd in ('list', 'info', 'remove', 'summary'): return False if cmd.startswith('mark') or cmd.startswith('unmark'): return False return True def needTsRemove(self, base, basecmd, extcmds): """Return whether a transaction set for removal only must be set up before this command can run. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: a list of arguments passed to *basecmd* :return: True if a remove-only transaction set is needed, False otherwise """ cmd, extcmds = self._grp_cmd(basecmd, extcmds) if cmd in ('remove',): return True return False def cacheRequirement(self, base, basecmd, extcmds): """Return the cache requirements for the remote repos. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: a list of arguments passed to *basecmd* :return: Type of requirement: read-only:past, read-only:present, read-only:future, write """ cmd, extcmds = self._grp_cmd(basecmd, extcmds) if cmd in ('list', 'info', 'summary'): return 'read-only:past' if cmd.startswith('mark') or cmd.startswith('unmark'): return 'read-only:past' return 'write' class MakeCacheCommand(YumCommand): """A class containing methods needed by the cli to execute the makecache command. """ def getNames(self): """Return a list containing the names of this command. This command can be called from the command line by using any of these names. :return: a list containing the names of this command """ return ['makecache'] def getUsage(self): """Return a usage string for this command. :return: a usage string for this command """ return "" def getSummary(self): """Return a one line summary of this command. :return: a one line summary of this command """ return _("Generate the metadata cache") def doCheck(self, base, basecmd, extcmds): """Verify that conditions are met so that this command can run; namely that there is an enabled repository. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: the command line arguments passed to *basecmd* """ checkEnabledRepo(base) def doCommand(self, base, basecmd, extcmds): """Execute this command. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: the command line arguments passed to *basecmd* :return: (exit_code, [ errors ]) exit_code is:: 0 = we're done, exit 1 = we've errored, exit with error string 2 = we've got work yet to do, onto the next stage """ base.logger.debug(_("Making cache files for all metadata files.")) base.logger.debug(_("This may take a while depending on the speed of this computer")) # Fast == don't download any extra MD fast = False if extcmds and extcmds[0] == 'fast': fast = True if True: # Try, YumBase... for repo in base.repos.sort(): repo.metadata_expire = 0 if not fast: repo.mdpolicy = "group:all" base.doRepoSetup(dosack=0) base.repos.doSetup() # These convert the downloaded data into usable data, # we can't remove them until *LoadRepo() can do: # 1. Download a .sqlite.bz2 and convert to .sqlite # 2. Download a .xml.gz and convert to .xml.gz.sqlite if fast: # Can't easily tell which other metadata each repo. has, so # just do primary. base.repos.populateSack(mdtype='metadata', cacheonly=1) else: base.repos.populateSack(mdtype='all', cacheonly=1) # Now decompress stuff, so that -C works, sigh. fname_map = {'group_gz' : 'groups.xml', 'pkgtags' : 'pkgtags.sqlite', 'updateinfo' : 'updateinfo.xml', 'prestodelta': 'prestodelta.xml', } for repo in base.repos.listEnabled(): for MD in repo.repoXML.fileTypes(): if MD not in fname_map: continue if MD not in repo.retrieved or not repo.retrieved[MD]: continue # For fast mode. misc.repo_gen_decompress(repo.retrieveMD(MD), fname_map[MD], cached=repo.cache) return 0, [_('Metadata Cache Created')] def needTs(self, base, basecmd, extcmds): """Return whether a transaction set must be set up before this command can run. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: a list of arguments passed to *basecmd* :return: True if a transaction set is needed, False otherwise """ return False class CleanCommand(YumCommand): """A class containing methods needed by the cli to execute the clean command. """ def getNames(self): """Return a list containing the names of this command. This command can be called from the command line by using any of these names. :return: a list containing the names of this command """ return ['clean'] def getUsage(self): """Return a usage string for this command. :return: a usage string for this command """ return "[headers|packages|metadata|dbcache|plugins|expire-cache|all]" def getSummary(self): """Return a one line summary of this command. :return: a one line summary of this command """ return _("Remove cached data") def doCheck(self, base, basecmd, extcmds): """Verify that conditions are met so that this command can run. These include that there is at least one enabled repository, and that this command is called with appropriate arguments. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: the command line arguments passed to *basecmd* """ checkCleanArg(base, basecmd, extcmds) checkEnabledRepo(base) def doCommand(self, base, basecmd, extcmds): """Execute this command. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: the command line arguments passed to *basecmd* :return: (exit_code, [ errors ]) exit_code is:: 0 = we're done, exit 1 = we've errored, exit with error string 2 = we've got work yet to do, onto the next stage """ base.conf.cache = 1 return base.cleanCli(extcmds) def needTs(self, base, basecmd, extcmds): """Return whether a transaction set must be set up before this command can run. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: a list of arguments passed to *basecmd* :return: True if a transaction set is needed, False otherwise """ return False def cacheRequirement(self, base, basecmd, extcmds): """Return the cache requirements for the remote repos. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: a list of arguments passed to *basecmd* :return: Type of requirement: read-only:past, read-only:present, read-only:future, write """ return 'read-only:past' class ProvidesCommand(YumCommand): """A class containing methods needed by the cli to execute the provides command. """ def getNames(self): """Return a list containing the names of this command. This command can be called from the command line by using any of these names. :return: a list containing the names of this command """ return ['provides', 'whatprovides'] def getUsage(self): """Return a usage string for this command. :return: a usage string for this command """ return "SOME_STRING" def getSummary(self): """Return a one line summary of this command. :return: a one line summary of this command """ return _("Find what package provides the given value") def doCheck(self, base, basecmd, extcmds): """Verify that conditions are met so that this command can run; namely that this command is called with appropriate arguments. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: the command line arguments passed to *basecmd* """ checkItemArg(base, basecmd, extcmds) def doCommand(self, base, basecmd, extcmds): """Execute this command. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: the command line arguments passed to *basecmd* :return: (exit_code, [ errors ]) exit_code is:: 0 = we're done, exit 1 = we've errored, exit with error string 2 = we've got work yet to do, onto the next stage """ base.logger.debug("Searching Packages: ") updateinfo.exclude_updates(base) return base.provides(extcmds) def cacheRequirement(self, base, basecmd, extcmds): """Return the cache requirements for the remote repos. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: a list of arguments passed to *basecmd* :return: Type of requirement: read-only:past, read-only:present, read-only:future, write """ return 'read-only:past' class CheckUpdateCommand(YumCommand): """A class containing methods needed by the cli to execute the check-update command. """ def getNames(self): """Return a list containing the names of this command. This command can be called from the command line by using any of these names. :return: a list containing the names of this command """ return ['check-update', 'check-updates', 'check-upgrade', 'check-upgrades'] def getUsage(self): """Return a usage string for this command. :return: a usage string for this command """ return "[PACKAGE...]" def getSummary(self): """Return a one line summary of this command. :return: a one line summary of this command """ return _("Check for available package updates") def doCheck(self, base, basecmd, extcmds): """Verify that conditions are met so that this command can run; namely that there is at least one enabled repository. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: the command line arguments passed to *basecmd* """ checkEnabledRepo(base) def doCommand(self, base, basecmd, extcmds, repoid=None): """Execute this command. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: the command line arguments passed to *basecmd* :return: (exit_code, [ errors ]) exit_code is:: 0 = we're done, exit 1 = we've errored, exit with error string 2 = we've got work yet to do, onto the next stage """ updateinfo.exclude_updates(base) obscmds = ['obsoletes'] + extcmds base.extcmds.insert(0, 'updates') result = 0 if True: ypl = base.returnPkgLists(extcmds, repoid=repoid) if (base.conf.obsoletes or base.verbose_logger.isEnabledFor(logginglevels.DEBUG_3)): typl = base.returnPkgLists(obscmds, repoid=repoid) ypl.obsoletes = typl.obsoletes ypl.obsoletesTuples = typl.obsoletesTuples columns = _list_cmd_calc_columns(base, ypl) if len(ypl.updates) > 0: local_pkgs = {} highlight = base.term.MODE['bold'] if highlight: # Do the local/remote split we get in "yum updates" for po in sorted(ypl.updates): if po.repo.id != 'installed' and po.verifyLocalPkg(): local_pkgs[(po.name, po.arch)] = po cul = base.conf.color_update_local cur = base.conf.color_update_remote base.listPkgs(ypl.updates, '', outputType='list', highlight_na=local_pkgs, columns=columns, highlight_modes={'=' : cul, 'not in' : cur}) result = 100 if len(ypl.obsoletes) > 0: # This only happens in verbose mode print _('Obsoleting Packages') # The tuple is (newPkg, oldPkg) ... so sort by new for obtup in sorted(ypl.obsoletesTuples, key=operator.itemgetter(0)): base.updatesObsoletesList(obtup, 'obsoletes', columns=columns, repoid=repoid) result = 100 # Add check_running_kernel call, if updateinfo is available. if (base.conf.autocheck_running_kernel and updateinfo._repos_downloaded(base.repos.listEnabled())): def _msg(x): base.verbose_logger.info("%s", x) updateinfo._check_running_kernel(base, base.upinfo, _msg) return result, [] def cacheRequirement(self, base, basecmd, extcmds): """Return the cache requirements for the remote repos. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: a list of arguments passed to *basecmd* :return: Type of requirement: read-only:past, read-only:present, read-only:future, write """ return 'read-only:future' class SearchCommand(YumCommand): """A class containing methods needed by the cli to execute the search command. """ def getNames(self): """Return a list containing the names of this command. This command can be called from the command line by using any of these names. :return: a list containing the names of this command """ return ['search'] def getUsage(self): """Return a usage string for this command. :return: a usage string for this command """ return "SOME_STRING" def getSummary(self): """Return a one line summary of this command. :return: a one line summary of this command """ return _("Search package details for the given string") def doCheck(self, base, basecmd, extcmds): """Verify that conditions are met so that this command can run; namely that this command is called with appropriate arguments. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: the command line arguments passed to *basecmd* """ checkItemArg(base, basecmd, extcmds) def doCommand(self, base, basecmd, extcmds): """Execute this command. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: the command line arguments passed to *basecmd* :return: (exit_code, [ errors ]) exit_code is:: 0 = we're done, exit 1 = we've errored, exit with error string 2 = we've got work yet to do, onto the next stage """ base.logger.debug(_("Searching Packages: ")) updateinfo.exclude_updates(base) return base.search(extcmds) def needTs(self, base, basecmd, extcmds): """Return whether a transaction set must be set up before this command can run. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: a list of arguments passed to *basecmd* :return: True if a transaction set is needed, False otherwise """ return False def cacheRequirement(self, base, basecmd, extcmds): """Return the cache requirements for the remote repos. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: a list of arguments passed to *basecmd* :return: Type of requirement: read-only:past, read-only:present, read-only:future, write """ return 'read-only:present' class UpgradeCommand(YumCommand): """A class containing methods needed by the cli to execute the upgrade command. """ def getNames(self): """Return a list containing the names of this command. This command can be called from the command line by using any of these names. :return: a list containing the names of this command """ return ['upgrade', 'upgrade-to'] def getUsage(self): """Return a usage string for this command. :return: a usage string for this command """ return 'PACKAGE...' def getSummary(self): """Return a one line summary of this command. :return: a one line summary of this command """ return _("Update packages taking obsoletes into account") def doCheck(self, base, basecmd, extcmds): """Verify that conditions are met so that this command can run. These include that the program is being run by the root user, and that there are enabled repositories with gpg. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: the command line arguments passed to *basecmd* """ checkRootUID(base) checkGPGKey(base) checkEnabledRepo(base, extcmds) def doCommand(self, base, basecmd, extcmds): """Execute this command. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: the command line arguments passed to *basecmd* :return: (exit_code, [ errors ]) exit_code is:: 0 = we're done, exit 1 = we've errored, exit with error string 2 = we've got work yet to do, onto the next stage """ base.conf.obsoletes = 1 self.doneCommand(base, _("Setting up Upgrade Process")) ret = base.updatePkgs(extcmds, update_to=(basecmd == 'upgrade-to')) updateinfo.remove_txmbrs(base) return ret class LocalInstallCommand(YumCommand): """A class containing methods needed by the cli to execute the localinstall command. """ def __init__(self): YumCommand.__init__(self) self.hidden = True def getNames(self): """Return a list containing the names of this command. This command can be called from the command line by using any of these names. :return: a list containing the names of this command """ return ['localinstall', 'localupdate'] def getUsage(self): """Return a usage string for this command. :return: a usage string for this command """ return "FILE" def getSummary(self): """Return a one line summary of this command. :return: a one line summary of this command """ return _("Install a local RPM") def doCheck(self, base, basecmd, extcmds): """Verify that conditions are met so that this command can run. These include that there are enabled repositories with gpg keys, and that this command is called with appropriate arguments. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: the command line arguments passed to *basecmd* """ checkRootUID(base) checkGPGKey(base) checkPackageArg(base, basecmd, extcmds) def doCommand(self, base, basecmd, extcmds): """Execute this command. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: the command line arguments passed to *basecmd* :return: (exit_code, [ errors ]) exit_code is: 0 = we're done, exit 1 = we've errored, exit with error string 2 = we've got work yet to do, onto the next stage """ self.doneCommand(base, _("Setting up Local Package Process")) updateonly = basecmd == 'localupdate' return base.localInstall(filelist=extcmds, updateonly=updateonly) def needTs(self, base, basecmd, extcmds): """Return whether a transaction set must be set up before this command can run. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: a list of arguments passed to *basecmd* :return: True if a transaction set is needed, False otherwise """ return False class ResolveDepCommand(YumCommand): """A class containing methods needed by the cli to execute the resolvedep command. """ def __init__(self): YumCommand.__init__(self) self.hidden = True def getNames(self): """Return a list containing the names of this command. This command can be called from the command line by using any of these names. :return: a list containing the names of this command """ return ['resolvedep'] def getUsage(self): """Return a usage string for this command. :return: a usage string for this command """ return "DEPENDENCY" def getSummary(self): """Return a one line summary of this command. :return: a one line summary of this command """ return "repoquery --pkgnarrow=all --whatprovides --qf '%{envra} %{ui_from_repo}'" def doCommand(self, base, basecmd, extcmds): """Execute this command. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: the command line arguments passed to *basecmd* :return: (exit_code, [ errors ]) exit_code is:: 0 = we're done, exit 1 = we've errored, exit with error string 2 = we've got work yet to do, onto the next stage """ base.logger.debug(_("Searching Packages for Dependency:")) updateinfo.exclude_updates(base) return base.resolveDepCli(extcmds) def cacheRequirement(self, base, basecmd, extcmds): """Return the cache requirements for the remote repos. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: a list of arguments passed to *basecmd* :return: Type of requirement: read-only:past, read-only:present, read-only:future, write """ return 'read-only:past' class ShellCommand(YumCommand): """A class containing methods needed by the cli to execute the shell command. """ def getNames(self): """Return a list containing the names of this command. This command can be called from the command line by using any of these names. :return: a list containing the names of this command """ return ['shell'] def getUsage(self): """Return a usage string for this command. :return: a usage string for this command """ return "[FILENAME]" def getSummary(self): """Return a one line summary of this command. :return: a one line summary of this command """ return _("Run an interactive yum shell") def doCheck(self, base, basecmd, extcmds): """Verify that conditions are met so that this command can run; namely that this command is called with appropriate arguments. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: the command line arguments passed to *basecmd* """ checkShellArg(base, basecmd, extcmds) def doCommand(self, base, basecmd, extcmds): """Execute this command. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: the command line arguments passed to *basecmd* :return: (exit_code, [ errors ]) exit_code is:: 0 = we're done, exit 1 = we've errored, exit with error string 2 = we've got work yet to do, onto the next stage """ self.doneCommand(base, _('Setting up Yum Shell')) return base.doShell() def needTs(self, base, basecmd, extcmds): """Return whether a transaction set must be set up before this command can run. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: a list of arguments passed to *basecmd* :return: True if a transaction set is needed, False otherwise """ return False class DepListCommand(YumCommand): """A class containing methods needed by the cli to execute the deplist command. """ def getNames(self): """Return a list containing the names of this command. This command can be called from the command line by using any of these names. :return: a list containing the names of this command """ return ['deplist'] def getUsage(self): """Return a usage string for this command. :return: a usage string for this command """ return 'PACKAGE...' def getSummary(self): """Return a one line summary of this command. :return: a one line summary of this command """ return _("List a package's dependencies") def doCheck(self, base, basecmd, extcmds): """Verify that conditions are met so that this command can run; namely that this command is called with appropriate arguments. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: the command line arguments passed to *basecmd* """ checkPackageArg(base, basecmd, extcmds) def doCommand(self, base, basecmd, extcmds): """Execute this command. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: the command line arguments passed to *basecmd* :return: (exit_code, [ errors ]) exit_code is:: 0 = we're done, exit 1 = we've errored, exit with error string 2 = we've got work yet to do, onto the next stage """ self.doneCommand(base, _("Finding dependencies: ")) updateinfo.exclude_updates(base) return base.deplist(extcmds) def cacheRequirement(self, base, basecmd, extcmds): """Return the cache requirements for the remote repos. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: a list of arguments passed to *basecmd* :return: Type of requirement: read-only:past, read-only:present, read-only:future, write """ return 'read-only:past' # read-only ? class RepoListCommand(YumCommand): """A class containing methods needed by the cli to execute the repolist command. """ def getNames(self): """Return a list containing the names of this command. This command can be called from the command line by using any of these names. :return: a list containing the names of this command """ return ('repolist', 'repoinfo') def getUsage(self): """Return a usage string for this command. :return: a usage string for this command """ return '[all|enabled|disabled]' def getSummary(self): """Return a one line summary of this command. :return: a one line summary of this command """ return _('Display the configured software repositories') def doCommand(self, base, basecmd, extcmds): """Execute this command. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: the command line arguments passed to *basecmd* :return: (exit_code, [ errors ]) exit_code is:: 0 = we're done, exit 1 = we've errored, exit with error string 2 = we've got work yet to do, onto the next stage """ def _repo_size(repo): ret = 0 for pkg in repo.sack.returnPackages(): ret += pkg.packagesize return base.format_number(ret) def _repo_match(repo, patterns): for pat in patterns: if repo in base.repos.findRepos(pat, name_match=True, ignore_case=True): return True return False def _num2ui_num(num): return to_unicode(locale.format("%d", num, True)) if len(extcmds) >= 1 and extcmds[0] in ('all', 'disabled', 'enabled'): arg = extcmds[0] extcmds = extcmds[1:] else: arg = 'enabled' extcmds = map(lambda x: x.lower(), extcmds) if basecmd == 'repoinfo': verbose = True else: verbose = base.verbose_logger.isEnabledFor(logginglevels.DEBUG_3) if arg != 'disabled' or extcmds: try: # Setup so len(repo.sack) is correct base.repos.populateSack() base.pkgSack # Need to setup the pkgSack, so excludes work except yum.Errors.RepoError: if verbose: raise # populate them by hand, so one failure doesn't kill everything # after it. for repo in base.repos.listEnabled(): try: base.repos.populateSack(repo.id) except yum.Errors.RepoError: pass repos = base.repos.repos.values() repos.sort() enabled_repos = base.repos.listEnabled() on_ehibeg = base.term.FG_COLOR['green'] + base.term.MODE['bold'] on_dhibeg = base.term.FG_COLOR['red'] on_hiend = base.term.MODE['normal'] tot_num = 0 cols = [] for repo in repos: if len(extcmds) and not _repo_match(repo, extcmds): continue (ehibeg, dhibeg, hiend) = '', '', '' ui_enabled = '' ui_endis_wid = 0 ui_num = "" ui_excludes_num = '' force_show = False if arg == 'all' or repo.id in extcmds or repo.name in extcmds: force_show = True (ehibeg, dhibeg, hiend) = (on_ehibeg, on_dhibeg, on_hiend) if repo in enabled_repos: enabled = True if arg == 'enabled': force_show = False elif arg == 'disabled' and not force_show: continue if force_show or verbose: ui_enabled = ehibeg + _('enabled') + hiend ui_endis_wid = utf8_width(_('enabled')) if not verbose: ui_enabled += ": " ui_endis_wid += 2 if verbose: ui_size = _repo_size(repo) # We don't show status for list disabled if arg != 'disabled' or verbose: if verbose or base.conf.exclude or repo.exclude: num = len(repo.sack.simplePkgList()) else: num = len(repo.sack) ui_num = _num2ui_num(num) excludes = repo.sack._excludes excludes = len([pid for r,pid in excludes if r == repo]) if excludes: ui_excludes_num = _num2ui_num(excludes) if not verbose: ui_num += "+%s" % ui_excludes_num tot_num += num else: enabled = False if arg == 'disabled': force_show = False elif arg == 'enabled' and not force_show: continue ui_enabled = dhibeg + _('disabled') + hiend ui_endis_wid = utf8_width(_('disabled')) if not verbose: rid = repo.ui_id # can't use str() if repo.metadata_expire >= 0: if os.path.exists(repo.metadata_cookie): last = os.stat(repo.metadata_cookie).st_mtime if last + repo.metadata_expire < time.time(): rid = '!' + rid if enabled and repo.metalink: mdts = repo.metalink_data.repomd.timestamp if mdts > repo.repoXML.timestamp: rid = '*' + rid cols.append((rid, repo.name, (ui_enabled, ui_endis_wid), ui_num)) else: if enabled: md = repo.repoXML else: md = None out = [base.fmtKeyValFill(_("Repo-id : "), repo.ui_id), base.fmtKeyValFill(_("Repo-name : "), repo.name)] if force_show or extcmds: out += [base.fmtKeyValFill(_("Repo-status : "), ui_enabled)] if md and md.revision is not None: out += [base.fmtKeyValFill(_("Repo-revision: "), md.revision)] if md and md.tags['content']: tags = md.tags['content'] out += [base.fmtKeyValFill(_("Repo-tags : "), ", ".join(sorted(tags)))] if md and md.tags['distro']: for distro in sorted(md.tags['distro']): tags = md.tags['distro'][distro] out += [base.fmtKeyValFill(_("Repo-distro-tags: "), "[%s]: %s" % (distro, ", ".join(sorted(tags))))] if md: out += [base.fmtKeyValFill(_("Repo-updated : "), time.ctime(md.timestamp)), base.fmtKeyValFill(_("Repo-pkgs : "),ui_num), base.fmtKeyValFill(_("Repo-size : "),ui_size)] if hasattr(repo, '_orig_baseurl'): baseurls = repo._orig_baseurl else: baseurls = repo.baseurl if baseurls: out += [base.fmtKeyValFill(_("Repo-baseurl : "), ", ".join(baseurls))] if enabled: # This needs to be here due to the mirrorlists are # metalinks hack. repo.urls if repo.metalink: out += [base.fmtKeyValFill(_("Repo-metalink: "), repo.metalink)] if enabled: ts = repo.metalink_data.repomd.timestamp out += [base.fmtKeyValFill(_(" Updated : "), time.ctime(ts))] elif repo.mirrorlist: out += [base.fmtKeyValFill(_("Repo-mirrors : "), repo.mirrorlist)] if enabled and repo.urls and not baseurls: url = repo.urls[0] if len(repo.urls) > 1: url += ' (%d more)' % (len(repo.urls) - 1) out += [base.fmtKeyValFill(_("Repo-baseurl : "), url)] if not os.path.exists(repo.metadata_cookie): last = _("Unknown") else: last = os.stat(repo.metadata_cookie).st_mtime last = time.ctime(last) if repo.metadata_expire <= -1: num = _("Never (last: %s)") % last elif not repo.metadata_expire: num = _("Instant (last: %s)") % last else: num = _num2ui_num(repo.metadata_expire) num = _("%s second(s) (last: %s)") % (num, last) out += [base.fmtKeyValFill(_("Repo-expire : "), num), base.fmtKeyValFill(_(" Filter : "), repo.metadata_expire_filter), ] if repo.exclude: out += [base.fmtKeyValFill(_("Repo-exclude : "), ", ".join(repo.exclude))] if repo.includepkgs: out += [base.fmtKeyValFill(_("Repo-include : "), ", ".join(repo.includepkgs))] if ui_excludes_num: out += [base.fmtKeyValFill(_("Repo-excluded: "), ui_excludes_num)] if repo.repofile: out += [base.fmtKeyValFill(_("Repo-filename: "), repo.repofile)] base.verbose_logger.info("%s\n", "\n".join(map(misc.to_unicode, out))) if not verbose and cols: # Work out the first (id) and last (enabled/disalbed/count), # then chop the middle (name)... id_len = utf8_width(_('repo id')) nm_len = 0 st_len = 0 ui_len = 0 for (rid, rname, (ui_enabled, ui_endis_wid), ui_num) in cols: if id_len < utf8_width(rid): id_len = utf8_width(rid) if nm_len < utf8_width(rname): nm_len = utf8_width(rname) if st_len < (ui_endis_wid + len(ui_num)): st_len = (ui_endis_wid + len(ui_num)) # Need this as well as above for: utf8_width_fill() if ui_len < len(ui_num): ui_len = len(ui_num) if arg == 'disabled': # Don't output a status column. left = base.term.columns - (id_len + 1) elif utf8_width(_('status')) > st_len: left = base.term.columns - (id_len + utf8_width(_('status')) +2) else: left = base.term.columns - (id_len + st_len + 2) if left < nm_len: # Name gets chopped nm_len = left else: # Share the extra... left -= nm_len id_len += left / 2 nm_len += left - (left / 2) txt_rid = utf8_width_fill(_('repo id'), id_len) txt_rnam = utf8_width_fill(_('repo name'), nm_len, nm_len) if arg == 'disabled': # Don't output a status column. base.verbose_logger.info("%s %s", txt_rid, txt_rnam) else: base.verbose_logger.info("%s %s %s", txt_rid, txt_rnam, _('status')) for (rid, rname, (ui_enabled, ui_endis_wid), ui_num) in cols: if arg == 'disabled': # Don't output a status column. base.verbose_logger.info("%s %s", utf8_width_fill(rid, id_len), utf8_width_fill(rname, nm_len, nm_len)) continue if ui_num: ui_num = utf8_width_fill(ui_num, ui_len, left=False) base.verbose_logger.info("%s %s %s%s", utf8_width_fill(rid, id_len), utf8_width_fill(rname, nm_len, nm_len), ui_enabled, ui_num) return 0, ['repolist: ' +to_unicode(locale.format("%d", tot_num, True))] def needTs(self, base, basecmd, extcmds): """Return whether a transaction set must be set up before this command can run. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: a list of arguments passed to *basecmd* :return: True if a transaction set is needed, False otherwise """ return False def cacheRequirement(self, base, basecmd, extcmds): """Return the cache requirements for the remote repos. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: a list of arguments passed to *basecmd* :return: Type of requirement: read-only:past, read-only:present, read-only:future, write """ return 'read-only:past' class HelpCommand(YumCommand): """A class containing methods needed by the cli to execute the help command. """ def getNames(self): """Return a list containing the names of this command. This command can be called from the command line by using any of these names. :return: a list containing the names of this command """ return ['help'] def getUsage(self): """Return a usage string for this command. :return: a usage string for this command """ return "COMMAND" def getSummary(self): """Return a one line summary of this command. :return: a one line summary of this command """ return _("Display a helpful usage message") def doCheck(self, base, basecmd, extcmds): """Verify that conditions are met so that this command can run; namely that this command is called with appropriate arguments. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: the command line arguments passed to *basecmd* """ if len(extcmds) == 0: base.usage() raise cli.CliError elif len(extcmds) > 1 or extcmds[0] not in base.yum_cli_commands: base.usage() raise cli.CliError @staticmethod def _makeOutput(command): canonical_name = command.getNames()[0] # Check for the methods in case we have plugins that don't # implement these. # XXX Remove this once usage/summary are common enough try: usage = command.getUsage() except (AttributeError, NotImplementedError): usage = None try: summary = command.getSummary() except (AttributeError, NotImplementedError): summary = None # XXX need detailed help here, too help_output = "" if usage is not None: help_output += "%s %s" % (canonical_name, usage) if summary is not None: help_output += "\n\n%s" % summary if usage is None and summary is None: help_output = _("No help available for %s") % canonical_name command_names = command.getNames() if len(command_names) > 1: if len(command_names) > 2: help_output += _("\n\naliases: ") else: help_output += _("\n\nalias: ") help_output += ', '.join(command.getNames()[1:]) return help_output def doCommand(self, base, basecmd, extcmds): """Execute this command. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: the command line arguments passed to *basecmd* :return: (exit_code, [ errors ]) exit_code is:: 0 = we're done, exit 1 = we've errored, exit with error string 2 = we've got work yet to do, onto the next stage """ if extcmds[0] in base.yum_cli_commands: command = base.yum_cli_commands[extcmds[0]] base.verbose_logger.info(self._makeOutput(command)) return 0, [] def needTs(self, base, basecmd, extcmds): """Return whether a transaction set must be set up before this command can run. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: a list of arguments passed to *basecmd* :return: True if a transaction set is needed, False otherwise """ return False class ReInstallCommand(YumCommand): """A class containing methods needed by the cli to execute the reinstall command. """ def getNames(self): """Return a list containing the names of this command. This command can be called from the command line by using any of these names. :return: a list containing the names of this command """ return ['reinstall'] def getUsage(self): """Return a usage string for this command. :return: a usage string for this command """ return "PACKAGE..." def doCheck(self, base, basecmd, extcmds): """Verify that conditions are met so that this command can run. These include that the program is being run by the root user, that there are enabled repositories with gpg keys, and that this command is called with appropriate arguments. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: the command line arguments passed to *basecmd* """ checkRootUID(base) checkGPGKey(base) checkPackageArg(base, basecmd, extcmds) checkEnabledRepo(base, extcmds) def doCommand(self, base, basecmd, extcmds): """Execute this command. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: the command line arguments passed to *basecmd* :return: (exit_code, [ errors ]) exit_code is:: 0 = we're done, exit 1 = we've errored, exit with error string 2 = we've got work yet to do, onto the next stage """ self.doneCommand(base, _("Setting up Reinstall Process")) return base.reinstallPkgs(extcmds) def getSummary(self): """Return a one line summary of this command. :return: a one line summary of this command """ return _("reinstall a package") def needTs(self, base, basecmd, extcmds): """Return whether a transaction set must be set up before this command can run. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: a list of arguments passed to *basecmd* :return: True if a transaction set is needed, False otherwise """ return False class DowngradeCommand(YumCommand): """A class containing methods needed by the cli to execute the downgrade command. """ def getNames(self): """Return a list containing the names of this command. This command can be called from the command line by using any of these names. :return: a list containing the names of this command """ return ['downgrade'] def getUsage(self): """Return a usage string for this command. :return: a usage string for this command """ return "PACKAGE..." def doCheck(self, base, basecmd, extcmds): """Verify that conditions are met so that this command can run. These include that the program is being run by the root user, that there are enabled repositories with gpg keys, and that this command is called with appropriate arguments. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: the command line arguments passed to *basecmd* """ checkRootUID(base) checkGPGKey(base) checkPackageArg(base, basecmd, extcmds) checkEnabledRepo(base, extcmds) def doCommand(self, base, basecmd, extcmds): """Execute this command. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: the command line arguments passed to *basecmd* :return: (exit_code, [ errors ]) exit_code is:: 0 = we're done, exit 1 = we've errored, exit with error string 2 = we've got work yet to do, onto the next stage """ self.doneCommand(base, _("Setting up Downgrade Process")) return base.downgradePkgs(extcmds) def getSummary(self): """Return a one line summary of this command. :return: a one line summary of this command """ return _("downgrade a package") def needTs(self, base, basecmd, extcmds): """Return whether a transaction set must be set up before this command can run. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: a list of arguments passed to *basecmd* :return: True if a transaction set is needed, False otherwise """ return False class VersionCommand(YumCommand): """A class containing methods needed by the cli to execute the version command. """ def getNames(self): """Return a list containing the names of this command. This command can be called from the command line by using any of these names. :return: a list containing the names of this command """ return ['version'] def getUsage(self): """Return a usage string for this command. :return: a usage string for this command """ return "[all|installed|available]" def getSummary(self): """Return a one line summary of this command. :return: a one line summary of this command """ return _("Display a version for the machine and/or available repos.") def doCommand(self, base, basecmd, extcmds): """Execute this command. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: the command line arguments passed to *basecmd* :return: (exit_code, [ errors ]) exit_code is:: 0 = we're done, exit 1 = we've errored, exit with error string 2 = we've got work yet to do, onto the next stage """ vcmd = 'installed' if extcmds: vcmd = extcmds[0] if vcmd in ('grouplist', 'groupinfo', 'nogroups', 'nogroups-installed', 'nogroups-available', 'nogroups-all', 'installed', 'all', 'group-installed', 'group-all', 'available', 'all', 'group-available', 'group-all'): extcmds = extcmds[1:] else: vcmd = 'installed' def _append_repos(cols, repo_data): for repoid in sorted(repo_data): cur = repo_data[repoid] ncols = [] last_rev = None for rev in sorted(cur): if rev is None: continue last_rev = cur[rev] ncols.append((" %s/%s" % (repoid, rev), str(cur[rev]))) if None in cur and (not last_rev or cur[None] != last_rev): cols.append((" %s" % repoid, str(cur[None]))) cols.extend(ncols) verbose = base.verbose_logger.isEnabledFor(logginglevels.DEBUG_3) groups = {} if vcmd in ('nogroups', 'nogroups-installed', 'nogroups-available', 'nogroups-all'): gconf = [] if vcmd == 'nogroups': vcmd = 'installed' else: vcmd = vcmd[len('nogroups-'):] else: gconf = yum.config.readVersionGroupsConfig() for group in gconf: groups[group] = set(gconf[group].pkglist) if gconf[group].run_with_packages: groups[group].update(base.run_with_package_names) if vcmd == 'grouplist': print _(" Yum version groups:") for group in sorted(groups): print " ", group return 0, ['version grouplist'] if vcmd == 'groupinfo': for group in groups: if group not in extcmds: continue print _(" Group :"), group print _(" Packages:") if not verbose: for pkgname in sorted(groups[group]): print " ", pkgname else: data = {'envra' : {}, 'rid' : {}} pkg_names = groups[group] pkg_names2pkgs = base._group_names2aipkgs(pkg_names) base._calcDataPkgColumns(data, pkg_names, pkg_names2pkgs) data = [data['envra'], data['rid']] columns = base.calcColumns(data) columns = (-columns[0], -columns[1]) base._displayPkgsFromNames(pkg_names, True, pkg_names2pkgs, columns=columns) return 0, ['version groupinfo'] # Have a way to manually specify a dynamic group of packages, whee. if not vcmd.startswith("group-") and extcmds: for dgrp in extcmds: if '/' not in dgrp: # It's a package name, add it to the cmd line group... if '' not in groups: groups[''] = set() groups[''].add(dgrp) else: # It's a file containing a list of packages... if not os.path.exists(dgrp): base.logger.warn(_(" File doesn't exist: %s"), dgrp) else: pkg_names = open(dgrp).readlines() pkg_names = set(n.strip() for n in pkg_names) dgrp = os.path.basename(dgrp) if dgrp in groups: for num in range(1, 100): ndgrp = dgrp + str(num) if ndgrp in groups: continue dgrp = ndgrp break groups[dgrp] = pkg_names rel = base.conf.yumvar['releasever'] ba = base.conf.yumvar['basearch'] cols = [] if vcmd in ('installed', 'all', 'group-installed', 'group-all'): if True: # Try, YumBase... data = base.rpmdb.simpleVersion(not verbose, groups=groups) lastdbv = base.history.last() if lastdbv is not None: lastdbv = lastdbv.end_rpmdbversion if lastdbv is not None and data[0] != lastdbv: base._rpmdb_warn_checks(warn=lastdbv is not None) if vcmd not in ('group-installed', 'group-all'): cols.append(("%s %s/%s" % (_("Installed:"), rel, ba), str(data[0]))) _append_repos(cols, data[1]) if groups: for grp in sorted(data[2]): if (vcmd.startswith("group-") and extcmds and grp not in extcmds): continue cols.append(("%s %s" % (_("Group-Installed:"), grp), str(data[2][grp]))) _append_repos(cols, data[3][grp]) if vcmd in ('available', 'all', 'group-available', 'group-all'): if True: # Try, YumBase... data = base.pkgSack.simpleVersion(not verbose, groups=groups) if vcmd not in ('group-available', 'group-all'): cols.append(("%s %s/%s" % (_("Available:"), rel, ba), str(data[0]))) if verbose: _append_repos(cols, data[1]) if groups: for grp in sorted(data[2]): if (vcmd.startswith("group-") and extcmds and grp not in extcmds): continue cols.append(("%s %s" % (_("Group-Available:"), grp), str(data[2][grp]))) if verbose: _append_repos(cols, data[3][grp]) data = {'rid' : {}, 'ver' : {}} for (rid, ver) in cols: for (d, v) in (('rid', len(rid)), ('ver', len(ver))): data[d].setdefault(v, 0) data[d][v] += 1 data = [data['rid'], data['ver']] columns = base.calcColumns(data) columns = (-columns[0], columns[1]) for line in cols: print base.fmtColumns(zip(line, columns)) return 0, ['version'] def needTs(self, base, basecmd, extcmds): """Return whether a transaction set must be set up before this command can run. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: a list of arguments passed to *basecmd* :return: True if a transaction set is needed, False otherwise """ vcmd = 'installed' if extcmds: vcmd = extcmds[0] verbose = base.verbose_logger.isEnabledFor(logginglevels.DEBUG_3) if vcmd == 'groupinfo' and verbose: return True return vcmd in ('available', 'all', 'group-available', 'group-all') def cacheRequirement(self, base, basecmd, extcmds): """Return the cache requirements for the remote repos. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: a list of arguments passed to *basecmd* :return: Type of requirement: read-only:past, read-only:present, read-only:future, write """ return 'read-only:present' class HistoryCommand(YumCommand): """A class containing methods needed by the cli to execute the history command. """ def getNames(self): """Return a list containing the names of this command. This command can be called from the command line by using any of these names. :return: a list containing the names of this command """ return ['history'] def getUsage(self): """Return a usage string for this command. :return: a usage string for this command """ return "[info|list|packages-list|summary|addon-info|redo|undo|rollback|new]" def getSummary(self): """Return a one line summary of this command. :return: a one line summary of this command """ return _("Display, or use, the transaction history") def _hcmd_redo(self, base, extcmds): kwargs = {'force_reinstall' : False, 'force_changed_removal' : False, } kwargs_map = {'reinstall' : 'force_reinstall', 'force-reinstall' : 'force_reinstall', 'remove' : 'force_changed_removal', 'force-remove' : 'force_changed_removal', } while len(extcmds) > 1: done = False for arg in extcmds[1].replace(' ', ',').split(','): if arg not in kwargs_map: continue done = True key = kwargs_map[extcmds[1]] kwargs[key] = not kwargs[key] if not done: break extcmds = [extcmds[0]] + extcmds[2:] old = base._history_get_transaction(extcmds) if old is None: return 1, ['Failed history redo'] tm = time.ctime(old.beg_timestamp) print "Repeating transaction %u, from %s" % (old.tid, tm) base.historyInfoCmdPkgsAltered(old) if base.history_redo(old, **kwargs): return 2, ["Repeating transaction %u" % (old.tid,)] def _hcmd_undo(self, base, extcmds): old = base._history_get_transaction(extcmds) if old is None: return 1, ['Failed history undo'] tm = time.ctime(old.beg_timestamp) print "Undoing transaction %u, from %s" % (old.tid, tm) base.historyInfoCmdPkgsAltered(old) if base.history_undo(old): return 2, ["Undoing transaction %u" % (old.tid,)] def _hcmd_rollback(self, base, extcmds): force = False if len(extcmds) > 1 and extcmds[1] == 'force': force = True extcmds = extcmds[:] extcmds.pop(0) old = base._history_get_transaction(extcmds) if old is None: return 1, ['Failed history rollback, no transaction'] last = base.history.last() if last is None: return 1, ['Failed history rollback, no last?'] if old.tid == last.tid: return 0, ['Rollback to current, nothing to do'] mobj = None for tid in base.history.old(range(old.tid + 1, last.tid + 1)): if not force and (tid.altered_lt_rpmdb or tid.altered_gt_rpmdb): if tid.altered_lt_rpmdb: msg = "Transaction history is incomplete, before %u." else: msg = "Transaction history is incomplete, after %u." print msg % tid.tid print " You can use 'history rollback force', to try anyway." return 1, ['Failed history rollback, incomplete'] if mobj is None: mobj = yum.history.YumMergedHistoryTransaction(tid) else: mobj.merge(tid) tm = time.ctime(old.beg_timestamp) print "Rollback to transaction %u, from %s" % (old.tid, tm) print base.fmtKeyValFill(" Undoing the following transactions: ", ", ".join((str(x) for x in mobj.tid))) base.historyInfoCmdPkgsAltered(mobj) if base.history_undo(mobj): return 2, ["Rollback to transaction %u" % (old.tid,)] def _hcmd_new(self, base, extcmds): base.history._create_db_file() def _hcmd_stats(self, base, extcmds): print "File :", base.history._db_file num = os.stat(base.history._db_file).st_size print "Size :", locale.format("%d", num, True) trans_N = base.history.last() if trans_N is None: print _("Transactions:"), 0 return counts = base.history._pkg_stats() trans_1 = base.history.old("1")[0] print _("Transactions:"), trans_N.tid print _("Begin time :"), time.ctime(trans_1.beg_timestamp) print _("End time :"), time.ctime(trans_N.end_timestamp) print _("Counts :") print _(" NEVRAC :"), locale.format("%6d", counts['nevrac'], True) print _(" NEVRA :"), locale.format("%6d", counts['nevra'], True) print _(" NA :"), locale.format("%6d", counts['na'], True) print _(" NEVR :"), locale.format("%6d", counts['nevr'], True) print _(" rpm DB :"), locale.format("%6d", counts['rpmdb'], True) print _(" yum DB :"), locale.format("%6d", counts['yumdb'], True) def _hcmd_sync(self, base, extcmds): extcmds = extcmds[1:] if not extcmds: extcmds = None for ipkg in sorted(base.rpmdb.returnPackages(patterns=extcmds)): if base.history.pkg2pid(ipkg, create=False) is None: continue print "Syncing rpm/yum DB data for:", ipkg, "...", if base.history.sync_alldb(ipkg): print "Done." else: print "FAILED." def doCheck(self, base, basecmd, extcmds): """Verify that conditions are met so that this command can run. The exact conditions checked will vary depending on the subcommand that is being called. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: the command line arguments passed to *basecmd* """ cmds = ('list', 'info', 'summary', 'repeat', 'redo', 'undo', 'new', 'rollback', 'addon', 'addon-info', 'stats', 'statistics', 'sync', 'synchronize' 'pkg', 'pkgs', 'pkg-list', 'pkgs-list', 'package', 'package-list', 'packages', 'packages-list', 'pkg-info', 'pkgs-info', 'package-info', 'packages-info') if extcmds and extcmds[0] not in cmds: base.logger.critical(_('Invalid history sub-command, use: %s.'), ", ".join(cmds)) raise cli.CliError if extcmds and extcmds[0] in ('repeat', 'redo', 'undo', 'rollback', 'new'): checkRootUID(base) checkGPGKey(base) elif not (base.history._db_file and os.access(base.history._db_file, os.R_OK)): base.logger.critical(_("You don't have access to the history DB.")) raise cli.CliError def doCommand(self, base, basecmd, extcmds): """Execute this command. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: the command line arguments passed to *basecmd* :return: (exit_code, [ errors ]) exit_code is:: 0 = we're done, exit 1 = we've errored, exit with error string 2 = we've got work yet to do, onto the next stage """ vcmd = 'list' if extcmds: vcmd = extcmds[0] if False: pass elif vcmd == 'list': ret = base.historyListCmd(extcmds) elif vcmd == 'info': ret = base.historyInfoCmd(extcmds) elif vcmd == 'summary': ret = base.historySummaryCmd(extcmds) elif vcmd in ('addon', 'addon-info'): ret = base.historyAddonInfoCmd(extcmds) elif vcmd in ('pkg', 'pkgs', 'pkg-list', 'pkgs-list', 'package', 'package-list', 'packages', 'packages-list'): ret = base.historyPackageListCmd(extcmds) elif vcmd == 'undo': ret = self._hcmd_undo(base, extcmds) elif vcmd in ('redo', 'repeat'): ret = self._hcmd_redo(base, extcmds) elif vcmd == 'rollback': ret = self._hcmd_rollback(base, extcmds) elif vcmd == 'new': ret = self._hcmd_new(base, extcmds) elif vcmd in ('stats', 'statistics'): ret = self._hcmd_stats(base, extcmds) elif vcmd in ('sync', 'synchronize'): ret = self._hcmd_sync(base, extcmds) elif vcmd in ('pkg-info', 'pkgs-info', 'package-info', 'packages-info'): ret = base.historyPackageInfoCmd(extcmds) if ret is None: return 0, ['history %s' % (vcmd,)] return ret def needTs(self, base, basecmd, extcmds): """Return whether a transaction set must be set up before this command can run. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: a list of arguments passed to *basecmd* :return: True if a transaction set is needed, False otherwise """ vcmd = 'list' if extcmds: vcmd = extcmds[0] return vcmd in ('repeat', 'redo', 'undo', 'rollback') def cacheRequirement(self, base, basecmd, extcmds): """Return the cache requirements for the remote repos. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: a list of arguments passed to *basecmd* :return: Type of requirement: read-only:past, read-only:present, read-only:future, write """ vcmd = 'list' if extcmds: vcmd = extcmds[0] if vcmd in ('repeat', 'redo', 'undo', 'rollback'): return 'write' return 'read-only:past' class CheckRpmdbCommand(YumCommand): """A class containing methods needed by the cli to execute the check-rpmdb command. """ def getNames(self): """Return a list containing the names of this command. This command can be called from the command line by using any of these names. :return: a list containing the names of this command """ return ['check', 'check-rpmdb'] def getUsage(self): """Return a usage string for this command. :return: a usage string for this command """ return "[dependencies|duplicates|all]" def getSummary(self): """Return a one line summary of this command. :return: a one line summary of this command """ return _("Check for problems in the rpmdb") def doCommand(self, base, basecmd, extcmds): """Execute this command. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: the command line arguments passed to *basecmd* :return: (exit_code, [ errors ]) exit_code is:: 0 = we're done, exit 1 = we've errored, exit with error string 2 = we've got work yet to do, onto the next stage """ chkcmd = 'all' if extcmds: chkcmd = extcmds def _out(x): print to_unicode(x.__str__()) rc = 0 if base._rpmdb_warn_checks(out=_out, warn=False, chkcmd=chkcmd, header=lambda x: None): rc = 1 return rc, ['%s %s' % (basecmd, chkcmd)] def needTs(self, base, basecmd, extcmds): """Return whether a transaction set must be set up before this command can run. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: a list of arguments passed to *basecmd* :return: True if a transaction set is needed, False otherwise """ return False def cacheRequirement(self, base, basecmd, extcmds): """Return the cache requirements for the remote repos. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: a list of arguments passed to *basecmd* :return: Type of requirement: read-only:past, read-only:present, read-only:future, write """ return 'read-only:past' class LoadTransactionCommand(YumCommand): """A class containing methods needed by the cli to execute the load-transaction command. """ def getNames(self): """Return a list containing the names of this command. This command can be called from the command line by using any of these names. :return: a list containing the names of this command """ return ['load-transaction', 'load-ts', 'ts-load'] def getUsage(self): """Return a usage string for this command. :return: a usage string for this command """ return "filename" def getSummary(self): """Return a one line summary of this command. :return: a one line summary of this command """ return _("load a saved transaction from filename") def doCommand(self, base, basecmd, extcmds): """Execute this command. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: the command line arguments passed to *basecmd* :return: (exit_code, [ errors ]) exit_code is:: 0 = we're done, exit 1 = we've errored, exit with error string 2 = we've got work yet to do, onto the next stage """ def _pkg_avail(l): if not l.startswith('mbr:'): return True # Kind of ... try: pkgtup, current_state = l.split(':')[1].strip().split(' ') current_state = int(current_state.strip()) pkgtup = tuple(pkgtup.strip().split(',')) if current_state == yum.TS_INSTALL: po = base.getInstalledPackageObject(pkgtup) elif current_state == yum.TS_AVAILABLE: po = base.getPackageObject(pkgtup) else: return False # Bad... except: return False # Bad... return True def _pkg_counts(l, counts): if not l.startswith(' ts_state: '): return state = l[len(' ts_state: '):] if state in ('e', 'od', 'ud'): counts['remove'] += 1 elif state in ('i', 'u'): counts['install'] += 1 if not extcmds: extcmds = [tempfile.gettempdir()] load_file = extcmds[0] if os.path.isdir(load_file): self.doneCommand(base, _("showing transaction files from %s") % load_file) yumtxs = sorted(glob.glob("%s/*.yumtx" % load_file)) currpmv = None done = False for yumtx in yumtxs: data = base._load_ts_data(yumtx) if data[0] is not None: continue # Bad file... data = data[1] rpmv = data[0].strip() if currpmv is None: currpmv = str(base.rpmdb.simpleVersion(main_only=True)[0]) if rpmv == currpmv: current = _('y') else: current = ' ' # Not usable is the most common # See load_ts() for data ... try: numrepos = int(data[2].strip()) pkgstart = 3+numrepos numpkgs = int(data[pkgstart].strip()) pkgstart += 1 except: continue counts = {'install' : 0, 'remove' : 0} for l in data[pkgstart:]: l = l.rstrip() _pkg_counts(l, counts) # Check to see if all the packages are available.. bad = ' ' for l in data[pkgstart:]: l = l.rstrip() if _pkg_avail(l): continue bad = '*' break # assert (counts['install'] + counts['remove']) == numpkgs current = '%s%s' % (bad, current) if not done: pkgititle = _("Install") pkgilen = utf8_width(pkgititle) if pkgilen < 6: pkgilen = 6 pkgititle = utf8_width_fill(pkgititle, pkgilen) pkgetitle = _("Remove") pkgelen = utf8_width(pkgetitle) if pkgelen < 6: pkgelen = 6 pkgetitle = utf8_width_fill(pkgetitle, pkgelen) print "?? |", pkgititle, "|", pkgetitle, "|", _("Filename") done = True numipkgs = locale.format("%d", counts['install'], True) numipkgs = "%*s" % (pkgilen, numipkgs) numepkgs = locale.format("%d", counts['remove'], True) numepkgs = "%*s" % (pkgelen, numepkgs) print "%s | %s | %s | %s" % (current, numipkgs, numepkgs, os.path.basename(yumtx)) return 0, [_('Saved transactions from %s; looked at %u files') % (load_file, len(yumtxs))] self.doneCommand(base, _("loading transaction from %s") % load_file) base.load_ts(load_file) return 2, [_('Transaction loaded from %s with %s members') % (load_file, len(base.tsInfo.getMembers()))] def needTs(self, base, basecmd, extcmds): """Return whether a transaction set must be set up before this command can run. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: a list of arguments passed to *basecmd* :return: True if a transaction set is needed, False otherwise """ if not extcmds or os.path.isdir(extcmds[0]): return False return True def cacheRequirement(self, base, basecmd, extcmds): """Return the cache requirements for the remote repos. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: a list of arguments passed to *basecmd* :return: Type of requirement: read-only:past, read-only:present, read-only:future, write """ if not extcmds or os.path.isdir(extcmds[0]): return 'read-only:past' return 'write' class SwapCommand(YumCommand): """A class containing methods needed by the cli to execute the swap command. """ def getNames(self): """Return a list containing the names of this command. This command can be called from the command line by using any of these names. :return: a list containing the names of this command """ return ['swap'] def getUsage(self): """Return a usage string for this command. :return: a usage string for this command """ return "[remove|cmd] [-- install|cmd] " def getSummary(self): """Return a one line summary of this command. :return: a one line summary of this command """ return _("Simple way to swap packages, instead of using shell") def doCheck(self, base, basecmd, extcmds): """Verify that conditions are met so that this command can run. These include that the program is being run by the root user, that there are enabled repositories with gpg keys, and that this command is called with appropriate arguments. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: the command line arguments passed to *basecmd* """ checkRootUID(base) checkGPGKey(base) checkSwapPackageArg(base, basecmd, extcmds) checkEnabledRepo(base, extcmds) def doCommand(self, base, basecmd, extcmds): """Execute this command. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: the command line arguments passed to *basecmd* :return: (exit_code, [ errors ]) exit_code is:: 0 = we're done, exit 1 = we've errored, exit with error string 2 = we've got work yet to do, onto the next stage """ if '--' in extcmds: off = extcmds.index('--') rextcmds = extcmds[:off] iextcmds = extcmds[off+1:] else: rextcmds = extcmds[:1] iextcmds = extcmds[1:] if not (rextcmds and iextcmds): return 1, ['swap'] # impossible if rextcmds[0] not in base.yum_cli_commands: rextcmds = ['remove'] + rextcmds if iextcmds[0] not in base.yum_cli_commands: iextcmds = ['install'] + iextcmds # Very similar to what the shell command does... ocmds = base.cmds oline = base.cmdstring for cmds in (rextcmds, iextcmds): base.cmdstring = " ".join(cmds) base.cmds = cmds # Don't call this atm. as the line has gone through it already, # also makes it hard to do the "is ?extcmds[0] a cmd" check. # base.plugins.run('args', args=base.cmds) # We don't catch exceptions, just pass them up and fail... base.parseCommands() cmdret = base.doCommands() if cmdret[0] != 2: return cmdret[0], ['%s %s' % (basecmd, " ".join(cmds))] base.cmds = ocmds base.cmdstring = oline return 2, ['%s %s' % (basecmd, " ".join(extcmds))] class RepoPkgsCommand(YumCommand): """A class containing methods needed by the cli to execute the repo command. """ def getNames(self): """Return a list containing the names of this command. This command can be called from the command line by using any of these names. :return: a list containing the names of this command """ return ['repo-pkgs', 'repo-packages', 'repository-pkgs', 'repository-packages'] def getUsage(self): """Return a usage string for this command. :return: a usage string for this command """ return " [pkg(s)]" def getSummary(self): """Return a one line summary of this command. :return: a one line summary of this command """ return _("Treat a repo. as a group of packages, so we can install/remove all of them") def doCheck(self, base, basecmd, extcmds): """Verify that conditions are met so that this command can run. These include that the program is being run by the root user, that there are enabled repositories with gpg keys, and that this command is called with appropriate arguments. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: the command line arguments passed to *basecmd* """ checkRootUID(base) checkGPGKey(base) checkRepoPackageArg(base, basecmd, extcmds) checkEnabledRepo(base, extcmds) def doCommand(self, base, basecmd, extcmds): """Execute this command. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: the command line arguments passed to *basecmd* :return: (exit_code, [ errors ]) exit_code is:: 0 = we're done, exit 1 = we've errored, exit with error string 2 = we've got work yet to do, onto the next stage """ def _add_repopkg2txmbrs(txmbrs, repoid): for txmbr in txmbrs: txmbr.repopkg = repoid repoid = extcmds[0] cmd = extcmds[1] args = extcmds[2:] noargs = False if not args: noargs = True args = ['*'] num = 0 remap = {'erase' : 'remove', 'erase-or-reinstall' : 'remove-or-reinstall', 'erase-or-sync' : 'remove-or-sync', 'erase-or-distro-sync' : 'remove-or-sync', 'remove-or-distro-sync' : 'remove-or-sync', 'erase-or-distribution-synchronization' : 'remove-or-sync', 'remove-or-distribution-synchronization' : 'remove-or-sync', 'upgrade' : 'update', # Hack, but meh. 'upgrade-to' : 'update-to', # Hack, but meh. 'check-upgrade' : 'check-update', # Hack, but meh. 'check-upgrades' : 'check-update', # Hack, but meh. 'check-updates' : 'check-update', # Hack, but meh. } cmd = remap.get(cmd, cmd) if False: pass elif cmd == 'list': # list/info is easiest... return ListCommand().doCommand(base, cmd, args, repoid=repoid) elif cmd == 'info': return InfoCommand().doCommand(base, cmd, args, repoid=repoid) elif cmd == 'check-update': return CheckUpdateCommand().doCommand(base, cmd, args,repoid=repoid) elif cmd == 'install': # install is simpler version of installPkgs... for arg in args: txmbrs = base.install(pattern=arg, repoid=repoid) _add_repopkg2txmbrs(txmbrs, repoid) num += len(txmbrs) if num: return 2, P_('%d package to install', '%d packages to install', num) elif cmd == 'update': # update is basically the same as install... for arg in args: txmbrs = base.update(pattern=arg, repoid=repoid) _add_repopkg2txmbrs(txmbrs, repoid) num += len(txmbrs) if num: updateinfo.remove_txmbrs(base) return 2, P_('%d package to update', '%d packages to update', num) elif cmd == 'update-to': # update is basically the same as install... for arg in args: txmbrs = base.update(pattern=arg, update_to=True, repoid=repoid) _add_repopkg2txmbrs(txmbrs, repoid) num += len(txmbrs) if num: updateinfo.remove_txmbrs(base) return 2, P_('%d package to update', '%d packages to update', num) elif cmd in ('reinstall-old', 'reinstall-installed'): # We have to choose for reinstall, for "reinstall foo" do we mean: # 1. reinstall the packages that are currently installed from "foo". # 2. reinstall the packages specified to the ones from "foo" # This is for installed.from_repo=foo if noargs: onot_found_a = base._not_found_a.copy() for arg in args: txmbrs = base.reinstall(pattern=arg, repoid=repoid, repoid_install=repoid) _add_repopkg2txmbrs(txmbrs, repoid) num += len(txmbrs) if noargs: base._not_found_a = onot_found_a if num: return 2, P_('%d package to reinstall', '%d packages to reinstall', num) elif cmd in ('reinstall-new', 'reinstall-available', 'move-to'): # This is for move-to the packages from this repo. if noargs: onot_found_a = base._not_found_a.copy() for arg in args: txmbrs = base.reinstall(pattern=arg, repoid_install=repoid) _add_repopkg2txmbrs(txmbrs, repoid) num += len(txmbrs) if noargs: base._not_found_a = onot_found_a if num: return 2, P_('%d package to move to', '%d packages to move to', num) elif cmd == 'reinstall': # This means "guess", so doing the old version unless it produces # nothing, in which case try switching. if noargs: onot_found_a = base._not_found_a.copy() for arg in args: try: txmbrs = base.reinstall(pattern=arg, repoid=repoid,repoid_install=repoid) except yum.Errors.ReinstallRemoveError: continue _add_repopkg2txmbrs(txmbrs, repoid) num += len(txmbrs) if noargs: base._not_found_a = onot_found_a.copy() if num: return 2, P_('%d package to reinstall', '%d packages to reinstall', num) for arg in args: txmbrs = base.reinstall(pattern=arg, repoid_install=repoid) _add_repopkg2txmbrs(txmbrs, repoid) num += len(txmbrs) if noargs: base._not_found_a = onot_found_a if num: return 2, P_('%d package to move to', '%d packages to move to', num) elif cmd == 'remove': # Also mostly the same... for arg in args: txmbrs = base.remove(pattern=arg, repoid=repoid) _add_repopkg2txmbrs(txmbrs, repoid) num += len(txmbrs) if num: return 2, P_('%d package to remove', '%d packages to remove', num) elif cmd == 'remove-or-reinstall': # More complicated... for arg in args: txmbrs = base.remove(pattern=arg, repoid=repoid) # Add an install() if it's in another repo. for txmbr in txmbrs[:]: pkgs = base.pkgSack.searchPkgTuple(txmbr.pkgtup) for pkg in sorted(pkgs): if pkg.repoid == repoid: continue txmbrs += base.install(po=pkg) break _add_repopkg2txmbrs(txmbrs, repoid) num += len(txmbrs) if num: return 2, P_('%d package to remove/reinstall', '%d packages to remove/reinstall', num) elif cmd == 'remove-or-sync': # Even more complicated... for arg in args: txmbrs = base.remove(pattern=arg, repoid=repoid) # Add an install/upgrade/downgrade if a version is in another # repo. for txmbr in txmbrs[:]: pkgs = base.pkgSack.searchNames([txmbr.name]) apkgs = None for pkg in sorted(pkgs): if pkg.repoid == repoid: # Backwards filter_pkgs_repoid continue if apkgs and pkg.verEQ(apkgs[0]): apkgs.append(pkg) else: apkgs = [pkg] if apkgs: for pkg in apkgs: if pkg.arch != txmbr.arch: continue apkgs = [pkg] break if len(apkgs) != 1: apkgs = base.bestPackagesFromList(apkgs) for toinst in apkgs: n,a,e,v,r = toinst.pkgtup if toinst.verEQ(txmbr.po): txmbrs += base.install(po=toinst) elif toinst.verGT(txmbr.po): txmbrs += base.update(po=toinst) else: base.tsInfo.remove(txmbr.pkgtup) txmbrs.remove(txmbr) txmbrs += base.downgrade(po=toinst) _add_repopkg2txmbrs(txmbrs, repoid) num += len(txmbrs) if num: return 2, P_('%d package to remove/sync', '%d packages to remove/sync', num) else: return 1, [_('Not a valid sub-command of %s') % basecmd] return 0, [_('Nothing to do')] def needTs(self, base, basecmd, extcmds): """Return whether a transaction set must be set up before this command can run. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: a list of arguments passed to *basecmd* :return: True if a transaction set is needed, False otherwise """ cmd = 'install' if len(extcmds) > 1: cmd = extcmds[1] if cmd in ('info', 'list'): return InfoCommand().needTs(base, cmd, extcmds[2:]) return True def cacheRequirement(self, base, basecmd, extcmds): """Return the cache requirements for the remote repos. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: a list of arguments passed to *basecmd* :return: Type of requirement: read-only:past, read-only:present, read-only:future, write """ cmd = 'install' if len(extcmds) > 1: cmd = extcmds[1] if cmd in ('info', 'list'): return InfoCommand().cacheRequirement(base, cmd, extcmds[2:]) if cmd in ('check-update', 'check-upgrade', 'check-updates', 'check-upgrades'): return CheckUpdateCommand().cacheRequirement(base, cmd, extcmds[2:]) return 'write' # Using this a lot, so make it easier... _upi = updateinfo class UpdateinfoCommand(YumCommand): # Old command names... direct_cmds = {'list-updateinfo' : 'list', 'list-security' : 'list', 'list-sec' : 'list', 'info-updateinfo' : 'info', 'info-security' : 'info', 'info-sec' : 'info', 'summary-updateinfo' : 'summary'} # Note that this code (instead of using inheritance and multiple # cmd classes) means that "yum help" only displays the updateinfo command. # Which is what we want, because the other commands are just backwards # compatible gunk we don't want the user using). def getNames(self): return ['updateinfo'] + sorted(self.direct_cmds.keys()) def getUsage(self): return "[info|list|...] [security|...] [installed|available|all] [pkgs|id]" def getSummary(self): return "Acts on repository update information" def doCheck(self, base, basecmd, extcmds): pass def list_show_pkgs(self, base, md_info, list_type, show_type, iname2tup, data, msg): n_maxsize = 0 r_maxsize = 0 t_maxsize = 0 for (notice, pkgtup, pkg) in data: n_maxsize = max(len(notice['update_id']), n_maxsize) tn = notice['type'] if tn == 'security' and notice['severity']: tn = notice['severity'] + '/Sec.' t_maxsize = max(len(tn), t_maxsize) if show_type: for ref in _upi._ysp_safe_refs(notice['references']): if ref['type'] != show_type: continue r_maxsize = max(len(str(ref['id'])), r_maxsize) for (notice, pkgtup, pkg) in data: mark = '' if list_type == 'all': mark = ' ' if pkgtup[0] in iname2tup and _upi._rpm_tup_vercmp(iname2tup[pkgtup[0]], pkgtup) >= 0: mark = 'i ' tn = notice['type'] if tn == 'security' and notice['severity']: tn = notice['severity'] + '/Sec.' if show_type and _upi._ysp_has_info_md(show_type, notice): for ref in _upi._ysp_safe_refs(notice['references']): if ref['type'] != show_type: continue msg("%s %-*s %-*s %s" % (mark, r_maxsize, str(ref['id']), t_maxsize, tn, pkg)) elif hasattr(pkg, 'name'): print base.fmtKeyValFill("%s: " % pkg.name, base._enc(pkg.summary)) else: msg("%s%-*s %-*s %s" % (mark, n_maxsize, notice['update_id'], t_maxsize, tn, pkg)) def info_show_pkgs(self, base, md_info, list_type, show_type, iname2tup, data, msg): show_pkg_info_done = {} for (notice, pkgtup, pkg) in data: if notice['update_id'] in show_pkg_info_done: continue show_pkg_info_done[notice['update_id']] = notice if hasattr(notice, 'text'): debug_log_lvl = yum.logginglevels.DEBUG_3 vlog = base.verbose_logger if vlog.isEnabledFor(debug_log_lvl): obj = notice.text(skip_data=[]) else: obj = notice.text() else: # Python-2.4.* doesn't understand str(x) returning unicode obj = notice.__str__() if list_type == 'all': if pkgtup[0] in iname2tup and _upi._rpm_tup_vercmp(iname2tup[pkgtup[0]], pkgtup) >= 0: obj = obj + "\n Installed : true" else: obj = obj + "\n Installed : false" msg(obj) def summary_show_pkgs(self, base, md_info, list_type, show_type, iname2tup, data, msg): def _msg(x): base.verbose_logger.info("%s", x) counts = {} sev_counts = {} show_pkg_info_done = {} for (notice, pkgtup, pkg) in data: if notice['update_id'] in show_pkg_info_done: continue show_pkg_info_done[notice['update_id']] = notice counts[notice['type']] = counts.get(notice['type'], 0) + 1 if notice['type'] == 'security': sev = notice['severity'] if sev is None: sev = '' sev_counts[sev] = sev_counts.get(sev, 0) + 1 maxsize = 0 for T in ('newpackage', 'security', 'bugfix', 'enhancement'): if T not in counts: continue size = len(str(counts[T])) if maxsize < size: maxsize = size if not maxsize: if base.conf.autocheck_running_kernel: _upi._check_running_kernel(base, md_info, _msg) return outT = {'newpackage' : 'New Package', 'security' : 'Security', 'bugfix' : 'Bugfix', 'enhancement' : 'Enhancement'} print "Updates Information Summary:", list_type for T in ('newpackage', 'security', 'bugfix', 'enhancement'): if T not in counts: continue n = outT[T] if T == 'security' and len(sev_counts) == 1: sn = sev_counts.keys()[0] if sn != '': n = sn + " " + n print " %*u %s notice(s)" % (maxsize, counts[T], n) if T == 'security' and len(sev_counts) != 1: def _sev_sort_key(key): # We want these in order, from "highest" to "lowest". # Anything unknown is "higher". meh. return {'Critical' : "zz1", 'Important': "zz2", 'Moderate' : "zz3", 'Low' : "zz4", }.get(key, key) for sn in sorted(sev_counts, key=_sev_sort_key): args = (maxsize, sev_counts[sn],sn or '?', outT['security']) print " %*u %s %s notice(s)" % args if base.conf.autocheck_running_kernel: _upi._check_running_kernel(base, md_info, _msg) self.show_pkg_info_done = {} def _get_new_pkgs(self, md_info): for notice in md_info.notices: if notice['type'] != "newpackage": continue for upkg in notice['pkglist']: for pkg in upkg['packages']: pkgtup = (pkg['name'], pkg['arch'], pkg['epoch'] or '0', pkg['version'], pkg['release']) yield (notice, pkgtup) _cmd2filt = {"bugzillas" : "bugzilla", "bugzilla" : "bugzilla", "bzs" : "bugzilla", "bz" : "bugzilla", "sec" : "security", "cves" : "cve", "cve" : "cve", "newpackages" : "newpackage", "new-packages" : "newpackage", "newpackage" : "newpackage", "new-package" : "newpackage", "new" : "newpackage"} for filt_type in _upi._update_info_types_: _cmd2filt[filt_type] = filt_type def doCommand(self, base, basecmd, extcmds): if basecmd in self.direct_cmds: subcommand = self.direct_cmds[basecmd] elif extcmds and extcmds[0] in ('list', 'info', 'summary', 'remove-pkgs-ts', 'exclude-updates', 'exclude-all', 'check-running-kernel'): subcommand = extcmds[0] extcmds = extcmds[1:] elif extcmds and extcmds[0] in self._cmd2filt: subcommand = 'list' elif extcmds: subcommand = 'info' else: subcommand = 'summary' if subcommand == 'list': return self.doCommand_li(base, 'updateinfo list', extcmds, self.list_show_pkgs) if subcommand == 'info': return self.doCommand_li(base, 'updateinfo info', extcmds, self.info_show_pkgs) if subcommand == 'summary': return self.doCommand_li(base, 'updateinfo summary', extcmds, self.summary_show_pkgs) if subcommand == 'remove-pkgs-ts': filters = None if extcmds: filters = updateinfo._args2filters(extcmds) updateinfo.remove_txmbrs(base, filters) return 0, [basecmd + ' ' + subcommand + ' done'] if subcommand == 'exclude-all': filters = None if extcmds: filters = updateinfo._args2filters(extcmds) updateinfo.exclude_all(base, filters) return 0, [basecmd + ' ' + subcommand + ' done'] if subcommand == 'exclude-updates': filters = None if extcmds: filters = updateinfo._args2filters(extcmds) updateinfo.exclude_updates(base, filters) return 0, [basecmd + ' ' + subcommand + ' done'] if subcommand == 'check-running-kernel': def _msg(x): base.verbose_logger.info("%s", x) updateinfo._check_running_kernel(base, base.upinfo, _msg) return 0, [basecmd + ' ' + subcommand + ' done'] def doCommand_li_new(self, base, list_type, extcmds, md_info, msg, show_pkgs, iname2tup): done_pkgs = set() data = [] for (notice, pkgtup) in sorted(self._get_new_pkgs(md_info), key=lambda x: x[1][0]): if extcmds and not _upi._match_sec_cmd(extcmds, pkgtup[0], notice): continue n = pkgtup[0] if n in done_pkgs: continue ipkgs = list(reversed(sorted(base.rpmdb.searchNames([n])))) if list_type in ('installed', 'updates') and not ipkgs: done_pkgs.add(n) continue if list_type == 'available' and ipkgs: done_pkgs.add(n) continue pkgs = base.pkgSack.searchPkgTuple(pkgtup) if not pkgs: continue if list_type == "updates" and pkgs[0].verLE(ipkgs[0]): done_pkgs.add(n) continue done_pkgs.add(n) data.append((notice, pkgtup, pkgs[0])) show_pkgs(base, md_info, list_type, None, iname2tup, data, msg) def _parse_extcmds(self, extcmds): filt_type = None show_type = None if len(extcmds) >= 1: filt_type = None if extcmds[0] in self._cmd2filt: filt_type = self._cmd2filt[extcmds.pop(0)] show_type = filt_type if filt_type and filt_type in _upi._update_info_types_: show_type = None return extcmds, show_type, filt_type def doCommand_li(self, base, basecmd, extcmds, show_pkgs): md_info = base.upinfo def msg(x): # Don't use: logger.log(logginglevels.INFO_2, x) # or -q deletes everything. print x extcmds, show_type, filt_type = self._parse_extcmds(extcmds) list_type = "available" list_type = "updates" if extcmds and extcmds[0] in ("updates","available","installed", "all"): list_type = extcmds.pop(0) if filt_type is None: extcmds, show_type, filt_type = self._parse_extcmds(extcmds) opts = _upi._ysp_gen_opts(base.updateinfo_filters, extcmds) used_map = _upi._ysp_gen_used_map(base.updateinfo_filters) iname2tup = {} if False: pass elif list_type == 'installed': name2tup = _upi._get_name2allpkgtup(base) iname2tup = _upi._get_name2instpkgtup(base) elif list_type == 'updates': name2tup = _upi._get_name2oldpkgtup(base) elif list_type in ('available', 'all'): name2tup = _upi._get_name2aallpkgtup(base) iname2tup = _upi._get_name2instpkgtup(base) if filt_type == "newpackage": self.doCommand_li_new(base, list_type, extcmds, md_info, msg, show_pkgs, iname2tup) return 0, [basecmd + ' new done'] def _show_pkgtup(pkgtup): name = pkgtup[0] notices = reversed(md_info.get_applicable_notices(pkgtup)) for (pkgtup, notice) in notices: if filt_type and not _upi._ysp_has_info_md(filt_type, notice): continue if list_type == 'installed': # Remove any that are newer than what we have installed if _upi._rpm_tup_vercmp(iname2tup[name], pkgtup) < 0: continue if list_type == 'available': # Remove any that are installed if name in iname2tup and _upi._rpm_tup_vercmp(iname2tup[name], pkgtup) >= 0: continue if _upi._ysp_should_filter_pkg(opts, name, notice, used_map): yield (pkgtup, notice) data = [] for pkgname in sorted(name2tup): for (pkgtup, notice) in _show_pkgtup(name2tup[pkgname]): d = {} (d['n'], d['a'], d['e'], d['v'], d['r']) = pkgtup if d['e'] == '0': d['epoch'] = '' else: d['epoch'] = "%s:" % d['e'] data.append((notice, pkgtup, "%(n)s-%(epoch)s%(v)s-%(r)s.%(a)s" % d)) show_pkgs(base, md_info, list_type, show_type, iname2tup, data, msg) _upi._ysp_chk_used_map(used_map, msg) return 0, [basecmd + ' done'] class UpdateMinimalCommand(YumCommand): def getNames(self): return ['update-minimal', 'upgrade-minimal'] def getUsage(self): return "[PACKAGE-wildcard]" def getSummary(self): return _("Works like upgrade, but goes to the 'newest' package match which fixes a problem that affects your system") def doCheck(self, base, basecmd, extcmds): """Verify that conditions are met so that this command can run. These include that the program is being run by the root user, that there are enabled repositories with gpg keys, and that this command is called with appropriate arguments. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: the command line arguments passed to *basecmd* """ checkRootUID(base) checkGPGKey(base) def doCommand(self, base, basecmd, extcmds): """Execute this command. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: the command line arguments passed to *basecmd* :return: (exit_code, [ errors ]) exit_code is:: 0 = we're done, exit 1 = we've errored, exit with error string 2 = we've got work yet to do, onto the next stage """ num = len(base.tsInfo) _upi.update_minimal(base, extcmds) num = len(base.tsInfo) - num if num > 0: msg = '%d packages marked for minimal Update' % num return 2, [msg] else: return 0, ['No Packages marked for minimal Update'] class FSSnapshotCommand(YumCommand): def getNames(self): return ['fssnapshot', 'fssnap'] def getUsage(self): return "[]" def getSummary(self): return _("Creates filesystem snapshots, or lists/deletes current snapshots.") def doCheck(self, base, basecmd, extcmds): """Verify that conditions are met so that this command can run. These include that the program is being run by the root user, that there are enabled repositories with gpg keys, and that this command is called with appropriate arguments. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: the command line arguments passed to *basecmd* """ checkRootUID(base) @staticmethod def _li_snaps(base, snaps): snaps = sorted(snaps, key=lambda x: x['dev']) max_dev = utf8_width(_('Snapshot')) max_ori = utf8_width(_('Origin')) for data in snaps: max_dev = max(max_dev, len(data['dev'])) max_ori = max(max_ori, len(data['origin'])) done = False for data in snaps: if not done: print ("%s %s %s %s %s %s" % (utf8_width_fill(_('Snapshot'), max_dev), utf8_width_fill(_('Size'), 6, left=False), utf8_width_fill(_('Used'), 6, left=False), utf8_width_fill(_('Free'), 6, left=False), utf8_width_fill(_('Origin'), max_ori), _('Tags'))) done = True print ("%*s %6s %5.1f%% %6s %*s %s" % (max_dev, data['dev'], base.format_number(data['size']), data['used'], base.format_number(data['free']), max_ori, data['origin'], ",".join(data['tags']))) def doCommand(self, base, basecmd, extcmds): """Execute this command. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: the command line arguments passed to *basecmd* :return: (exit_code, [ errors ]) exit_code is:: 0 = we're done, exit 1 = we've errored, exit with error string 2 = we've got work yet to do, onto the next stage """ if extcmds and extcmds[0] in ('list', 'delete', 'create', 'summary', 'have-space', 'has-space'): subcommand = extcmds[0] extcmds = extcmds[1:] else: subcommand = 'summary' if not base.fssnap.available: msg = _("Snapshot support not available, please check your lvm installation.") if not base.rpmdb.searchNames(['lvm2']): msg += " " + _("No lvm2 package installed.") if not base.rpmdb.searchNames(['lvm2-python-libs']): msg += " " + _("No lvm2-python-libs package installed.") print msg return 1, [basecmd + ' ' + subcommand + ' done'] if subcommand == 'list': try: snaps = base.fssnap.old_snapshots() except LibLVMError as e: return 1, [_("Failed to list snapshots: ") + lvmerr2str(e)] print _("List of %u snapshosts:") % len(snaps) self._li_snaps(base, snaps) if subcommand == 'delete': msg = _("Failed to delete snapshots: ") try: snaps = base.fssnap.old_snapshots() except LibLVMError as e: return 1, [msg + lvmerr2str(e)] devs = [x['dev'] for x in snaps] snaps = set() for dev in devs: if dev in snaps: continue for extcmd in extcmds: if dev == extcmd or fnmatch.fnmatch(dev, extcmd): snaps.add(dev) break try: snaps = base.fssnap.del_snapshots(devices=snaps) except LibLVMError as e: return 1, [msg + lvmerr2str(e)] print _("Deleted %u snapshosts:") % len(snaps) self._li_snaps(base, snaps) if subcommand in ('have-space', 'has-space'): pc = base.conf.fssnap_percentage try: has_space = base.fssnap.has_space(pc) except LibLVMError as e: return 1, [_("Could not determine free space on logical volumes: ") + lvmerr2str(e)] if has_space: print _("Space available to take a snapshot.") else: print _("Not enough space available on logical volumes to take a snapshot.") if subcommand == 'create': tags = {'*': ['reason=manual']} pc = base.conf.fssnap_percentage msg = _("Failed to create snapshots") try: snaps = base.fssnap.snapshot(pc, tags=tags) except LibLVMError as e: msg += ": " + lvmerr2str(e) snaps = [] if not snaps: print msg for (odev, ndev) in snaps: print _("Created snapshot from %s, results is: %s") %(odev,ndev) if subcommand == 'summary': try: snaps = base.fssnap.old_snapshots() except LibLVMError as e: return 1, [_("Failed to list snapshots: ") + lvmerr2str(e)] if not snaps: print _("No snapshots, LVM version:"), base.fssnap.version return 0, [basecmd + ' ' + subcommand + ' done'] used = 0 dev_oris = set() for snap in snaps: used += snap['used'] dev_oris.add(snap['origin_dev']) msg = _("Have %u snapshots, using %s space, from %u origins.") print msg % (len(snaps), base.format_number(used), len(dev_oris)) return 0, [basecmd + ' ' + subcommand + ' done'] class FSCommand(YumCommand): def getNames(self): return ['fs'] def getUsage(self): return "[]" def getSummary(self): return _("Acts on the filesystem data of the host, mainly for removing docs/lanuages for minimal hosts.") def doCheck(self, base, basecmd, extcmds): """Verify that conditions are met so that this command can run. These include that the program is being run by the root user, that there are enabled repositories with gpg keys, and that this command is called with appropriate arguments. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: the command line arguments passed to *basecmd* """ if extcmds and extcmds[0] in ('du', 'status', 'diff'): # Anyone can go for it... return if len(extcmds) == 1 and extcmds[0] in ('filters', 'filter'): # Can look, but not touch. return checkRootUID(base) def _fs_pkg_walk(self, pkgs, prefix, modified=False, verbose=False): pfr = {'norm' : {}, 'mod' : {}, 'ghost' : {}, 'miss' : {}, 'not' : {} } def quick_match(pkgs): for pkg in pkgs: for fname in pkg.filelist + pkg.dirlist: if not fname.startswith(prefix): continue pfr['norm'][fname] = pkg for fname in pkg.ghostlist: if not fname.startswith(prefix): continue pfr['ghost'][fname] = pkg return pfr def _quick_match_iter(pkgs): # Walking the fi information is much slower than filelist/dirlist for pkg in pkgs: found = False for fname in pkg.dirlist: if fname.startswith(prefix): yield pkg found = True break if found: continue for fname in pkg.filelist: if fname.startswith(prefix): yield pkg found = True break if found: continue for fname in pkg.ghostlist: if fname.startswith(prefix): yield pkg break def verify_match(pkgs): _pfs = [] def scoop_pfs(pfs): _pfs.append(pfs) if not modified: return [] return pfs if prefix != '/': pkgs = _quick_match_iter(pkgs) for pkg in pkgs: _pfs = [] probs = pkg.verify(patterns=[prefix+'*'], fake_problems=False, callback=scoop_pfs, failfast=True) for pf in _pfs[0]: if pf.filename in probs: pfr['mod'][pf.filename] = pkg elif pf.rpmfile_state == 'not installed': pfr['not'][pf.filename] = pkg elif 'ghost' in pf.rpmfile_types: pfr['ghost'][pf.filename] = pkg elif 'missing ok' in pf.rpmfile_types: pfr['miss'][pf.filename] = pkg else: pfr['norm'][pf.filename] = pkg return pfr # return quick_match(pkgs) return verify_match(pkgs) def _fs_du(self, base, extcmds): def _dir_prefixes(path): while path != '/': path = os.path.dirname(path) yield path def loc_num(x): """ String of a number in the readable "locale" format. """ return locale.format("%d", int(x), True) data = {'pkgs_size' : {}, 'pkgs_not_size' : {}, 'pkgs_ghost_size' : {}, 'pkgs_miss_size' : {}, 'pkgs_mod_size' : {}, 'pres_size' : {}, 'data_size' : {}, 'data_not_size' : {}, 'pkgs_count' : 0, 'pkgs_not_count' : 0, 'pkgs_ghost_count' : 0, 'pkgs_miss_count' : 0, 'pkgs_mod_count' : 0, 'data_count' : 0} # data_not_count == pkgs_not_count def _add_size(d, v, size): if v not in d: d[v] = 0 d[v] += size def deal_with_file(fpath, need_prefix=True): size = os.path.getsize(fpath) if fpath in pfr['norm']: data['pkgs_count'] += size _add_size(data['pkgs_size'], pfr['norm'][fpath], size) elif fpath in pfr['ghost']: data['pkgs_ghost_count'] += size _add_size(data['pkgs_ghost_size'], pfr['ghost'][fpath], size) elif fpath in pfr['not']: data['pkgs_not_count'] += size _add_size(data['pkgs_not_size'], pfr['not'][fpath], size) data['data_not_size'][fpath] = size elif fpath in pfr['miss']: data['pkgs_miss_count'] += size _add_size(data['pkgs_miss_size'], pfr['miss'][fpath], size) elif fpath in pfr['mod']: data['pkgs_mod_count'] += size _add_size(data['pkgs_mod_size'], pfr['mod'][fpath], size) elif need_prefix and False: for fpre_path in _dir_prefixes(fpath): if fpre_path not in pkg_files: continue _add_size(data['pres_size'], pkg_files[fpre_path], size) break data['data_count'] += size data['data_size'][fpath] = size else: data['data_count'] += size data['data_size'][fpath] = size prefix = "." if extcmds: prefix = extcmds[0] extcmds = extcmds[1:] if not os.path.exists(prefix): return 1, [_('No such file or directory: ' + prefix)] max_show_len = 4 if extcmds: try: max_show_len = int(extcmds[0]) except: pass verbose = base.verbose_logger.isEnabledFor(logginglevels.DEBUG_3) pfr = self._fs_pkg_walk(base.rpmdb, prefix, verbose=verbose) base.closeRpmDB() # C-c ftw. num = 0 if os.path.isfile(prefix): num += 1 deal_with_file(prefix) for root, dirs, files in os.walk(prefix): for fname in files: num += 1 fpath = os.path.normpath(root + '/' + fname) if os.path.islink(fpath): continue deal_with_file(fpath, need_prefix=verbose) # output print "Files :", loc_num(num) tot = 0 tot += data['pkgs_count'] tot += data['pkgs_ghost_count'] tot += data['pkgs_not_count'] tot += data['pkgs_miss_count'] tot += data['pkgs_mod_count'] tot += data['data_count'] print "Total size :", base.format_number(tot) if not tot: return num = data['pkgs_count'] if not verbose: num += data['pkgs_ghost_count'] num += data['pkgs_miss_count'] num += data['pkgs_mod_count'] print " Pkgs size :", "%-5s" % base.format_number(num), print "(%3.0f%%)" % ((num * 100.0) / tot) if verbose: for (title, num) in ((_(" Ghost pkgs size :"), data['pkgs_ghost_count']), (_(" Not pkgs size :"), data['pkgs_not_count']), (_(" Miss pkgs size :"), data['pkgs_miss_count']), (_(" Mod. pkgs size :"), data['pkgs_mod_count'])): if not num: continue print title, "%-5s" % base.format_number(num), print "(%3.0f%%)" % ((num * 100.0) / tot) num = data['data_count'] if not verbose: num += data['pkgs_not_count'] print _(" Data size :"), "%-5s" % base.format_number(num), print "(%3.0f%%)" % ((num * 100.0) / tot) if verbose: print '' print _("Pkgs :"), loc_num(len(data['pkgs_size'])) print _("Ghost Pkgs :"), loc_num(len(data['pkgs_ghost_size'])) print _("Not Pkgs :"), loc_num(len(data['pkgs_not_size'])) print _("Miss. Pkgs :"), loc_num(len(data['pkgs_miss_size'])) print _("Mod. Pkgs :"), loc_num(len(data['pkgs_mod_size'])) def _pkgs(p_size, msg): tot = min(max_show_len, len(p_size)) if tot: print '' print msg % tot num = 0 for pkg in sorted(p_size, key=lambda x: p_size[x], reverse=True): num += 1 print _("%*d. %60s %-5s") % (len(str(tot)), num, pkg, base.format_number(p_size[pkg])) if num >= tot: break if verbose: _pkgs(data['pkgs_size'], _('Top %d packages:')) _pkgs(data['pkgs_ghost_size'], _('Top %d ghost packages:')) _pkgs(data['pkgs_not_size'], _('Top %d not. packages:')) _pkgs(data['pkgs_miss_size'], _('Top %d miss packages:')) _pkgs(data['pkgs_mod_size'], _('Top %d mod. packages:')) _pkgs(data['pres_size'], _('Top %d prefix packages:')) else: tmp = {} tmp.update(data['pkgs_size']) for d in data['pkgs_ghost_size']: _add_size(tmp, d, data['pkgs_ghost_size'][d]) for d in data['pkgs_miss_size']: _add_size(tmp, d, data['pkgs_miss_size'][d]) for d in data['pkgs_mod_size']: _add_size(tmp, d, data['pkgs_mod_size'][d]) _pkgs(tmp, _('Top %d packages:')) print '' if verbose: data_size = data['data_size'] else: data_size = {} data_size.update(data['data_size']) data_size.update(data['data_not_size']) tot = min(max_show_len, len(data_size)) if tot: print _('Top %d non-package files:') % tot num = 0 for fname in sorted(data_size, key=lambda x: data_size[x], reverse=True): num += 1 dsznum = data_size[fname] print _("%*d. %60s %-5s") % (len(str(tot)), num, fname, base.format_number(dsznum)) if num >= tot: break def _fs_filters(self, base, extcmds): def _save(confkey): writeRawConfigFile = yum.config._writeRawConfigFile # Always create installroot, so we can change it. if not os.path.exists(base.conf.installroot + '/etc/yum'): os.makedirs(base.conf.installroot + '/etc/yum') fn = base.conf.installroot+'/etc/yum/yum.conf' if not os.path.exists(fn): # Try the old default nfn = base.conf.installroot+'/etc/yum.conf' if os.path.exists(nfn): fn = nfn else: shutil.copy2(base.conf.config_file_path, fn) ybc = base.conf writeRawConfigFile(fn, 'main', ybc.yumvar, ybc.cfg.options, ybc.iteritems, ybc.optionobj, only=[confkey]) if not extcmds: oil = base.conf.override_install_langs if not oil: oil = "rpm: " + rpm.expandMacro("%_install_langs") print _("File system filters:") print _(" Nodocs:"), 'nodocs' in base.conf.tsflags print _(" Languages:"), oil elif extcmds[0] in ('docs', 'nodocs', 'documentation', 'nodocumentation'): c_f = 'nodocs' in base.conf.tsflags n_f = not extcmds[0].startswith('no') if n_f == c_f: if n_f: print _("Already enabled documentation filter.") else: print _("Already disabled documentation filter.") return if n_f: print _("Enabling documentation filter.") else: print _("Disabling documentation filter.") nts = base.conf.tsflags if n_f: nts = nts + ['nodocs'] else: nts = [x for x in nts if x != 'nodocs'] base.conf.tsflags = " ".join(nts) _save('tsflags') elif extcmds[0] in ('langs', 'nolangs', 'lang', 'nolang', 'languages', 'nolanguages', 'language', 'nolanguage'): if extcmds[0].startswith('no') or len(extcmds) < 2 or 'all' in extcmds: val = 'all' else: val = ":".join(extcmds[1:]) if val == base.conf.override_install_langs: if val: print _("Already filtering languages to: %s") % val else: print _("Already disabled language filter.") return if val: print _("Setting language filter to: %s") % val else: print _("Disabling language filter.") base.conf.override_install_langs = val _save('override_install_langs') else: return 1, [_('Not a valid sub-command of fs filter')] def _fs_refilter(self, base, extcmds): c_f = 'nodocs' in base.conf.tsflags # FIXME: C&P from init. oil = base.conf.override_install_langs if not oil: oil = rpm.expandMacro("%_install_langs") if oil == 'all': oil = '' elif oil: oil = ":".join(sorted(oil.split(':'))) found = False num = 0 for pkg in base.rpmdb.returnPackages(patterns=extcmds): if False: pass elif oil != pkg.yumdb_info.get('ts_install_langs', ''): txmbrs = base.reinstall(po=pkg) num += len(txmbrs) elif c_f != ('true' == pkg.yumdb_info.get('tsflag_nodocs')): txmbrs = base.reinstall(po=pkg) num += len(txmbrs) else: found = True if num: return 2,P_('%d package to reinstall','%d packages to reinstall', num) if not found: return 1, [_('No valid packages: %s') % " ".join(extcmds)] def _fs_refilter_cleanup(self, base, extcmds): pkgs = base.rpmdb.returnPackages(patterns=extcmds) verbose = base.verbose_logger.isEnabledFor(logginglevels.DEBUG_3) pfr = self._fs_pkg_walk(pkgs, "/", verbose=verbose, modified=True) base.closeRpmDB() # C-c ftw. for fname in sorted(pfr['not']): print _('Removing:'), fname try: # Ignore everything, unlink_f() doesn't. os.unlink(fname) except OSError, e: if e.errno == errno.EISDIR: try: os.rmdir(fname) except: pass except: pass def _fs_diff(self, base, extcmds): def deal_with_file(fpath): if fpath in pfr['norm']: pass elif fpath in pfr['ghost']: pass elif fpath in pfr['not']: print >>sys.stderr, _('Not installed:'), fpath elif fpath in pfr['miss']: pass elif fpath in pfr['mod']: pkg = apkgs[pfr['mod'][fpath].pkgtup] # Hacky ... but works. sys.stdout.flush() extract_cmd = "cd %s; rpm2cpio %s | cpio --quiet -id .%s" extract_cmd = extract_cmd % (tmpdir, pkg.localPkg(), fpath) os.system(extract_cmd) diff_cmd = "diff -ru %s %s" % (tmpdir + fpath, fpath) print diff_cmd sys.stdout.flush() os.system(diff_cmd) else: print >>sys.stderr, _('Not packaged?:'), fpath if not distutils.spawn.find_executable("diff"): raise yum.Errors.YumBaseError, _("Can't find diff command") # These just shouldn't happen... if not distutils.spawn.find_executable("cpio"): raise yum.Errors.YumBaseError, _("Can't find cpio command") if not distutils.spawn.find_executable("rpm2cpio"): raise yum.Errors.YumBaseError, _("Can't find rpm2cpio command") prefix = "." if extcmds: prefix = extcmds[0] extcmds = extcmds[1:] pkgs = base.rpmdb.returnPackages(patterns=extcmds) verbose = base.verbose_logger.isEnabledFor(logginglevels.DEBUG_3) pfr = self._fs_pkg_walk(pkgs, prefix, verbose=verbose, modified=True) base.closeRpmDB() # C-c ftw. apkgs = {} downloadpkgs = [] for ipkg in set(pfr['mod'].values()): iyi = ipkg.yumdb_info if 'from_repo' in iyi: # Updates-testing etc. if iyi.from_repo in base.repos.repos: repo = base.repos.getRepo(iyi.from_repo) if not repo.isEnabled(): base.repos.enableRepo(repo.id) for apkg in base.pkgSack.searchPkgTuple(ipkg.pkgtup): if ('checksum_type' in iyi and 'checksum_data' in iyi and iyi.checksum_type == apkg.checksum_type and iyi.checksum_data == apkg.pkgId): apkgs[ipkg.pkgtup] = apkg downloadpkgs.append(apkg) break if ipkg.pkgtup not in apkgs: raise yum.Errors.YumBaseError, _("Can't find package: %s") %ipkg if downloadpkgs: tmpdir = tempfile.mkdtemp() problems = base.downloadPkgs(downloadpkgs, callback_total=base.download_callback_total_cb) if len(problems) > 0: errstring = '' errstring += _('Error downloading packages:\n') for key in problems: errors = yum.misc.unique(problems[key]) for error in errors: errstring += ' %s: %s\n' % (key, error) raise yum.Errors.YumBaseError, errstring for root, dirs, files in os.walk(prefix): for fname in files: fpath = os.path.normpath(root + '/' + fname) if os.path.islink(fpath): continue deal_with_file(fpath) if downloadpkgs: shutil.rmtree(tmpdir) def _fs_status(self, base, extcmds): def deal_with_file(fpath): if fpath in pfr['norm']: pass elif fpath in pfr['ghost']: pass elif fpath in pfr['not']: print _('Not installed:'), fpath elif fpath in pfr['miss']: pass elif fpath in pfr['mod']: print _('Modified:'), fpath else: print _('Not packaged?:'), fpath prefix = "." if extcmds: prefix = extcmds[0] extcmds = extcmds[1:] pkgs = base.rpmdb.returnPackages(patterns=extcmds) verbose = base.verbose_logger.isEnabledFor(logginglevels.DEBUG_3) pfr = self._fs_pkg_walk(pkgs, prefix, verbose=verbose, modified=True) base.closeRpmDB() # C-c ftw. for root, dirs, files in os.walk(prefix): for fname in files: fpath = os.path.normpath(root + '/' + fname) if os.path.islink(fpath): continue deal_with_file(fpath) def doCommand(self, base, basecmd, extcmds): """Execute this command. :param base: a :class:`yum.Yumbase` object :param basecmd: the name of the command :param extcmds: the command line arguments passed to *basecmd* :return: (exit_code, [ errors ]) exit_code is:: 0 = we're done, exit 1 = we've errored, exit with error string 2 = we've got work yet to do, onto the next stage """ if extcmds and extcmds[0] in ('filters', 'filter', 'refilter', 'refilter-cleanup', 'du', 'status', 'diff', 'snap'): subcommand = extcmds[0] extcmds = extcmds[1:] else: subcommand = 'filters' if False: pass elif subcommand == 'du': ret = self._fs_du(base, extcmds) elif subcommand in ('filter', 'filters'): ret = self._fs_filters(base, extcmds) elif subcommand == 'refilter': ret = self._fs_refilter(base, extcmds) elif subcommand == 'refilter-cleanup': ret = self._fs_refilter_cleanup(base, extcmds) elif subcommand == 'diff': ret = self._fs_diff(base, extcmds) elif subcommand == 'status': ret = self._fs_status(base, extcmds) elif subcommand == 'snap': ret = FSSnapshotCommand().doCommand(base, 'fs snap', args) else: return 1, [_('Not a valid sub-command of %s') % basecmd] if ret is not None: return ret return 0, [basecmd + ' ' + subcommand + ' done']