diff --git a/cli.py b/cli.py index 640f190..2267b86 100644 --- a/cli.py +++ b/cli.py @@ -45,6 +45,11 @@ import yumcommands from yum.i18n import to_unicode, to_utf8 +# This is for yum-utils/yumdownloader in RHEL-5, where it isn't importing this +# directly but did do "from cli import *", and we did have this in 3.2.22. I +# just _love_ how python re-exports these by default. +from yum.packages import parsePackages + def sigquit(signum, frame): """ SIGQUIT handler for the yum cli. """ print >> sys.stderr, "Quit signal sent - exiting immediately" @@ -73,6 +78,7 @@ class YumBaseCli(yum.YumBase, output.YumOutput): self.logger = logging.getLogger("yum.cli") self.verbose_logger = logging.getLogger("yum.verbose.cli") self.yum_cli_commands = {} + self.use_txmbr_in_callback = True self.registerCommand(yumcommands.InstallCommand()) self.registerCommand(yumcommands.UpdateCommand()) self.registerCommand(yumcommands.InfoCommand()) @@ -504,30 +510,35 @@ class YumBaseCli(yum.YumBase, output.YumOutput): if self.gpgsigcheck(downloadpkgs) != 0: return 1 - if self.conf.rpm_check_debug: - rcd_st = time.time() - self.verbose_logger.log(yum.logginglevels.INFO_2, - _('Running rpm_check_debug')) - msgs = self._run_rpm_check_debug() - if msgs: - rpmlib_only = True - for msg in msgs: - if msg.startswith('rpmlib('): - continue - rpmlib_only = False - if rpmlib_only: - print _("ERROR You need to update rpm to handle:") - else: - print _('ERROR with rpm_check_debug vs depsolve:') + self.initActionTs() + # save our dsCallback out + dscb = self.dsCallback + self.dsCallback = None # dumb, dumb dumb dumb! + self.populateTs(keepold=0) # sigh - for msg in msgs: - print to_utf8(msg) + rcd_st = time.time() + self.verbose_logger.log(yum.logginglevels.INFO_2, + _('Running Transaction Check')) + msgs = self._run_rpm_check() + if msgs: + rpmlib_only = True + for msg in msgs: + if msg.startswith('rpmlib('): + continue + rpmlib_only = False + if rpmlib_only: + print _("ERROR You need to update rpm to handle:") + else: + print _('ERROR with transaction check vs depsolve:') - if rpmlib_only: - return 1, [_('RPM needs to be updated')] - return 1, [_('Please report this error in %s') % self.conf.bugtracker_url] + for msg in msgs: + print to_utf8(msg) - self.verbose_logger.debug('rpm_check_debug time: %0.3f' % (time.time() - rcd_st)) + if rpmlib_only: + return 1, [_('RPM needs to be updated')] + return 1, [_('Please report this error in %s') % self.conf.bugtracker_url] + + self.verbose_logger.debug('Transaction Check time: %0.3f' % (time.time() - rcd_st)) tt_st = time.time() self.verbose_logger.log(yum.logginglevels.INFO_2, @@ -535,14 +546,10 @@ class YumBaseCli(yum.YumBase, output.YumOutput): if not self.conf.diskspacecheck: self.tsInfo.probFilterFlags.append(rpm.RPMPROB_FILTER_DISKSPACE) + self.ts.order() # order the transaction + self.ts.clean() # release memory not needed beyond this point testcb = RPMTransaction(self, test=True) - - self.initActionTs() - # save our dsCallback out - dscb = self.dsCallback - self.dsCallback = None # dumb, dumb dumb dumb! - self.populateTs(keepold=0) # sigh tserrors = self.ts.test(testcb) del testcb @@ -555,7 +562,6 @@ class YumBaseCli(yum.YumBase, output.YumOutput): self.errorSummary(errstring) self.verbose_logger.log(yum.logginglevels.INFO_2, _('Transaction Test Succeeded')) - del self.ts self.verbose_logger.debug('Transaction Test time: %0.3f' % (time.time() - tt_st)) @@ -563,10 +569,6 @@ class YumBaseCli(yum.YumBase, output.YumOutput): signal.signal(signal.SIGQUIT, signal.SIG_DFL) ts_st = time.time() - self.initActionTs() # make a new, blank ts to populate - self.populateTs(keepold=0) # populate the ts - self.ts.check() #required for ordering - self.ts.order() # order # put back our depcheck callback self.dsCallback = dscb @@ -629,7 +631,7 @@ class YumBaseCli(yum.YumBase, output.YumOutput): ", ".join(matches)) self.verbose_logger.log(yum.logginglevels.INFO_2, to_unicode(msg)) - def _checkMaybeYouMeant(self, arg, always_output=True): + def _checkMaybeYouMeant(self, arg, always_output=True, rpmdb_only=False): """ If the update/remove argument doesn't match with case, or due to not being installed, tell the user. """ # always_output is a wart due to update/remove not producing the @@ -638,7 +640,12 @@ class YumBaseCli(yum.YumBase, output.YumOutput): # skip it. if not arg or arg[0] == '@': return - matches = self.doPackageLists(patterns=[arg], ignore_case=False) + + pkgnarrow='all' + if rpmdb_only: + pkgnarrow='installed' + + matches = self.doPackageLists(pkgnarrow=pkgnarrow, patterns=[arg], ignore_case=False) if (matches.installed or (not matches.available and self.returnInstalledPackagesByDep(arg))): return # Found a match so ignore @@ -651,7 +658,7 @@ class YumBaseCli(yum.YumBase, output.YumOutput): return # No package name, so do the maybeYouMeant thing here too - matches = self.doPackageLists(patterns=[arg], ignore_case=True) + matches = self.doPackageLists(pkgnarrow=pkgnarrow, patterns=[arg], ignore_case=True) if not matches.installed and matches.available: self.verbose_logger.log(yum.logginglevels.INFO_2, _('Package(s) %s%s%s available, but not installed.'), @@ -822,7 +829,7 @@ class YumBaseCli(yum.YumBase, output.YumOutput): for arg in userlist: rms = self.remove(pattern=arg) if not rms: - self._checkMaybeYouMeant(arg, always_output=False) + self._checkMaybeYouMeant(arg, always_output=False, rpmdb_only=True) all_rms.extend(rms) if all_rms: @@ -1063,13 +1070,16 @@ class YumBaseCli(yum.YumBase, output.YumOutput): os.path.exists(arg))): thispkg = yum.packages.YumUrlPackage(self, self.ts, arg) pkgs.append(thispkg) + elif self.conf.showdupesfromrepos: + pkgs.extend(self.pkgSack.returnPackages(patterns=[arg])) else: - ematch, match, unmatch = self.pkgSack.matchPackageNames([arg]) - for po in ematch + match: - pkgs.append(po) + try: + pkgs.extend(self.pkgSack.returnNewestByName(patterns=[arg])) + except yum.Errors.PackageSackError: + pass - results = self.findDeps(pkgs) - self.depListOutput(results) + results = self.findDeps(pkgs) + self.depListOutput(results) return 0, [] diff --git a/docs/yum.8 b/docs/yum.8 index 52f6b53..360a976 100644 --- a/docs/yum.8 +++ b/docs/yum.8 @@ -73,7 +73,7 @@ gnome\-packagekit application\&. .br .I \fR * version [ all | installed | available | group-* | nogroups* | grouplist | groupinfo ] .br -.I \fR * history [info|list|summary|redo|undo|new|addon-info] +.I \fR * history [info|list|packages-list|summary|redo|undo|new|addon-info] .br .I \fR * check .br @@ -253,7 +253,10 @@ on groups, files, provides, filelists and rpm files just like the "install" comm .IP .IP "\fBdeplist\fP" Produces a list of all dependencies and what packages provide those -dependencies for the given packages. +dependencies for the given packages. As of 3.2.30 it now just shows the latest +version of each package that matches (this can be changed by +using --showduplicates) and it only shows the newest providers (which can be +changed by using --verbose). .IP .IP "\fBrepolist\fP" Produces a list of configured repositories. The default is to list all @@ -316,8 +319,12 @@ The undo/redo commands take either a transaction id or the keyword last and an offset from the last transaction (Eg. if you've done 250 transactions, "last" refers to transaction 250, and "last-4" refers to transaction 246). +The addon-info command takes a transaction ID, and the packages-list command +takes a package (with wildcards). + In "history list" output the Altered column also gives some extra information -if there was something not good with the transaction. +if there was something not good with the transaction (this is also shown at the +end of the package column in the packages-list command). .I \fB>\fR - The rpmdb was changed, outside yum, after the transaction. .br @@ -402,7 +409,11 @@ Doesn't limit packages to their latest versions in the info, list and search commands (will also affect plugins which use the doPackageLists() API). .IP "\fB\-\-installroot=root\fP" Specifies an alternative installroot, relative to which all packages will be -installed. +installed. Think of this like doing "chroot yum" except using +\-\-installroot allows yum to work before the chroot is created. +Note: You may also want to use the option \-\-releasever=/ when creating the +installroot as otherwise the $releasever value is taken from the rpmdb within +the installroot (and thus. will be empty, before creation). .br Configuration Option: \fBinstallroot\fP .IP "\fB\-\-enablerepo=repoidglob\fP" @@ -458,9 +469,11 @@ Configuration Option: \fBskip_broken\fP .br .IP "\fB\-\-releasever=version\fP" Pretend the current release version is the given string. This is very useful -when combined with \-\-installroot. Note that with the default upstream cachedir, -of /var/cache/yum, using this option will corrupt your cache (and you can use -$releasever in your cachedir configuration to stop this). +when combined with \-\-installroot. You can also use \-\-releasever=/ to take +the releasever information from outside the installroot. +Note that with the default upstream cachedir, of /var/cache/yum, using this +option will corrupt your cache (and you can use $releasever in your cachedir +configuration to stop this). .PP .IP "\fB\-t, \-\-tolerant\fP" This option currently does nothing. diff --git a/docs/yum.conf.5 b/docs/yum.conf.5 index e1c3480..a535b79 100644 --- a/docs/yum.conf.5 +++ b/docs/yum.conf.5 @@ -474,6 +474,8 @@ bugtrackers. \fBcolor \fR Display colorized output automatically, depending on the output terminal, always (using ANSI codes) or never. +Default is `auto'. +Possible values are: auto, never, always. Command-line option: \fB\-\-color\fP .IP diff --git a/etc/yum.bash b/etc/yum.bash index f4be628..1ccb83d 100644 --- a/etc/yum.bash +++ b/etc/yum.bash @@ -176,9 +176,13 @@ _yum() { COMPREPLY=() local yum=$1 - local cur - type _get_cword &>/dev/null && cur=`_get_cword` || cur=$2 - local prev=$3 + local cur prev + local -a words + if type _get_comp_words_by_ref &>/dev/null ; then + _get_comp_words_by_ref cur prev words + else + cur=$2 prev=$3 words=("${COMP_WORDS[@]}") + fi # Commands offered as completions local cmds=( check check-update clean deplist distro-sync downgrade groupinfo groupinstall grouplist groupremove help history info install @@ -186,12 +190,12 @@ _yum() shell update upgrade version ) local i c cmd subcmd - for (( i=1; i < ${#COMP_WORDS[@]}-1; i++ )) ; do - [[ -n $cmd ]] && subcmd=${COMP_WORDS[i]} && break + for (( i=1; i < ${#words[@]}-1; i++ )) ; do + [[ -n $cmd ]] && subcmd=${words[i]} && break # Recognize additional commands and aliases for c in ${cmds[@]} check-rpmdb distribution-synchronization erase \ groupupdate grouperase localinstall localupdate whatprovides ; do - [[ ${COMP_WORDS[i]} == $c ]] && cmd=$c && break + [[ ${words[i]} == $c ]] && cmd=$c && break done done @@ -251,7 +255,7 @@ _yum() COMPREPLY=( $( compgen -W 'info list summary undo redo new addon-info package-list' -- "$cur" ) ) ;; - undo|redo|addon|addon-info) + undo|redo|repeat|addon|addon-info) COMPREPLY=( $( compgen -W "last $( $yum -d 0 -C history \ 2>/dev/null | \ sed -ne 's/^[[:space:]]*\([0-9]\{1,\}\).*/\1/p' )" \ diff --git a/output.py b/output.py index b1d92e5..85b21f8 100755 --- a/output.py +++ b/output.py @@ -809,20 +809,26 @@ class YumOutput: def depListOutput(self, results): """take a list of findDeps results and 'pretty print' the output""" - for pkg in results: + verb = self.verbose_logger.isEnabledFor(logginglevels.DEBUG_3) + for pkg in sorted(results): print _("package: %s") % pkg.compactPrint() if len(results[pkg]) == 0: print _(" No dependencies for this package") continue - for req in results[pkg]: + for req in sorted(results[pkg]): reqlist = results[pkg][req] print _(" dependency: %s") % prco_tuple_to_string(req) if not reqlist: print _(" Unsatisfied dependency") continue - for po in reqlist: + seen = {} + for po in reversed(sorted(reqlist)): + key = (po.name, po.arch) + if not verb and key in seen: + continue + seen[key] = po print " provider: %s" % po.compactPrint() def format_number(self, number, SI=0, space=' '): @@ -1619,6 +1625,18 @@ to exit. self._historyInfoCmd(mobj) + def _hpkg2from_repo(self, hpkg): + """ Given a pkg, find the ipkg.ui_from_repo ... if none, then + get an apkg. ... and put a ? in there. """ + ipkgs = self.rpmdb.searchPkgTuple(hpkg.pkgtup) + if not ipkgs: + apkgs = self.pkgSack.searchPkgTuple(hpkg.pkgtup) + if not apkgs: + return '?' + return '@?' + str(apkgs[0].repoid) + + return ipkgs[0].ui_from_repo + def _historyInfoCmd(self, old, pats=[]): name = self._pwd_ui_username(old.loginuid) @@ -1631,7 +1649,8 @@ to exit. _pkg_states_available.values())])[-1] _pkg_states_installed['maxlen'] = maxlen _pkg_states_available['maxlen'] = maxlen - def _simple_pkg(pkg, prefix_len, was_installed=False, highlight=False): + def _simple_pkg(pkg, prefix_len, was_installed=False, highlight=False, + pkg_max_len=0): prefix = " " * prefix_len if was_installed: _pkg_states = _pkg_states_installed @@ -1655,7 +1674,9 @@ to exit. else: (hibeg, hiend) = self._highlight('normal') state = utf8_width_fill(state, _pkg_states['maxlen']) - print "%s%s%s%s %s" % (prefix, hibeg, state, hiend, hpkg) + print "%s%s%s%s %-*s %s" % (prefix, hibeg, state, hiend, + pkg_max_len, hpkg, + self._hpkg2from_repo(hpkg)) if type(old.tid) == type([]): print _("Transaction ID :"), "%u..%u" % (old.tid[0], old.tid[-1]) @@ -1726,30 +1747,37 @@ to exit. addon_info = self.history.return_addon_data(old.tid) # for the ones we create by default - don't display them as there - default_addons = set(['config-main', 'config-repos']) + default_addons = set(['config-main', 'config-repos', 'saved_tx']) non_default = set(addon_info).difference(default_addons) if len(non_default) > 0: print _("Additional non-default information stored: %d" % len(non_default)) - print _("Transaction performed with:") + if old.trans_with: + # This is _possible_, but not common + print _("Transaction performed with:") + pkg_max_len = max((len(str(hpkg)) for hpkg in old.trans_with)) for hpkg in old.trans_with: - _simple_pkg(hpkg, 4, was_installed=True) + _simple_pkg(hpkg, 4, was_installed=True, pkg_max_len=pkg_max_len) print _("Packages Altered:") self.historyInfoCmdPkgsAltered(old, pats) if old.trans_skip: print _("Packages Skipped:") + pkg_max_len = max((len(str(hpkg)) for hpkg in old.trans_skip)) for hpkg in old.trans_skip: - _simple_pkg(hpkg, 4) + _simple_pkg(hpkg, 4, pkg_max_len=pkg_max_len) if old.rpmdb_problems: print _("Rpmdb Problems:") for prob in old.rpmdb_problems: key = "%s%s: " % (" " * 4, prob.problem) print self.fmtKeyValFill(key, prob.text) + if prob.packages: + pkg_max_len = max((len(str(hpkg)) for hpkg in prob.packages)) for hpkg in prob.packages: - _simple_pkg(hpkg, 8, was_installed=True, highlight=hpkg.main) + _simple_pkg(hpkg, 8, was_installed=True, highlight=hpkg.main, + pkg_max_len=pkg_max_len) if old.output: print _("Scriptlet output:") @@ -1783,10 +1811,13 @@ to exit. # version in the transaction and now. all_uistates = self._history_state2uistate maxlen = 0 + pkg_max_len = 0 for hpkg in old.trans_data: uistate = all_uistates.get(hpkg.state, hpkg.state) if maxlen < len(uistate): maxlen = len(uistate) + if pkg_max_len < len(str(hpkg)): + pkg_max_len = len(str(hpkg)) for hpkg in old.trans_data: prefix = " " * 4 @@ -1813,18 +1844,18 @@ to exit. hpkg.state == 'Update'): ln = len(hpkg.name) + 1 cn = (" " * ln) + cn[ln:] - print "%s%s%s%s %s" % (prefix, hibeg, uistate, hiend, cn) elif (last is not None and last.state == 'Downgrade' and last.name == hpkg.name and hpkg.state == 'Downgraded'): ln = len(hpkg.name) + 1 cn = (" " * ln) + cn[ln:] - print "%s%s%s%s %s" % (prefix, hibeg, uistate, hiend, cn) else: last = None if hpkg.state in ('Updated', 'Downgrade'): last = hpkg - print "%s%s%s%s %s" % (prefix, hibeg, uistate, hiend, cn) + print "%s%s%s%s %-*s %s" % (prefix, hibeg, uistate, hiend, + pkg_max_len, cn, + self._hpkg2from_repo(hpkg)) def historySummaryCmd(self, extcmds): tids, printall = self._history_list_transactions(extcmds) @@ -1936,6 +1967,9 @@ to exit. of a package(s) instead of via. transactions. """ tids = self.history.search(extcmds) limit = None + if extcmds and not tids: + self.logger.critical(_('Bad transaction IDs, or package(s), given')) + return 1, ['Failed history packages-list'] if not tids: limit = 20 diff --git a/rpmUtils/transaction.py b/rpmUtils/transaction.py index e8f4459..121ad5b 100644 --- a/rpmUtils/transaction.py +++ b/rpmUtils/transaction.py @@ -22,18 +22,13 @@ ts = None class TransactionWrapper: def __init__(self, root='/'): self.ts = rpm.TransactionSet(root) - self._methods = ['dbMatch', - 'check', + self._methods = ['check', 'order', 'addErase', 'addInstall', 'run', - 'IDTXload', - 'IDTXglob', - 'rollback', 'pgpImportPubkey', 'pgpPrtPkts', - 'Debug', 'problems', 'setFlags', 'setVSFlags', @@ -54,6 +49,17 @@ class TransactionWrapper: self.ts = None self.open = False + def dbMatch(self, *args, **kwds): + if 'patterns' in kwds: + patterns = kwds.pop('patterns') + else: + patterns = [] + + mi = self.ts.dbMatch(*args, **kwds) + for (tag, tp, pat) in patterns: + mi.pattern(tag, tp, pat) + return mi + def __getattr__(self, attr): if attr in self._methods: return self.getMethod(attr) @@ -91,6 +97,9 @@ class TransactionWrapper: def isTsFlagSet(self, flag): val = self.getTsFlags() return bool(flag & val) + + def setScriptFd(self, fd): + self.ts.scriptFd = fd.fileno() # def addProblemFilter(self, filt): # curfilter = self.ts.setProbFilter(0) @@ -100,12 +109,14 @@ class TransactionWrapper: """tests the ts we've setup, takes a callback function and a conf dict for flags and what not""" + origflags = self.getTsFlags() self.addTsFlag(rpm.RPMTRANS_FLAG_TEST) # FIXME GARBAGE - remove once this is reimplemented elsehwere # KEEPING FOR API COMPLIANCE ONLY if conf.get('diskspacecheck') == 0: self.ts.setProbFilter(rpm.RPMPROB_FILTER_DISKSPACE) tserrors = self.ts.run(cb.callback, '') + self.ts.setFlags(origflags) reserrors = [] if tserrors: diff --git a/test/skipbroken-tests.py b/test/skipbroken-tests.py index 4e6b2c8..36a4a6d 100644 --- a/test/skipbroken-tests.py +++ b/test/skipbroken-tests.py @@ -1,8 +1,11 @@ import unittest import logging import sys +import re from testbase import * +REGEX_PKG = re.compile(r"(\d*):?(.*)-(.*)-(.*)\.(.*)$") + class SkipBrokenTests(DepsolveTests): ''' Test cases to test skip-broken''' @@ -20,6 +23,36 @@ class SkipBrokenTests(DepsolveTests): po = FakePackage(name, version, release, epoch, arch, repo=self.repo) self.rpmdb.addPackage(po) return po + + def _pkgstr_to_nevra(self, pkg_str): + ''' + Get a nevra from from a epoch:name-version-release.arch string + @param pkg_str: package string + ''' + res = REGEX_PKG.search(pkg_str) + if res: + (e,n,v,r,a) = res.groups() + if e == "": + e = "0" + return (n,e,v,r,a) + else: + raise AttributeError("Illegal package string : %s" % pkg_str) + + def repoString(self, pkg_str): + ''' + Add an available package from a epoch:name-version-release.arch string + ''' + (n,e,v,r,a) = self._pkgstr_to_nevra(pkg_str) + return self.repoPackage(n,v,r,e,a) + + + def instString(self, pkg_str): + ''' + Add an installed package from a epoch:name-version-release.arch string + ''' + (n,e,v,r,a) = self._pkgstr_to_nevra(pkg_str) + return self.instPackage(n,v,r,e,a) + def testMissingReqNoSkip(self): ''' install fails, because of missing req. @@ -669,8 +702,37 @@ class SkipBrokenTests(DepsolveTests): self.tsInfo.addUpdate(u7, oldpo=i7) self.assertEquals('ok', *self.resolveCode(skip=True)) # uncomment this line and the test will fail and you can see the output - self.assertResult([i1]) + # self.assertResult([i1]) + def test_conflict_looping(self): + ''' + Skip-broken is looping + https://bugzilla.redhat.com/show_bug.cgi?id=681806 + ''' + members = [] # the result after the transaction + # Installed package conflicts with u1 + i0 = self.instString('kde-l10n-4.6.0-3.fc15.1.noarch') + i0.addConflicts('kdepim', 'GT', ('6', '4.5.9', '0')) + members.append(i0) + i1 = self.instString('6:kdepim-4.5.94.1-1.fc14.x86_64') + u1 = self.repoString('7:kdepim-4.4.10-1.fc15.x86_64') + self.tsInfo.addUpdate(u1, oldpo=i1) + # u1 should be removed, because of the conflict + members.append(i1) + i2 = self.instString('6:kdepim-libs-4.5.94.1-1.fc14.x86_64') + u2 = self.repoString('7:kdepim-libs-4.4.10-1.fc15.x86_64') + self.tsInfo.addUpdate(u2, oldpo=i2) + members.append(u2) + i3 = self.instString('kdepim-runtime-libs-4.5.94.1-2.fc14.x86_64') + u3 = self.repoString('1:kdepim-runtime-libs-4.4.10-2.fc15.x86_64') + self.tsInfo.addUpdate(u3, oldpo=i3) + members.append(u3) + i4 = self.instString('kdepim-runtime-4.5.94.1-2.fc14.x86_64') + u4 = self.repoString('1:kdepim-runtime-4.4.10-2.fc15.x86_64') + self.tsInfo.addUpdate(u4, oldpo=i4) + members.append(u4) + self.assertEquals('ok', *self.resolveCode(skip=True)) + self.assertResult(members) def resolveCode(self,skip = False): diff --git a/yum.spec b/yum.spec index a1fbc72..65a2397 100644 --- a/yum.spec +++ b/yum.spec @@ -194,8 +194,8 @@ exit 0 %defattr(-,root,root) %doc COPYING %{_sysconfdir}/cron.daily/0yum.cron -%{_sysconfdir}/yum/yum-daily.yum -%{_sysconfdir}/yum/yum-weekly.yum +%config(noreplace) %{_sysconfdir}/yum/yum-daily.yum +%config(noreplace) %{_sysconfdir}/yum/yum-weekly.yum %{_sysconfdir}/rc.d/init.d/yum-cron %config(noreplace) %{_sysconfdir}/sysconfig/yum-cron diff --git a/yum/__init__.py b/yum/__init__.py index f6e8a6b..36fc203 100644 --- a/yum/__init__.py +++ b/yum/__init__.py @@ -349,7 +349,10 @@ class YumBase(depsolve.Depsolve): # who are we: self.conf.uid = os.geteuid() - + # repos are ver/arch specific so add $basearch/$releasever + self.conf._repos_persistdir = os.path.normpath('%s/repos/%s/%s/' + % (self.conf.persistdir, self.yumvar.get('basearch', '$basearch'), + self.yumvar.get('releasever', '$releasever'))) self.doFileLogSetup(self.conf.uid, self.conf.logfile) self.verbose_logger.debug('Config time: %0.3f' % (time.time() - conf_st)) self.plugins.run('init') @@ -418,10 +421,7 @@ class YumBase(depsolve.Depsolve): else: thisrepo.repo_config_age = repo_age thisrepo.repofile = repofn - # repos are ver/arch specific so add $basearch/$releasever - self.conf._repos_persistdir = os.path.normpath('%s/repos/%s/%s/' - % (self.conf.persistdir, self.yumvar.get('basearch', '$basearch'), - self.yumvar.get('releasever', '$releasever'))) + thisrepo.base_persistdir = self.conf._repos_persistdir @@ -574,6 +574,11 @@ class YumBase(depsolve.Depsolve): self.getReposFromConfig() + # For rhnplugin, and in theory other stuff, calling + # .getReposFromConfig() recurses back into this function but only once. + # This means that we have two points on the stack leaving the above call + # but only one of them can do the repos setup. BZ 678043. + if hasattr(self, 'prerepoconf'): # Recursion prerepoconf = self.prerepoconf del self.prerepoconf @@ -1024,6 +1029,11 @@ class YumBase(depsolve.Depsolve): for txmbr in txmbrs: if kern_pkgtup is not None and txmbr.pkgtup == kern_pkgtup: pass + elif kern_pkgtup is not None and txmbr.name == kern_pkgtup[0]: + # We don't care if they've explicitly set protected on the + # kernel package. Because we don't allow you to uninstall the + # running one so it has _special_ semantics anyway. + continue elif txmbr.name not in protected: continue if txmbr.name not in bad_togo: @@ -1435,12 +1445,17 @@ class YumBase(depsolve.Depsolve): # will be we store what we thought, not what happened (so it'll be an # invalid cache). self.rpmdb.transactionResultVersion(frpmdbv) - # transaction has started - all bets are off on our saved ts file - try: - os.unlink(self._ts_save_file) - except (IOError, OSError), e: - pass + if self._ts_save_file is not None: + # write the saved transaction data to the addon location in history + # so we can pull it back later if we need to + savetx_msg = open(self._ts_save_file, 'r').read() + self.history.write_addon_data('saved_tx', savetx_msg) + + try: + os.unlink(self._ts_save_file) + except (IOError, OSError), e: + pass self._ts_save_file = None errors = self.ts.run(cb.callback, '') @@ -1485,7 +1500,12 @@ class YumBase(depsolve.Depsolve): # drop out the rpm cache so we don't step on bad hdr indexes - self.rpmdb.dropCachedDataPostTransaction(list(self.tsInfo)) + if (self.ts.isTsFlagSet(rpm.RPMTRANS_FLAG_TEST) or + resultobject.return_code): + self.rpmdb.dropCachedData() + else: + self.rpmdb.dropCachedDataPostTransaction(list(self.tsInfo)) + self.plugins.run('posttrans') # sync up what just happened versus what is in the rpmdb if not self.ts.isTsFlagSet(rpm.RPMTRANS_FLAG_TEST): @@ -1674,8 +1694,11 @@ class YumBase(depsolve.Depsolve): def doLock(self, lockfile = YUM_PID_FILE): """perform the yum locking, raise yum-based exceptions, not OSErrors""" - # if we're not root then lock the cache if self.conf.uid != 0: + # If we are a user, assume we are using the root cache ... so don't + # bother locking. + if self.conf.cache: + return root = self.conf.cachedir # Don't want /var/run/yum.pid ... just: /yum.pid lockfile = os.path.basename(lockfile) @@ -1690,7 +1713,7 @@ class YumBase(depsolve.Depsolve): fd = open(lockfile, 'r') except (IOError, OSError), e: msg = _("Could not open lock %s: %s") % (lockfile, e) - raise Errors.LockError(1, msg) + raise Errors.LockError(errno.EPERM, msg) try: oldpid = int(fd.readline()) except ValueError: @@ -1707,7 +1730,7 @@ class YumBase(depsolve.Depsolve): else: # Whoa. What the heck happened? msg = _('Unable to check if PID %s is active') % oldpid - raise Errors.LockError(1, msg, oldpid) + raise Errors.LockError(errno.EPERM, msg, oldpid) else: # Another copy seems to be running. msg = _('Existing lock %s: another copy is running as pid %s.') % (lockfile, oldpid) @@ -1752,7 +1775,7 @@ class YumBase(depsolve.Depsolve): if not msg.errno == errno.EEXIST: # Whoa. What the heck happened? errmsg = _('Could not create lock at %s: %s ') % (filename, str(msg)) - raise Errors.LockError(msg.errno, errmsg, contents) + raise Errors.LockError(msg.errno, errmsg, int(contents)) return 0 else: os.write(fd, contents) @@ -3417,7 +3440,6 @@ class YumBase(depsolve.Depsolve): pkgs = po.obsoletedBy(pkgs, limit=1) if pkgs: already_obs = pkgs[0] - continue if already_obs: self.verbose_logger.warning(_('Package %s is obsoleted by %s which is already installed'), @@ -3934,11 +3956,17 @@ class YumBase(depsolve.Depsolve): if (po.arch != installed_pkg.arch and (isMultiLibArch(po.arch) or isMultiLibArch(installed_pkg.arch))): - installpkgs.append(po) + if updateonly: + self.logger.warning(_('Package %s.%s not installed, cannot update it. Run yum install to install it instead.'), po.name, po.arch) + else: + installpkgs.append(po) else: donothingpkgs.append(po) elif self.allowedMultipleInstalls(po): - installpkgs.append(po) + if updateonly: + self.logger.warning(_('Package %s.%s not installed, cannot update it. Run yum install to install it instead.'), po.name, po.arch) + else: + installpkgs.append(po) else: donothingpkgs.append(po) @@ -4462,17 +4490,20 @@ class YumBase(depsolve.Depsolve): be imported using askcb. @param po: Package object to retrieve the key of. - @param askcb: Callback function to use for asking for verification. + @param askcb: Callback function to use for asking for permission to + import a key. This is verification, but also "choice". Takes arguments of the po, the userid for the key, and the keyid. - @param fullaskcb: Callback function to use for asking for verification - of a key. Differs from askcb in that it gets passed - a dictionary so that we can expand the values passed. + @param fullaskcb: Callback function to use for asking for permission to + import a key. This is verification, but also "choice". + Differs from askcb in that it gets passed a + dictionary so that we can expand the values passed. """ repo = self.repos.getRepo(po.repoid) keyurls = repo.gpgkey key_installed = False + user_cb_fail = False for keyurl in keyurls: keys = self._retrievePublicKey(keyurl, repo) @@ -4509,7 +4540,8 @@ class YumBase(depsolve.Depsolve): rc = askcb(po, info['userid'], info['hexkeyid']) if not rc: - raise Errors.YumBaseError, _("Not installing key") + user_cb_fail = True + continue # Import the key ts = self.rpmdb.readOnlyTS() @@ -4520,6 +4552,9 @@ class YumBase(depsolve.Depsolve): self.logger.info(_('Key imported successfully')) key_installed = True + if not key_installed and user_cb_fail: + raise Errors.YumBaseError, _("Didn't install any keys") + if not key_installed: raise Errors.YumBaseError, \ _('The GPG keys listed for the "%s" repository are ' \ @@ -4543,11 +4578,13 @@ class YumBase(depsolve.Depsolve): @param destdir: destination of the gpg pub ring @param keyurl_list: list of urls for gpg keys @param is_cakey: bool - are we pulling in a ca key or not - @param callback: Callback function to use for asking for verification - of a key. Takes a dictionary of key info. + @param callback: Callback function to use for asking for permission to + import a key. This is verification, but also "choice". + Takes a dictionary of key info. """ key_installed = False + user_cb_fail = False for keyurl in keyurl_list: keys = self._retrievePublicKey(keyurl, repo, getSig=not is_cakey) for info in keys: @@ -4557,16 +4594,25 @@ class YumBase(depsolve.Depsolve): keyurl, info['hexkeyid'])) key_installed = True continue - # Try installing/updating GPG key if is_cakey: + # know where the 'imported_cakeys' file is + ikf = repo.base_persistdir + '/imported_cakeys' keytype = 'CA' + cakeys = [] + try: + cakeys_d = open(ikf, 'r').read() + cakeys = cakeys_d.split('\n') + except (IOError, OSError): + pass + if str(info['hexkeyid']) in cakeys: + key_installed = True else: keytype = 'GPG' - - if repo.gpgcakey and info['has_sig'] and info['valid_sig']: - key_installed = True - else: + if repo.gpgcakey and info['has_sig'] and info['valid_sig']: + key_installed = True + + if not key_installed: self._getKeyImportMessage(info, keyurl, keytype) rc = False if self.conf.assumeyes: @@ -4579,7 +4625,8 @@ class YumBase(depsolve.Depsolve): if not rc: - raise Errors.YumBaseError, _("Not installing key for repo %s") % repo + user_cb_fail = True + continue # Import the key result = misc.import_key_to_pubring(info['raw_key'], info['hexkeyid'], gpgdir=destdir) @@ -4587,6 +4634,20 @@ class YumBase(depsolve.Depsolve): raise Errors.YumBaseError, _('Key import failed') self.logger.info(_('Key imported successfully')) key_installed = True + # write out the key id to imported_cakeys in the repos basedir + if is_cakey and key_installed: + if info['hexkeyid'] not in cakeys: + ikfo = open(ikf, 'a') + try: + ikfo.write(info['hexkeyid']+'\n') + ikfo.flush() + ikfo.close() + except (IOError, OSError): + # maybe a warning - but in general this is not-critical, just annoying to the user + pass + + if not key_installed and user_cb_fail: + raise Errors.YumBaseError, _("Didn't install any keys for repo %s") % repo if not key_installed: raise Errors.YumBaseError, \ @@ -4775,26 +4836,31 @@ class YumBase(depsolve.Depsolve): def _doTestTransaction(self,callback,display=None): ''' Do the RPM test transaction ''' + self.initActionTs() + # save our dsCallback out + dscb = self.dsCallback + self.dsCallback = None # dumb, dumb dumb dumb! + self.populateTs( keepold=0 ) # sigh + # This can be overloaded by a subclass. - if self.conf.rpm_check_debug: - self.verbose_logger.log(logginglevels.INFO_2, - _('Running rpm_check_debug')) - msgs = self._run_rpm_check_debug() - if msgs: - rpmlib_only = True - for msg in msgs: - if msg.startswith('rpmlib('): - continue - rpmlib_only = False - if rpmlib_only: - retmsgs = [_("ERROR You need to update rpm to handle:")] - retmsgs.extend(msgs) - raise Errors.YumRPMCheckError, retmsgs - retmsgs = [_('ERROR with rpm_check_debug vs depsolve:')] - retmsgs.extend(msgs) - retmsgs.append(_('Please report this error at %s') - % self.conf.bugtracker_url) - raise Errors.YumRPMCheckError,retmsgs + self.verbose_logger.log(logginglevels.INFO_2, + _('Running Transaction Check')) + msgs = self._run_rpm_check() + if msgs: + rpmlib_only = True + for msg in msgs: + if msg.startswith('rpmlib('): + continue + rpmlib_only = False + if rpmlib_only: + retmsgs = [_("ERROR You need to update rpm to handle:")] + retmsgs.extend(msgs) + raise Errors.YumRPMCheckError, retmsgs + retmsgs = [_('ERROR with transaction check vs depsolve:')] + retmsgs.extend(msgs) + retmsgs.append(_('Please report this error at %s') + % self.conf.bugtracker_url) + raise Errors.YumRPMCheckError,retmsgs tsConf = {} for feature in ['diskspacecheck']: # more to come, I'm sure @@ -4804,14 +4870,7 @@ class YumBase(depsolve.Depsolve): # overwrite the default display class if display: testcb.display = display - # clean out the ts b/c we have to give it new paths to the rpms - del self.ts - self.initActionTs() - # save our dsCallback out - dscb = self.dsCallback - self.dsCallback = None # dumb, dumb dumb dumb! - self.populateTs( keepold=0 ) # sigh tserrors = self.ts.test( testcb, conf=tsConf ) del testcb @@ -4839,12 +4898,8 @@ class YumBase(depsolve.Depsolve): cb.display = display self.runTransaction( cb=cb ) - def _run_rpm_check_debug(self): + def _run_rpm_check(self): results = [] - # save our dsCallback out - dscb = self.dsCallback - self.dsCallback = None # dumb, dumb dumb dumb! - self.populateTs(test=1) self.ts.check() for prob in self.ts.problems(): # Newer rpm (4.8.0+) has problem objects, older have just strings. @@ -4852,7 +4907,6 @@ class YumBase(depsolve.Depsolve): # now just be compatible. results.append(to_str(prob)) - self.dsCallback = dscb return results def add_enable_repo(self, repoid, baseurls=[], mirrorlist=None, **kwargs): diff --git a/yum/config.py b/yum/config.py index 97e5e3d..8c966f8 100644 --- a/yum/config.py +++ b/yum/config.py @@ -691,6 +691,7 @@ class YumConf(StartupConf): metadata_expire = SecondsOption(60 * 60 * 6) # Time in seconds (6h). # Time in seconds (1 day). NOTE: This isn't used when using metalinks mirrorlist_expire = SecondsOption(60 * 60 * 24) + # XXX rpm_check_debug is unused, left around for API compatibility for now rpm_check_debug = BoolOption(True) disable_excludes = ListOption() skip_broken = BoolOption(False) @@ -1050,10 +1051,24 @@ def writeRawRepoFile(repo,only=None): # Updated the ConfigParser with the changed values cfgOptions = repo.cfg.options(repo.id) for name,value in repo.iteritems(): + if value is None: # Proxy + continue + + if only is not None and name not in only: + continue + option = repo.optionobj(name) - if option.default != value or name in cfgOptions : - if only == None or name in only: - ini[section_id][name] = option.tostring(value) + ovalue = option.tostring(value) + # If the value is the same, but just interpreted ... when we don't want + # to keep the interpreted values. + if (name in ini[section_id] and + ovalue == varReplace(ini[section_id][name], yumvar)): + ovalue = ini[section_id][name] + + if name not in cfgOptions and option.default == value: + continue + + ini[section_id][name] = ovalue fp =file(repo.repofile,"w") fp.write(str(ini)) fp.close() diff --git a/yum/depsolve.py b/yum/depsolve.py index de2849a..388811d 100644 --- a/yum/depsolve.py +++ b/yum/depsolve.py @@ -69,6 +69,8 @@ class Depsolve(object): self._ts = None self._tsInfo = None self.dsCallback = None + # Callback-style switch, default to legacy (hdr, file) mode + self.use_txmbr_in_callback = False self.logger = logging.getLogger("yum.Depsolve") self.verbose_logger = logging.getLogger("yum.verbose.Depsolve") @@ -220,8 +222,13 @@ class Depsolve(object): txmbr.ts_state = 'i' txmbr.output_state = TS_INSTALL + # New-style callback with just txmbr instead of full headers? + if self.use_txmbr_in_callback: + cbkey = txmbr + else: + cbkey = (hdr, rpmfile) - self.ts.addInstall(hdr, (hdr, rpmfile), txmbr.ts_state) + self.ts.addInstall(hdr, cbkey, txmbr.ts_state) self.verbose_logger.log(logginglevels.DEBUG_1, _('Adding Package %s in mode %s'), txmbr.po, txmbr.ts_state) if self.dsCallback: @@ -673,11 +680,12 @@ class Depsolve(object): if len(self.tsInfo) != length and txmbrs: return CheckDeps, errormsgs - msg = '%s conflicts with %s' % (name, conflicting_po.name) + msg = '%s conflicts with %s' % (name, str(conflicting_po)) errormsgs.append(msg) self.verbose_logger.log(logginglevels.DEBUG_1, msg) CheckDeps = False - self.po_with_problems.add((po,None,errormsgs[-1])) + # report the conflicting po, so skip-broken can remove it + self.po_with_problems.add((po,conflicting_po,errormsgs[-1])) return CheckDeps, errormsgs def _undoDepInstalls(self): @@ -799,9 +807,9 @@ class Depsolve(object): continue done.add((po, err)) self.verbose_logger.log(logginglevels.DEBUG_4, - _("%s from %s has depsolving problems") % (po, po.repoid)) + "SKIPBROKEN: %s from %s has depsolving problems" % (po, po.repoid)) err = err.replace('\n', '\n --> ') - self.verbose_logger.log(logginglevels.DEBUG_4," --> %s" % err) + self.verbose_logger.log(logginglevels.DEBUG_4,"SKIPBROKEN: --> %s" % err) return (1, errors) if not len(self.tsInfo): diff --git a/yum/misc.py b/yum/misc.py index 15e571f..8e81c34 100644 --- a/yum/misc.py +++ b/yum/misc.py @@ -252,6 +252,9 @@ class Checksums: def __len__(self): return self._len + # Note that len(x) is assert limited to INT_MAX, which is 2GB on i686. + length = property(fget=lambda self: self._len) + def update(self, data): self._len += len(data) for sumalgo in self._sumalgos: @@ -323,7 +326,7 @@ def checksum(sumtype, file, CHUNK=2**16, datasize=None): data = Checksums([sumtype]) while data.read(fo, CHUNK): - if datasize is not None and len(data) > datasize: + if datasize is not None and data.length > datasize: break if type(file) is types.StringType: @@ -332,7 +335,7 @@ def checksum(sumtype, file, CHUNK=2**16, datasize=None): # This screws up the length, but that shouldn't matter. We only care # if this checksum == what we expect. - if datasize is not None and datasize != len(data): + if datasize is not None and datasize != data.length: return '!%u!%s' % (datasize, data.hexdigest(sumtype)) return data.hexdigest(sumtype) diff --git a/yum/packages.py b/yum/packages.py index 6f61fea..db3e973 100644 --- a/yum/packages.py +++ b/yum/packages.py @@ -1069,6 +1069,9 @@ class YumAvailablePackage(PackageObject, RpmBase): if self.sourcerpm: msg += """ %s\n""" % misc.to_xml(self.sourcerpm) + else: # b/c yum 2.4.3 and OLD y-m-p willgfreak out if it is not there. + msg += """ \n""" + msg +=""" """ % (self.hdrstart, self.hdrend) msg += self._dump_pco('provides') @@ -1243,18 +1246,32 @@ class YumHeaderPackage(YumAvailablePackage): self.ver = self.version self.rel = self.release self.pkgtup = (self.name, self.arch, self.epoch, self.version, self.release) - # Summaries "can be" empty, which rpm return [], see BZ 473239, *sigh* - self.summary = self.hdr['summary'] or '' - self.summary = misc.share_data(self.summary.replace('\n', '')) - self.description = self.hdr['description'] or '' - self.description = misc.share_data(self.description) + self._loaded_summary = None + self._loaded_description = None self.pkgid = self.hdr[rpm.RPMTAG_SHA1HEADER] if not self.pkgid: self.pkgid = "%s.%s" %(self.hdr['name'], self.hdr['buildtime']) self.packagesize = self.hdr['size'] self.__mode_cache = {} self.__prcoPopulated = False - + + def _loadSummary(self): + # Summaries "can be" empty, which rpm return [], see BZ 473239, *sigh* + if self._loaded_summary is None: + summary = self._get_hdr()['summary'] or '' + summary = misc.share_data(summary.replace('\n', '')) + self._loaded_summary = summary + return self._loaded_summary + summary = property(lambda x: x._loadSummary()) + + def _loadDescription(self): + if self._loaded_description is None: + description = self._get_hdr()['description'] or '' + description = misc.share_data(description) + self._loaded_description = description + return self._loaded_description + description = property(lambda x: x._loadDescription()) + def __str__(self): if self.epoch == '0': val = '%s-%s-%s.%s' % (self.name, self.version, self.release, @@ -1828,7 +1845,6 @@ class YumInstalledPackage(YumHeaderPackage): my_mode = my_st.st_mode if 'ghost' in ftypes: # This is what rpm does, although it my_mode &= 0777 # doesn't usually get here. - mode &= 0777 if check_perms and pf.verify_mode and my_mode != pf.mode: prob = _PkgVerifyProb('mode', 'mode does not match', ftypes) prob.database_value = pf.mode diff --git a/yum/parser.py b/yum/parser.py index e46d611..fccf528 100644 --- a/yum/parser.py +++ b/yum/parser.py @@ -144,6 +144,11 @@ class ConfigPreProcessor: # the current file returned EOF, pop it off the stack. self._popfile() + # if the section is prefixed by a space then it is breaks iniparser/configparser + # so fix it + broken_sec_match = re.match(r'\s+\[(?P
.*)\]', line) + if broken_sec_match: + line = line.lstrip() # at this point we have a line from the topmost file on the stack # or EOF if the stack is empty if self._vars: diff --git a/yum/rpmsack.py b/yum/rpmsack.py index 0982a7c..e93df20 100644 --- a/yum/rpmsack.py +++ b/yum/rpmsack.py @@ -42,11 +42,6 @@ class RPMInstalledPackage(YumInstalledPackage): def __init__(self, rpmhdr, index, rpmdb): self._has_hdr = True YumInstalledPackage.__init__(self, rpmhdr, yumdb=rpmdb.yumdb) - # NOTE: We keep summary/description/url because it doesn't add much - # and "yum search" uses them all. - self.url = rpmhdr['url'] - # Also keep sourcerpm for pirut/etc. - self.sourcerpm = rpmhdr['sourcerpm'] self.idx = index self.rpmdb = rpmdb @@ -67,13 +62,19 @@ class RPMInstalledPackage(YumInstalledPackage): raise Errors.PackageSackError, 'Rpmdb changed underneath us' def __getattr__(self, varname): - self.hdr = val = self._get_hdr() - self._has_hdr = True - # If these existed, then we wouldn't get here ... and nothing in the DB - # starts and ends with __'s. So these are missing. - if varname.startswith('__') and varname.endswith('__'): + # If these existed, then we wouldn't get here... + # Prevent access of __foo__, _cached_foo etc from loading the header + if varname.startswith('_'): raise AttributeError, "%s has no attribute %s" % (self, varname) - + + if varname != 'hdr': # Don't cache the hdr, unless explicitly requested + # Note that we don't even cache the .blah value, but looking up the + # header is _really_ fast so it's not obvious any of it is worth it. + # This is different to prco etc. data, which is loaded separately. + val = self._get_hdr() + else: + self.hdr = val = self._get_hdr() + self._has_hdr = True if varname != 'hdr': # This is unusual, for anything that happens val = val[varname] # a lot we should preload at __init__. # Also note that pkg.no_value raises KeyError. @@ -234,7 +235,7 @@ class RPMDBPackageSack(PackageSackBase): self._simple_pkgtup_list = csumpkgtups.keys() if not self._simple_pkgtup_list: - for (hdr, mi) in self._all_packages(): + for (hdr, mi) in self._get_packages(): self._simple_pkgtup_list.append(self._hdr2pkgTuple(hdr)) return self._simple_pkgtup_list @@ -378,54 +379,36 @@ class RPMDBPackageSack(PackageSackBase): pass def searchAll(self, name, query_type='like'): - ts = self.readOnlyTS() result = {} # check provides tag = self.DEP_TABLE['provides'][0] - mi = ts.dbMatch() - mi.pattern(tag, rpm.RPMMIRE_GLOB, name) - for hdr in mi: - if hdr['name'] == 'gpg-pubkey': - continue - pkg = self._makePackageObject(hdr, mi.instance()) + mi = self._get_packages(patterns=[(tag, rpm.RPMMIRE_GLOB, name)]) + for hdr, idx in mi: + pkg = self._makePackageObject(hdr, idx) result.setdefault(pkg.pkgid, pkg) - del mi fileresults = self.searchFiles(name) for pkg in fileresults: result.setdefault(pkg.pkgid, pkg) - if self.auto_close: - self.ts.close() - return result.values() def searchFiles(self, name): """search the filelists in the rpms for anything matching name""" - ts = self.readOnlyTS() result = {} name = os.path.normpath(name) - mi = ts.dbMatch('basenames', name) # Note that globs can't be done. As of 4.8.1: # mi.pattern('basenames', rpm.RPMMIRE_GLOB, name) # ...produces no results. - for hdr in mi: - if hdr['name'] == 'gpg-pubkey': - continue - pkg = self._makePackageObject(hdr, mi.instance()) + for hdr, idx in self._get_packages('basenames', name): + pkg = self._makePackageObject(hdr, idx) result.setdefault(pkg.pkgid, pkg) - del mi - - result = result.values() - - if self.auto_close: - self.ts.close() - return result + return result.values() def searchPrco(self, name, prcotype): @@ -438,21 +421,15 @@ class RPMDBPackageSack(PackageSackBase): if misc.re_glob(n): glob = True - ts = self.readOnlyTS() result = {} tag = self.DEP_TABLE[prcotype][0] - mi = ts.dbMatch(tag, misc.to_utf8(n)) - for hdr in mi: - if hdr['name'] == 'gpg-pubkey': - continue - po = self._makePackageObject(hdr, mi.instance()) + for hdr, idx in self._get_packages(tag, misc.to_utf8(n)): + po = self._makePackageObject(hdr, idx) if not glob: if po.checkPrco(prcotype, (n, f, (e,v,r))): result[po.pkgid] = po else: result[po.pkgid] = po - del mi - # If it's not a provides or filename, we are done if prcotype == 'provides' and name[0] == '/': @@ -463,9 +440,6 @@ class RPMDBPackageSack(PackageSackBase): result = result.values() self._cache[prcotype][name] = result - if self.auto_close: - self.ts.close() - return result def searchProvides(self, name): @@ -607,7 +581,7 @@ class RPMDBPackageSack(PackageSackBase): if not self._completely_loaded: rpats = self._compile_patterns(patterns, ignore_case) - for hdr, idx in self._all_packages(): + for hdr, idx in self._get_packages(): if self._match_repattern(rpats, hdr, ignore_case): self._makePackageObject(hdr, idx) self._completely_loaded = patterns is None @@ -636,18 +610,13 @@ class RPMDBPackageSack(PackageSackBase): if self._cached_conflicts_data is None: result = {} - ts = self.readOnlyTS() - mi = ts.dbMatch('conflictname') - - for hdr in mi: - if hdr['name'] == 'gpg-pubkey': # Just in case... - continue + for hdr, idx in self._get_packages('conflictname'): if not hdr[rpm.RPMTAG_CONFLICTNAME]: # Pre. rpm-4.9.x the above dbMatch() does nothing. continue - po = self._makePackageObject(hdr, mi.instance()) + po = self._makePackageObject(hdr, idx) result[po.pkgid] = po if po._has_hdr: continue # Unlikely, but, meh... @@ -659,9 +628,6 @@ class RPMDBPackageSack(PackageSackBase): del po.hdr self._cached_conflicts_data = result.values() - if self.auto_close: - self.ts.close() - return self._cached_conflicts_data def _write_conflicts_new(self, pkgs, rpmdbv): @@ -1168,7 +1134,7 @@ class RPMDBPackageSack(PackageSackBase): if not lowered: searchstrings = map(lambda x: x.lower(), searchstrings) ret = [] - for hdr, idx in self._all_packages(): + for hdr, idx in self._get_packages(): n = self._find_search_fields(fields, searchstrings, hdr) if n > 0: ret.append((self._makePackageObject(hdr, idx), n)) @@ -1190,41 +1156,20 @@ class RPMDBPackageSack(PackageSackBase): return [ self._makePackageObject(h, mi) for (h, mi) in ts.returnLeafNodes(headers=True) ] # Helper functions - def _all_packages(self): - '''Generator that yield (header, index) for all packages + def _get_packages(self, *args, **kwds): + '''dbMatch() wrapper generator that yields (header, index) for matches ''' ts = self.readOnlyTS() - mi = ts.dbMatch() - for hdr in mi: - if hdr['name'] != 'gpg-pubkey': - yield (hdr, mi.instance()) + mi = ts.dbMatch(*args, **kwds) + for h in mi: + if h['name'] != 'gpg-pubkey': + yield (h, mi.instance()) del mi - if self.auto_close: - self.ts.close() - def _header_from_index(self, idx): - """returns a package header having been given an index""" - warnings.warn('_header_from_index() will go away in a future version of Yum.\n', - Errors.YumFutureDeprecationWarning, stacklevel=2) - - ts = self.readOnlyTS() - try: - mi = ts.dbMatch(0, idx) - except (TypeError, StopIteration), e: - #FIXME: raise some kind of error here - print 'No index matching %s found in rpmdb, this is bad' % idx - yield None # it should REALLY not be returning none - this needs to be right - else: - hdr = mi.next() - yield hdr - del hdr - - del mi if self.auto_close: self.ts.close() - def _search(self, name=None, epoch=None, ver=None, rel=None, arch=None): '''List of matching packages, to zero or more of NEVRA.''' if name is not None and name in self._pkgname_fails: @@ -1254,18 +1199,16 @@ class RPMDBPackageSack(PackageSackBase): ts = self.readOnlyTS() if name is not None: - mi = ts.dbMatch('name', name) + mi = self._get_packages('name', name) elif arch is not None: - mi = ts.dbMatch('arch', arch) + mi = self._get_packages('arch', arch) else: - mi = ts.dbMatch() + mi = self._get_packages() self._completely_loaded = True done = False - for hdr in mi: - if hdr['name'] == 'gpg-pubkey': - continue - po = self._makePackageObject(hdr, mi.instance()) + for hdr, idx in mi: + po = self._makePackageObject(hdr, idx) # We create POs out of all matching names, even if we don't return # them. self._pkgnames_loaded.add(po.name) @@ -1277,9 +1220,6 @@ class RPMDBPackageSack(PackageSackBase): else: ret.append(po) - if self.auto_close: - self.ts.close() - if not done and name is not None: self._pkgname_fails.add(name) @@ -1323,7 +1263,7 @@ class RPMDBPackageSack(PackageSackBase): def getHdrList(self): warnings.warn('getHdrList() will go away in a future version of Yum.\n', DeprecationWarning, stacklevel=2) - return [ hdr for hdr, idx in self._all_packages() ] + return [ hdr for hdr, idx in self._get_packages() ] def getNameArchPkgList(self): warnings.warn('getNameArchPkgList() will go away in a future version of Yum.\n', diff --git a/yum/rpmtrans.py b/yum/rpmtrans.py index 0340153..08bf99d 100644 --- a/yum/rpmtrans.py +++ b/yum/rpmtrans.py @@ -25,6 +25,7 @@ import types import sys from yum.constants import * from yum import _ +from yum.transactioninfo import TransactionMember import misc import tempfile @@ -174,11 +175,11 @@ class RPMTransaction: self.base = base # base yum object b/c we need so much self.test = test # are we a test? self.trans_running = False - self.filehandles = {} + self.fd = None self.total_actions = 0 self.total_installed = 0 self.complete_actions = 0 - self.installed_pkg_names = [] + self.installed_pkg_names = set() self.total_removed = 0 self.logger = logging.getLogger('yum.filelogging.RPMInstallCallback') self.filelog = False @@ -209,8 +210,7 @@ class RPMTransaction: io_r = tempfile.NamedTemporaryFile() self._readpipe = io_r self._writepipe = open(io_r.name, 'w+b') - # This is dark magic, it really needs to be "base.ts.ts". - self.base.ts.ts.scriptFd = self._writepipe.fileno() + self.base.ts.setScriptFd(self._writepipe) rpmverbosity = {'critical' : 'crit', 'emergency' : 'emerg', 'error' : 'err', @@ -255,12 +255,23 @@ class RPMTransaction: return (hdr['name'], hdr['arch'], epoch, hdr['version'], hdr['release']) - def _makeHandle(self, hdr): - handle = '%s:%s.%s-%s-%s' % (hdr['epoch'], hdr['name'], hdr['version'], - hdr['release'], hdr['arch']) + # Find out txmbr based on the callback key. On erasures we dont know + # the exact txmbr but we always have a name, so return (name, txmbr) + # tuples so callers have less twists to deal with. + def _getTxmbr(self, cbkey): + if isinstance(cbkey, TransactionMember): + return (cbkey.name, cbkey) + elif isinstance(cbkey, tuple): + pkgtup = self._dopkgtup(cbkey[0]) + txmbrs = self.base.tsInfo.getMembers(pkgtup=pkgtup) + # if this is not one, somebody screwed up + assert len(txmbrs) == 1 + return (txmbrs[0].name, txmbrs[0]) + elif isinstance(cbkey, basestring): + return (cbkey, None) + else: + return (None, None) - return handle - def ts_done(self, package, action): """writes out the portions of the transaction which have completed""" @@ -409,11 +420,10 @@ class RPMTransaction: def _transStart(self, bytes, total, h): - if bytes == 6: - self.total_actions = total - if self.test: return - self.trans_running = True - self.ts_all() # write out what transaction will do + self.total_actions = total + if self.test: return + self.trans_running = True + self.ts_all() # write out what transaction will do def _transProgress(self, bytes, total, h): pass @@ -423,62 +433,52 @@ class RPMTransaction: def _instOpenFile(self, bytes, total, h): self.lastmsg = None - hdr = None - if h is not None: - hdr, rpmloc = h[0], h[1] - handle = self._makeHandle(hdr) + name, txmbr = self._getTxmbr(h) + if txmbr is not None: + rpmloc = txmbr.po.localPkg() try: - fd = os.open(rpmloc, os.O_RDONLY) - except OSError, e: + self.fd = file(rpmloc) + except IOError, e: self.display.errorlog("Error: Cannot open file %s: %s" % (rpmloc, e)) else: - self.filehandles[handle]=fd if self.trans_running: self.total_installed += 1 self.complete_actions += 1 - self.installed_pkg_names.append(hdr['name']) - return fd + self.installed_pkg_names.add(name) + return self.fd.fileno() else: self.display.errorlog("Error: No Header to INST_OPEN_FILE") def _instCloseFile(self, bytes, total, h): - hdr = None - if h is not None: - hdr, rpmloc = h[0], h[1] - handle = self._makeHandle(hdr) - os.close(self.filehandles[handle]) - fd = 0 + name, txmbr = self._getTxmbr(h) + if txmbr is not None: + self.fd.close() + self.fd = None if self.test: return if self.trans_running: - pkgtup = self._dopkgtup(hdr) - txmbrs = self.base.tsInfo.getMembers(pkgtup=pkgtup) - for txmbr in txmbrs: - self.display.filelog(txmbr.po, txmbr.output_state) - self._scriptout(txmbr.po) - # NOTE: We only do this for install, not erase atm. - # because we don't get pkgtup data for erase (this - # includes "Updated" pkgs). - pid = self.base.history.pkg2pid(txmbr.po) - state = self.base.history.txmbr2state(txmbr) - self.base.history.trans_data_pid_end(pid, state) - self.ts_done(txmbr.po, txmbr.output_state) + self.display.filelog(txmbr.po, txmbr.output_state) + self._scriptout(txmbr.po) + # NOTE: We only do this for install, not erase atm. + # because we don't get pkgtup data for erase (this + # includes "Updated" pkgs). + pid = self.base.history.pkg2pid(txmbr.po) + state = self.base.history.txmbr2state(txmbr) + self.base.history.trans_data_pid_end(pid, state) + self.ts_done(txmbr.po, txmbr.output_state) def _instProgress(self, bytes, total, h): - if h is not None: - # If h is a string, we're repackaging. + name, txmbr = self._getTxmbr(h) + if name is not None: + # If we only have a name, we're repackaging. # Why the RPMCALLBACK_REPACKAGE_PROGRESS flag isn't set, I have no idea - if type(h) == type(""): - self.display.event(h, 'repackaging', bytes, total, + if txmbr is None: + self.display.event(name, 'repackaging', bytes, total, self.complete_actions, self.total_actions) - else: - hdr, rpmloc = h[0], h[1] - pkgtup = self._dopkgtup(hdr) - txmbrs = self.base.tsInfo.getMembers(pkgtup=pkgtup) - for txmbr in txmbrs: - action = txmbr.output_state - self.display.event(txmbr.po, action, bytes, total, - self.complete_actions, self.total_actions) + action = txmbr.output_state + self.display.event(txmbr.po, action, bytes, total, + self.complete_actions, self.total_actions) + def _unInstStart(self, bytes, total, h): pass @@ -486,20 +486,21 @@ class RPMTransaction: pass def _unInstStop(self, bytes, total, h): + name, txmbr = self._getTxmbr(h) self.total_removed += 1 self.complete_actions += 1 - if h not in self.installed_pkg_names: - self.display.filelog(h, TS_ERASE) + if name not in self.installed_pkg_names: + self.display.filelog(name, TS_ERASE) action = TS_ERASE else: action = TS_UPDATED - self.display.event(h, action, 100, 100, self.complete_actions, + self.display.event(name, action, 100, 100, self.complete_actions, self.total_actions) - self._scriptout(h) + self._scriptout(name) if self.test: return # and we're done - self.ts_done(h, action) + self.ts_done(name, action) def _rePackageStart(self, bytes, total, h): @@ -512,20 +513,16 @@ class RPMTransaction: pass def _cpioError(self, bytes, total, h): - hdr, rpmloc = h[0], h[1] - pkgtup = self._dopkgtup(hdr) - txmbrs = self.base.tsInfo.getMembers(pkgtup=pkgtup) - for txmbr in txmbrs: + name, txmbr = self._getTxmbr(h) + if txmbr is not None: msg = "Error in cpio payload of rpm package %s" % txmbr.po txmbr.output_state = TS_FAILED self.display.errorlog(msg) # FIXME - what else should we do here? raise a failure and abort? def _unpackError(self, bytes, total, h): - hdr, rpmloc = h[0], h[1] - pkgtup = self._dopkgtup(hdr) - txmbrs = self.base.tsInfo.getMembers(pkgtup=pkgtup) - for txmbr in txmbrs: + name, txmbr = self._getTxmbr(h) + if txmbr is not None: txmbr.output_state = TS_FAILED msg = "Error unpacking rpm package %s" % txmbr.po self.display.errorlog(msg) @@ -533,35 +530,24 @@ class RPMTransaction: # right behavior should be def _scriptError(self, bytes, total, h): - if not isinstance(h, types.TupleType): - # fun with install/erase transactions, see rhbz#484729 - h = (h, None) - hdr, rpmloc = h[0], h[1] - remove_hdr = False # if we're in a clean up/remove then hdr will not be an rpm.hdr - if not isinstance(hdr, rpm.hdr): - txmbrs = [hdr] - remove_hdr = True + # "bytes" carries the failed scriptlet tag, + # "total" carries fatal/non-fatal status + scriptlet_name = rpm.tagnames.get(bytes, "") + + name, txmbr = self._getTxmbr(h) + if txmbr is None: + package_name = name else: - pkgtup = self._dopkgtup(hdr) - txmbrs = self.base.tsInfo.getMembers(pkgtup=pkgtup) + package_name = txmbr.po - for pkg in txmbrs: - # "bytes" carries the failed scriptlet tag, - # "total" carries fatal/non-fatal status - scriptlet_name = rpm.tagnames.get(bytes, "") - if remove_hdr: - package_name = pkg - else: - package_name = pkg.po - - if total: - msg = ("Error in %s scriptlet in rpm package %s" % - (scriptlet_name, package_name)) - if not remove_hdr: - pkg.output_state = TS_FAILED - else: - msg = ("Non-fatal %s scriptlet failure in rpm package %s" % - (scriptlet_name, package_name)) - self.display.errorlog(msg) - # FIXME - what else should we do here? raise a failure and abort? + if total: + msg = ("Error in %s scriptlet in rpm package %s" % + (scriptlet_name, package_name)) + if txmbr is not None: + txmbr.output_state = TS_FAILED + else: + msg = ("Non-fatal %s scriptlet failure in rpm package %s" % + (scriptlet_name, package_name)) + self.display.errorlog(msg) + # FIXME - what else should we do here? raise a failure and abort? diff --git a/yum/update_md.py b/yum/update_md.py index 83e56c6..39fa72e 100644 --- a/yum/update_md.py +++ b/yum/update_md.py @@ -32,6 +32,15 @@ import Errors import rpmUtils.miscutils + +def safe_iterparse(filename): + """ Works like iterparse, but hides XML errors (prints a warning). """ + try: + for event, elem in iterparse(filename): + yield event, elem + except SyntaxError: # Bad XML + print >> sys.stderr, "File is not valid XML:", filename + class UpdateNoticeException(Exception): """ An exception thrown for bad UpdateNotice data. """ pass @@ -445,7 +454,7 @@ class UpdateMetadata(object): else: # obj is a file object infile = obj - for event, elem in iterparse(infile): + for event, elem in safe_iterparse(infile): if elem.tag == 'update': try: un = UpdateNotice(elem) diff --git a/yumcommands.py b/yumcommands.py index ecce347..41f0092 100644 --- a/yumcommands.py +++ b/yumcommands.py @@ -46,7 +46,7 @@ def checkRootUID(base): def checkGPGKey(base): if not base.gpgKeyCheck(): for repo in base.repos.listEnabled(): - if (repo.gpgcheck or repo.repo_gpgcheck) and repo.gpgkey == '': + 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 @@ -283,7 +283,7 @@ class InfoCommand(YumCommand): return ['info'] def getUsage(self): - return "[PACKAGE|all|installed|updates|extras|obsoletes|recent]" + return "[PACKAGE|all|available|installed|updates|extras|obsoletes|recent]" def getSummary(self): return _("Display details about a package or group of packages") @@ -626,13 +626,14 @@ class CheckUpdateCommand(YumCommand): checkEnabledRepo(base) def doCommand(self, base, basecmd, extcmds): + obscmds = ['obsoletes'] + extcmds base.extcmds.insert(0, 'updates') result = 0 try: ypl = base.returnPkgLists(extcmds) if (base.conf.obsoletes or base.verbose_logger.isEnabledFor(logginglevels.DEBUG_3)): - typl = base.returnPkgLists(['obsoletes']) + typl = base.returnPkgLists(obscmds) ypl.obsoletes = typl.obsoletes ypl.obsoletesTuples = typl.obsoletesTuples @@ -972,6 +973,12 @@ class RepoListCommand(YumCommand): elif repo.mirrorlist: out += [base.fmtKeyValFill(_("Repo-mirrors : "), repo.mirrorlist)] + if enabled and repo.urls: + 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") diff --git a/yummain.py b/yummain.py index c64b140..d0b8251 100755 --- a/yummain.py +++ b/yummain.py @@ -23,6 +23,7 @@ import os.path import sys import logging import time +import errno from yum import Errors from yum import plugins @@ -75,6 +76,15 @@ def main(args): return 200 return 0 + def rpmdb_warn_checks(): + try: + probs = base._rpmdb_warn_checks(out=verbose_logger.info, warn=False) + except YumBaseError, e: + # This is mainly for PackageSackError from rpmdb. + verbose_logger.info(_(" Yum checks failed: %s"), exception2msg(e)) + probs = [] + if not probs: + verbose_logger.info(_(" You could try running: rpm -Va --nofiles --nodigest")) logger = logging.getLogger("yum.main") verbose_logger = logging.getLogger("yum.verbose.main") @@ -99,12 +109,16 @@ def main(args): if exception2msg(e) != lockerr: lockerr = exception2msg(e) logger.critical(lockerr) - if not base.conf.exit_on_lock: + if (e.errno not in (errno.EPERM, errno.EACCES) and + not base.conf.exit_on_lock): logger.critical(_("Another app is currently holding the yum lock; waiting for it to exit...")) tm = 0.1 if show_lock_owner(e.pid, logger): tm = 2 time.sleep(tm) + elif e.errno in (errno.EPERM, errno.EACCES): + logger.critical(_("Can't create lock file; exiting")) + return 1 else: logger.critical(_("Another app is currently holding the yum lock; exiting as configured by exit_on_lock")) return 1 @@ -177,8 +191,7 @@ def main(args): logger.critical(prefix, msg.replace('\n', '\n' + prefix2nd)) if not base.conf.skip_broken: verbose_logger.info(_(" You could try using --skip-broken to work around the problem")) - if not base._rpmdb_warn_checks(out=verbose_logger.info, warn=False): - verbose_logger.info(_(" You could try running: rpm -Va --nofiles --nodigest")) + rpmdb_warn_checks() if unlock(): return 200 return 1 elif result == 2: @@ -205,13 +218,12 @@ def main(args): except IOError, e: return exIOError(e) - # rpm_check_debug failed. + # rpm ts.check() failed. if type(return_code) == type((0,)) and len(return_code) == 2: (result, resultmsgs) = return_code for msg in resultmsgs: logger.critical("%s", msg) - if not base._rpmdb_warn_checks(out=verbose_logger.info, warn=False): - verbose_logger.info(_(" You could try running: rpm -Va --nofiles --nodigest")) + rpmdb_warn_checks() return_code = result if base._ts_save_file: verbose_logger.info(_("Your transaction was saved, rerun it with: yum load-transaction %s") % base._ts_save_file)