Fix update of obsoleted pkg. just as install does, testRLDaplMessWeirdUp1 fix
[yum.git] / yum / __init__.py
1 #!/usr/bin/python -tt
2 # This program is free software; you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation; either version 2 of the License, or
5 # (at your option) any later version.
6 #
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10 # GNU Library General Public License for more details.
11 #
12 # You should have received a copy of the GNU General Public License
13 # along with this program; if not, write to the Free Software
14 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
15 # Copyright 2005 Duke University
16
17 """
18 The Yum RPM software updater.
19 """
20
21 import os
22 import os.path
23 import rpm
24 import re
25 import types
26 import errno
27 import time
28 import glob
29 import fnmatch
30 import logging
31 import logging.config
32 import operator
33 import gzip
34
35 import yum.i18n
36 _ = yum.i18n._
37 P_ = yum.i18n.P_
38
39 import config
40 from config import ParsingError, ConfigParser
41 import Errors
42 import rpmsack
43 import rpmUtils.updates
44 from rpmUtils.arch import canCoinstall, ArchStorage, isMultiLibArch
45 import rpmUtils.transaction
46 import comps
47 from repos import RepoStorage
48 import misc
49 from parser import ConfigPreProcessor, varReplace
50 import transactioninfo
51 import urlgrabber
52 from urlgrabber.grabber import URLGrabber, URLGrabError
53 from urlgrabber.progress import format_number
54 from packageSack import packagesNewestByNameArch, ListPackageSack
55 import depsolve
56 import plugins
57 import logginglevels
58 import yumRepo
59 import callbacks
60
61 import warnings
62 warnings.simplefilter("ignore", Errors.YumFutureDeprecationWarning)
63
64 from packages import parsePackages, YumAvailablePackage, YumLocalPackage, YumInstalledPackage, comparePoEVR
65 from constants import *
66 from yum.rpmtrans import RPMTransaction,SimpleCliCallBack
67 from yum.i18n import to_unicode
68
69 import string
70
71 from urlgrabber.grabber import default_grabber
72
73 __version__ = '3.2.23'
74 __version_info__ = tuple([ int(num) for num in __version__.split('.')])
75
76 #  Setup a default_grabber UA here that says we are yum, done using the global
77 # so that other API users can easily add to it if they want.
78 #  Don't do it at init time, or we'll get multiple additions if you create
79 # multiple YumBase() objects.
80 default_grabber.opts.user_agent += " yum/" + __version__
81
82 class _YumPreBaseConf:
83     """This is the configuration interface for the YumBase configuration.
84        So if you want to change if plugins are on/off, or debuglevel/etc.
85        you tweak it here, and when yb.conf does it's thing ... it happens. """
86
87     def __init__(self):
88         self.fn = '/etc/yum/yum.conf'
89         self.root = '/'
90         self.init_plugins = True
91         self.plugin_types = (plugins.TYPE_CORE,)
92         self.optparser = None
93         self.debuglevel = None
94         self.errorlevel = None
95         self.disabled_plugins = None
96         self.enabled_plugins = None
97         self.syslog_ident = None
98         self.syslog_facility = None
99         self.syslog_device = '/dev/log'
100         self.arch = None
101         self.releasever = None
102
103 class YumBase(depsolve.Depsolve):
104     """This is a primary structure and base class. It houses the objects and
105        methods needed to perform most things in yum. It is almost an abstract
106        class in that you will need to add your own class above it for most
107        real use."""
108     
109     def __init__(self):
110         depsolve.Depsolve.__init__(self)
111         self._conf = None
112         self._tsInfo = None
113         self._rpmdb = None
114         self._up = None
115         self._comps = None
116         self._pkgSack = None
117         self._lockfile = None
118         self.skipped_packages = []   # packages skip by the skip-broken code
119         self.logger = logging.getLogger("yum.YumBase")
120         self.verbose_logger = logging.getLogger("yum.verbose.YumBase")
121         self._repos = RepoStorage(self)
122
123         # Start with plugins disabled
124         self.disablePlugins()
125
126         self.localPackages = [] # for local package handling
127
128         self.mediagrabber = None
129         self.arch = ArchStorage()
130         self.preconf = _YumPreBaseConf()
131
132
133     def __del__(self):
134         self.close()
135         self.closeRpmDB()
136         self.doUnlock()
137
138     def close(self):
139         if self._repos:
140             self._repos.close()
141
142     def _transactionDataFactory(self):
143         """Factory method returning TransactionData object"""
144         return transactioninfo.TransactionData()
145
146     def doGenericSetup(self, cache=0):
147         """do a default setup for all the normal/necessary yum components,
148            really just a shorthand for testing"""
149
150         self.preconf.init_plugins = False
151         self.conf.cache = cache
152
153     def doConfigSetup(self, fn='/etc/yum/yum.conf', root='/', init_plugins=True,
154             plugin_types=(plugins.TYPE_CORE,), optparser=None, debuglevel=None,
155             errorlevel=None):
156         warnings.warn(_('doConfigSetup() will go away in a future version of Yum.\n'),
157                 Errors.YumFutureDeprecationWarning, stacklevel=2)
158
159         if hasattr(self, 'preconf'):
160             self.preconf.fn = fn
161             self.preconf.root = root
162             self.preconf.init_plugins = init_plugins
163             self.preconf.plugin_types = plugin_types
164             self.preconf.optparser = optparser
165             self.preconf.debuglevel = debuglevel
166             self.preconf.errorlevel = errorlevel
167
168         return self.conf
169         
170     def _getConfig(self, **kwargs):
171         '''
172         Parse and load Yum's configuration files and call hooks initialise
173         plugins and logging. Uses self.preconf for pre-configuration,
174         configuration. '''
175
176         # ' xemacs syntax hack
177
178         if kwargs:
179             warnings.warn('Use .preconf instead of passing args to _getConfig')
180
181         if self._conf:
182             return self._conf
183         conf_st = time.time()            
184
185         if kwargs:
186             for arg in ('fn', 'root', 'init_plugins', 'plugin_types',
187                         'optparser', 'debuglevel', 'errorlevel',
188                         'disabled_plugins', 'enabled_plugins'):
189                 if arg in kwargs:
190                     setattr(self.preconf, arg, kwargs[arg])
191
192         fn = self.preconf.fn
193         root = self.preconf.root
194         init_plugins = self.preconf.init_plugins
195         plugin_types = self.preconf.plugin_types
196         optparser = self.preconf.optparser
197         debuglevel = self.preconf.debuglevel
198         errorlevel = self.preconf.errorlevel
199         disabled_plugins = self.preconf.disabled_plugins
200         enabled_plugins = self.preconf.enabled_plugins
201         syslog_ident    = self.preconf.syslog_ident
202         syslog_facility = self.preconf.syslog_facility
203         syslog_device   = self.preconf.syslog_device
204         releasever = self.preconf.releasever
205         arch = self.preconf.arch
206
207         if arch: # if preconf is setting an arch we need to pass that up
208             self.arch.setup_arch(arch)
209         else:
210             arch = self.arch.canonarch
211
212         # TODO: Remove this block when we no longer support configs outside
213         # of /etc/yum/
214         if fn == '/etc/yum/yum.conf' and not os.path.exists(fn):
215             # Try the old default
216             fn = '/etc/yum.conf'
217
218         startupconf = config.readStartupConfig(fn, root)
219         startupconf.arch = arch
220         startupconf.basearch = self.arch.basearch
221
222         if startupconf.gaftonmode:
223             global _
224             _ = yum.i18n.dummy_wrapper
225
226         if debuglevel != None:
227             startupconf.debuglevel = debuglevel
228         if errorlevel != None:
229             startupconf.errorlevel = errorlevel
230         if syslog_ident != None:
231             startupconf.syslog_ident = syslog_ident
232         if syslog_facility != None:
233             startupconf.syslog_facility = syslog_facility
234         if releasever != None:
235             startupconf.releasever = releasever
236
237         self.doLoggingSetup(startupconf.debuglevel, startupconf.errorlevel,
238                             startupconf.syslog_ident,
239                             startupconf.syslog_facility, syslog_device)
240
241         if init_plugins and startupconf.plugins:
242             self.doPluginSetup(optparser, plugin_types, startupconf.pluginpath,
243                     startupconf.pluginconfpath,disabled_plugins,enabled_plugins)
244
245         self._conf = config.readMainConfig(startupconf)
246
247         #  We don't want people accessing/altering preconf after it becomes
248         # worthless. So we delete it, and thus. it'll raise AttributeError
249         del self.preconf
250
251         # run the postconfig plugin hook
252         self.plugins.run('postconfig')
253         self.yumvar = self.conf.yumvar
254
255         self.getReposFromConfig()
256
257         # who are we:
258         self.conf.uid = os.geteuid()
259         
260         
261         self.doFileLogSetup(self.conf.uid, self.conf.logfile)
262         self.verbose_logger.debug('Config time: %0.3f' % (time.time() - conf_st))
263         self.plugins.run('init')
264         return self._conf
265         
266
267     def doLoggingSetup(self, debuglevel, errorlevel,
268                        syslog_ident=None, syslog_facility=None,
269                        syslog_device='/dev/log'):
270         '''
271         Perform logging related setup.
272
273         @param debuglevel: Debug logging level to use.
274         @param errorlevel: Error logging level to use.
275         '''
276         logginglevels.doLoggingSetup(debuglevel, errorlevel,
277                                      syslog_ident, syslog_facility,
278                                      syslog_device)
279
280     def doFileLogSetup(self, uid, logfile):
281         logginglevels.setFileLog(uid, logfile)
282
283     def getReposFromConfigFile(self, repofn, repo_age=None, validate=None):
284         """read in repositories from a config .repo file"""
285
286         if repo_age is None:
287             repo_age = os.stat(repofn)[8]
288         
289         confpp_obj = ConfigPreProcessor(repofn, vars=self.yumvar)
290         parser = ConfigParser()
291         try:
292             parser.readfp(confpp_obj)
293         except ParsingError, e:
294             msg = str(e)
295             raise Errors.ConfigError, msg
296
297         # Check sections in the .repo file that was just slurped up
298         for section in parser.sections():
299
300             if section in ['main', 'installed']:
301                 continue
302
303             # Check the repo.id against the valid chars
304             bad = None
305             for byte in section:
306                 if byte in string.ascii_letters:
307                     continue
308                 if byte in string.digits:
309                     continue
310                 if byte in "-_.:":
311                     continue
312                 
313                 bad = byte
314                 break
315
316             if bad:
317                 self.logger.warning("Bad id for repo: %s, byte = %s %d" %
318                                     (section, bad, section.find(bad)))
319                 continue
320
321             try:
322                 thisrepo = self.readRepoConfig(parser, section)
323             except (Errors.RepoError, Errors.ConfigError), e:
324                 self.logger.warning(e)
325                 continue
326             else:
327                 thisrepo.repo_config_age = repo_age
328                 thisrepo.repofile = repofn
329
330             if validate and not validate(thisrepo):
331                 continue
332                     
333             # Got our list of repo objects, add them to the repos
334             # collection
335             try:
336                 self._repos.add(thisrepo)
337             except Errors.RepoError, e:
338                 self.logger.warning(e)
339         
340     def getReposFromConfig(self):
341         """read in repositories from config main and .repo files"""
342
343         # Read .repo files from directories specified by the reposdir option
344         # (typically /etc/yum/repos.d)
345         repo_config_age = self.conf.config_file_age
346         
347         # Get the repos from the main yum.conf file
348         self.getReposFromConfigFile(self.conf.config_file_path, repo_config_age)
349
350         for reposdir in self.conf.reposdir:
351             # this check makes sure that our dirs exist properly.
352             # if they aren't in the installroot then don't prepent the installroot path
353             # if we don't do this then anaconda likes to not  work.
354             if os.path.exists(self.conf.installroot+'/'+reposdir):
355                 reposdir = self.conf.installroot + '/' + reposdir
356
357             if os.path.isdir(reposdir):
358                 for repofn in sorted(glob.glob('%s/*.repo' % reposdir)):
359                     thisrepo_age = os.stat(repofn)[8]
360                     if thisrepo_age < repo_config_age:
361                         thisrepo_age = repo_config_age
362                     self.getReposFromConfigFile(repofn, repo_age=thisrepo_age)
363
364     def readRepoConfig(self, parser, section):
365         '''Parse an INI file section for a repository.
366
367         @param parser: ConfParser or similar to read INI file values from.
368         @param section: INI file section to read.
369         @return: YumRepository instance.
370         '''
371         repo = yumRepo.YumRepository(section)
372         repo.populate(parser, section, self.conf)
373
374         # Ensure that the repo name is set
375         if not repo.name:
376             repo.name = section
377             self.logger.error(_('Repository %r is missing name in configuration, '
378                     'using id') % section)
379         repo.name = to_unicode(repo.name)
380
381         # Set attributes not from the config file
382         repo.basecachedir = self.conf.cachedir
383         repo.yumvar.update(self.conf.yumvar)
384         repo.cfg = parser
385
386         return repo
387
388     def disablePlugins(self):
389         '''Disable yum plugins
390         '''
391         self.plugins = plugins.DummyYumPlugins()
392     
393     def doPluginSetup(self, optparser=None, plugin_types=None, searchpath=None,
394             confpath=None,disabled_plugins=None,enabled_plugins=None):
395         '''Initialise and enable yum plugins. 
396
397         Note: _getConfig() will initialise plugins if instructed to. Only
398         call this method directly if not calling _getConfig() or calling
399         doConfigSetup(init_plugins=False).
400
401         @param optparser: The OptionParser instance for this run (optional)
402         @param plugin_types: A sequence specifying the types of plugins to load.
403             This should be sequnce containing one or more of the
404             yum.plugins.TYPE_...  constants. If None (the default), all plugins
405             will be loaded.
406         @param searchpath: A list of directories to look in for plugins. A
407             default will be used if no value is specified.
408         @param confpath: A list of directories to look in for plugin
409             configuration files. A default will be used if no value is
410             specified.
411         @param disabled_plugins: Plugins to be disabled    
412         @param enabled_plugins: Plugins to be enabled
413         '''
414         if isinstance(self.plugins, plugins.YumPlugins):
415             raise RuntimeError(_("plugins already initialised"))
416
417         self.plugins = plugins.YumPlugins(self, searchpath, optparser,
418                 plugin_types, confpath, disabled_plugins, enabled_plugins)
419
420     
421     def doRpmDBSetup(self):
422         warnings.warn(_('doRpmDBSetup() will go away in a future version of Yum.\n'),
423                 Errors.YumFutureDeprecationWarning, stacklevel=2)
424
425         return self._getRpmDB()
426     
427     def _getRpmDB(self):
428         """sets up a holder object for important information from the rpmdb"""
429
430         if self._rpmdb is None:
431             rpmdb_st = time.time()
432             self.verbose_logger.log(logginglevels.DEBUG_4,
433                                     _('Reading Local RPMDB'))
434             self._rpmdb = rpmsack.RPMDBPackageSack(root=self.conf.installroot)
435             self.verbose_logger.debug('rpmdb time: %0.3f' % (time.time() - rpmdb_st))
436         return self._rpmdb
437
438     def closeRpmDB(self):
439         """closes down the instances of the rpmdb we have wangling around"""
440         if self._rpmdb is not None:
441             self._rpmdb.ts = None
442             self._rpmdb.dropCachedData()
443         self._rpmdb = None
444         self._ts = None
445         self._tsInfo = None
446         self._up = None
447         self.comps = None
448     
449     def _deleteTs(self):
450         del self._ts
451         self._ts = None
452
453     def doRepoSetup(self, thisrepo=None):
454         warnings.warn(_('doRepoSetup() will go away in a future version of Yum.\n'),
455                 Errors.YumFutureDeprecationWarning, stacklevel=2)
456
457         return self._getRepos(thisrepo, True)
458
459     def _getRepos(self, thisrepo=None, doSetup = False):
460         """ For each enabled repository set up the basics of the repository. """
461         self.conf # touch the config class first
462
463         if doSetup:
464             repo_st = time.time()        
465             self._repos.doSetup(thisrepo)
466             self.verbose_logger.debug('repo time: %0.3f' % (time.time() - repo_st))        
467         return self._repos
468
469     def _delRepos(self):
470         del self._repos
471         self._repos = RepoStorage(self)
472     
473     def doSackSetup(self, archlist=None, thisrepo=None):
474         warnings.warn(_('doSackSetup() will go away in a future version of Yum.\n'),
475                 Errors.YumFutureDeprecationWarning, stacklevel=2)
476
477         return self._getSacks(archlist=archlist, thisrepo=thisrepo)
478         
479     def _getSacks(self, archlist=None, thisrepo=None):
480         """populates the package sacks for information from our repositories,
481            takes optional archlist for archs to include"""
482
483         # FIXME: Fist of death ... normally we'd do either:
484         #
485         # 1. use self._pkgSack is not None, and only init. once.
486         # 2. auto. correctly re-init each time a repo is added/removed
487         #
488         # ...we should probably just smeg it and do #2, but it's hard and will
489         # probably break something (but it'll "fix" excludes).
490         #  #1 can't be done atm. because we did self._pkgSack and external
491         # tools now rely on being able to create an empty sack and then have it
492         # auto. re-init when they add some stuff. So we add a bit more "clever"
493         # and don't setup the pkgSack to not be None when it's empty. This means
494         # we skip excludes/includes/etc. ... but there's no packages, so
495         # hopefully that's ok.
496         if self._pkgSack is not None and thisrepo is None:
497             return self._pkgSack
498         
499         if thisrepo is None:
500             repos = 'enabled'
501         else:
502             repos = self.repos.findRepos(thisrepo)
503         
504         self.verbose_logger.debug(_('Setting up Package Sacks'))
505         sack_st = time.time()
506         if not archlist:
507             archlist = self.arch.archlist
508         
509         archdict = {}
510         for arch in archlist:
511             archdict[arch] = 1
512         
513         self.repos.getPackageSack().setCompatArchs(archdict)
514         self.repos.populateSack(which=repos)
515         if not self.repos.getPackageSack():
516             return self.repos.getPackageSack() # ha ha, see above
517         self._pkgSack = self.repos.getPackageSack()
518         
519         self.excludePackages()
520         self._pkgSack.excludeArchs(archlist)
521         
522         #FIXME - this could be faster, too.
523         if repos == 'enabled':
524             repos = self.repos.listEnabled()
525         for repo in repos:
526             self.excludePackages(repo)
527             self.includePackages(repo)
528         self.plugins.run('exclude')
529         self._pkgSack.buildIndexes()
530
531         # now go through and kill pkgs based on pkg.repo.cost()
532         self.costExcludePackages()
533         self.verbose_logger.debug('pkgsack time: %0.3f' % (time.time() - sack_st))
534         return self._pkgSack
535     
536     
537     def _delSacks(self):
538         """reset the package sacks back to zero - making sure to nuke the ones
539            in the repo objects, too - where it matters"""
540            
541         # nuke the top layer
542         
543         self._pkgSack = None
544            
545         for repo in self.repos.repos.values():
546             if hasattr(repo, '_resetSack'):
547                 repo._resetSack()
548             else:
549                 warnings.warn(_('repo object for repo %s lacks a _resetSack method\n') +
550                         _('therefore this repo cannot be reset.\n'),
551                         Errors.YumFutureDeprecationWarning, stacklevel=2)
552             
553            
554     def doUpdateSetup(self):
555         warnings.warn(_('doUpdateSetup() will go away in a future version of Yum.\n'),
556                 Errors.YumFutureDeprecationWarning, stacklevel=2)
557
558         return self._getUpdates()
559         
560     def _getUpdates(self):
561         """setups up the update object in the base class and fills out the
562            updates, obsoletes and others lists"""
563         
564         if self._up:
565             return self._up
566         
567         self.verbose_logger.debug(_('Building updates object'))
568
569         up_st = time.time()
570
571         self._up = rpmUtils.updates.Updates(self.rpmdb.simplePkgList(), self.pkgSack.simplePkgList())
572         if self.conf.debuglevel >= 7:
573             self._up.debug = 1
574         
575         if self.conf.obsoletes:
576             obs_init = time.time()    
577             self._up.rawobsoletes = self.pkgSack.returnObsoletes()
578             self.verbose_logger.debug('up:Obs Init time: %0.3f' % (time.time() - obs_init))
579
580         self._up.myarch = self.arch.canonarch
581         self._up._is_multilib = self.arch.multilib
582         self._up._archlist = self.arch.archlist
583         self._up._multilib_compat_arches = self.arch.compatarches
584         self._up.exactarch = self.conf.exactarch
585         self._up.exactarchlist = self.conf.exactarchlist
586         up_pr_st = time.time()
587         self._up.doUpdates()
588         self.verbose_logger.debug('up:simple updates time: %0.3f' % (time.time() - up_pr_st))
589
590         if self.conf.obsoletes:
591             obs_st = time.time()
592             self._up.doObsoletes()
593             self.verbose_logger.debug('up:obs time: %0.3f' % (time.time() - obs_st))
594
595         cond_up_st = time.time()                    
596         self._up.condenseUpdates()
597         self.verbose_logger.debug('up:condense time: %0.3f' % (time.time() - cond_up_st))
598         self.verbose_logger.debug('updates time: %0.3f' % (time.time() - up_st))        
599         return self._up
600     
601     def doGroupSetup(self):
602         warnings.warn(_('doGroupSetup() will go away in a future version of Yum.\n'),
603                 Errors.YumFutureDeprecationWarning, stacklevel=2)
604
605         self.comps = None
606         return self._getGroups()
607
608     def _setGroups(self, val):
609         if val is None:
610             # if we unset the comps object, we need to undo which repos have
611             # been added to the group file as well
612             if self._repos:
613                 for repo in self._repos.listGroupsEnabled():
614                     repo.groups_added = False
615         self._comps = val
616     
617     def _getGroups(self):
618         """create the groups object that will store the comps metadata
619            finds the repos with groups, gets their comps data and merge it
620            into the group object"""
621         
622         if self._comps:
623             return self._comps
624
625         group_st = time.time()            
626         self.verbose_logger.log(logginglevels.DEBUG_4,
627                                 _('Getting group metadata'))
628         reposWithGroups = []
629         self.repos.doSetup()
630         for repo in self.repos.listGroupsEnabled():
631             if repo.groups_added: # already added the groups from this repo
632                 reposWithGroups.append(repo)
633                 continue
634                 
635             if not repo.ready():
636                 raise Errors.RepoError, "Repository '%s' not yet setup" % repo
637             try:
638                 groupremote = repo.getGroupLocation()
639             except Errors.RepoMDError, e:
640                 pass
641             else:
642                 reposWithGroups.append(repo)
643         
644         # now we know which repos actually have groups files.
645         overwrite = self.conf.overwrite_groups
646         self._comps = comps.Comps(overwrite_groups = overwrite)
647
648         for repo in reposWithGroups:
649             if repo.groups_added: # already added the groups from this repo
650                 continue
651                 
652             self.verbose_logger.log(logginglevels.DEBUG_4,
653                 _('Adding group file from repository: %s'), repo)
654             groupfile = repo.getGroups()
655             # open it up as a file object so iterparse can cope with our gz file
656             if groupfile is not None and groupfile.endswith('.gz'):
657                 groupfile = gzip.open(groupfile)
658                 
659             try:
660                 self._comps.add(groupfile)
661             except (Errors.GroupsError,Errors.CompsException), e:
662                 msg = _('Failed to add groups file for repository: %s - %s') % (repo, str(e))
663                 self.logger.critical(msg)
664             else:
665                 repo.groups_added = True
666
667         if self._comps.compscount == 0:
668             raise Errors.GroupsError, _('No Groups Available in any repository')
669
670         self._comps.compile(self.rpmdb.simplePkgList())
671         self.verbose_logger.debug('group time: %0.3f' % (time.time() - group_st))                
672         return self._comps
673     
674     # properties so they auto-create themselves with defaults
675     repos = property(fget=lambda self: self._getRepos(),
676                      fset=lambda self, value: setattr(self, "_repos", value),
677                      fdel=lambda self: self._delRepos())
678     pkgSack = property(fget=lambda self: self._getSacks(),
679                        fset=lambda self, value: setattr(self, "_pkgSack", value),
680                        fdel=lambda self: self._delSacks())
681     conf = property(fget=lambda self: self._getConfig(),
682                     fset=lambda self, value: setattr(self, "_conf", value),
683                     fdel=lambda self: setattr(self, "_conf", None))
684     rpmdb = property(fget=lambda self: self._getRpmDB(),
685                      fset=lambda self, value: setattr(self, "_rpmdb", value),
686                      fdel=lambda self: setattr(self, "_rpmdb", None))
687     tsInfo = property(fget=lambda self: self._getTsInfo(), 
688                       fset=lambda self,value: self._setTsInfo(value), 
689                       fdel=lambda self: self._delTsInfo())
690     ts = property(fget=lambda self: self._getActionTs(), fdel=lambda self: self._deleteTs())
691     up = property(fget=lambda self: self._getUpdates(),
692                   fset=lambda self, value: setattr(self, "_up", value),
693                   fdel=lambda self: setattr(self, "_up", None))
694     comps = property(fget=lambda self: self._getGroups(),
695                      fset=lambda self, value: self._setGroups(value),
696                      fdel=lambda self: setattr(self, "_comps", None))
697     
698     
699     def doSackFilelistPopulate(self):
700         """convenience function to populate the repos with the filelist metadata
701            it also is simply to only emit a log if anything actually gets populated"""
702         
703         necessary = False
704         
705         # I can't think of a nice way of doing this, we have to have the sack here
706         # first or the below does nothing so...
707         if self.pkgSack:
708             for repo in self.repos.listEnabled():
709                 if repo in repo.sack.added:
710                     if 'filelists' in repo.sack.added[repo]:
711                         continue
712                     else:
713                         necessary = True
714                 else:
715                     necessary = True
716
717         if necessary:
718             msg = _('Importing additional filelist information')
719             self.verbose_logger.log(logginglevels.INFO_2, msg)
720             self.repos.populateSack(mdtype='filelists')
721            
722     def yumUtilsMsg(self, func, prog):
723         """ Output a message that the tool requires the yum-utils package,
724             if not installed. """
725         if self.rpmdb.contains(name="yum-utils"):
726             return
727
728         hibeg, hiend = "", ""
729         if hasattr(self, 'term'):
730             hibeg, hiend = self.term.MODE['bold'], self.term.MODE['normal']
731
732         func(_("The program %s%s%s is found in the yum-utils package.") %
733              (hibeg, prog, hiend))
734
735     def buildTransaction(self, unfinished_transactions_check=True):
736         """go through the packages in the transaction set, find them in the
737            packageSack or rpmdb, and pack up the ts accordingly"""
738         if (unfinished_transactions_check and
739             misc.find_unfinished_transactions(yumlibpath=self.conf.persistdir)):
740             msg = _('There are unfinished transactions remaining. You might ' \
741                     'consider running yum-complete-transaction first to finish them.' )
742             self.logger.critical(msg)
743             self.yumUtilsMsg(self.logger.critical, "yum-complete-transaction")
744             time.sleep(3)
745
746         self.plugins.run('preresolve')
747         ds_st = time.time()
748
749         (rescode, restring) = self.resolveDeps()
750         self._limit_installonly_pkgs()
751         
752         #  We _must_ get rid of all the used tses before we go on, so that C-c
753         # works for downloads / mirror failover etc.
754         self.rpmdb.ts = None
755
756         # if depsolve failed and skipbroken is enabled
757         # The remove the broken packages from the transactions and
758         # Try another depsolve
759         if self.conf.skip_broken and rescode==1:
760             self.skipped_packages = []    # reset the public list of skipped packages.
761             sb_st = time.time()
762             rescode, restring = self._skipPackagesWithProblems(rescode, restring)
763             self._printTransaction()        
764             self.verbose_logger.debug('Skip-Broken time: %0.3f' % (time.time() - sb_st))
765
766         self.plugins.run('postresolve', rescode=rescode, restring=restring)
767
768         if self.tsInfo.changed:
769             (rescode, restring) = self.resolveDeps(rescode == 1)
770         if self.tsInfo.pkgSack is not None: # rm Transactions don't have pkgSack
771             self.tsInfo.pkgSack.dropCachedData()
772         self.rpmdb.dropCachedData()
773
774         self.verbose_logger.debug('Depsolve time: %0.3f' % (time.time() - ds_st))
775         return rescode, restring
776
777     def _skipPackagesWithProblems(self, rescode, restring):
778         ''' Remove the packages with depsolve errors and depsolve again '''
779
780         def _remove(po, depTree, toRemove):
781             if not po:
782                 return
783             self._getPackagesToRemove(po, depTree, toRemove)
784             # Only remove non installed packages from pkgSack
785             _remove_from_sack(po)
786
787         def _remove_from_sack(po):
788             # get all compatible arch packages from pkgSack
789             # we need to remove them to so a i386 paqckages is not 
790             # dragged in when a x86_64 is skipped.
791             pkgs = self._getPackagesToRemoveAllArch(po)
792             for pkg in pkgs:
793                 if not po.repoid == 'installed' and pkg not in removed_from_sack:             
794                     self.verbose_logger.debug('SKIPBROKEN: removing %s from pkgSack & updates' % str(po))
795                     self.pkgSack.delPackage(pkg)
796                     self.up.delPackage(pkg.pkgtup)
797                     removed_from_sack.add(pkg)
798
799         # Keep removing packages & Depsolve until all errors is gone
800         # or the transaction is empty
801         count = 0
802         skipped_po = set()
803         removed_from_sack = set()
804         orig_restring = restring    # Keep the old error messages 
805         looping = 0 
806         while (len(self.po_with_problems) > 0 and rescode == 1):
807             count += 1
808             self.verbose_logger.debug(_("Skip-broken round %i"), count)
809             self._printTransaction()        
810             depTree = self._buildDepTree()
811             startTs = set(self.tsInfo)
812             toRemove = set()
813             for po,wpo,err in self.po_with_problems:
814                 # check if the problem is caused by a package in the transaction
815                 if not self.tsInfo.exists(po.pkgtup):
816                     _remove(wpo, depTree, toRemove)
817                 else:
818                     _remove(po,  depTree, toRemove)
819             for po in toRemove:
820                 skipped = self._skipFromTransaction(po)
821                 for skip in skipped:
822                     skipped_po.add(skip)
823                     # make sure we get the compat arch packages skip from pkgSack and up too.
824                     if skip not in removed_from_sack and skip.repoid == 'installed':
825                         _remove_from_sack(skip)
826             # Nothing was removed, so we still got a problem
827              # the first time we get here we reset the resolved members of
828              # tsInfo and takes a new run all members in the current transaction
829             if not toRemove: 
830                 looping += 1
831                 if looping > 2:
832                     break # Bail out
833                 else:
834                     self.verbose_logger.debug('SKIPBROKEN: resetting already resovled packages (no packages to skip)' )
835                     self.tsInfo.resetResolved(hard=True)
836             rescode, restring = self.resolveDeps(True)
837             endTs = set(self.tsInfo)
838              # Check if tsInfo has changes since we started to skip packages
839              # if there is no changes then we got a loop.
840              # the first time we get here we reset the resolved members of
841              # tsInfo and takes a new run all members in the current transaction
842             if startTs-endTs == set():
843                 looping += 1
844                 if looping > 2:
845                     break # Bail out
846                 else:
847                     self.verbose_logger.debug('SKIPBROKEN: resetting already resovled packages (transaction not changed)' )
848                     self.tsInfo.resetResolved(hard=True)
849                     
850             # if we are alel clear, then we have to check that the whole current transaction 
851             # can complete the depsolve without error, because the packages skipped
852             # can have broken something that passed the tests earliere.
853             # FIXME: We need do this in a better way.
854             if rescode != 1:
855                 self.verbose_logger.debug('SKIPBROKEN: sanity check the current transaction' )
856                 self.tsInfo.resetResolved(hard=True)
857                 self._checkMissingObsoleted() # This is totally insane, but needed :(
858                 rescode, restring = self.resolveDeps()
859         if rescode != 1:
860             self.verbose_logger.debug(_("Skip-broken took %i rounds "), count)
861             self.verbose_logger.info(_('\nPackages skipped because of dependency problems:'))
862             skipped_list = [p for p in skipped_po]
863             skipped_list.sort()
864             for po in skipped_list:
865                 msg = _("    %s from %s") % (str(po),po.repo.id)
866                 self.verbose_logger.info(msg)
867             self.skipped_packages = skipped_list    # make the skipped packages public
868         else:
869             # If we cant solve the problems the show the original error messages.
870             self.verbose_logger.info("Skip-broken could not solve problems")
871             return 1, orig_restring
872         return rescode, restring
873
874     def _checkMissingObsoleted(self):
875         """ 
876         If multiple packages is obsoleting the same package
877         then the TS_OBSOLETED can get removed from the transaction
878         so we must make sure that they, exist and else create them
879         """
880         for txmbr in self.tsInfo:
881             for pkg in txmbr.obsoletes:
882                 if not self.tsInfo.exists(pkg.pkgtup):
883                     obs = self.tsInfo.addObsoleted(pkg,txmbr.po)
884                     self.verbose_logger.debug('SKIPBROKEN: Added missing obsoleted %s (%s)' % (pkg,txmbr.po) )
885             for pkg in txmbr.obsoleted_by:
886                 # check if the obsoleting txmbr is in the transaction
887                 # else remove the obsoleted txmbr
888                 # it clean out some really wierd cases
889                 if not self.tsInfo.exists(pkg.pkgtup):
890                     self.verbose_logger.debug('SKIPBROKEN: Remove extra obsoleted %s (%s)' % (txmbr.po,pkg) )
891                     self.tsInfo.remove(txmbr.po.pkgtup)
892
893     def _getPackagesToRemoveAllArch(self,po):
894         ''' get all compatible arch packages in pkgSack'''
895         pkgs = []
896         if self.arch.multilib:
897             n,a,e,v,r = po.pkgtup
898             # skip for all compat archs
899             for a in self.arch.archlist:
900                 pkgtup = (n,a,e,v,r)
901                 matched = self.pkgSack.searchNevra(n,e,v,r,a) 
902                 pkgs.extend(matched)
903         else:
904             pkgs.append(po)
905         return pkgs   
906         
907                 
908                 
909         
910
911     def _skipFromTransaction(self,po):
912         skipped =  []
913         n,a,e,v,r = po.pkgtup
914         # skip for all compat archs
915         for a in self.arch.archlist:
916             pkgtup = (n,a,e,v,r)
917             if self.tsInfo.exists(pkgtup):
918                 for txmbr in self.tsInfo.getMembers(pkgtup):
919                     pkg = txmbr.po
920                     skip = self._removePoFromTransaction(pkg)
921                     skipped.extend(skip)
922         return skipped
923
924     def _removePoFromTransaction(self,po):
925         skip =  []
926         if self.tsInfo.exists(po.pkgtup):
927             self.verbose_logger.debug('SKIPBROKEN: removing %s from transaction' % str(po))
928             self.tsInfo.remove(po.pkgtup)
929             if not po.repoid == 'installed':
930                 skip.append(po)
931         return skip 
932               
933     def _buildDepTree(self):
934         ''' create a dictionary with po and deps '''
935         depTree = { }
936         for txmbr in self.tsInfo:
937             for dep in txmbr.depends_on:
938                 depTree.setdefault(dep, []).append(txmbr.po)
939         # self._printDepTree(depTree)
940         return depTree
941
942     def _printDepTree(self, tree):
943         for pkg, l in tree.iteritems():
944             print pkg
945             for p in l:
946                 print "\t", p
947
948     def _printTransaction(self):
949         #transaction set states
950         state = { TS_UPDATE     : "update",
951                   TS_INSTALL    : "install",
952                   TS_TRUEINSTALL: "trueinstall",
953                   TS_ERASE      : "erase",
954                   TS_OBSOLETED  : "obsoleted",
955                   TS_OBSOLETING : "obsoleting",
956                   TS_AVAILABLE  : "available",
957                   TS_UPDATED    : "updated"}
958
959         self.verbose_logger.log(logginglevels.DEBUG_2,"TSINFO: Current Transaction : %i member(s) " % len(self.tsInfo))
960         for txmbr in self.tsInfo:
961             msg = "  %-11s : %s " % (state[txmbr.output_state],txmbr.po)
962             self.verbose_logger.log(logginglevels.DEBUG_2, msg)
963             for po,rel in txmbr.relatedto:
964                 msg = "                   %s : %s" % (rel,po)
965                 self.verbose_logger.log(logginglevels.DEBUG_2, msg)
966                 
967                                     
968     def _getPackagesToRemove(self,po,deptree,toRemove):
969         '''
970         get the (related) pos to remove.
971         '''
972         toRemove.add(po)
973         for txmbr in self.tsInfo.getMembers(po.pkgtup):
974             for pkg in (txmbr.updates + txmbr.obsoletes):
975                 toRemove.add(pkg)
976                 self._getDepsToRemove(pkg, deptree, toRemove)
977         self._getDepsToRemove(po, deptree, toRemove)
978
979     def _getDepsToRemove(self,po, deptree, toRemove):
980         for dep in deptree.get(po, []): # Loop trough all deps of po
981             for txmbr in self.tsInfo.getMembers(dep.pkgtup):
982                 for pkg in (txmbr.updates + txmbr.obsoletes):
983                     toRemove.add(pkg)
984             toRemove.add(dep)
985             self._getDepsToRemove(dep, deptree, toRemove)
986
987     def runTransaction(self, cb):
988         """takes an rpm callback object, performs the transaction"""
989
990         self.plugins.run('pretrans')
991
992         errors = self.ts.run(cb.callback, '')
993         # ts.run() exit codes are, hmm, "creative": None means all ok, empty 
994         # list means some errors happened in the transaction and non-empty 
995         # list that there were errors preventing the ts from starting...
996         
997         # make resultobject - just a plain yumgenericholder object
998         resultobject = misc.GenericHolder()
999         resultobject.return_code = 0
1000         if errors is None:
1001             pass
1002         elif len(errors) == 0:
1003             errstring = _('Warning: scriptlet or other non-fatal errors occurred during transaction.')
1004             self.verbose_logger.debug(errstring)
1005             resultobject.return_code = 1
1006         else:
1007             raise Errors.YumBaseError, errors
1008                           
1009         if not self.conf.keepcache:
1010             self.cleanUsedHeadersPackages()
1011         
1012         for i in ('ts_all_fn', 'ts_done_fn'):
1013             if hasattr(cb, i):
1014                 fn = getattr(cb, i)
1015                 try:
1016                     misc.unlink_f(fn)
1017                 except (IOError, OSError), e:
1018                     self.logger.critical(_('Failed to remove transaction file %s') % fn)
1019
1020         self.rpmdb.dropCachedData() # drop out the rpm cache so we don't step on bad hdr indexes
1021         self.plugins.run('posttrans')
1022         # sync up what just happened versus what is in the rpmdb
1023         self.verifyTransaction()
1024         return resultobject
1025
1026     def verifyTransaction(self):
1027         """checks that the transaction did what we expected it to do. Also 
1028            propagates our external yumdb info"""
1029         
1030         # check to see that the rpmdb and the tsInfo roughly matches
1031         # push package object metadata outside of rpmdb into yumdb
1032         # delete old yumdb metadata entries
1033         
1034         # for each pkg in the tsInfo
1035         # if it is an install - see that the pkg is installed
1036         # if it is a remove - see that the pkg is no longer installed, provided
1037         #    that there is not also an install of this pkg in the tsInfo (reinstall)
1038         # for any kind of install add from_repo to the yumdb, and the cmdline
1039         # and the install reason
1040
1041         self.rpmdb.dropCachedData()
1042         for txmbr in self.tsInfo:
1043             if txmbr.output_state in TS_INSTALL_STATES:
1044                 if not self.rpmdb.contains(po=txmbr.po):
1045                     # maybe a file log here, too
1046                     # but raising an exception is not going to do any good
1047                     self.logger.critical(_('%s was supposed to be installed' \
1048                                            ' but is not!' % txmbr.po))
1049                     continue
1050                 po = self.rpmdb.searchPkgTuple(txmbr.pkgtup)[0]
1051                 rpo = txmbr.po
1052                 po.yumdb_info.from_repo = rpo.repoid
1053                 po.yumdb_info.reason = txmbr.reason
1054                 po.yumdb_info.releasever = self.yumvar['releasever']
1055                 if hasattr(self, 'cmds') and self.cmds:
1056                     po.yumdb_info.command_line = ' '.join(self.cmds)
1057                 csum = rpo.returnIdSum()
1058                 if csum is not None:
1059                     po.yumdb_info.checksum_type = str(csum[0])
1060                     po.yumdb_info.checksum_data = str(csum[1])
1061
1062                 if isinstance(rpo, YumLocalPackage):
1063                     try:
1064                         st = os.stat(rpo.localPkg())
1065                         lp_ctime = str(int(st.st_ctime))
1066                         lp_mtime = str(int(st.st_mtime))
1067                         po.yumdb_info.from_repo_revision  = lp_ctime
1068                         po.yumdb_info.from_repo_timestamp = lp_mtime
1069                     except: pass
1070
1071                 if not hasattr(rpo.repo, 'repoXML'):
1072                     continue
1073
1074                 md = rpo.repo.repoXML
1075                 if md and md.revision is not None:
1076                     po.yumdb_info.from_repo_revision  = str(md.revision)
1077                 if md:
1078                     po.yumdb_info.from_repo_timestamp = str(md.timestamp)
1079             
1080             elif txmbr.output_state in TS_REMOVE_STATES:
1081                 if self.rpmdb.contains(po=txmbr.po):
1082                     if not self.tsInfo.getMembersWithState(pkgtup=txmbr.pkgtup,
1083                                 output_states=TS_INSTALL_STATES):
1084                         # maybe a file log here, too
1085                         # but raising an exception is not going to do any good
1086                         self.logger.critical(_('%s was supposed to be removed' \
1087                                                ' but is not!' % txmbr.po))
1088                         continue
1089                 yumdb_item = self.rpmdb.yumdb.get_package(po=txmbr.po)
1090                 yumdb_item.clean()
1091             else:
1092                 self.verbose_logger.log(logginglevels.DEBUG_2, 'What is this? %s' % txmbr.po)
1093
1094         self.rpmdb.dropCachedData()
1095
1096     def costExcludePackages(self):
1097         """exclude packages if they have an identical package in another repo
1098         and their repo.cost value is the greater one"""
1099         
1100         # check to see if the cost per repo is anything other than equal
1101         # if all the repo.costs are equal then don't bother running things
1102         costs = {}
1103         for r in self.repos.listEnabled():
1104             costs[r.cost] = 1
1105
1106         if len(costs) <= 1: # if all of our costs are the same then return
1107             return
1108             
1109         def _sort_by_cost(a, b):
1110             if a.repo.cost < b.repo.cost:
1111                 return -1
1112             if a.repo.cost == b.repo.cost:
1113                 return 0
1114             if a.repo.cost > b.repo.cost:
1115                 return 1
1116                 
1117         pkgdict = {}
1118         for po in self.pkgSack:
1119             if not pkgdict.has_key(po.pkgtup):
1120                 pkgdict[po.pkgtup] = []
1121             pkgdict[po.pkgtup].append(po)
1122         
1123         for pkgs in pkgdict.values():
1124             if len(pkgs) == 1:
1125                 continue
1126                 
1127             pkgs.sort(_sort_by_cost)
1128             lowcost = pkgs[0].repo.cost
1129             #print '%s : %s : %s' % (pkgs[0], pkgs[0].repo, pkgs[0].repo.cost)
1130             for pkg in pkgs[1:]:
1131                 if pkg.repo.cost > lowcost:
1132                     msg = _('excluding for cost: %s from %s') % (pkg, pkg.repo.id)
1133                     self.verbose_logger.log(logginglevels.DEBUG_3, msg)
1134                     pkg.repo.sack.delPackage(pkg)
1135             
1136
1137     def excludePackages(self, repo=None):
1138         """removes packages from packageSacks based on global exclude lists,
1139            command line excludes and per-repository excludes, takes optional 
1140            repo object to use."""
1141
1142         if "all" in self.conf.disable_excludes:
1143             return
1144         
1145         # if not repo: then assume global excludes, only
1146         # if repo: then do only that repos' packages and excludes
1147         
1148         if not repo: # global only
1149             if "main" in self.conf.disable_excludes:
1150                 return
1151             excludelist = self.conf.exclude
1152             repoid = None
1153         else:
1154             if repo.id in self.conf.disable_excludes:
1155                 return
1156             excludelist = repo.getExcludePkgList()
1157             repoid = repo.id
1158
1159         for match in excludelist:
1160             self.pkgSack.addPackageExcluder(repoid, 'exclude.match', match)
1161
1162     def includePackages(self, repo):
1163         """removes packages from packageSacks based on list of packages, to include.
1164            takes repoid as a mandatory argument."""
1165         
1166         includelist = repo.getIncludePkgList()
1167         
1168         if len(includelist) == 0:
1169             return
1170         
1171         for match in includelist:
1172             self.pkgSack.addPackageExcluder(repo.id, 'include.match', match)
1173         self.pkgSack.addPackageExcluder(repo.id, 'exclude.*')
1174         
1175     def doLock(self, lockfile = YUM_PID_FILE):
1176         """perform the yum locking, raise yum-based exceptions, not OSErrors"""
1177         
1178         # if we're not root then we don't lock - just return nicely
1179         if self.conf.uid != 0:
1180             return
1181             
1182         root = self.conf.installroot
1183         lockfile = root + '/' + lockfile # lock in the chroot
1184         lockfile = os.path.normpath(lockfile) # get rid of silly preceding extra /
1185         
1186         mypid=str(os.getpid())    
1187         while not self._lock(lockfile, mypid, 0644):
1188             fd = open(lockfile, 'r')
1189             try: oldpid = int(fd.readline())
1190             except ValueError:
1191                 # bogus data in the pid file. Throw away.
1192                 self._unlock(lockfile)
1193             else:
1194                 if oldpid == os.getpid(): # if we own the lock, we're fine
1195                     break
1196                 try: os.kill(oldpid, 0)
1197                 except OSError, e:
1198                     if e[0] == errno.ESRCH:
1199                         # The pid doesn't exist
1200                         self._unlock(lockfile)
1201                     else:
1202                         # Whoa. What the heck happened?
1203                         msg = _('Unable to check if PID %s is active') % oldpid
1204                         raise Errors.LockError(1, msg, oldpid)
1205                 else:
1206                     # Another copy seems to be running.
1207                     msg = _('Existing lock %s: another copy is running as pid %s.') % (lockfile, oldpid)
1208                     raise Errors.LockError(0, msg, oldpid)
1209         # We've got the lock, store it so we can auto-unlock on __del__...
1210         self._lockfile = lockfile
1211     
1212     def doUnlock(self, lockfile=None):
1213         """do the unlock for yum"""
1214         
1215         # if we're not root then we don't lock - just return nicely
1216         #  Note that we can get here from __del__, so if we haven't created
1217         # YumBase.conf we don't want to do so here as creating stuff inside
1218         # __del__ is bad.
1219         if hasattr(self, 'preconf') or self.conf.uid != 0:
1220             return
1221         
1222         if lockfile is not None:
1223             root = self.conf.installroot
1224             lockfile = root + '/' + lockfile # lock in the chroot
1225         elif self._lockfile is None:
1226             return # Don't delete other people's lock files on __del__
1227         else:
1228             lockfile = self._lockfile # Get the value we locked with
1229         
1230         self._unlock(lockfile)
1231         self._lockfile = None
1232         
1233     def _lock(self, filename, contents='', mode=0777):
1234         lockdir = os.path.dirname(filename)
1235         try:
1236             if not os.path.exists(lockdir):
1237                 os.makedirs(lockdir, mode=0755)
1238             fd = os.open(filename, os.O_EXCL|os.O_CREAT|os.O_WRONLY, mode)    
1239         except OSError, msg:
1240             if not msg.errno == errno.EEXIST: raise msg
1241             return 0
1242         else:
1243             os.write(fd, contents)
1244             os.close(fd)
1245             return 1
1246     
1247     def _unlock(self, filename):
1248         misc.unlink_f(filename)
1249
1250     def verifyPkg(self, fo, po, raiseError):
1251         """verifies the package is what we expect it to be
1252            raiseError  = defaults to 0 - if 1 then will raise
1253            a URLGrabError if the file does not check out.
1254            otherwise it returns false for a failure, true for success"""
1255         failed = False
1256
1257         if type(fo) is types.InstanceType:
1258             fo = fo.filename
1259         
1260         if fo != po.localPkg():
1261             po.localpath = fo
1262
1263         if not po.verifyLocalPkg():
1264             failed = True
1265         else:
1266             ylp = YumLocalPackage(self.rpmdb.readOnlyTS(), fo)
1267             if ylp.pkgtup != po.pkgtup:
1268                 failed = True
1269
1270
1271         if failed:            
1272             # if the file is wrong AND it is >= what we expected then it
1273             # can't be redeemed. If we can, kill it and start over fresh
1274             cursize = os.stat(fo)[6]
1275             totsize = long(po.size)
1276             if cursize >= totsize and not po.repo.cache:
1277                 # if the path to the file is NOT inside the cachedir then don't
1278                 # unlink it b/c it is probably a file:// url and possibly
1279                 # unlinkable
1280                 if fo.startswith(po.repo.cachedir):
1281                     os.unlink(fo)
1282
1283             if raiseError:
1284                 raise URLGrabError(-1, _('Package does not match intended download'))
1285             else:
1286                 return False
1287
1288         
1289         return True
1290         
1291         
1292     def verifyChecksum(self, fo, checksumType, csum):
1293         """Verify the checksum of the file versus the 
1294            provided checksum"""
1295
1296         try:
1297             filesum = misc.checksum(checksumType, fo)
1298         except Errors.MiscError, e:
1299             raise URLGrabError(-3, _('Could not perform checksum'))
1300             
1301         if filesum != csum:
1302             raise URLGrabError(-1, _('Package does not match checksum'))
1303         
1304         return 0
1305
1306     def downloadPkgs(self, pkglist, callback=None, callback_total=None):
1307         def mediasort(apo, bpo):
1308             # FIXME: we should probably also use the mediaid; else we
1309             # could conceivably ping-pong between different disc1's
1310             a = apo.getDiscNum()
1311             b = bpo.getDiscNum()
1312             if a is None and b is None:
1313                 return cmp(apo, bpo)
1314             if a is None:
1315                 return -1
1316             if b is None:
1317                 return 1
1318             if a < b:
1319                 return -1
1320             elif a > b:
1321                 return 1
1322             return 0
1323         
1324         """download list of package objects handed to you, output based on
1325            callback, raise yum.Errors.YumBaseError on problems"""
1326
1327         errors = {}
1328         def adderror(po, msg):
1329             errors.setdefault(po, []).append(msg)
1330
1331         self.plugins.run('predownload', pkglist=pkglist)
1332         repo_cached = False
1333         remote_pkgs = []
1334         remote_size = 0
1335         for po in pkglist:
1336             if hasattr(po, 'pkgtype') and po.pkgtype == 'local':
1337                 continue
1338                     
1339             local = po.localPkg()
1340             if os.path.exists(local):
1341                 if not self.verifyPkg(local, po, False):
1342                     if po.repo.cache:
1343                         repo_cached = True
1344                         adderror(po, _('package fails checksum but caching is '
1345                             'enabled for %s') % po.repo.id)
1346                 else:
1347                     self.verbose_logger.debug(_("using local copy of %s") %(po,))
1348                     continue
1349                         
1350             remote_pkgs.append(po)
1351             remote_size += po.size
1352             
1353             # caching is enabled and the package 
1354             # just failed to check out there's no 
1355             # way to save this, report the error and return
1356             if (self.conf.cache or repo_cached) and errors:
1357                 return errors
1358                 
1359
1360         remote_pkgs.sort(mediasort)
1361         #  This is kind of a hack and does nothing in non-Fedora versions,
1362         # we'll fix it one way or anther soon.
1363         if (hasattr(urlgrabber.progress, 'text_meter_total_size') and
1364             len(remote_pkgs) > 1):
1365             urlgrabber.progress.text_meter_total_size(remote_size)
1366         beg_download = time.time()
1367         i = 0
1368         local_size = 0
1369         for po in remote_pkgs:
1370             #  Recheck if the file is there, works around a couple of weird
1371             # edge cases.
1372             local = po.localPkg()
1373             i += 1
1374             if os.path.exists(local):
1375                 if self.verifyPkg(local, po, False):
1376                     self.verbose_logger.debug(_("using local copy of %s") %(po,))
1377                     remote_size -= po.size
1378                     if hasattr(urlgrabber.progress, 'text_meter_total_size'):
1379                         urlgrabber.progress.text_meter_total_size(remote_size,
1380                                                                   local_size)
1381                     continue
1382                 if os.path.getsize(local) >= po.size:
1383                     os.unlink(local)
1384
1385             checkfunc = (self.verifyPkg, (po, 1), {})
1386             dirstat = os.statvfs(po.repo.pkgdir)
1387             if (dirstat.f_bavail * dirstat.f_bsize) <= long(po.size):
1388                 adderror(po, _('Insufficient space in download directory %s\n'
1389                         "    * free   %s\n"
1390                         "    * needed %s") %
1391                          (po.repo.pkgdir,
1392                           format_number(dirstat.f_bavail * dirstat.f_bsize),
1393                           format_number(po.size)))
1394                 continue
1395             
1396             try:
1397                 if i == 1 and not local_size and remote_size == po.size:
1398                     text = os.path.basename(po.relativepath)
1399                 else:
1400                     text = '(%s/%s): %s' % (i, len(remote_pkgs),
1401                                             os.path.basename(po.relativepath))
1402                 mylocal = po.repo.getPackage(po,
1403                                    checkfunc=checkfunc,
1404                                    text=text,
1405                                    cache=po.repo.http_caching != 'none',
1406                                    )
1407                 local_size += po.size
1408                 if hasattr(urlgrabber.progress, 'text_meter_total_size'):
1409                     urlgrabber.progress.text_meter_total_size(remote_size,
1410                                                               local_size)
1411             except Errors.RepoError, e:
1412                 adderror(po, str(e))
1413             else:
1414                 po.localpath = mylocal
1415                 if errors.has_key(po):
1416                     del errors[po]
1417
1418         if hasattr(urlgrabber.progress, 'text_meter_total_size'):
1419             urlgrabber.progress.text_meter_total_size(0)
1420         if callback_total is not None and not errors:
1421             callback_total(remote_pkgs, remote_size, beg_download)
1422
1423         self.plugins.run('postdownload', pkglist=pkglist, errors=errors)
1424
1425         return errors
1426
1427     def verifyHeader(self, fo, po, raiseError):
1428         """check the header out via it's naevr, internally"""
1429         if type(fo) is types.InstanceType:
1430             fo = fo.filename
1431             
1432         try:
1433             hlist = rpm.readHeaderListFromFile(fo)
1434             hdr = hlist[0]
1435         except (rpm.error, IndexError):
1436             if raiseError:
1437                 raise URLGrabError(-1, _('Header is not complete.'))
1438             else:
1439                 return 0
1440                 
1441         yip = YumInstalledPackage(hdr) # we're using YumInstalledPackage b/c
1442                                        # it takes headers <shrug>
1443         if yip.pkgtup != po.pkgtup:
1444             if raiseError:
1445                 raise URLGrabError(-1, 'Header does not match intended download')
1446             else:
1447                 return 0
1448         
1449         return 1
1450         
1451     def downloadHeader(self, po):
1452         """download a header from a package object.
1453            output based on callback, raise yum.Errors.YumBaseError on problems"""
1454
1455         if hasattr(po, 'pkgtype') and po.pkgtype == 'local':
1456             return
1457                 
1458         errors = {}
1459         local =  po.localHdr()
1460         repo = self.repos.getRepo(po.repoid)
1461         if os.path.exists(local):
1462             try:
1463                 result = self.verifyHeader(local, po, raiseError=1)
1464             except URLGrabError, e:
1465                 # might add a check for length of file - if it is < 
1466                 # required doing a reget
1467                 misc.unlink_f(local)
1468             else:
1469                 po.hdrpath = local
1470                 return
1471         else:
1472             if self.conf.cache:
1473                 raise Errors.RepoError, \
1474                 _('Header not in local cache and caching-only mode enabled. Cannot download %s') % po.hdrpath
1475         
1476         if self.dsCallback: self.dsCallback.downloadHeader(po.name)
1477         
1478         try:
1479             if not os.path.exists(repo.hdrdir):
1480                 os.makedirs(repo.hdrdir)
1481             checkfunc = (self.verifyHeader, (po, 1), {})
1482             hdrpath = repo.getHeader(po, checkfunc=checkfunc,
1483                     cache=repo.http_caching != 'none',
1484                     )
1485         except Errors.RepoError, e:
1486             saved_repo_error = e
1487             try:
1488                 misc.unlink_f(local)
1489             except OSError, e:
1490                 raise Errors.RepoError, saved_repo_error
1491             else:
1492                 raise Errors.RepoError, saved_repo_error
1493         else:
1494             po.hdrpath = hdrpath
1495             return
1496
1497     def sigCheckPkg(self, po):
1498         '''
1499         Take a package object and attempt to verify GPG signature if required
1500
1501         Returns (result, error_string) where result is:
1502             - 0 - GPG signature verifies ok or verification is not required.
1503             - 1 - GPG verification failed but installation of the right GPG key
1504                   might help.
1505             - 2 - Fatal GPG verifcation error, give up.
1506         '''
1507         if hasattr(po, 'pkgtype') and po.pkgtype == 'local':
1508             check = self.conf.gpgcheck
1509             hasgpgkey = 0
1510         else:
1511             repo = self.repos.getRepo(po.repoid)
1512             check = repo.gpgcheck
1513             hasgpgkey = not not repo.gpgkey 
1514         
1515         if check:
1516             ts = self.rpmdb.readOnlyTS()
1517             sigresult = rpmUtils.miscutils.checkSig(ts, po.localPkg())
1518             localfn = os.path.basename(po.localPkg())
1519             
1520             if sigresult == 0:
1521                 result = 0
1522                 msg = ''
1523
1524             elif sigresult == 1:
1525                 if hasgpgkey:
1526                     result = 1
1527                 else:
1528                     result = 2
1529                 msg = _('Public key for %s is not installed') % localfn
1530
1531             elif sigresult == 2:
1532                 result = 2
1533                 msg = _('Problem opening package %s') % localfn
1534
1535             elif sigresult == 3:
1536                 if hasgpgkey:
1537                     result = 1
1538                 else:
1539                     result = 2
1540                 result = 1
1541                 msg = _('Public key for %s is not trusted') % localfn
1542
1543             elif sigresult == 4:
1544                 result = 2 
1545                 msg = _('Package %s is not signed') % localfn
1546             
1547         else:
1548             result =0
1549             msg = ''
1550
1551         return result, msg
1552
1553     def cleanUsedHeadersPackages(self):
1554         filelist = []
1555         for txmbr in self.tsInfo:
1556             if txmbr.po.state not in TS_INSTALL_STATES:
1557                 continue
1558             if txmbr.po.repoid == "installed":
1559                 continue
1560             if not self.repos.repos.has_key(txmbr.po.repoid):
1561                 continue
1562             
1563             # make sure it's not a local file
1564             repo = self.repos.repos[txmbr.po.repoid]
1565             local = False
1566             for u in repo.baseurl:
1567                 if u.startswith("file:"):
1568                     local = True
1569                     break
1570                 
1571             if local:
1572                 filelist.extend([txmbr.po.localHdr()])
1573             else:
1574                 filelist.extend([txmbr.po.localPkg(), txmbr.po.localHdr()])
1575
1576         # now remove them
1577         for fn in filelist:
1578             if not os.path.exists(fn):
1579                 continue
1580             try:
1581                 misc.unlink_f(fn)
1582             except OSError, e:
1583                 self.logger.warning(_('Cannot remove %s'), fn)
1584                 continue
1585             else:
1586                 self.verbose_logger.log(logginglevels.DEBUG_4,
1587                     _('%s removed'), fn)
1588         
1589     def cleanHeaders(self):
1590         exts = ['hdr']
1591         return self._cleanFiles(exts, 'hdrdir', 'header')
1592
1593     def cleanPackages(self):
1594         exts = ['rpm']
1595         return self._cleanFiles(exts, 'pkgdir', 'package')
1596
1597     def cleanSqlite(self):
1598         exts = ['sqlite', 'sqlite.bz2']
1599         return self._cleanFiles(exts, 'cachedir', 'sqlite')
1600
1601     def cleanMetadata(self):
1602         exts = ['xml.gz', 'xml', 'cachecookie', 'mirrorlist.txt', 'asc']
1603         # Metalink is also here, but is a *.xml file
1604         return self._cleanFiles(exts, 'cachedir', 'metadata') 
1605
1606     def cleanExpireCache(self):
1607         exts = ['cachecookie', 'mirrorlist.txt']
1608         return self._cleanFiles(exts, 'cachedir', 'metadata')
1609
1610     def _cleanFiles(self, exts, pathattr, filetype):
1611         filelist = []
1612         removed = 0
1613         for ext in exts:
1614             for repo in self.repos.listEnabled():
1615                 path = getattr(repo, pathattr)
1616                 if os.path.exists(path) and os.path.isdir(path):
1617                     filelist = misc.getFileList(path, ext, filelist)
1618
1619         for item in filelist:
1620             try:
1621                 misc.unlink_f(item)
1622             except OSError, e:
1623                 self.logger.critical(_('Cannot remove %s file %s'), filetype, item)
1624                 continue
1625             else:
1626                 self.verbose_logger.log(logginglevels.DEBUG_4,
1627                     _('%s file %s removed'), filetype, item)
1628                 removed+=1
1629         msg = _('%d %s files removed') % (removed, filetype)
1630         return 0, [msg]
1631
1632     def doPackageLists(self, pkgnarrow='all', patterns=None, showdups=None,
1633                        ignore_case=False):
1634         """generates lists of packages, un-reduced, based on pkgnarrow option"""
1635
1636         if showdups is None:
1637             showdups = self.conf.showdupesfromrepos
1638         ygh = misc.GenericHolder(iter=pkgnarrow)
1639         
1640         installed = []
1641         available = []
1642         reinstall_available = []
1643         old_available = []
1644         updates = []
1645         obsoletes = []
1646         obsoletesTuples = []
1647         recent = []
1648         extras = []
1649
1650         ic = ignore_case
1651         # list all packages - those installed and available, don't 'think about it'
1652         if pkgnarrow == 'all': 
1653             dinst = {}
1654             ndinst = {} # Newest versions by name.arch
1655             for po in self.rpmdb.returnPackages(patterns=patterns,
1656                                                 ignore_case=ic):
1657                 dinst[po.pkgtup] = po
1658                 if showdups:
1659                     continue
1660                 key = (po.name, po.arch)
1661                 if key not in ndinst or po.verGT(ndinst[key]):
1662                     ndinst[key] = po
1663             installed = dinst.values()
1664                         
1665             if showdups:
1666                 avail = self.pkgSack.returnPackages(patterns=patterns,
1667                                                     ignore_case=ic)
1668             else:
1669                 try:
1670                     avail = self.pkgSack.returnNewestByNameArch(patterns=patterns,
1671                                                               ignore_case=ic)
1672                 except Errors.PackageSackError:
1673                     avail = []
1674             
1675             for pkg in avail:
1676                 if showdups:
1677                     if pkg.pkgtup in dinst:
1678                         reinstall_available.append(pkg)
1679                     else:
1680                         available.append(pkg)
1681                 else:
1682                     key = (pkg.name, pkg.arch)
1683                     if pkg.pkgtup in dinst:
1684                         reinstall_available.append(pkg)
1685                     elif key not in ndinst or pkg.verGT(ndinst[key]):
1686                         available.append(pkg)
1687                     else:
1688                         old_available.append(pkg)
1689
1690         # produce the updates list of tuples
1691         elif pkgnarrow == 'updates':
1692             for (n,a,e,v,r) in self.up.getUpdatesList():
1693                 matches = self.pkgSack.searchNevra(name=n, arch=a, epoch=e, 
1694                                                    ver=v, rel=r)
1695                 if len(matches) > 1:
1696                     updates.append(matches[0])
1697                     self.verbose_logger.log(logginglevels.DEBUG_1,
1698                         _('More than one identical match in sack for %s'), 
1699                         matches[0])
1700                 elif len(matches) == 1:
1701                     updates.append(matches[0])
1702                 else:
1703                     self.verbose_logger.log(logginglevels.DEBUG_1,
1704                         _('Nothing matches %s.%s %s:%s-%s from update'), n,a,e,v,r)
1705             if patterns:
1706                 exactmatch, matched, unmatched = \
1707                    parsePackages(updates, patterns, casematch=not ignore_case)
1708                 updates = exactmatch + matched
1709
1710         # installed only
1711         elif pkgnarrow == 'installed':
1712             installed = self.rpmdb.returnPackages(patterns=patterns,
1713                                                   ignore_case=ic)
1714         
1715         # available in a repository
1716         elif pkgnarrow == 'available':
1717
1718             if showdups:
1719                 avail = self.pkgSack.returnPackages(patterns=patterns,
1720                                                     ignore_case=ic)
1721             else:
1722                 try:
1723                     avail = self.pkgSack.returnNewestByNameArch(patterns=patterns,
1724                                                               ignore_case=ic)
1725                 except Errors.PackageSackError:
1726                     avail = []
1727             
1728             for pkg in avail:
1729                 if showdups:
1730                     if self.rpmdb.contains(po=pkg):
1731                         reinstall_available.append(pkg)
1732                     else:
1733                         available.append(pkg)
1734                 else:
1735                     ipkgs = self.rpmdb.searchNevra(pkg.name, arch=pkg.arch)
1736                     if ipkgs:
1737                         latest = sorted(ipkgs, reverse=True)[0]
1738                     if not ipkgs or pkg.verGT(latest):
1739                         available.append(pkg)
1740                     elif pkg.verEQ(latest):
1741                         reinstall_available.append(pkg)
1742                     else:
1743                         old_available.append(pkg)
1744
1745         # not in a repo but installed
1746         elif pkgnarrow == 'extras':
1747             # we must compare the installed set versus the repo set
1748             # anything installed but not in a repo is an extra
1749             avail = self.pkgSack.simplePkgList(patterns=patterns,
1750                                                ignore_case=ic)
1751             avail = set(avail)
1752             for po in self.rpmdb.returnPackages(patterns=patterns,
1753                                                 ignore_case=ic):
1754                 if po.pkgtup not in avail:
1755                     extras.append(po)
1756
1757         # obsoleting packages (and what they obsolete)
1758         elif pkgnarrow == 'obsoletes':
1759             self.conf.obsoletes = 1
1760
1761             for (pkgtup, instTup) in self.up.getObsoletesTuples():
1762                 (n,a,e,v,r) = pkgtup
1763                 pkgs = self.pkgSack.searchNevra(name=n, arch=a, ver=v, rel=r, epoch=e)
1764                 instpo = self.rpmdb.searchPkgTuple(instTup)[0] # the first one
1765                 for po in pkgs:
1766                     obsoletes.append(po)
1767                     obsoletesTuples.append((po, instpo))
1768             if patterns:
1769                 exactmatch, matched, unmatched = \
1770                    parsePackages(obsoletes, patterns, casematch=not ignore_case)
1771                 obsoletes = exactmatch + matched
1772                 matched_obsoletes = set(obsoletes)
1773                 nobsoletesTuples = []
1774                 for po, instpo in obsoletesTuples:
1775                     if po not in matched_obsoletes:
1776                         continue
1777                     nobsoletesTuples.append((po, instpo))
1778                 obsoletesTuples = nobsoletesTuples
1779         
1780         # packages recently added to the repositories
1781         elif pkgnarrow == 'recent':
1782             now = time.time()
1783             recentlimit = now-(self.conf.recent*86400)
1784             ftimehash = {}
1785             if showdups:
1786                 avail = self.pkgSack.returnPackages(patterns=patterns,
1787                                                     ignore_case=ic)
1788             else:
1789                 try:
1790                     avail = self.pkgSack.returnNewestByNameArch(patterns=patterns,
1791                                                               ignore_case=ic)
1792                 except Errors.PackageSackError:
1793                     avail = []
1794             
1795             for po in avail:
1796                 ftime = int(po.filetime)
1797                 if ftime > recentlimit:
1798                     if not ftimehash.has_key(ftime):
1799                         ftimehash[ftime] = [po]
1800                     else:
1801                         ftimehash[ftime].append(po)
1802
1803             for sometime in ftimehash:
1804                 for po in ftimehash[sometime]:
1805                     recent.append(po)
1806         
1807         
1808         ygh.installed = installed
1809         ygh.available = available
1810         ygh.reinstall_available = reinstall_available
1811         ygh.old_available = old_available
1812         ygh.updates = updates
1813         ygh.obsoletes = obsoletes
1814         ygh.obsoletesTuples = obsoletesTuples
1815         ygh.recent = recent
1816         ygh.extras = extras
1817
1818         return ygh
1819
1820
1821         
1822     def findDeps(self, pkgs):
1823         """
1824         Return the dependencies for a given package object list, as well
1825         possible solutions for those dependencies.
1826            
1827         Returns the deps as a dict of dicts::
1828             packageobject = [reqs] = [list of satisfying pkgs]
1829         """
1830         
1831         results = {}
1832
1833         for pkg in pkgs:
1834             results[pkg] = {} 
1835             reqs = pkg.requires
1836             reqs.sort()
1837             pkgresults = results[pkg] # shorthand so we don't have to do the
1838                                       # double bracket thing
1839             
1840             for req in reqs:
1841                 (r,f,v) = req
1842                 if r.startswith('rpmlib('):
1843                     continue
1844                 
1845                 satisfiers = []
1846
1847                 for po in self.whatProvides(r, f, v):
1848                     satisfiers.append(po)
1849
1850                 pkgresults[req] = satisfiers
1851         
1852         return results
1853     
1854     # pre 3.2.10 API used to always showdups, so that's the default atm.
1855     def searchGenerator(self, fields, criteria, showdups=True, keys=False):
1856         """Generator method to lighten memory load for some searches.
1857            This is the preferred search function to use. Setting keys to True
1858            will use the search keys that matched in the sorting, and return
1859            the search keys in the results. """
1860         sql_fields = []
1861         for f in fields:
1862             if RPM_TO_SQLITE.has_key(f):
1863                 sql_fields.append(RPM_TO_SQLITE[f])
1864             else:
1865                 sql_fields.append(f)
1866
1867         matched_values = {}
1868
1869         # yield the results in order of most terms matched first
1870         sorted_lists = {}
1871         tmpres = []
1872         real_crit = []
1873         for s in criteria:
1874             real_crit.append(s)
1875         real_crit_lower = [] # Take the s.lower()'s out of the loop
1876         rcl2c = {}
1877         for s in criteria:
1878             real_crit_lower.append(s.lower())
1879             rcl2c[s.lower()] = s
1880
1881         for sack in self.pkgSack.sacks.values():
1882             tmpres.extend(sack.searchPrimaryFieldsMultipleStrings(sql_fields, real_crit))
1883
1884         def results2sorted_lists(tmpres, sorted_lists):
1885             for (po, count) in tmpres:
1886                 # check the pkg for sanity
1887                 # pop it into the sorted lists
1888                 tmpkeys   = set()
1889                 tmpvalues = []
1890                 if count not in sorted_lists: sorted_lists[count] = []
1891                 for s in real_crit_lower:
1892                     for field in fields:
1893                         value = to_unicode(getattr(po, field))
1894                         if value and value.lower().find(s) != -1:
1895                             tmpvalues.append(value)
1896                             tmpkeys.add(rcl2c[s])
1897
1898                 if len(tmpvalues) > 0:
1899                     sorted_lists[count].append((po, tmpkeys, tmpvalues))
1900         results2sorted_lists(tmpres, sorted_lists)
1901
1902         tmpres = self.rpmdb.searchPrimaryFieldsMultipleStrings(fields,
1903                                                                real_crit_lower,
1904                                                                lowered=True)
1905         # close our rpmdb connection so we can ctrl-c, kthxbai
1906         self.closeRpmDB()
1907
1908         results2sorted_lists(tmpres, sorted_lists)
1909         del tmpres
1910
1911         # By default just sort using package sorting
1912         sort_func = operator.itemgetter(0)
1913         if keys:
1914             # Take into account the keys found, as well
1915             sort_func = lambda x: "%s%s" % ("\0".join(sorted(x[1])), str(x[0]))
1916         yielded = {}
1917         for val in reversed(sorted(sorted_lists)):
1918             for (po, ks, vs) in sorted(sorted_lists[val], key=sort_func):
1919                 if not showdups and (po.name, po.arch) in yielded:
1920                     continue
1921
1922                 if keys:
1923                     yield (po, ks, vs)
1924                 else:
1925                     yield (po, vs)
1926
1927                 if not showdups:
1928                     yielded[(po.name, po.arch)] = 1
1929
1930
1931     def searchPackages(self, fields, criteria, callback=None):
1932         """Search specified fields for matches to criteria
1933            optional callback specified to print out results
1934            as you go. Callback is a simple function of:
1935            callback(po, matched values list). It will 
1936            just return a dict of dict[po]=matched values list"""
1937         warnings.warn(_('searchPackages() will go away in a future version of Yum.\
1938                       Use searchGenerator() instead. \n'),
1939                 Errors.YumFutureDeprecationWarning, stacklevel=2)           
1940         matches = {}
1941         match_gen = self.searchGenerator(fields, criteria)
1942         
1943         for (po, matched_strings) in match_gen:
1944             if callback:
1945                 callback(po, matched_strings)
1946             if not matches.has_key(po):
1947                 matches[po] = []
1948             
1949             matches[po].extend(matched_strings)
1950         
1951         return matches
1952     
1953     def searchPackageProvides(self, args, callback=None,
1954                               callback_has_matchfor=False):
1955         
1956         matches = {}
1957         for arg in args:
1958             arg = to_unicode(arg)
1959             if not misc.re_glob(arg):
1960                 isglob = False
1961                 if arg[0] != '/':
1962                     canBeFile = False
1963                 else:
1964                     canBeFile = True
1965             else:
1966                 isglob = True
1967                 if arg[0] != '/' and not misc.re_glob(arg[0]):
1968                     canBeFile = False
1969                 else:
1970                     canBeFile = True
1971                 
1972             if not isglob:
1973                 usedDepString = True
1974                 where = self.returnPackagesByDep(arg)
1975             else:
1976                 usedDepString = False
1977                 where = self.pkgSack.searchAll(arg, False)
1978             self.verbose_logger.log(logginglevels.DEBUG_1,
1979                 _('Searching %d packages'), len(where))
1980             
1981             for po in where:
1982                 self.verbose_logger.log(logginglevels.DEBUG_2,
1983                     _('searching package %s'), po)
1984                 tmpvalues = []
1985                 
1986                 if usedDepString:
1987                     tmpvalues.append(arg)
1988
1989                 if not isglob and canBeFile:
1990                     # then it is not a globbed file we have matched it precisely
1991                     tmpvalues.append(arg)
1992                     
1993                 if isglob and canBeFile:
1994                     self.verbose_logger.log(logginglevels.DEBUG_2,
1995                         _('searching in file entries'))
1996                     for thisfile in po.dirlist + po.filelist + po.ghostlist:
1997                         if fnmatch.fnmatch(thisfile, arg):
1998                             tmpvalues.append(thisfile)
1999                 
2000
2001                 self.verbose_logger.log(logginglevels.DEBUG_2,
2002                     _('searching in provides entries'))
2003                 for (p_name, p_flag, (p_e, p_v, p_r)) in po.provides:
2004                     prov = misc.prco_tuple_to_string((p_name, p_flag, (p_e, p_v, p_r)))
2005                     if not usedDepString:
2006                         if fnmatch.fnmatch(p_name, arg) or fnmatch.fnmatch(prov, arg):
2007                             tmpvalues.append(prov)
2008
2009                 if len(tmpvalues) > 0:
2010                     if callback: # No matchfor, on globs
2011                         if not isglob and callback_has_matchfor:
2012                             callback(po, tmpvalues, args)
2013                         else:
2014                             callback(po, tmpvalues)
2015                     matches[po] = tmpvalues
2016         
2017         # installed rpms, too
2018         taglist = ['filelist', 'dirnames', 'provides_names']
2019         for arg in args:
2020             if not misc.re_glob(arg):
2021                 isglob = False
2022                 if arg[0] != '/':
2023                     canBeFile = False
2024                 else:
2025                     canBeFile = True
2026             else:
2027                 isglob = True
2028                 canBeFile = True
2029             
2030             if not isglob:
2031                 where = self.returnInstalledPackagesByDep(arg)
2032                 usedDepString = True
2033                 for po in where:
2034                     tmpvalues = []
2035                     msg = _('Provides-match: %s') % to_unicode(arg)
2036                     tmpvalues.append(msg)
2037
2038                     if len(tmpvalues) > 0:
2039                         if callback:
2040                             if callback_has_matchfor:
2041                                 callback(po, tmpvalues, args)
2042                             else:
2043                                 callback(po, tmpvalues)
2044                         matches[po] = tmpvalues
2045
2046             else:
2047                 usedDepString = False
2048                 where = self.rpmdb
2049                 
2050                 for po in where:
2051                     searchlist = []
2052                     tmpvalues = []
2053                     for tag in taglist:
2054                         tagdata = getattr(po, tag)
2055                         if tagdata is None:
2056                             continue
2057                         if type(tagdata) is types.ListType:
2058                             searchlist.extend(tagdata)
2059                         else:
2060                             searchlist.append(tagdata)
2061                     
2062                     for item in searchlist:
2063                         if fnmatch.fnmatch(item, arg):
2064                             tmpvalues.append(item)
2065                 
2066                     if len(tmpvalues) > 0:
2067                         if callback: # No matchfor, on globs
2068                             callback(po, tmpvalues)
2069                         matches[po] = tmpvalues
2070             
2071             
2072         return matches
2073
2074     def doGroupLists(self, uservisible=0, patterns=None, ignore_case=True):
2075         """returns two lists of groups, installed groups and available groups
2076            optional 'uservisible' bool to tell it whether or not to return
2077            only groups marked as uservisible"""
2078         
2079         
2080         installed = []
2081         available = []
2082
2083         if self.comps.compscount == 0:
2084             raise Errors.GroupsError, _('No group data available for configured repositories')
2085         
2086         if patterns is None:
2087             grps = self.comps.groups
2088         else:
2089             grps = self.comps.return_groups(",".join(patterns),
2090                                             case_sensitive=not ignore_case)
2091         for grp in grps:
2092             if grp.installed:
2093                 if uservisible:
2094                     if grp.user_visible:
2095                         installed.append(grp)
2096                 else:
2097                     installed.append(grp)
2098             else:
2099                 if uservisible:
2100                     if grp.user_visible:
2101                         available.append(grp)
2102                 else:
2103                     available.append(grp)
2104             
2105         return sorted(installed), sorted(available)
2106     
2107     
2108     def groupRemove(self, grpid):
2109         """mark all the packages in this group to be removed"""
2110         
2111         txmbrs_used = []
2112         
2113         thesegroups = self.comps.return_groups(grpid)
2114         if not thesegroups:
2115             raise Errors.GroupsError, _("No Group named %s exists") % grpid
2116
2117         for thisgroup in thesegroups:
2118             thisgroup.toremove = True
2119             pkgs = thisgroup.packages
2120             for pkg in thisgroup.packages:
2121                 txmbrs = self.remove(name=pkg, silence_warnings=True)
2122                 txmbrs_used.extend(txmbrs)
2123                 for txmbr in txmbrs:
2124                     txmbr.groups.append(thisgroup.groupid)
2125             
2126         return txmbrs_used
2127
2128     def groupUnremove(self, grpid):
2129         """unmark any packages in the group from being removed"""
2130         
2131
2132         thesegroups = self.comps.return_groups(grpid)
2133         if not thesegroups:
2134             raise Errors.GroupsError, _("No Group named %s exists") % grpid
2135
2136         for thisgroup in thesegroups:
2137             thisgroup.toremove = False
2138             pkgs = thisgroup.packages
2139             for pkg in thisgroup.packages:
2140                 for txmbr in self.tsInfo:
2141                     if txmbr.po.name == pkg and txmbr.po.state in TS_INSTALL_STATES:
2142                         try:
2143                             txmbr.groups.remove(grpid)
2144                         except ValueError:
2145                             self.verbose_logger.log(logginglevels.DEBUG_1,
2146                                _("package %s was not marked in group %s"), txmbr.po,
2147                                 grpid)
2148                             continue
2149                         
2150                         # if there aren't any other groups mentioned then remove the pkg
2151                         if len(txmbr.groups) == 0:
2152                             self.tsInfo.remove(txmbr.po.pkgtup)
2153         
2154         
2155     def selectGroup(self, grpid, group_package_types=[], enable_group_conditionals=None):
2156         """mark all the packages in the group to be installed
2157            returns a list of transaction members it added to the transaction 
2158            set
2159            Optionally take:
2160            group_package_types=List - overrides self.conf.group_package_types
2161            enable_group_conditionals=Bool - overrides self.conf.enable_group_conditionals
2162         """
2163
2164         if not self.comps.has_group(grpid):
2165             raise Errors.GroupsError, _("No Group named %s exists") % grpid
2166         
2167         txmbrs_used = []
2168         thesegroups = self.comps.return_groups(grpid)
2169      
2170         if not thesegroups:
2171             raise Errors.GroupsError, _("No Group named %s exists") % grpid
2172
2173         package_types = self.conf.group_package_types
2174         if group_package_types:
2175             package_types = group_package_types
2176
2177         for thisgroup in thesegroups:
2178             if thisgroup.selected:
2179                 continue
2180             
2181             thisgroup.selected = True
2182             
2183             pkgs = []
2184             if 'mandatory' in package_types:
2185                 pkgs.extend(thisgroup.mandatory_packages)
2186             if 'default' in package_types:
2187                 pkgs.extend(thisgroup.default_packages)
2188             if 'optional' in package_types:
2189                 pkgs.extend(thisgroup.optional_packages)
2190
2191             for pkg in pkgs:
2192                 self.verbose_logger.log(logginglevels.DEBUG_2,
2193                     _('Adding package %s from group %s'), pkg, thisgroup.groupid)
2194                 try:
2195                     txmbrs = self.install(name = pkg)
2196                 except Errors.InstallError, e:
2197                     self.verbose_logger.debug(_('No package named %s available to be installed'),
2198                         pkg)
2199                 else:
2200                     txmbrs_used.extend(txmbrs)
2201                     for txmbr in txmbrs:
2202                         txmbr.groups.append(thisgroup.groupid)
2203             
2204             group_conditionals = self.conf.enable_group_conditionals
2205             if enable_group_conditionals is not None: # has to be this way so we can set it to False
2206                 group_conditionals = enable_group_conditionals
2207
2208             if group_conditionals:
2209                 for condreq, cond in thisgroup.conditional_packages.iteritems():
2210                     if self.isPackageInstalled(cond):
2211                         try:
2212                             txmbrs = self.install(name = condreq)
2213                         except Errors.InstallError:
2214                             # we don't care if the package doesn't exist
2215                             continue
2216                         else:
2217                             if cond not in self.tsInfo.conditionals:
2218                                 self.tsInfo.conditionals[cond]=[]
2219
2220                         txmbrs_used.extend(txmbrs)
2221                         for txmbr in txmbrs:
2222                             txmbr.groups.append(thisgroup.groupid)
2223                             self.tsInfo.conditionals[cond].append(txmbr.po)
2224                         continue
2225                     # Otherwise we hook into tsInfo.add to make 
2226                     # sure we'll catch it if its added later in this transaction
2227                     pkgs = self.pkgSack.searchNevra(name=condreq)
2228                     if pkgs:
2229                         if self.arch.multilib:
2230                             if self.conf.multilib_policy == 'best':
2231                                 use = []
2232                                 best = self.arch.legit_multi_arches
2233                                 best.append('noarch')
2234                                 for pkg in pkgs:
2235                                     if pkg.arch in best:
2236                                         use.append(pkg)
2237                                 pkgs = use
2238                                
2239                         pkgs = packagesNewestByNameArch(pkgs)
2240
2241                         if not self.tsInfo.conditionals.has_key(cond):
2242                             self.tsInfo.conditionals[cond] = []
2243                         self.tsInfo.conditionals[cond].extend(pkgs)
2244         return txmbrs_used
2245
2246     def deselectGroup(self, grpid):
2247         """de-mark all the packages in the group for install"""
2248         
2249         if not self.comps.has_group(grpid):
2250             raise Errors.GroupsError, _("No Group named %s exists") % grpid
2251             
2252         thesegroups = self.comps.return_groups(grpid)
2253         if not thesegroups:
2254             raise Errors.GroupsError, _("No Group named %s exists") % grpid
2255         
2256         for thisgroup in thesegroups:
2257             thisgroup.selected = False
2258             
2259             for pkgname in thisgroup.packages:
2260             
2261                 for txmbr in self.tsInfo:
2262                     if txmbr.po.name == pkgname and txmbr.po.state in TS_INSTALL_STATES:
2263                         try: 
2264                             txmbr.groups.remove(grpid)
2265                         except ValueError:
2266                             self.verbose_logger.log(logginglevels.DEBUG_1,
2267                                _("package %s was not marked in group %s"), txmbr.po,
2268                                 grpid)
2269                             continue
2270                         
2271                         # if there aren't any other groups mentioned then remove the pkg
2272                         if len(txmbr.groups) == 0:
2273                             self.tsInfo.remove(txmbr.po.pkgtup)
2274                             
2275                             for pkg in self.tsInfo.conditionals.get(txmbr.name, []):
2276                                 self.tsInfo.remove(pkg.pkgtup)
2277
2278         
2279     def getPackageObject(self, pkgtup):
2280         """retrieves a packageObject from a pkgtuple - if we need
2281            to pick and choose which one is best we better call out
2282            to some method from here to pick the best pkgobj if there are
2283            more than one response - right now it's more rudimentary."""
2284            
2285         
2286         # look it up in the self.localPackages first:
2287         for po in self.localPackages:
2288             if po.pkgtup == pkgtup:
2289                 return po
2290                 
2291         pkgs = self.pkgSack.searchPkgTuple(pkgtup)
2292
2293         if len(pkgs) == 0:
2294             raise Errors.DepError, _('Package tuple %s could not be found in packagesack') % str(pkgtup)
2295             
2296         if len(pkgs) > 1: # boy it'd be nice to do something smarter here FIXME
2297             result = pkgs[0]
2298         else:
2299             result = pkgs[0] # which should be the only
2300         
2301             # this is where we could do something to figure out which repository
2302             # is the best one to pull from
2303         
2304         return result
2305
2306     def getInstalledPackageObject(self, pkgtup):
2307         """returns a YumInstallPackage object for the pkgtup specified"""
2308         warnings.warn(_('getInstalledPackageObject() will go away, use self.rpmdb.searchPkgTuple().\n'),
2309                 Errors.YumFutureDeprecationWarning, stacklevel=2)
2310         
2311         po = self.rpmdb.searchPkgTuple(pkgtup)[0] # take the first one
2312         return po
2313         
2314     def gpgKeyCheck(self):
2315         """checks for the presence of gpg keys in the rpmdb
2316            returns 0 if no keys returns 1 if keys"""
2317
2318         gpgkeyschecked = self.conf.cachedir + '/.gpgkeyschecked.yum'
2319         if os.path.exists(gpgkeyschecked):
2320             return 1
2321             
2322         myts = rpmUtils.transaction.initReadOnlyTransaction(root=self.conf.installroot)
2323         myts.pushVSFlags(~(rpm._RPMVSF_NOSIGNATURES|rpm._RPMVSF_NODIGESTS))
2324         idx = myts.dbMatch('name', 'gpg-pubkey')
2325         keys = idx.count()
2326         del idx
2327         del myts
2328         
2329         if keys == 0:
2330             return 0
2331         else:
2332             mydir = os.path.dirname(gpgkeyschecked)
2333             if not os.path.exists(mydir):
2334                 os.makedirs(mydir)
2335                 
2336             fo = open(gpgkeyschecked, 'w')
2337             fo.close()
2338             del fo
2339             return 1
2340
2341     def returnPackagesByDep(self, depstring):
2342         """Pass in a generic [build]require string and this function will 
2343            pass back the packages it finds providing that dep."""
2344         
2345         results = []
2346         # parse the string out
2347         #  either it is 'dep (some operator) e:v-r'
2348         #  or /file/dep
2349         #  or packagename
2350         # or a full dep tuple
2351         if type(depstring) == types.TupleType:
2352             (depname, depflags, depver) = depstring
2353         else:
2354             depname = depstring
2355             depflags = None
2356             depver = None
2357         
2358             if depstring[0] != '/':
2359                 # not a file dep - look at it for being versioned
2360                 dep_split = depstring.split()
2361                 if len(dep_split) == 3:
2362                     depname, flagsymbol, depver = dep_split
2363                     if not flagsymbol in SYMBOLFLAGS:
2364                         raise Errors.YumBaseError, _('Invalid version flag')
2365                     depflags = SYMBOLFLAGS[flagsymbol]
2366                 
2367         sack = self.whatProvides(depname, depflags, depver)
2368         results = sack.returnPackages()
2369         return results
2370         
2371
2372     def returnPackageByDep(self, depstring):
2373         """Pass in a generic [build]require string and this function will 
2374            pass back the best(or first) package it finds providing that dep."""
2375         
2376         try:
2377             pkglist = self.returnPackagesByDep(depstring)
2378         except Errors.YumBaseError:
2379             raise Errors.YumBaseError, _('No Package found for %s') % depstring
2380         
2381         ps = ListPackageSack(pkglist)
2382         result = self._bestPackageFromList(ps.returnNewestByNameArch())
2383         if result is None:
2384             raise Errors.YumBaseError, _('No Package found for %s') % depstring
2385         
2386         return result
2387
2388     def returnInstalledPackagesByDep(self, depstring):
2389         """Pass in a generic [build]require string and this function will 
2390            pass back the installed packages it finds providing that dep."""
2391         
2392         # parse the string out
2393         #  either it is 'dep (some operator) e:v-r'
2394         #  or /file/dep
2395         #  or packagename
2396         if type(depstring) == types.TupleType:
2397             (depname, depflags, depver) = depstring
2398         else:
2399             depname = depstring
2400             depflags = None
2401             depver = None
2402             
2403             if depstring[0] != '/':
2404                 # not a file dep - look at it for being versioned
2405                 dep_split = depstring.split()
2406                 if len(dep_split) == 3:
2407                     depname, flagsymbol, depver = dep_split
2408                     if not flagsymbol in SYMBOLFLAGS:
2409                         raise Errors.YumBaseError, _('Invalid version flag')
2410                     depflags = SYMBOLFLAGS[flagsymbol]
2411
2412         return self.rpmdb.getProvides(depname, depflags, depver).keys()
2413
2414     def _bestPackageFromList(self, pkglist):
2415         """take list of package objects and return the best package object.
2416            If the list is empty, return None. 
2417            
2418            Note: this is not aware of multilib so make sure you're only
2419            passing it packages of a single arch group."""
2420         
2421         
2422         if len(pkglist) == 0:
2423             return None
2424             
2425         if len(pkglist) == 1:
2426             return pkglist[0]
2427
2428         bestlist = self._compare_providers(pkglist, None)
2429         return bestlist[0][0]
2430
2431     def bestPackagesFromList(self, pkglist, arch=None, single_name=False):
2432         """Takes a list of packages, returns the best packages.
2433            This function is multilib aware so that it will not compare
2434            multilib to singlelib packages""" 
2435     
2436         returnlist = []
2437         compatArchList = self.arch.get_arch_list(arch)
2438         multiLib = []
2439         singleLib = []
2440         noarch = []
2441         for po in pkglist:
2442             if po.arch not in compatArchList:
2443                 continue
2444             elif po.arch in ("noarch"):
2445                 noarch.append(po)
2446             elif isMultiLibArch(arch=po.arch):
2447                 multiLib.append(po)
2448             else:
2449                 singleLib.append(po)
2450                 
2451         # we now have three lists.  find the best package(s) of each
2452         multi = self._bestPackageFromList(multiLib)
2453         single = self._bestPackageFromList(singleLib)
2454         no = self._bestPackageFromList(noarch)
2455
2456         if single_name and multi and single and multi.name != single.name:
2457             # Sinlge _must_ match multi, if we want a single package name
2458             single = None
2459
2460         # now, to figure out which arches we actually want
2461         # if there aren't noarch packages, it's easy. multi + single
2462         if no is None:
2463             if multi: returnlist.append(multi)
2464             if single: returnlist.append(single)
2465         # if there's a noarch and it's newer than the multilib, we want
2466         # just the noarch.  otherwise, we want multi + single
2467         elif multi:
2468             best = self._bestPackageFromList([multi,no])
2469             if best.arch == "noarch":
2470                 returnlist.append(no)
2471             else:
2472                 if multi: returnlist.append(multi)
2473                 if single: returnlist.append(single)
2474         # similar for the non-multilib case
2475         elif single:
2476             best = self._bestPackageFromList([single,no])
2477             if best.arch == "noarch":
2478                 returnlist.append(no)
2479             else:
2480                 returnlist.append(single)
2481         # if there's not a multi or single lib, then we want the noarch
2482         else:
2483             returnlist.append(no)
2484
2485         return returnlist
2486
2487     # FIXME: This doesn't really work, as it assumes one obsoleter for each pkg
2488     # when we can have:
2489     # 1 pkg obsoleted by multiple pkgs _and_
2490     # 1 pkg obsoleting multiple pkgs
2491     # ...and we need to detect loops, and get the arches "right" and do this
2492     # for chains. Atm. I hate obsoletes, and I can't get it to work better,
2493     # easily ... so screw it, don't create huge chains of obsoletes with some
2494     # loops in there too ... or I'll have to hurt you.
2495     def _pkg2obspkg(self, po):
2496         """ Given a package return the package it's obsoleted by and so
2497             we should install instead. Or None if there isn't one. """
2498         thispkgobsdict = self.up.checkForObsolete([po.pkgtup])
2499         if thispkgobsdict.has_key(po.pkgtup):
2500             obsoleting = thispkgobsdict[po.pkgtup][0]
2501             obsoleting_pkg = self.getPackageObject(obsoleting)
2502             return obsoleting_pkg
2503         return None
2504
2505     def _test_loop(self, node, next_func):
2506         """ Generic comp. sci. test for looping, walk the list with two pointers
2507             moving one twice as fast as the other. If they are ever == you have
2508             a loop. If loop we return None, if no loop the last element. """
2509         slow = node
2510         done = False
2511         while True:
2512             next = next_func(node)
2513             if next is None and not done: return None
2514             if next is None: return node
2515             node = next_func(next)
2516             if node is None: return next
2517             done = True
2518
2519             slow = next_func(slow)
2520             if next == slow:
2521                 return None
2522
2523     def _at_groupinstall(self, pattern):
2524         " Do groupinstall via. leading @ on the cmd line, for install/update."
2525         assert pattern[0] == '@'
2526         group_string = pattern[1:]
2527         tx_return = []
2528         for group in self.comps.return_groups(group_string):
2529             try:
2530                 txmbrs = self.selectGroup(group.groupid)
2531                 tx_return.extend(txmbrs)
2532             except yum.Errors.GroupsError:
2533                 self.logger.critical(_('Warning: Group %s does not exist.'), group_string)
2534                 continue
2535         return tx_return
2536         
2537     def _at_groupremove(self, pattern):
2538         " Do groupremove via. leading @ on the cmd line, for remove."
2539         assert pattern[0] == '@'
2540         group_string = pattern[1:]
2541         tx_return = []
2542         try:
2543             txmbrs = self.groupRemove(group_string)
2544         except yum.Errors.GroupsError:
2545             self.logger.critical(_('No group named %s exists'), group_string)
2546         else:
2547             tx_return.extend(txmbrs)
2548         return tx_return
2549
2550     #  Note that this returns available pkgs, and not txmbrs like the other
2551     # _at_group* functions.
2552     def _at_groupdowngrade(self, pattern):
2553         " Do downgrade of a group via. leading @ on the cmd line."
2554         assert pattern[0] == '@'
2555         grpid = pattern[1:]
2556
2557         thesegroups = self.comps.return_groups(grpid)
2558         if not thesegroups:
2559             raise Errors.GroupsError, _("No Group named %s exists") % grpid
2560         pkgnames = set()
2561         for thisgroup in thesegroups:
2562             pkgnames.update(thisgroup.packages)
2563         return self.pkgSack.searchNames(pkgnames)
2564
2565     def _find_obsoletees(self, po):
2566         """ Return the pkgs. that are obsoleted by the po we pass in. """
2567         for (obstup, inst_tup) in self.up.getObsoletersTuples(name=po.name):
2568             if po.pkgtup == obstup:
2569                 installed_pkg =  self.rpmdb.searchPkgTuple(inst_tup)[0]
2570                 yield installed_pkg
2571
2572     def _add_prob_flags(self, *flags):
2573         """ Add all of the passed flags to the tsInfo.probFilterFlags array. """
2574         for flag in flags:
2575             if flag not in self.tsInfo.probFilterFlags:
2576                 self.tsInfo.probFilterFlags.append(flag)
2577
2578     def install(self, po=None, **kwargs):
2579         """try to mark for install the item specified. Uses provided package 
2580            object, if available. If not it uses the kwargs and gets the best
2581            packages from the keyword options provided 
2582            returns the list of txmbr of the items it installs
2583            
2584            """
2585         
2586         pkgs = []
2587         was_pattern = False
2588         if po:
2589             if isinstance(po, YumAvailablePackage) or isinstance(po, YumLocalPackage):
2590                 pkgs.append(po)
2591             else:
2592                 raise Errors.InstallError, _('Package Object was not a package object instance')
2593             
2594         else:
2595             if not kwargs:
2596                 raise Errors.InstallError, _('Nothing specified to install')
2597
2598             if kwargs.has_key('pattern'):
2599                 if kwargs['pattern'][0] == '@':
2600                     return self._at_groupinstall(kwargs['pattern'])
2601
2602                 was_pattern = True
2603                 pats = [kwargs['pattern']]
2604                 mypkgs = self.pkgSack.returnPackages(patterns=pats,
2605                                                       ignore_case=False)
2606                 pkgs.extend(mypkgs)
2607                 # if we have anything left unmatched, let's take a look for it
2608                 # being a dep like glibc.so.2 or /foo/bar/baz
2609                 
2610                 if not mypkgs:
2611                     arg = kwargs['pattern']
2612                     self.verbose_logger.debug(_('Checking for virtual provide or file-provide for %s'), 
2613                         arg)
2614
2615                     try:
2616                         mypkgs = self.returnPackagesByDep(arg)
2617                     except yum.Errors.YumBaseError, e:
2618                         self.logger.critical(_('No Match for argument: %s') % arg)
2619                     else:
2620                         # install MTA* == fail, because provides don't do globs
2621                         # install /usr/kerberos/bin/* == success (and we want
2622                         #                                all of the pkgs)
2623                         if mypkgs and not misc.re_glob(arg):
2624                             mypkgs = self.bestPackagesFromList(mypkgs,
2625                                                                single_name=True)
2626                         if mypkgs:
2627                             pkgs.extend(mypkgs)
2628                         
2629             else:
2630                 nevra_dict = self._nevra_kwarg_parse(kwargs)
2631
2632                 pkgs = self.pkgSack.searchNevra(name=nevra_dict['name'],
2633                      epoch=nevra_dict['epoch'], arch=nevra_dict['arch'],
2634                      ver=nevra_dict['version'], rel=nevra_dict['release'])
2635                 
2636             if pkgs:
2637                 # if was_pattern or nevra-dict['arch'] is none, take the list
2638                 # of arches based on our multilib_compat config and 
2639                 # toss out any pkgs of any arch NOT in that arch list
2640
2641                 
2642                 # only do these things if we're multilib
2643                 if self.arch.multilib:
2644                     if was_pattern or not nevra_dict['arch']: # and only if they
2645                                                               # they didn't specify an arch
2646                         if self.conf.multilib_policy == 'best':
2647                             pkgs_by_name = {}
2648                             use = []
2649                             not_added = []
2650                             best = self.arch.legit_multi_arches
2651                             best.append('noarch')
2652                             for pkg in pkgs:
2653                                 if pkg.arch in best:
2654                                     pkgs_by_name[pkg.name] = 1    
2655                                     use.append(pkg)  
2656                                 else:
2657                                     not_added.append(pkg)
2658                             for pkg in not_added:
2659                                 if not pkg.name in pkgs_by_name:
2660                                     use.append(pkg)
2661                            
2662                             pkgs = use
2663                            
2664                 pkgs = packagesNewestByNameArch(pkgs)
2665
2666                 pkgbyname = {}
2667                 for pkg in pkgs:
2668                     if not pkgbyname.has_key(pkg.name):
2669                         pkgbyname[pkg.name] = [ pkg ]
2670                     else:
2671                         pkgbyname[pkg.name].append(pkg)
2672
2673                 lst = []
2674                 for pkgs in pkgbyname.values():
2675                     lst.extend(self.bestPackagesFromList(pkgs))
2676                 pkgs = lst
2677
2678
2679         if not pkgs:
2680             # Do we still want to return errors here?
2681             # We don't in the cases below, so I didn't here...
2682             if 'pattern' in kwargs:
2683                 pkgs = self.rpmdb.returnPackages(patterns=[kwargs['pattern']],
2684                                                  ignore_case=False)
2685             if 'name' in kwargs:
2686                 pkgs = self.rpmdb.searchNevra(name=kwargs['name'])
2687             # Warning here does "weird" things when doing:
2688             # yum --disablerepo='*' install '*'
2689             # etc. ... see RHBZ#480402
2690             if False:
2691                 for pkg in pkgs:
2692                     self.verbose_logger.warning(_('Package %s installed and not available'), pkg)
2693             if pkgs:
2694                 return []
2695             raise Errors.InstallError, _('No package(s) available to install')
2696         
2697         # FIXME - lots more checking here
2698         #  - install instead of erase
2699         #  - better error handling/reporting
2700
2701
2702         tx_return = []
2703         for po in pkgs:
2704             if self.tsInfo.exists(pkgtup=po.pkgtup):
2705                 if self.tsInfo.getMembersWithState(po.pkgtup, TS_INSTALL_STATES):
2706                     self.verbose_logger.log(logginglevels.DEBUG_1,
2707                         _('Package: %s  - already in transaction set'), po)
2708                     tx_return.extend(self.tsInfo.getMembers(pkgtup=po.pkgtup))
2709                     continue
2710             
2711             # make sure this shouldn't be passed to update:
2712             if self.up.updating_dict.has_key(po.pkgtup):
2713                 txmbrs = self.update(po=po)
2714                 tx_return.extend(txmbrs)
2715                 continue
2716             
2717             #  Make sure we're not installing a package which is obsoleted by
2718             # something else in the repo. Unless there is a obsoletion loop,
2719             # at which point ignore everything.
2720             obsoleting_pkg = self._test_loop(po, self._pkg2obspkg)
2721             if obsoleting_pkg is not None:
2722                 # this is not a definitive check but it'll make sure we don't
2723                 # pull in foo.i586 when foo.x86_64 already obsoletes the pkg and
2724                 # is already installed
2725                 already_obs = None
2726                 poprovtup = (po.name, 'EQ', (po.epoch, po.ver, po.release))
2727                 for pkg in self.rpmdb.searchNevra(name=obsoleting_pkg.name):
2728                     if pkg.inPrcoRange('obsoletes', poprovtup):
2729                         already_obs = pkg
2730                         continue
2731
2732                 if already_obs:
2733                     self.verbose_logger.warning(_('Package %s is obsoleted by %s which is already installed'), 
2734                                                 po, already_obs)
2735                 else:
2736                     self.verbose_logger.warning(_('Package %s is obsoleted by %s, trying to install %s instead'),
2737                         po.name, obsoleting_pkg.name, obsoleting_pkg)               
2738                     tx_return.extend(self.install(po=obsoleting_pkg))
2739                 continue
2740             
2741             # make sure it's not already installed
2742             if self.rpmdb.contains(po=po):
2743                 if not self.tsInfo.getMembersWithState(po.pkgtup, TS_REMOVE_STATES):
2744                     self.verbose_logger.warning(_('Package %s already installed and latest version'), po)
2745                     continue
2746
2747             # make sure we don't have a name.arch of this already installed
2748             # if so pass it to update b/c it should be able to figure it out
2749             # if self.rpmdb.contains(name=po.name, arch=po.arch) and not self.allowedMultipleInstalls(po):
2750             if not self.allowedMultipleInstalls(po):
2751                 found = True
2752                 for ipkg in self.rpmdb.searchNevra(name=po.name, arch=po.arch):
2753                     found = False
2754                     if self.tsInfo.getMembersWithState(ipkg.pkgtup, TS_REMOVE_STATES):
2755                         found = True
2756                         break
2757                 if not found:
2758                     self.verbose_logger.warning(_('Package matching %s already installed. Checking for update.'), po)            
2759                     txmbrs = self.update(po=po)
2760                     tx_return.extend(txmbrs)
2761                     continue
2762
2763                 
2764             # at this point we are going to mark the pkg to be installed, make sure
2765             # it's not an older package that is allowed in due to multiple installs
2766             # or some other oddity. If it is - then modify the problem filter to cope
2767             
2768             for ipkg in self.rpmdb.searchNevra(name=po.name, arch=po.arch):
2769                 if ipkg.verEQ(po):
2770                     self._add_prob_flags(rpm.RPMPROB_FILTER_REPLACEPKG,
2771                                          rpm.RPMPROB_FILTER_REPLACENEWFILES,
2772                                          rpm.RPMPROB_FILTER_REPLACEOLDFILES)
2773                     break
2774                 if ipkg.verGT(po):
2775                     self._add_prob_flags(rpm.RPMPROB_FILTER_OLDPACKAGE)
2776                     break
2777             
2778             # it doesn't obsolete anything. If it does, mark that in the tsInfo, too
2779             if po.pkgtup in self.up.getObsoletesList(name=po.name):
2780                 for obsoletee in self._find_obsoletees(po):
2781                     txmbr = self.tsInfo.addObsoleting(po, obsoletee)
2782                     self.tsInfo.addObsoleted(obsoletee, po)
2783                     tx_return.append(txmbr)
2784             else:
2785                 txmbr = self.tsInfo.addInstall(po)
2786                 tx_return.append(txmbr)
2787
2788         return tx_return
2789
2790     def _check_new_update_provides(self, opkg, npkg):
2791         """ Check for any difference in the provides of the old and new update
2792             that is needed by the transaction. If so we "update" those pkgs
2793             too, to the latest version. """
2794         oprovs = set(opkg.returnPrco('provides'))
2795         nprovs = set(npkg.returnPrco('provides'))
2796         tx_return = []
2797         for prov in oprovs.difference(nprovs):
2798             reqs = self.tsInfo.getRequires(*prov)
2799             for pkg in reqs:
2800                 for req in reqs[pkg]:
2801                     if not npkg.inPrcoRange('provides', req):
2802                         naTup = (pkg.name, pkg.arch)
2803                         for pkg in self.pkgSack.returnNewestByNameArch(naTup):
2804                             tx_return.extend(self.update(po=pkg))
2805                         break
2806         return tx_return
2807
2808     def _newer_update_in_trans(self, pkgtup, available_pkg, tx_return):
2809         """ We return True if there is a newer package already in the
2810             transaction. If there is an older one, we remove it (and update any
2811             deps. that aren't satisfied by the newer pkg) and return False so
2812             we'll update to this newer pkg. """
2813         found = False
2814         for txmbr in self.tsInfo.getMembersWithState(pkgtup, [TS_UPDATED]):
2815             count = 0
2816             for po in txmbr.updated_by:
2817                 if available_pkg.verLE(po):
2818                     count += 1
2819                 else:
2820                     for ntxmbr in self.tsInfo.getMembers(po.pkgtup):
2821                         self.tsInfo.remove(ntxmbr.po.pkgtup)
2822                         txs = self._check_new_update_provides(ntxmbr.po,
2823                                                               available_pkg)
2824                         tx_return.extend(txs)
2825             if count:
2826                 found = True
2827             else:
2828                 self.tsInfo.remove(txmbr.po.pkgtup)
2829         return found
2830
2831     def update(self, po=None, requiringPo=None, **kwargs):
2832         """try to mark for update the item(s) specified. 
2833             po is a package object - if that is there, mark it for update,
2834             if possible
2835             else use **kwargs to match the package needing update
2836             if nothing is specified at all then attempt to update everything
2837             
2838             returns the list of txmbr of the items it marked for update"""
2839         
2840         # check for args - if no po nor kwargs, do them all
2841         # if po, do it, ignore all else
2842         # if no po do kwargs
2843         # uninstalled pkgs called for update get returned with errors in a list, maybe?
2844
2845         tx_return = []
2846         if not po and not kwargs: # update everything (the easy case)
2847             self.verbose_logger.log(logginglevels.DEBUG_2, _('Updating Everything'))
2848             updates = self.up.getUpdatesTuples()
2849             if self.conf.obsoletes:
2850                 obsoletes = self.up.getObsoletesTuples(newest=1)
2851             else:
2852                 obsoletes = []
2853
2854             for (obsoleting, installed) in obsoletes:
2855                 obsoleting_pkg = self.getPackageObject(obsoleting)
2856                 topkg = self._test_loop(obsoleting_pkg, self._pkg2obspkg)
2857                 if topkg is not None:
2858                     obsoleting_pkg = topkg
2859                 installed_pkg =  self.rpmdb.searchPkgTuple(installed)[0]
2860                 txmbr = self.tsInfo.addObsoleting(obsoleting_pkg, installed_pkg)
2861                 self.tsInfo.addObsoleted(installed_pkg, obsoleting_pkg)
2862                 if requiringPo:
2863                     txmbr.setAsDep(requiringPo)
2864                 tx_return.append(txmbr)
2865                 
2866             for (new, old) in updates:
2867                 if self.tsInfo.isObsoleted(pkgtup=old):
2868                     self.verbose_logger.log(logginglevels.DEBUG_2, _('Not Updating Package that is already obsoleted: %s.%s %s:%s-%s'), 
2869                         old)
2870                 else:
2871                     tx_return.extend(self.update(po=self.getPackageObject(new)))
2872             
2873             return tx_return
2874
2875         # complications
2876         # the user has given us something - either a package object to be
2877         # added to the transaction as an update or they've given us a pattern 
2878         # of some kind
2879         
2880         instpkgs = []
2881         availpkgs = []
2882         if po: # just a po
2883             if po.repoid == 'installed':
2884                 instpkgs.append(po)
2885             else:
2886                 availpkgs.append(po)
2887                 
2888                 
2889         elif kwargs.has_key('pattern'):
2890             if kwargs['pattern'][0] == '@':
2891                 return self._at_groupinstall(kwargs['pattern'])
2892
2893             (e, m, u) = self.rpmdb.matchPackageNames([kwargs['pattern']])
2894             instpkgs.extend(e)
2895             instpkgs.extend(m)
2896
2897             if u:
2898                 depmatches = []
2899                 arg = u[0]
2900                 try:
2901                     depmatches = self.returnInstalledPackagesByDep(arg)
2902                 except yum.Errors.YumBaseError, e:
2903                     self.logger.critical(_('%s') % e)
2904                 
2905                 instpkgs.extend(depmatches)
2906
2907             #  Always look for available packages, it doesn't seem to do any
2908             # harm (apart from some time). And it fixes weird edge cases where
2909             # "update a" (which requires a new b) is different from "update b"
2910             m =self.pkgSack.returnNewestByNameArch(patterns=[kwargs['pattern']])
2911             availpkgs.extend(m)
2912
2913             if not availpkgs and not instpkgs:
2914                 self.logger.critical(_('No Match for argument: %s') % arg)
2915         
2916         else: # we have kwargs, sort them out.
2917             nevra_dict = self._nevra_kwarg_parse(kwargs)
2918
2919             instpkgs = self.rpmdb.searchNevra(name=nevra_dict['name'], 
2920                         epoch=nevra_dict['epoch'], arch=nevra_dict['arch'], 
2921                         ver=nevra_dict['version'], rel=nevra_dict['release'])
2922
2923             if not instpkgs:
2924                 availpkgs = self.pkgSack.searchNevra(name=nevra_dict['name'],
2925                             epoch=nevra_dict['epoch'], arch=nevra_dict['arch'],
2926                             ver=nevra_dict['version'], rel=nevra_dict['release'])
2927                 if len(availpkgs) > 1:
2928                     availpkgs = self._compare_providers(availpkgs, requiringPo)
2929                     availpkgs = map(lambda x: x[0], availpkgs)
2930
2931        
2932         # for any thing specified
2933         # get the list of available pkgs matching it (or take the po)
2934         # get the list of installed pkgs matching it (or take the po)
2935         # go through each list and look for:
2936            # things obsoleting it if it is an installed pkg
2937            # things it updates if it is an available pkg
2938            # things updating it if it is an installed pkg
2939            # in that order
2940            # all along checking to make sure we:
2941             # don't update something that's already been obsoleted
2942             # don't update something that's already been updated
2943             
2944         # if there are more than one package that matches an update from
2945         # a pattern/kwarg then:
2946             # if it is a valid update and we'
2947         
2948         # TODO: we should search the updates and obsoletes list and
2949         # mark the package being updated or obsoleted away appropriately
2950         # and the package relationship in the tsInfo
2951         
2952
2953         # check for obsoletes first
2954         if self.conf.obsoletes:
2955             for installed_pkg in instpkgs:
2956                 obs_tups = self.up.obsoleted_dict.get(installed_pkg.pkgtup, [])
2957                 # This is done so we don't have to returnObsoletes(newest=True)
2958                 # It's a minor UI problem for RHEL, but might as well dtrt.
2959                 obs_pkgs = [self.getPackageObject(tup) for tup in obs_tups]
2960                 for obsoleting_pkg in packagesNewestByNameArch(obs_pkgs):
2961                     tx_return.extend(self.install(po=obsoleting_pkg))
2962             for available_pkg in availpkgs:
2963                 for obsoleted in self.up.obsoleting_dict.get(available_pkg.pkgtup, []):
2964                     obsoleted_pkg = self.getInstalledPackageObject(obsoleted)
2965                     txmbr = self.tsInfo.addObsoleting(available_pkg, obsoleted_pkg)
2966                     if requiringPo:
2967                         txmbr.setAsDep(requiringPo)
2968                     tx_return.append(txmbr)
2969                     if self.tsInfo.isObsoleted(obsoleted):
2970                         self.verbose_logger.log(logginglevels.DEBUG_2, _('Package is already obsoleted: %s.%s %s:%s-%s'), obsoleted)
2971                     else:
2972                         txmbr = self.tsInfo.addObsoleted(obsoleted_pkg, available_pkg)
2973                         tx_return.append(txmbr)
2974
2975         for installed_pkg in instpkgs:
2976             for updating in self.up.updatesdict.get(installed_pkg.pkgtup, []):
2977                 po = self.getPackageObject(updating)
2978                 if self.tsInfo.isObsoleted(installed_pkg.pkgtup):
2979                     self.verbose_logger.log(logginglevels.DEBUG_2, _('Not Updating Package that is already obsoleted: %s.%s %s:%s-%s'), 
2980                                             installed_pkg.pkgtup)                                               
2981                 # at this point we are going to mark the pkg to be installed, make sure
2982                 # it doesn't obsolete anything. If it does, mark that in the tsInfo, too
2983                 elif po.pkgtup in self.up.getObsoletesList(name=po.name):
2984                     for obsoletee in self._find_obsoletees(po):
2985                         txmbr = self.tsInfo.addUpdate(po, installed_pkg)
2986                         if requiringPo:
2987                             txmbr.setAsDep(requiringPo)
2988                         self.tsInfo.addObsoleting(po, obsoletee)
2989                         self.tsInfo.addObsoleted(obsoletee, po)
2990                         tx_return.append(txmbr)
2991                 else:
2992                     txmbr = self.tsInfo.addUpdate(po, installed_pkg)
2993                     if requiringPo:
2994                         txmbr.setAsDep(requiringPo)
2995                     tx_return.append(txmbr)
2996                         
2997         for available_pkg in availpkgs:
2998             #  Make sure we're not installing a package which is obsoleted by
2999             # something else in the repo. Unless there is a obsoletion loop,
3000             # at which point ignore everything.
3001             obsoleting_pkg = self._test_loop(available_pkg, self._pkg2obspkg)
3002             if obsoleting_pkg is not None:
3003                 self.verbose_logger.log(logginglevels.DEBUG_2, _('Not Updating Package that is obsoleted: %s'), available_pkg)
3004                 tx_return.extend(self.install(po=obsoleting_pkg))
3005                 continue
3006             for updated in self.up.updating_dict.get(available_pkg.pkgtup, []):
3007                 if self.tsInfo.isObsoleted(updated):
3008                     self.verbose_logger.log(logginglevels.DEBUG_2, _('Not Updating Package that is already obsoleted: %s.%s %s:%s-%s'), 
3009                                             updated)
3010                 elif self._newer_update_in_trans(updated, available_pkg,
3011                                                  tx_return):
3012                     self.verbose_logger.log(logginglevels.DEBUG_2, _('Not Updating Package that is already updated: %s.%s %s:%s-%s'), 
3013                                             updated)
3014                 
3015                 else:
3016                     updated_pkg =  self.rpmdb.searchPkgTuple(updated)[0]
3017                     txmbr = self.tsInfo.addUpdate(available_pkg, updated_pkg)
3018                     if requiringPo:
3019                         txmbr.setAsDep(requiringPo)
3020                     tx_return.append(txmbr)
3021                     
3022             # check to see if the pkg we want to install is not _quite_ the newest
3023             # one but still technically an update over what is installed.
3024             #FIXME - potentially do the comparables thing from what used to
3025             #        be in cli.installPkgs() to see what we should be comparing
3026             #        it to of what is installed. in the meantime name.arch is
3027             #        most likely correct
3028             pot_updated = self.rpmdb.searchNevra(name=available_pkg.name, arch=available_pkg.arch)
3029             if pot_updated and self.allowedMultipleInstalls(available_pkg):
3030                 # only compare against the newest of what's installed for kernel
3031                 pot_updated = sorted(pot_updated)[-1:]
3032
3033             for ipkg in pot_updated:
3034                 if self.tsInfo.isObsoleted(ipkg.pkgtup):
3035                     self.verbose_logger.log(logginglevels.DEBUG_2, _('Not Updating Package that is already obsoleted: %s.%s %s:%s-%s'), 
3036                                             ipkg.pkgtup)
3037                 elif self._newer_update_in_trans(ipkg.pkgtup, available_pkg,
3038                                                  tx_return):
3039                     self.verbose_logger.log(logginglevels.DEBUG_2, _('Not Updating Package that is already updated: %s.%s %s:%s-%s'), 
3040                                             ipkg.pkgtup)
3041                 elif ipkg.verLT(available_pkg):
3042                     txmbr = self.tsInfo.addUpdate(available_pkg, ipkg)
3043                     if requiringPo:
3044                         txmbr.setAsDep(requiringPo)
3045                     tx_return.append(txmbr)
3046
3047         return tx_return
3048         
3049     def remove(self, po=None, **kwargs):
3050         """try to find and mark for remove the specified package(s) -
3051             if po is specified then that package object (if it is installed) 
3052             will be marked for removal.
3053             if no po then look at kwargs, if neither then raise an exception"""
3054
3055         if not po and not kwargs:
3056             raise Errors.RemoveError, 'Nothing specified to remove'
3057         
3058         tx_return = []
3059         pkgs = []
3060         
3061         
3062         if po:
3063             pkgs = [po]  
3064         else:
3065             if kwargs.has_key('pattern'):
3066                 if kwargs['pattern'][0] == '@':
3067                     return self._at_groupremove(kwargs['pattern'])
3068
3069                 (e,m,u) = self.rpmdb.matchPackageNames([kwargs['pattern']])
3070                 pkgs.extend(e)
3071                 pkgs.extend(m)
3072                 if u:
3073                     depmatches = []
3074                     arg = u[0]
3075                     try:
3076                         depmatches = self.returnInstalledPackagesByDep(arg)
3077                     except yum.Errors.YumBaseError, e:
3078                         self.logger.critical(_('%s') % e)
3079                     
3080                     if not depmatches:
3081                         self.logger.critical(_('No Match for argument: %s') % arg)
3082                     else:
3083                         pkgs.extend(depmatches)
3084                 
3085             else:    
3086                 nevra_dict = self._nevra_kwarg_parse(kwargs)
3087
3088                 pkgs = self.rpmdb.searchNevra(name=nevra_dict['name'], 
3089                             epoch=nevra_dict['epoch'], arch=nevra_dict['arch'], 
3090                             ver=nevra_dict['version'], rel=nevra_dict['release'])
3091
3092                 if len(pkgs) == 0:
3093                     if not kwargs.get('silence_warnings', False):
3094                         self.logger.warning(_("No package matched to remove"))
3095
3096         for po in pkgs:
3097             txmbr = self.tsInfo.addErase(po)
3098             tx_return.append(txmbr)
3099         
3100         return tx_return
3101
3102     def installLocal(self, pkg, po=None, updateonly=False):
3103         """
3104         handles installs/updates of rpms provided on the filesystem in a
3105         local dir (ie: not from a repo)
3106
3107         Return the added transaction members.
3108
3109         @param pkg: a path to an rpm file on disk.
3110         @param po: A YumLocalPackage
3111         @param updateonly: Whether or not true installs are valid.
3112         """
3113
3114         # read in the package into a YumLocalPackage Object
3115         # append it to self.localPackages
3116         # check if it can be installed or updated based on nevra versus rpmdb
3117         # don't import the repos until we absolutely need them for depsolving
3118
3119         tx_return = []
3120         installpkgs = []
3121         updatepkgs = []
3122         donothingpkgs = []
3123
3124         if not po:
3125             try:
3126                 po = YumLocalPackage(ts=self.rpmdb.readOnlyTS(), filename=pkg)
3127             except Errors.MiscError:
3128                 self.logger.critical(_('Cannot open file: %s. Skipping.'), pkg)
3129                 return tx_return
3130             self.verbose_logger.log(logginglevels.INFO_2,
3131                 _('Examining %s: %s'), po.localpath, po)
3132
3133         # if by any chance we're a noncompat arch rpm - bail and throw out an error
3134         # FIXME -our archlist should be stored somewhere so we don't have to
3135         # do this: but it's not a config file sort of thing
3136         # FIXME: Should add noarch, yum localinstall works ...
3137         # just rm this method?
3138         if po.arch not in self.arch.archlist:
3139             self.logger.critical(_('Cannot add package %s to transaction. Not a compatible architecture: %s'), pkg, po.arch)
3140             return tx_return
3141         
3142         # everything installed that matches the name
3143         installedByKey = self.rpmdb.searchNevra(name=po.name)
3144         # go through each package
3145         if len(installedByKey) == 0: # nothing installed by that name
3146             if updateonly:
3147                 self.logger.warning(_('Package %s not installed, cannot update it. Run yum install to install it instead.'), po.name)
3148                 return tx_return
3149             else:
3150                 installpkgs.append(po)
3151
3152         for installed_pkg in installedByKey:
3153             if po.verGT(installed_pkg): # we're newer - this is an update, pass to them
3154                 if installed_pkg.name in self.conf.exactarchlist:
3155                     if po.arch == installed_pkg.arch:
3156                         updatepkgs.append((po, installed_pkg))
3157                     else:
3158                         donothingpkgs.append(po)
3159                 else:
3160                     updatepkgs.append((po, installed_pkg))
3161             elif po.verEQ(installed_pkg):
3162                 if (po.arch != installed_pkg.arch and
3163                     (isMultiLibArch(po.arch) or
3164                      isMultiLibArch(installed_pkg.arch))):
3165                     installpkgs.append(po)
3166                 else:
3167                     donothingpkgs.append(po)
3168             elif self.allowedMultipleInstalls(po):
3169                 installpkgs.append(po)
3170             else:
3171                 donothingpkgs.append(po)
3172
3173         # handle excludes for a localinstall
3174         toexc = []
3175         if len(self.conf.exclude) > 0:
3176             exactmatch, matched, unmatched = \
3177                    parsePackages(installpkgs + map(lambda x: x[0], updatepkgs),
3178                                  self.conf.exclude, casematch=1)
3179             toexc = exactmatch + matched
3180
3181         if po in toexc:
3182             self.verbose_logger.debug(_('Excluding %s'), po)
3183             return tx_return
3184
3185         for po in installpkgs:
3186             self.verbose_logger.log(logginglevels.INFO_2,
3187                 _('Marking %s to be installed'), po.localpath)
3188             self.localPackages.append(po)
3189             tx_return.extend(self.install(po=po))
3190
3191         for (po, oldpo) in updatepkgs:
3192             self.verbose_logger.log(logginglevels.INFO_2,
3193                 _('Marking %s as an update to %s'), po.localpath, oldpo)
3194             self.localPackages.append(po)
3195             txmbrs = self.update(po=po)
3196             tx_return.extend(txmbrs)
3197
3198         for po in donothingpkgs:
3199             self.verbose_logger.log(logginglevels.INFO_2,
3200                 _('%s: does not update installed package.'), po.localpath)
3201
3202         return tx_return
3203
3204     def reinstallLocal(self, pkg, po=None):
3205         """
3206         handles reinstall of rpms provided on the filesystem in a
3207         local dir (ie: not from a repo)
3208
3209         Return the added transaction members.
3210
3211         @param pkg: a path to an rpm file on disk.
3212         @param po: A YumLocalPackage
3213         """
3214
3215         if not po:
3216             try:
3217                 po = YumLocalPackage(ts=self.rpmdb.readOnlyTS(), filename=pkg)
3218             except Errors.MiscError:
3219                 self.logger.critical(_('Cannot open file: %s. Skipping.'), pkg)
3220                 return []
3221             self.verbose_logger.log(logginglevels.INFO_2,
3222                 _('Examining %s: %s'), po.localpath, po)
3223
3224         if po.arch not in self.arch.archlist:
3225             self.logger.critical(_('Cannot add package %s to transaction. Not a compatible architecture: %s'), pkg, po.arch)
3226             return []
3227
3228         # handle excludes for a local reinstall
3229         toexc = []
3230         if len(self.conf.exclude) > 0:
3231             exactmatch, matched, unmatched = \
3232                    parsePackages([po], self.conf.exclude, casematch=1)
3233             toexc = exactmatch + matched
3234
3235         if po in toexc:
3236             self.verbose_logger.debug(_('Excluding %s'), po)
3237             return []
3238
3239         return self.reinstall(po=po)
3240
3241     def reinstall(self, po=None, **kwargs):
3242         """Setup the problem filters to allow a reinstall to work, then
3243            pass everything off to install"""
3244            
3245         self._add_prob_flags(rpm.RPMPROB_FILTER_REPLACEPKG,
3246                              rpm.RPMPROB_FILTER_REPLACENEWFILES,
3247                              rpm.RPMPROB_FILTER_REPLACEOLDFILES)
3248
3249         tx_mbrs = []
3250         if po: # The po, is the "available" po ... we want the installed po
3251             tx_mbrs.extend(self.remove(pkgtup=po.pkgtup))
3252         else:
3253             tx_mbrs.extend(self.remove(**kwargs))
3254         if not tx_mbrs:
3255             raise Errors.ReinstallRemoveError, _("Problem in reinstall: no package matched to remove")
3256         templen = len(tx_mbrs)
3257         # this is a reinstall, so if we can't reinstall exactly what we uninstalled
3258         # then we really shouldn't go on
3259         new_members = []
3260         for item in tx_mbrs:
3261             #FIXME future - if things in the rpm transaction handling get
3262             # a bit finer-grained, then we should allow reinstalls of kernels
3263             # for now, banned and dropped.
3264             if self.allowedMultipleInstalls(item.po):
3265                 self.tsInfo.remove(item.pkgtup)
3266                 tx_mbrs.remove(item)
3267                 msg = _("Package %s is allowed multiple installs, skipping") % item.po
3268                 self.verbose_logger.log(logginglevels.INFO_2, msg)
3269                 continue
3270             
3271             #  Make sure obsoletes processing is off, so we can reinstall()