# # yuminstall.py # # Copyright (C) 2005, 2006, 2007 Red Hat, Inc. All rights reserved. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # from flags import flags from errors import * from ConfigParser import ConfigParser import sys import os import os.path import shutil import time import warnings import types import locale import glob import tempfile import itertools import re import anaconda_log import rpm import rpmUtils import urlgrabber.progress import urlgrabber.grabber from urlgrabber.grabber import URLGrabber, URLGrabError import yum import iniparse from yum.constants import * from yum.Errors import * from yum.misc import to_unicode from yum.yumRepo import YumRepository from backend import AnacondaBackend from product import * from sortedtransaction import SplitMediaTransactionData from constants import * from image import * from compssort import * import packages import iutil import gettext _ = lambda x: gettext.ldgettext("anaconda", x) P_ = lambda x, y, z: gettext.ldngettext("anaconda", x, y, z) import network # specspo stuff rpm.addMacro("_i18ndomains", "redhat-dist") import logging log = logging.getLogger("anaconda") import urlparse urlparse.uses_fragment.append('media') urlgrabber.grabber.default_grabber.opts.user_agent = "%s (anaconda)/%s" %(productName, productVersion) import iutil import isys def size_string (size): def number_format(s): return locale.format("%s", s, 1) retval = None if size > 1024 * 1024: size = size / (1024*1024) retval = _("%s MB") %(number_format(size),) elif size > 1024: size = size / 1024 retval = _("%s KB") %(number_format(size),) else: retval = P_("%s Byte", "%s Bytes", size) % (number_format(size),) return to_unicode(retval) class AnacondaCallback: def __init__(self, ayum, anaconda, instLog, modeText): self.repos = ayum.repos self.ts = ayum.ts self.ayum = ayum self.messageWindow = anaconda.intf.messageWindow self.pulseWindow = anaconda.intf.progressWindow self.progress = anaconda.id.instProgress self.progressWindowClass = anaconda.intf.progressWindow self.rootPath = anaconda.rootPath self.initWindow = None self.progressWindow = None self.lastprogress = 0 self.incr = 20 self.instLog = instLog self.modeText = modeText self.openfile = None self.inProgressPo = None def setSizes(self, numpkgs, totalSize, totalFiles): self.numpkgs = numpkgs self.totalSize = totalSize self.totalFiles = totalFiles self.donepkgs = 0 self.doneSize = 0 self.doneFiles = 0 def callback(self, what, amount, total, h, user): if what == rpm.RPMCALLBACK_TRANS_START: # step 6 is the bulk of the ts processing time if amount == 6: self.progressWindow = \ self.progressWindowClass (_("Preparing to install"), _("Preparing transaction from installation source"), total) self.incr = total / 10 if what == rpm.RPMCALLBACK_TRANS_PROGRESS: if self.progressWindow and amount > self.lastprogress + self.incr: self.progressWindow.set(amount) self.lastprogress = amount if what == rpm.RPMCALLBACK_TRANS_STOP and self.progressWindow: self.progressWindow.pop() if what == rpm.RPMCALLBACK_INST_OPEN_FILE: (hdr, rpmloc) = h # hate hate hate at epochs... epoch = hdr['epoch'] if epoch is not None: epoch = str(epoch) txmbrs = self.ayum.tsInfo.matchNaevr(hdr['name'], hdr['arch'], epoch, hdr['version'], hdr['release']) if len(txmbrs) == 0: raise RuntimeError, "Unable to find package %s-%s-%s.%s" %(hdr['name'], hdr['version'], hdr['release'], hdr['arch']) po = txmbrs[0].po repo = self.repos.getRepo(po.repoid) pkgStr = "%s-%s-%s.%s" % (po.name, po.version, po.release, po.arch) s = to_unicode(_("Installing %(pkgStr)s (%(size)s)\n")) \ % {'pkgStr': pkgStr, 'size': size_string(hdr['size'])} summary = to_unicode(gettext.ldgettext("redhat-dist", hdr['summary'] or "")) s += summary.strip() self.progress.set_label(s) self.instLog.write(self.modeText % str(pkgStr)) self.instLog.flush() self.openfile = None trynumber = 0 while self.openfile is None: trynumber += 1 try: # checkfunc gets passed to yum's use of URLGrabber which # then calls it with the file being fetched. verifyPkg # makes sure the checksum matches the one in the metadata. # # From the URLGrab documents: # checkfunc=(function, ('arg1', 2), {'kwarg': 3}) # results in a callback like: # function(obj, 'arg1', 2, kwarg=3) # obj.filename = '/tmp/stuff' # obj.url = 'http://foo.com/stuff' checkfunc = (self.ayum.verifyPkg, (po, 1), {}) fn = repo.getPackage(po, checkfunc=checkfunc) f = open(fn, 'r') self.openfile = f except yum.Errors.NoMoreMirrorsRepoError: self.ayum._handleFailure(po, trynumber) except IOError: self.ayum._handleFailure(po, trynumber) except URLGrabError as e: log.error("URLGrabError: %s" % (e,)) self.ayum._handleFailure(po, trynumber) except yum.Errors.RepoError, e: continue self.inProgressPo = po return self.openfile.fileno() elif what == rpm.RPMCALLBACK_INST_CLOSE_FILE: if self.initWindow: self.initWindow.pop() self.initWindow = None (hdr, rpmloc) = h fn = self.openfile.name self.openfile.close() self.openfile = None if os.path.dirname(fn).startswith("%s/var/cache/yum/" % self.rootPath): try: os.unlink(fn) except OSError as e: log.debug("unable to remove file %s" %(e.strerror,)) self.donepkgs += 1 self.doneSize += self.inProgressPo.returnSimple("installedsize") / 1024.0 self.doneFiles += len(hdr[rpm.RPMTAG_BASENAMES]) if self.donepkgs <= self.numpkgs: self.progress.set_text(P_("Packages completed: " "%(donepkgs)d of %(numpkgs)d", "Packages completed: " "%(donepkgs)d of %(numpkgs)d", self.numpkgs) % {'donepkgs': self.donepkgs, 'numpkgs': self.numpkgs}) if self.totalSize > 0: self.progress.set_fraction(float(self.doneSize / self.totalSize)) else: self.progress.set_fraction(0.0) self.progress.processEvents() self.inProgressPo = None elif what in (rpm.RPMCALLBACK_UNINST_START, rpm.RPMCALLBACK_UNINST_STOP): if self.initWindow is None: self.initWindow = self.pulseWindow(_("Finishing upgrade"), _("Finishing upgrade process. This may take a little while."), 0, pulse=True) else: self.initWindow.pulse() elif what in (rpm.RPMCALLBACK_CPIO_ERROR, rpm.RPMCALLBACK_UNPACK_ERROR, rpm.RPMCALLBACK_SCRIPT_ERROR): if not isinstance(h, types.TupleType): h = (h, None) (hdr, rpmloc) = h # Script errors store whether or not they're fatal in "total". So, # we should only error out for fatal script errors or the cpio and # unpack problems. if what != rpm.RPMCALLBACK_SCRIPT_ERROR or total: self.messageWindow(_("Error Installing Package"), _("A fatal error occurred when installing the %s " "package. This could indicate errors when reading " "the installation media. Installation cannot " "continue.") % hdr, type="custom", custom_icon="error", custom_buttons=[_("_Exit installer")]) sys.exit(1) if self.initWindow is None: self.progress.processEvents() class AnacondaYumRepo(YumRepository): def __init__(self, *args, **kwargs): YumRepository.__init__(self, *args, **kwargs) self.enablegroups = True self.sslverify = True self._anacondaBaseURLs = [] self.proxy_url = None def needsNetwork(self): def _isURL(s): return s.startswith("http") or s.startswith("ftp") if len(self.baseurl) > 0: return len(filter(lambda s: _isURL(s), self.baseurl)) > 0 elif self.mirrorlist: return _isURL(self.mirrorlist) else: return False def dirCleanup(self, upgrade=False): cachedir = self.getAttribute('cachedir') if os.path.isdir(cachedir): if upgrade: log.debug("Removing contents of %s" % (cachedir)) for f in filter(os.path.isfile, glob.glob("%s/*" % (cachedir))): try: os.unlink(f) except Exception, e: log.debug("error %s removing: %s" %(e,f)) elif not self.needsNetwork() or self.name == "Installation Repo" or self.id.startswith("anaconda-"): log.debug("Removing cachedir: %s" % (cachedir)) shutil.rmtree(cachedir) else: log.debug("Removing headers and packages from %s" % (cachedir)) if os.path.exists("%s/headers" % cachedir): shutil.rmtree("%s/headers" % cachedir) if os.path.exists("%s/packages" % cachedir): shutil.rmtree("%s/packages" % cachedir) # needed to store nfs: repo url that yum doesn't know def _getAnacondaBaseURLs(self): return self._anacondaBaseURLs or self.baseurl or [self.mirrorlist] def _setAnacondaBaseURLs(self, value): self._anacondaBaseURLs = value anacondaBaseURLs = property(_getAnacondaBaseURLs, _setAnacondaBaseURLs, doc="Extends AnacondaYum.baseurl to store non-yum urls:") class YumSorter(yum.YumBase): def _transactionDataFactory(self): return SplitMediaTransactionData() class AnacondaYum(YumSorter): def __init__(self, anaconda): YumSorter.__init__(self) self.anaconda = anaconda self._timestamp = None self.repoIDcounter = itertools.count() # Only needed for hard drive and nfsiso installs. self._discImages = {} self.isodir = None # Only needed for media installs. self.currentMedia = None self.mediagrabber = None self._loopdev_used = None # Where is the source media mounted? This is the directory # where Packages/ is located. self.tree = "/mnt/source" self.macros = {} if flags.selinux: for directory in ("/tmp/updates", "/etc/selinux/targeted/contexts/files", "/etc/security/selinux/src/policy/file_contexts", "/etc/security/selinux"): fn = "%s/file_contexts" %(directory,) if os.access(fn, os.R_OK): break self.macros["__file_context_path"] = fn else: self.macros["__file_context_path"] = "%{nil}" self.updates = [] self.localPackages = [] # Parse proxy values from anaconda self.proxy = None self.proxy_url = None self.proxy_username = None self.proxy_password = None if self.anaconda.proxy: self.setProxy(self.anaconda, self) def setup(self): # yum doesn't understand all our method URLs, so use this for all # except FTP and HTTP installs. self._baseRepoURL = "file://%s" % self.tree while True: try: self.configBaseURL() break except SystemError as exception: self.anaconda.methodstr = self.anaconda.intf.methodstrRepoWindow(self.anaconda.methodstr or "cdrom:", exception) self.doConfigSetup(root=self.anaconda.rootPath) if not self.anaconda.id.getUpgrade(): self.conf.installonlypkgs = [] def _switchCD(self, discnum): if os.access("%s/.discinfo" % self.tree, os.R_OK): f = open("%s/.discinfo" % self.tree) self._timestamp = f.readline().strip() f.close() dev = self.anaconda.id.storage.devicetree.getDeviceByName(self.anaconda.mediaDevice) dev.format.mountpoint = self.tree # If self.currentMedia is None, then there shouldn't be anything # mounted. Before going further, see if the correct disc is already # in the drive. This saves a useless eject and insert if the user # has for some reason already put the disc in the drive. if self.currentMedia is None: try: dev.format.mount() if verifyMedia(self.tree, discnum, None): self.currentMedia = discnum return dev.format.unmount() except: pass else: unmountCD(dev, self.anaconda.intf.messageWindow) self.currentMedia = None dev.eject() while True: if self.anaconda.intf: self.anaconda.intf.beep() self.anaconda.intf.messageWindow(_("Change Disc"), _("Please insert %(productName)s disc %(discnum)d to continue.") % {'productName': productName, 'discnum': discnum}) try: dev.format.mount() if verifyMedia(self.tree, discnum, self._timestamp): self.currentMedia = discnum break self.anaconda.intf.messageWindow(_("Wrong Disc"), _("That's not the correct %s disc.") % (productName,)) dev.format.unmount() dev.eject() except: self.anaconda.intf.messageWindow(_("Error"), _("Unable to access the disc.")) def _switchImage(self, discnum): umountImage(self.tree, self.currentMedia, self._loopdev_used) self.currentMedia = None # mountDirectory checks before doing anything, so it's safe to # call this repeatedly. mountDirectory(self.anaconda.methodstr, self.anaconda.intf.messageWindow) (self._loopdev_used, self._discImages) = mountImage(self.isodir, self.tree, discnum, self.anaconda.intf.messageWindow, discImages=self._discImages) self.currentMedia = discnum def configBaseURL(self): # We only have a methodstr if method= or repo= was passed to # anaconda. No source for this base repo (the CD media, NFS, # whatever) is mounted yet since loader only mounts the source # for the stage2 image. We need to set up the source mount # now. if flags.cmdline.has_key("preupgrade"): path = "/var/cache/yum/preupgrade" self.anaconda.methodstr = "hd::%s" % path self._baseRepoURL = "file:///mnt/sysimage/%s" % path elif self.anaconda.methodstr: m = self.anaconda.methodstr if m.startswith("hd:"): if m.count(":") == 2: (device, path) = m[3:].split(":") fstype = "auto" else: (device, fstype, path) = m[3:].split(":") # First check for an installable tree isys.mount(device, self.tree, fstype=fstype) if os.path.exists("%s/%s/repodata/repomd.xml" % (self.tree, path)): self._baseRepoURL = "file://%s/%s" % (self.tree, path) return isys.umount(self.tree, removeDir=False) # Look for .iso images self.isodir = "/mnt/isodir/%s" % path # This takes care of mounting /mnt/isodir first. self._switchImage(1) self.mediagrabber = self.mediaHandler elif m.startswith("nfsiso:"): self.isodir = "/mnt/isodir" # Calling _switchImage takes care of mounting /mnt/isodir first. if not network.hasActiveNetDev(): if not self.anaconda.intf.enableNetwork(): self._baseRepoURL = None return urlgrabber.grabber.reset_curl_obj() self._switchImage(1) self.mediagrabber = self.mediaHandler elif m.startswith("http") or m.startswith("ftp:"): self._baseRepoURL = m elif m.startswith("nfs:"): if not network.hasActiveNetDev(): if not self.anaconda.intf.enableNetwork(): self._baseRepoURL = None urlgrabber.grabber.reset_curl_obj() (opts, server, path) = iutil.parseNfsUrl(m) isys.mount(server+":"+path, self.tree, "nfs", options=opts) # This really should be fixed in loader instead but for now see # if there's images and if so go with this being an NFSISO # install instead. images = findIsoImages(self.tree, self.anaconda.intf.messageWindow) if images != {}: isys.umount(self.tree, removeDir=False) self.anaconda.methodstr = "nfsiso:%s" % m[4:] self.configBaseURL() return elif m.startswith("cdrom:"): self._switchCD(1) self.mediagrabber = self.mediaHandler self._baseRepoURL = "file://%s" % self.tree else: # No methodstr was given. In order to find an installation source, # we should first check to see if there's a CD/DVD with packages # on it, and then default to the mirrorlist URL. The user can # always change the repo with the repo editor later. cdr = scanForMedia(self.tree, self.anaconda.id.storage) if cdr: self.mediagrabber = self.mediaHandler self.anaconda.mediaDevice = cdr self.currentMedia = 1 log.info("found installation media on %s" % cdr) else: # No CD with media on it and no repo=/method= parameter, so # default to using whatever's enabled in /etc/yum.repos.d/ self._baseRepoURL = None def configBaseRepo(self, root='/'): # Create the "base" repo object, assuming there is one. Otherwise we # just skip all this and use the defaults from /etc/yum.repos.d. if not self._baseRepoURL: return # add default repos anacondabaseurl = (self.anaconda.methodstr or "cdrom:%s" % (self.anaconda.mediaDevice)) anacondabasepaths = self.anaconda.id.instClass.getPackagePaths(anacondabaseurl) for (name, uri) in self.anaconda.id.instClass.getPackagePaths(self._baseRepoURL).items(): rid = name.replace(" ", "") repo = AnacondaYumRepo("anaconda-%s-%s" % (rid, productStamp)) repo.baseurl = uri repo.anacondaBaseURLs = anacondabasepaths[name] repo.name = name repo.cost = 100 if self.anaconda.mediaDevice or self.isodir: repo.mediaid = getMediaId(self.tree) log.info("set mediaid of repo %s to: %s" % (rid, repo.mediaid)) if self.anaconda.proxy: self.setProxy(self.anaconda, repo) if flags.noverifyssl: repo.sslverify = False repo.enable() self.repos.add(repo) def mediaHandler(self, *args, **kwargs): mediaid = kwargs["mediaid"] discnum = kwargs["discnum"] relative = kwargs["relative"] # The package exists on media other than what's mounted right now. if discnum != self.currentMedia: log.info("switching from media #%s to #%s for %s" % (self.currentMedia, discnum, relative)) # Unmount any currently mounted ISO images and mount the one # containing the requested packages. if self.isodir: self._switchImage(discnum) else: self._switchCD(discnum) ug = URLGrabber(checkfunc=kwargs["checkfunc"]) ug.urlgrab("%s/%s" % (self.tree, kwargs["relative"]), kwargs["local"], text=kwargs["text"], range=kwargs["range"], copy_local=1) return kwargs["local"] # XXX: This is straight out of yum, but we need to override it here in # order to use our own repo class. def readRepoConfig(self, parser, section): '''Parse an INI file section for a repository. @param parser: ConfParser or similar to read INI file values from. @param section: INI file section to read. @return: YumRepository instance. ''' repo = AnacondaYumRepo(section) repo.populate(parser, section, self.conf) # Ensure that the repo name is set if not repo.name: repo.name = section self.logger.error(_('Repository %r is missing name in configuration, ' 'using id') % section) # Set attributes not from the config file repo.yumvar.update(self.conf.yumvar) repo.cfg = parser if "-source" in repo.id or "-debuginfo" in repo.id: name = repo.name del(repo) raise RepoError, "Repo %s contains -source or -debuginfo, excluding" % name #### JAREK if "UNSUPPORTED" in repo.name: name = repo.name del(repo) raise RepoError, "Repo %s contains UNSUPPORTED, excluding" % name #### JAREK # this is a little hard-coded, but it's effective if productIsFinal and ("rawhide" in repo.id or "development" in repo.id): name = repo.name del(repo) raise RepoError, "Excluding devel repo %s for non-devel anaconda" % name if not productIsFinal and not repo.enabled: name = repo.name del(repo) raise RepoError, "Excluding disabled repo %s for prerelease" % name # If repo=/method= was passed in, we want to default these extra # repos to off. if self._baseRepoURL: repo.enabled = False return repo def setProxy(self, src, dest): """ Set the proxy settings from a string in src.proxy If the string includes un/pw use those, otherwise set the un/pw from src.proxyUsername and src.proxyPassword dest has dest.proxy set to the host and port (no un/pw) dest.proxy_username and dest.proxy_password are set if present in src """ # This is the same pattern as from loader/urls.c:splitProxyParam # except that the POSIX classes have been replaced with character # ranges # NOTE: If this changes, update tests/regex/proxy.py # # proxy=[protocol://][username[:password]@]host[:port][path] pattern = re.compile("([A-Za-z]+://)?(([A-Za-z0-9]+)(:[^:@]+)?@)?([^:/]+)(:[0-9]+)?(/.*)?") m = pattern.match(src.proxy) if m and m.group(3): dest.proxy_username = m.group(3) elif getattr(src, "proxyUsername", None): dest.proxy_username = src.proxyUsername if m and m.group(4): # Skip the leading colon. dest.proxy_password = m.group(4)[1:] elif getattr(src, "proxyPassword", None): dest.proxy_password = src.proxyPassword if dest.proxy_username or dest.proxy_password: proxy_auth = "%s:%s@" % (dest.proxy_username or '', dest.proxy_password or '') else: proxy_auth = "" if m and m.group(5): # If both a host and port was found, just paste them # together using the colon at the beginning of the port # match as a separator. Otherwise, just use the host. if m.group(6): proxy = m.group(5) + m.group(6) else: proxy = m.group(5) # yum also requires a protocol. If none was given, # default to http. if m.group(1): dest.proxy_url = m.group(1) + proxy_auth + proxy proxy = m.group(1) + proxy else: dest.proxy_url = "http://" + proxy_auth + proxy proxy = "http://" + proxy # Set the repo proxy. NOTE: yum immediately parses this and # raises an error if it isn't correct dest.proxy = proxy def _getAddons(self, repo): """ Check the baseurl or mirrorlist for a repository, see if it has any valid addon repos and if so, return a list of (repo name, repo URL). """ baseurl = repo.mirrorlist or repo.baseurl[0] retval = [] c = ConfigParser() # If there's no .treeinfo for this repo, don't bother looking for addons. treeinfo = self._getTreeinfo(baseurl, repo.proxy_url, repo.sslverify) if not treeinfo: return retval # We need to know which variant is being installed so we know what addons # are valid options. try: ConfigParser.read(c, treeinfo) variant = c.get("general", "variant") except: return retval section = "variant-%s" % variant if c.has_section(section) and c.has_option(section, "addons"): validAddons = c.get(section, "addons").split(",") else: return retval for addon in validAddons: addonSection = "addon-%s" % addon if not c.has_section(addonSection) or not c.has_option(addonSection, "repository"): continue url = "%s/%s" % (baseurl, c.get(addonSection, "repository")) retval.append((addon, c.get(addonSection, "name"), url)) return retval def _getTreeinfo(self, baseurl, proxy_url, sslverify): """ Try to get .treeinfo file from baseurl, optionally using proxy_url Saves the file into /tmp/.treeinfo """ if not baseurl: return None if baseurl.startswith("http") or baseurl.startswith("ftp"): if not network.hasActiveNetDev(): if not self.anaconda.intf.enableNetwork(): log.error("Error downloading %s/.treeinfo: network enablement failed" % (baseurl)) return None urlgrabber.grabber.reset_curl_obj() ug = URLGrabber() ugopts = { "ssl_verify_peer" : sslverify, "ssl_verify_host" : sslverify } if proxy_url and proxy_url.startswith("http"): proxies = { 'http' : proxy_url, 'https' : proxy_url } elif proxy_url and proxy_url.startswith("ftp"): proxies = { 'ftp' : proxy_url } else: proxies = {} try: ug.urlgrab("%s/.treeinfo" % baseurl, "/tmp/.treeinfo", copy_local=1, proxies=proxies, **ugopts) except Exception as e: try: ug.urlgrab("%s/treeinfo" % baseurl, "/tmp/.treeinfo", copy_local=1, proxies=proxies) except Exception as e: log.error("Error downloading treeinfo file: %s" % e) return None return "/tmp/.treeinfo" def _getReleasever(self): """ We need to make sure $releasever gets set up before .repo files are read. Since there's no redhat-release package in /mnt/sysimage (and won't be for quite a while), we need to do our own substutition. """ c = ConfigParser() treeinfo = self._getTreeinfo(self._baseRepoURL, self.proxy_url, not flags.noverifyssl) if not treeinfo: return productVersion ConfigParser.read(c, treeinfo) try: return c.get("general", "version") except: return productVersion # Override this method so yum doesn't nuke our existing logging config. def doLoggingSetup(self, *args, **kwargs): import yum.logginglevels file_handler = logging.FileHandler("/tmp/yum.log") file_formatter = logging.Formatter("[%(asctime)s] %(levelname)-8s: %(message)s") file_handler.setFormatter(file_formatter) tty3_handler = logging.FileHandler("/dev/tty3") tty3_formatter = logging.Formatter("%(asctime)s %(levelname)-8s: %(name)s: %(message)s", "%H:%M:%S") tty3_handler.setFormatter(tty3_formatter) verbose = logging.getLogger("yum.verbose") verbose.setLevel(logging.DEBUG) verbose.propagate = False verbose.addHandler(file_handler) logger = logging.getLogger("yum") logger.propagate = False logger.setLevel(yum.logginglevels.INFO_2) logger.addHandler(file_handler) anaconda_log.autoSetLevel(tty3_handler, True) tty3_handler.setLevel(anaconda_log.logger.loglevel) logger.addHandler(tty3_handler) # XXX filelogger is set in setFileLog - do we or user want it? filelogger = logging.getLogger("yum.filelogging") filelogger.setLevel(logging.INFO) filelogger.propagate = False def doConfigSetup(self, fn='/tmp/anaconda-yum.conf', root='/'): if hasattr(self, "preconf"): self.preconf.fn = fn self.preconf.root = root self.preconf.releasever = self._getReleasever() self.preconf.enabled_plugins = ["whiteout", "blacklist", "pidplugin"] YumSorter._getConfig(self) else: YumSorter._getConfig(self, fn=fn, root=root, enabled_plugins=["whiteout", "blacklist", "pidplugin"]) self.configBaseRepo(root=root) extraRepos = [] ddArch = os.uname()[4] #Add the Driver disc repos to Yum for d in glob.glob(DD_RPMS): dirname = os.path.basename(d) rid = "anaconda-%s" % dirname repo = AnacondaYumRepo(rid) repo.baseurl = [ "file://%s" % d ] repo.name = "Driver Disk %s" % dirname.split("-")[1] repo.enable() extraRepos.append(repo) if self.anaconda.isKickstart: for ksrepo in self.anaconda.id.ksdata.repo.repoList: anacondaBaseURLs = [ksrepo.baseurl] # yum doesn't understand nfs:// and doesn't want to. We need # to first do the mount, then translate it into a file:// that # yum does understand. # "nfs:" and "nfs://" prefixes are accepted in ks repo --baseurl if ksrepo.baseurl and ksrepo.baseurl.startswith("nfs:"): dest = tempfile.mkdtemp("", ksrepo.name.replace(" ", ""), "/mnt") # handle "nfs://" prefix if ksrepo.baseurl[4:6] == '//': ksrepo.baseurl = ksrepo.baseurl.replace('//', '', 1) anacondaBaseURLs = [ksrepo.baseurl] try: isys.mount(ksrepo.baseurl[4:], dest, "nfs") except Exception as e: log.error("error mounting NFS repo: %s" % e) ksrepo.baseurl = "file://%s" % dest repo = AnacondaYumRepo(ksrepo.name) repo.mirrorlist = ksrepo.mirrorlist repo.name = ksrepo.name if not ksrepo.baseurl: repo.baseurl = [] else: repo.baseurl = [ ksrepo.baseurl ] repo.anacondaBaseURLs = anacondaBaseURLs if ksrepo.cost: repo.cost = ksrepo.cost if ksrepo.excludepkgs: repo.exclude = ksrepo.excludepkgs if ksrepo.includepkgs: repo.includepkgs = ksrepo.includepkgs if ksrepo.noverifyssl: repo.sslverify = False if ksrepo.proxy: self.setProxy(ksrepo, repo) elif self.anaconda.proxy: log.debug("%s will use the global proxy configuration", repo.name) self.setProxy(self.anaconda, repo) repo.enable() extraRepos.append(repo) initialRepos = self.repos.repos.values() + extraRepos for repo in initialRepos: addons = self._getAddons(repo) for addon in addons: addonRepo = AnacondaYumRepo(addon[0]) addonRepo.name = addon[1] addonRepo.baseurl = [ addon[2] ] if self.anaconda.proxy: self.setProxy(self.anaconda, addonRepo) extraRepos.append(addonRepo) for repo in extraRepos: try: self.repos.add(repo) log.info("added repository %s with URL %s" % (repo.name, repo.mirrorlist or repo.baseurl[0])) except: log.warning("ignoring duplicate repository %s with URL %s" % (repo.name, repo.mirrorlist or repo.baseurl[0])) self.repos.setCacheDir(self.conf.cachedir) # When upgrading cleanup the yum cache and enable the addons # This has to be called after setCacheDir if self.anaconda.id.getUpgrade(): for repo in extraRepos: repo.dirCleanup(upgrade=True) repo.enable() log.info("enabled %s for upgrade" % (repo.name)) if os.path.exists("%s/boot/upgrade/install.img" % self.anaconda.rootPath): log.info("REMOVING stage2 image from %s /boot/upgrade" % self.anaconda.rootPath ) try: os.unlink("%s/boot/upgrade/install.img" % self.anaconda.rootPath) except: log.warning("failed to clean /boot/upgrade") def downloadHeader(self, po): trynumber = 0 while True: # retrying version of download header trynumber += 1 try: YumSorter.downloadHeader(self, po) break except yum.Errors.NoMoreMirrorsRepoError: self._handleFailure(po, trynumber) except IOError: self._handleFailure(po, trynumber) except yum.Errors.RepoError, e: continue def _handleFailure(self, package, trynumber=YUM_DOWNLOAD_RETRIES): if not self.isodir and self.currentMedia: buttons = [_("Re_boot"), _("_Eject")] else: buttons = [_("Re_boot"), _("_Retry")] pkgFile = to_unicode(os.path.basename(package.remote_path)) if package.repo.needsNetwork() and not network.hasActiveNetDev(): if not self.anaconda.intf.enableNetwork(): return urlgrabber.grabber.reset_curl_obj() # only show the retry window after 3 tries if trynumber < YUM_DOWNLOAD_RETRIES: log.warning('package download failure, retrying automatically') time.sleep(YUM_DOWNLOAD_DELAY * trynumber) rc = 1 else: rc = self.anaconda.intf.messageWindow(_("Error"), _("The file %s cannot be opened. This is due to a missing " "file, a corrupt package or corrupt media. Please " "verify your installation source.\n\n" "If you exit, your system will be left in an inconsistent " "state that will likely require reinstallation.\n\n") % (pkgFile,), type="custom", custom_icon="error", custom_buttons=buttons) if rc == 0: sys.exit(0) else: if os.path.exists(package.localPkg()): os.unlink(package.localPkg()) if not self.isodir and self.currentMedia: self._switchCD(self.currentMedia) else: return def mirrorFailureCB (self, obj, *args, **kwargs): # This gets called when a mirror fails, but it cannot know whether # or not there are other mirrors left to try, since it cannot know # which mirror we were on when we started this particular download. # Whenever we have run out of mirrors the grabber's get/open/retrieve # method will raise a URLGrabError exception with errno 256. grab = self.repos.getRepo(kwargs["repo"]).grab log.warning("Failed to get %s from mirror %d/%d, " "or downloaded file is corrupt" % (obj.url, grab._next + 1, len(grab.mirrors))) if self.currentMedia: dev = self.anaconda.id.storage.devicetree.getDeviceByName(self.anaconda.mediaDevice) dev.format.mountpoint = self.tree unmountCD(dev, self.anaconda.intf.messageWindow) self.currentMedia = None def urlgrabberFailureCB (self, obj, *args, **kwargs): if hasattr(obj, "exception"): log.warning("Try %s/%s for %s failed: %s" % (obj.tries, obj.retry, obj.url, obj.exception)) else: log.warning("Try %s/%s for %s failed" % (obj.tries, obj.retry, obj.url)) if obj.tries == obj.retry: return delay = 0.25*(2**(obj.tries-1)) if delay > 1: w = self.anaconda.intf.waitWindow(_("Retrying"), _("Retrying download.")) time.sleep(delay) w.pop() else: time.sleep(delay) def getDownloadPkgs(self): downloadpkgs = [] totalSize = 0 totalFiles = 0 for txmbr in self.tsInfo.getMembersWithState(output_states=TS_INSTALL_STATES): if txmbr.po: totalSize += int(txmbr.po.returnSimple("installedsize")) / 1024 for filetype in txmbr.po.returnFileTypes(): totalFiles += len(txmbr.po.returnFileEntries(ftype=filetype)) downloadpkgs.append(txmbr.po) return (downloadpkgs, totalSize, totalFiles) def setColor(self): if rpmUtils.arch.isMultiLibArch(): self.ts.ts.setColor(3) def run(self, instLog, cb, intf, id): def mediasort(a, b): # sort so that first CD comes first, etc. -99 is a magic number # to tell us that the cd should be last if a == -99: return 1 elif b == -99: return -1 if a < b: return -1 elif a > b: return 1 return 0 self.initActionTs() if id.getUpgrade(): self.ts.ts.setProbFilter(~rpm.RPMPROB_FILTER_DISKSPACE) self.setColor() # If we don't have any required media assume single disc if self.tsInfo.reqmedia == {}: self.tsInfo.reqmedia[0] = None mkeys = self.tsInfo.reqmedia.keys() mkeys.sort(mediasort) if os.path.ismount("/mnt/stage2"): isys.umount("/mnt/stage2") for i in mkeys: self.tsInfo.curmedia = i if i > 0: pkgtup = self.tsInfo.reqmedia[i][0] try: self.dsCallback = DownloadHeaderProgress(intf, self) self.populateTs(keepold=0) self.dsCallback.pop() self.dsCallback = None except RepoError, e: msg = _("There was an error running your transaction for " "the following reason: %s\n") % str(e) if self.anaconda.id.upgrade or self.anaconda.isKickstart: rc = intf.messageWindow(_("Error"), msg, type="custom", custom_icon="error", custom_buttons=[_("_Exit installer")]) sys.exit(1) else: rc = intf.messageWindow(_("Error"), msg, type="custom", custom_icon="error", custom_buttons=[_("_Back"), _("_Exit installer")]) if rc == 1: sys.exit(1) else: self.tsInfo.curmedia = None return DISPATCH_BACK self.ts.check() self.ts.order() self.anaconda.id.bootloader.trusted_boot = self.isPackageInstalled(name="tboot") and not iutil.inXen() if self._run(instLog, cb, intf) == DISPATCH_BACK: self.tsInfo.curmedia = None return DISPATCH_BACK self.ts.close() def _run(self, instLog, cb, intf): # set log fd. FIXME: this is ugly. see changelog entry from 2005-09-13 self.ts.ts.scriptFd = instLog.fileno() rpm.setLogFile(instLog) uniqueProbs = {} spaceneeded = {} spaceprob = "" fileConflicts = [] fileprob = "" try: self.runTransaction(cb=cb) except YumBaseError, probs: # FIXME: we need to actually look at these problems... probTypes = { rpm.RPMPROB_NEW_FILE_CONFLICT : _('file conflicts'), rpm.RPMPROB_FILE_CONFLICT : _('file conflicts'), rpm.RPMPROB_OLDPACKAGE: _('older package(s)'), rpm.RPMPROB_DISKSPACE: _('insufficient disk space'), rpm.RPMPROB_DISKNODES: _('insufficient disk inodes'), rpm.RPMPROB_CONFLICT: _('package conflicts'), rpm.RPMPROB_PKG_INSTALLED: _('package already installed'), rpm.RPMPROB_REQUIRES: _('required package'), rpm.RPMPROB_BADARCH: _('package for incorrect arch'), rpm.RPMPROB_BADOS: _('package for incorrect os'), } for (descr, (ty, mount, need)) in probs.value: # FIXME: probs.value??? log.error("%s: %s" %(probTypes[ty], descr)) if not uniqueProbs.has_key(ty) and probTypes.has_key(ty): uniqueProbs[ty] = probTypes[ty] if ty == rpm.RPMPROB_DISKSPACE: spaceneeded[mount] = need elif ty in [rpm.RPMPROB_NEW_FILE_CONFLICT, rpm.RPMPROB_FILE_CONFLICT]: fileConflicts.append(descr) if spaceneeded: spaceprob = _("You need more space on the following " "file systems:\n") for (mount, need) in spaceneeded.items(): log.info("(%s, %s)" %(mount, need)) if mount.startswith("/mnt/sysimage/"): mount.replace("/mnt/sysimage", "") elif mount.startswith("/mnt/sysimage"): mount = "/" + mount.replace("/mnt/sysimage", "") spaceprob += "%d M on %s\n" % (need / (1024*1024), mount) elif fileConflicts: fileprob = _("There were file conflicts when checking the " "packages to be installed:\n%s\n") % ("\n".join(fileConflicts),) msg = _("There was an error running your transaction for " "the following reason(s): %s.\n") % ', '.join(uniqueProbs.values()) spaceprob = to_unicode(spaceprob) fileprob = to_unicode(fileprob) if len(self.anaconda.backend.getRequiredMedia()) > 1 or \ self.anaconda.id.upgrade or self.anaconda.isKickstart: intf.detailedMessageWindow(_("Error Running Transaction"), msg, spaceprob + "\n" + fileprob, type="custom", custom_icon="error", custom_buttons=[_("_Exit installer")]) sys.exit(1) else: rc = intf.detailedMessageWindow(_("Error Running Transaction"), msg, spaceprob + "\n" + fileprob, type="custom", custom_icon="error", custom_buttons=[_("_Back"), _("_Exit installer")]) if rc == 1: sys.exit(1) else: self._undoDepInstalls() return DISPATCH_BACK def doMacros(self): for (key, val) in self.macros.items(): rpm.addMacro(key, val) def simpleDBInstalled(self, name, arch=None): # FIXME: doing this directly instead of using self.rpmdb.installed() # speeds things up by 400% mi = self.ts.ts.dbMatch('name', name) if mi.count() == 0: return False if arch is None: return True if arch in map(lambda h: h['arch'], mi): return True return False def isPackageInstalled(self, name = None, epoch = None, version = None, release = None, arch = None, po = None): # FIXME: this sucks. we should probably suck it into yum proper # but it'll need a bit of cleanup first. if po is not None: (name, epoch, version, release, arch) = po.returnNevraTuple() installed = False if name and not (epoch or version or release or arch): installed = self.simpleDBInstalled(name) elif self.rpmdb.installed(name = name, epoch = epoch, ver = version, rel = release, arch = arch): installed = True lst = self.tsInfo.matchNaevr(name = name, epoch = epoch, ver = version, rel = release, arch = arch) for txmbr in lst: if txmbr.output_state in TS_INSTALL_STATES: return True if installed and len(lst) > 0: # if we get here, then it was installed, but it's in the tsInfo # for an erase or obsoleted --> not going to be installed at end return False return installed def isGroupInstalled(self, grp): if grp.selected: return True elif grp.installed and not grp.toremove: return True return False def _pkgExists(self, pkg): """Whether or not a given package exists in our universe.""" try: pkgs = self.pkgSack.returnNewestByName(pkg) return True except yum.Errors.PackageSackError: pass try: pkgs = self.rpmdb.returnNewestByName(pkg) return True except (IndexError, yum.Errors.PackageSackError): pass return False def _groupHasPackages(self, grp): # this checks to see if the given group has any packages available # (ie, already installed or in the sack of available packages) # so that we don't show empty groups. also, if there are mandatory # packages and we have none of them, don't show for pkg in grp.mandatory_packages.keys(): if self._pkgExists(pkg): return True if len(grp.mandatory_packages) > 0: return False for pkg in grp.default_packages.keys() + grp.optional_packages.keys() + \ grp.conditional_packages.keys(): if self._pkgExists(pkg): return True return False class YumBackend(AnacondaBackend): def __init__ (self, anaconda): AnacondaBackend.__init__(self, anaconda) self.supportsPackageSelection = True buf = """ [main] installroot=%s cachedir=/var/cache/yum keepcache=0 logfile=/tmp/yum.log metadata_expire=0 obsoletes=True pluginpath=/usr/lib/yum-plugins,/tmp/updates/yum-plugins pluginconfpath=/etc/yum/pluginconf.d,/tmp/updates/pluginconf.d plugins=1 reposdir=/etc/anaconda.repos.d,/tmp/updates/anaconda.repos.d,/tmp/product/anaconda.repos.d debuglevel=10 """ % (anaconda.rootPath) if anaconda.proxy: buf += "proxy=%s\n" % anaconda.proxy if anaconda.proxyUsername: buf += "proxy_username=%s\n" % anaconda.proxyUsername if anaconda.proxyPassword: buf += "proxy_password=%s\n" % anaconda.proxyPassword fd = open("/tmp/anaconda-yum.conf", "w") fd.write(buf) fd.close() def complete(self, anaconda): if not anaconda.mediaDevice and os.path.ismount(self.ayum.tree): isys.umount(self.ayum.tree) # Attempt to clean up Yum so that running yum in post will work (#1110148) self.ayum.closeRpmDB() del self.ayum.tsInfo del self.ayum.ts self.ayum.close() # clean up rpmdb locks so that kickstart %post scripts aren't # unhappy (#496961) iutil.resetRpmDb(anaconda.rootPath) def doBackendSetup(self, anaconda): if anaconda.dir == DISPATCH_BACK: return DISPATCH_BACK if anaconda.id.getUpgrade(): # FIXME: make sure that the rpmdb doesn't have stale locks :/ iutil.resetRpmDb(anaconda.rootPath) iutil.writeRpmPlatform() self.ayum = AnacondaYum(anaconda) self.ayum.setup() self.ayum.doMacros() # If any enabled repositories require networking, go ahead and bring # it up now. No need to have people wait for the timeout when we # know this in advance. if len(filter(lambda r: r.needsNetwork(), self.ayum.repos.listEnabled())) > 0 and \ not network.hasActiveNetDev(): if not anaconda.intf.enableNetwork(): anaconda.intf.messageWindow(_("No Network Available"), _("Some of your software repositories require " "networking, but there was an error enabling the " "network on your system."), type="custom", custom_icon="error", custom_buttons=[_("_Exit installer")]) sys.exit(1) urlgrabber.grabber.reset_curl_obj() self.doRepoSetup(anaconda) self.doSackSetup(anaconda) self.doGroupSetup(anaconda) self.ayum.doMacros() def doGroupSetup(self, anaconda): while True: try: # FIXME: this is a pretty ugly hack to make it so that we don't lose # groups being selected (#237708) sel = filter(lambda g: g.selected, self.ayum.comps.get_groups()) self.ayum.doGroupSetup() # now we'll actually reselect groups.. map(lambda g: self.selectGroup(g.groupid), sel) # and now, to add to the hacks, we'll make sure that packages don't # have groups double-listed. this avoids problems with deselecting # groups later for txmbr in self.ayum.tsInfo.getMembers(): txmbr.groups = yum.misc.unique(txmbr.groups) except (GroupsError, NoSuchGroup, RepoError), e: buttons = [_("_Exit installer"), _("_Retry")] else: break # success rc = anaconda.intf.messageWindow(_("Error"), _("Unable to read group information " "from repositories. This is " "a problem with the generation " "of your install tree."), type="custom", custom_icon="error", custom_buttons = buttons) if rc == 0: sys.exit(0) else: self.ayum._setGroups(None) continue def doRepoSetup(self, anaconda, thisrepo = None, fatalerrors = True): self.__withFuncDo(anaconda, lambda r: self.ayum.doRepoSetup(thisrepo=r.id), thisrepo=thisrepo, fatalerrors=fatalerrors, callback=RepoSetupPulseProgress(anaconda.intf)) def doSackSetup(self, anaconda, thisrepo = None, fatalerrors = True): self.__withFuncDo(anaconda, lambda r: self.ayum.doSackSetup(thisrepo=r.id), thisrepo=thisrepo, fatalerrors=fatalerrors, callback=SackSetupProgress(anaconda.intf)) def __withFuncDo(self, anaconda, fn, thisrepo=None, fatalerrors=True, callback=None): # Don't do this if we're being called as a dispatcher step (instead # of being called when a repo is added via the UI) and we're going # back. if thisrepo is None and anaconda.dir == DISPATCH_BACK: return # We want to call the function one repo at a time so we have some # concept of which repo didn't set up correctly. if thisrepo is not None: repos = [self.ayum.repos.getRepo(thisrepo)] else: repos = self.ayum.repos.listEnabled() for repo in repos: if callback: callback.connect(repo) while True: try: fn(repo) if callback: callback.disconnect() except RepoError, e: if callback: callback.disconnect() if repo.needsNetwork() and not network.hasActiveNetDev(): if anaconda.intf.enableNetwork(): repo.mirrorlistparsed = False continue urlgrabber.grabber.reset_curl_obj() buttons = [_("_Exit installer"), _("Edit"), _("_Retry")] else: break # success if anaconda.isKickstart: buttons.append(_("_Continue")) if not fatalerrors: raise RepoError, e rc = anaconda.intf.messageWindow(_("Error"), _("Unable to read package metadata. This may be " "due to a missing repodata directory. Please " "ensure that your install tree has been " "correctly generated.\n\n%s" % e), type="custom", custom_icon="error", custom_buttons=buttons) if rc == 0: # abort sys.exit(0) elif rc == 1: # edit anaconda.intf.editRepoWindow(repo) break elif rc == 2: # retry, but only if button is present continue else: # continue, but only if button is present self.ayum.repos.delete(repo.id) break # if we're in kickstart the repo may have been deleted just above try: self.ayum.repos.getRepo(repo.id) except RepoError: log.debug("repo %s has been removed" % (repo.id,)) continue repo.setFailureObj(self.ayum.urlgrabberFailureCB) repo.setMirrorFailureObj((self.ayum.mirrorFailureCB, (), {"repo": repo.id})) self.ayum.repos.callback = None def getDefaultGroups(self, anaconda): langs = anaconda.id.instLanguage.getCurrentLangSearchList() rc = map(lambda x: x.groupid, filter(lambda x: x.default, self.ayum.comps.groups)) for g in self.ayum.comps.groups: if g.langonly in langs: rc.append(g.groupid) return rc def resetPackageSelections(self): """Reset the package selection to an empty state.""" for txmbr in self.ayum.tsInfo: self.ayum.tsInfo.remove(txmbr.pkgtup) self.ayum.tsInfo.conditionals.clear() for grp in self.ayum.comps.groups: grp.selected = False def selectModulePackages(self, anaconda, kernelPkgName): (base, sep, ext) = kernelPkgName.partition("-") moduleProvides = [] for (path, name) in anaconda.id.extraModules: if ext != "": moduleProvides.append("dud-%s-%s" % (name, ext)) else: moduleProvides.append("dud-%s" % name) #We need to install the packages which contain modules from DriverDiscs for modPath in isys.modulesWithPaths(): log.debug("Checking for DUD module "+modPath) match = DD_EXTRACTED.match(modPath) if match: log.info("Requesting install of kmod-%s" % (match.group("modulename"))) moduleProvides.append("kmod-"+match.group("modulename")) else: continue for module in moduleProvides: pkgs = self.ayum.returnPackagesByDep(module) if not pkgs: log.warning("Didn't find any package providing %s" % module) for pkg in pkgs: log.info("selecting package %s for %s" % (pkg.name, module)) self.ayum.install(po=pkg) def selectBestKernel(self, anaconda): """Find the best kernel package which is available and select it.""" def getBestKernelByArch(pkgname, ayum): """Convenience func to find the best arch of a kernel by name""" try: pkgs = ayum.pkgSack.returnNewestByName(pkgname) except yum.Errors.PackageSackError: return None pkgs = self.ayum.bestPackagesFromList(pkgs) if len(pkgs) == 0: return None return pkgs[0] def selectKernel(pkgname): try: pkg = getBestKernelByArch(pkgname, self.ayum) except PackageSackError: log.debug("no %s package" % pkgname) return False if not pkg: return False log.info("selected %s package for kernel" % pkg.name) self.ayum.install(po=pkg) self.selectModulePackages(anaconda, pkg.name) if len(self.ayum.tsInfo.matchNaevr(name="gcc")) > 0: log.debug("selecting %s-devel" % pkg.name) self.selectPackage("%s-devel.%s" % (pkg.name, pkg.arch)) return True if anaconda.isKickstart and "kernel" in anaconda.id.ksdata.packages.excludedList: return foundkernel = False if not foundkernel and isys.isPaeAvailable(): if selectKernel("kernel-PAE"): foundkernel = True if not foundkernel: selectKernel("kernel") ###### select corresponding kernel-modules if needed #### sladdonmodules=[ ("openafs-client" , "kernel-module-openafs"), ] # I don;t think we can have more than one at this stage ... JP slkernname="kernel" for k in ("kernel","kernel-PAE","kernel-debug"): if len(self.ayum.tsInfo.matchNaevr(name=k)) > 0: slkernname=k log.debug("sladdon - trying with "+slkernname) for slkernpkg in self.ayum.tsInfo.matchNaevr(name=slkernname): (n,a,e,v,r) = slkernpkg.pkgtup slkernvers = v+"-"+r log.debug("sladdon - looking at kernel: "+v+"-"+r) for (slpkg,slkernmod) in sladdonmodules: if len(self.ayum.tsInfo.matchNaevr(name=slpkg)) >0: log.debug("sladdon - working on: "+ slpkg +" ("+ slkernmod +").") if len(self.ayum.tsInfo.matchNaevr(name="kernel")) >0: if self.selectPackage(slkernmod +"-"+slkernvers): log.debug("sladdon - added: "+slkernmod+"-"+slkernvers) if self.selectPackage(slkernmod): log.debug("sladdon - added: "+slkernmod) if len(self.ayum.tsInfo.matchNaevr(name="kernel-PAE")) >0: if self.selectPackage(slkernmod +"-"+ slkernvers+"PAE"): log.debug("sladdon - added: "+slkernmod+"-"+slkernvers+"PAE") if self.selectPackage(slkernmod +"-PAE"): log.debug("sladdon - added: "+slkernmod +"-PAE") if len(self.ayum.tsInfo.matchNaevr(name="kernel-debug")) >0: if self.selectPackage(slkernmod+"-"+slkernvers+"debug"): log.debug("sladdon - added: "+slkernmod+"-"+slkernvers+"debug") if self.selectPackage(slkernmod+"-debug"): log.debug("sladdon - added: "+slkernmod+"-debug") log.debug("sladdon - finished.") ###### jaroslaw.polok@cern.ch ################################## def selectFSPackages(self, storage): fspkgs = set() for device in storage.fsset.devices: # this takes care of device and filesystem packages for pkg in device.packages: fspkgs.add(pkg) map(self.selectPackage, fspkgs) def selectAnacondaNeeds(self, anaconda): # Only add in chkconfig if they did something that needs it. if anaconda.isKickstart and (anaconda.id.ksdata.services.disabled or anaconda.id.ksdata.services.enabled) or \ anaconda.id.storage.services or anaconda.id.network.hasActiveIPoIBDevice(): self.selectPackage("chkconfig") def doPostSelection(self, anaconda): # Only solve dependencies on the way through the installer, not the way back. if anaconda.dir == DISPATCH_BACK: return dscb = YumDepSolveProgress(anaconda.intf, self.ayum) self.ayum.dsCallback = dscb # do some sanity checks for kernel and bootloader if not anaconda.id.getUpgrade(): # New installs only - upgrades will already have all this stuff. self.selectBestKernel(anaconda) map(self.selectPackage, anaconda.platform.packages) map(self.deselectPackage, anaconda.platform.excluded_packages) self.selectFSPackages(anaconda.id.storage) if anaconda.id.network.hasActiveIPoIBDevice(): self.selectPackage("rdma") self.selectAnacondaNeeds(anaconda) else: self.ayum.update() while True: try: (code, msgs) = self.ayum.buildTransaction() # If %packages --ignoremissing was given, don't bother # prompting for missing dependencies. if anaconda.isKickstart and anaconda.id.ksdata.packages.handleMissing == KS_MISSING_IGNORE: break if code == 1 and not anaconda.id.upgrade: # resolveDeps returns 0 if empty transaction, 1 if error, # 2 if success depprob = "\n".join(msgs) custom_buttons = [_("_Exit installer"), _("_Continue")] if not anaconda.isKickstart: custom_buttons.insert(1, _("_Back")) rc = anaconda.intf.detailedMessageWindow(_("Warning"), _("Some of the packages you have selected for " "install are missing dependencies or conflict " "with another package. You can exit the " "installation, go back and change your package " "selections, or continue installing these " "packages without their dependencies."), depprob + "\n", type="custom", custom_icon="error", custom_buttons=custom_buttons) dscb.pop() if rc == 0: sys.exit(1) elif rc == 1 and not anaconda.isKickstart: self.ayum._undoDepInstalls() return DISPATCH_BACK break except RepoError, e: # FIXME: would be nice to be able to recover here rc = anaconda.intf.messageWindow(_("Error"), _("Unable to read package metadata. This may be " "due to a missing repodata directory. Please " "ensure that your install tree has been " "correctly generated.\n\n%s" % e), type="custom", custom_icon="error", custom_buttons=[_("_Exit installer"), _("_Retry")]) dscb.pop() if rc == 0: sys.exit(0) else: continue else: break (self.dlpkgs, self.totalSize, self.totalFiles) = self.ayum.getDownloadPkgs() if not anaconda.id.upgrade: largePart = anaconda.id.storage.mountpoints.get("/usr", anaconda.id.storage.rootDevice) if largePart and largePart.size < self.totalSize / 1024: rc = anaconda.intf.messageWindow(_("Error"), _("Your selected packages require %d MB " "of free space for installation, but " "you do not have enough available. " "You can change your selections or " "exit the installer." % (self.totalSize / 1024)), type="custom", custom_icon="error", custom_buttons=[_("_Back"), _("_Exit installer")]) dscb.pop() if rc == 1: sys.exit(1) else: self.ayum._undoDepInstalls() return DISPATCH_BACK dscb.pop() if anaconda.mediaDevice and not anaconda.isKickstart: rc = presentRequiredMediaMessage(anaconda) if rc == 0: rc2 = anaconda.intf.messageWindow(_("Reboot?"), _("The system will be rebooted now."), type="custom", custom_icon="warning", custom_buttons=[_("_Back"), _("_Reboot")]) if rc2 == 1: sys.exit(0) else: return DISPATCH_BACK elif rc == 1: # they asked to go back return DISPATCH_BACK self.ayum.dsCallback = None def doPreInstall(self, anaconda): if anaconda.dir == DISPATCH_BACK: for d in ("/selinux", "/dev", "/proc/bus/usb"): try: isys.umount(anaconda.rootPath + d, removeDir = False) except Exception, e: log.error("unable to unmount %s: %s" %(d, e)) return # shorthand upgrade = anaconda.id.getUpgrade() if upgrade: # An old mtab can cause confusion (esp if loop devices are # in it). Be extra special careful and delete any mtab first, # in case the user has done something funny like make it into # a symlink. if os.access(anaconda.rootPath + "/etc/mtab", os.F_OK): os.remove(anaconda.rootPath + "/etc/mtab") f = open(anaconda.rootPath + "/etc/mtab", "w+") f.close() # we really started writing modprobe.conf out before things were # all completely ready. so now we need to nuke old modprobe.conf's # if you're upgrading from a 2.4 dist so that we can get the # transition right if (os.path.exists(anaconda.rootPath + "/etc/modules.conf") and os.path.exists(anaconda.rootPath + "/etc/modprobe.conf") and not os.path.exists(anaconda.rootPath + "/etc/modprobe.conf.anacbak")): log.info("renaming old modprobe.conf -> modprobe.conf.anacbak") os.rename(anaconda.rootPath + "/etc/modprobe.conf", anaconda.rootPath + "/etc/modprobe.conf.anacbak") dirList = ['/var', '/var/lib', '/var/lib/rpm', '/tmp', '/dev', '/etc', '/etc/sysconfig', '/etc/sysconfig/network-scripts', '/etc/X11', '/root', '/var/tmp', '/etc/rpm', '/var/cache', '/var/cache/yum', '/etc/modprobe.d'] # If there are any protected partitions we want to mount, create their # mount points now. for protected in anaconda.id.storage.protectedDevices: if getattr(protected.format, "mountpoint", None): dirList.append(protected.format.mountpoint) for i in dirList: try: os.mkdir(anaconda.rootPath + i) except os.error, (errno, msg): pass # log.error("Error making directory %s: %s" % (i, msg)) self.initLog(anaconda.id, anaconda.rootPath) # setup /etc/rpm/ for the post-install environment iutil.writeRpmPlatform(anaconda.rootPath) try: # FIXME: making the /var/lib/rpm symlink here is a hack to # workaround db->close() errors from rpm iutil.mkdirChain("/var/lib") for path in ("/var/tmp", "/var/lib/rpm"): if os.path.exists(path) and not os.path.islink(path): shutil.rmtree(path) if not os.path.islink(path): os.symlink("%s/%s" %(anaconda.rootPath, path), "%s" %(path,)) else: log.warning("%s already exists as a symlink to %s" %(path, os.readlink(path),)) except Exception, e: # how this could happen isn't entirely clear; log it in case # it does and causes problems later log.error("error creating symlink, continuing anyway: %s" %(e,)) # SELinux hackery (#121369) if flags.selinux: try: os.mkdir(anaconda.rootPath + "/selinux") except Exception, e: pass try: isys.mount("/selinux", anaconda.rootPath + "/selinux", "selinuxfs") except Exception, e: log.error("error mounting selinuxfs: %s" %(e,)) # For usbfs try: isys.mount("/proc/bus/usb", anaconda.rootPath + "/proc/bus/usb", "usbfs") except Exception, e: log.error("error mounting usbfs: %s" %(e,)) # write out the fstab if not upgrade: anaconda.id.storage.fsset.write(anaconda.rootPath) if os.access("/etc/modprobe.d/anaconda.conf", os.R_OK): shutil.copyfile("/etc/modprobe.d/anaconda.conf", anaconda.rootPath + "/etc/modprobe.d/anaconda.conf") anaconda.id.network.write() anaconda.id.network.copyConfigToPath(instPath=anaconda.rootPath) anaconda.id.storage.write(anaconda.rootPath) if not anaconda.id.isHeadless: anaconda.id.keyboard.write(anaconda.rootPath) # make a /etc/mtab so mkinitrd can handle certain hw (usb) correctly f = open(anaconda.rootPath + "/etc/mtab", "w+") f.write(anaconda.id.storage.mtab) f.close() def checkSupportedUpgrade(self, anaconda): if anaconda.dir == DISPATCH_BACK: return self._checkUpgradeVersion(anaconda) self._checkUpgradeArch(anaconda) def _checkUpgradeVersion(self, anaconda): # Figure out current version for upgrade nag and for determining weird # upgrade cases supportedUpgradeVersion = -1 for pkgtup in self.ayum.rpmdb.whatProvides('redhat-release', None, None): n, a, e, v, r = pkgtup if supportedUpgradeVersion <= 0: val = rpmUtils.miscutils.compareEVR((None, '3', '1'), (e, v,r)) if val > 0: supportedUpgradeVersion = 0 else: supportedUpgradeVersion = 1 break if "Red Hat Enterprise Linux" not in productName: supportedUpgradeVersion = 1 if supportedUpgradeVersion == 0: rc = anaconda.intf.messageWindow(_("Warning"), _("You appear to be upgrading from a system " "which is too old to upgrade to this " "version of %s. Are you sure you wish to " "continue the upgrade " "process?") %(productName,), type = "yesno") if rc == 0: iutil.resetRpmDb(anaconda.rootPath) sys.exit(0) def _checkUpgradeArch(self, anaconda): def compareArch(a, b): if re.match("i.86", a) and re.match("i.86", b): return True else: return a == b # get the arch of the initscripts package try: pkgs = self.ayum.pkgSack.returnNewestByName('initscripts') except yum.Errors.PackageSackError: log.info("no packages named initscripts") return None pkgs = self.ayum.bestPackagesFromList(pkgs) if len(pkgs) == 0: log.info("no best package") return myarch = pkgs[0].arch log.info("initscripts is arch: %s" %(myarch,)) for po in self.ayum.rpmdb.getProvides('initscripts'): log.info("po.arch is arch: %s" %(po.arch,)) if not compareArch(po.arch, myarch): rc = anaconda.intf.messageWindow(_("Warning"), _("The arch of the release of %(productName)s you " "are upgrading to appears to be %(myarch)s which " "does not match your previously installed arch of " "%(arch)s. This is likely to not succeed. Are " "you sure you wish to continue the upgrade " "process?") % {'productName': productName, 'myarch': myarch, 'arch': po.arch}, type="yesno") if rc == 0: iutil.resetRpmDb(anaconda.rootPath) sys.exit(0) else: log.warning("upgrade between possibly incompatible " "arches %s -> %s" %(po.arch, myarch)) break def doInstall(self, anaconda): log.info("Preparing to install packages") if not anaconda.id.upgrade: rpm.addMacro("__dbi_htconfig", "hash nofsync %{__dbi_other} %{__dbi_perms}") if anaconda.isKickstart and anaconda.id.ksdata.packages.excludeDocs: rpm.addMacro("_excludedocs", "1") if anaconda.isKickstart and anaconda.id.ksdata.packages.instLangs is not None: # Use nil if instLangs is empty rpm.addMacro('_install_langs', anaconda.id.ksdata.packages.instLangs or '%{nil}') cb = AnacondaCallback(self.ayum, anaconda, self.instLog, self.modeText) cb.setSizes(len(self.dlpkgs), self.totalSize, self.totalFiles) rc = self.ayum.run(self.instLog, cb, anaconda.intf, anaconda.id) if cb.initWindow is not None: cb.initWindow.pop() self.instLog.write("*** FINISHED INSTALLING PACKAGES ***") self.instLog.close () anaconda.id.instProgress = None if rc == DISPATCH_BACK: return DISPATCH_BACK def doPostInstall(self, anaconda): if anaconda.id.getUpgrade(): w = anaconda.intf.waitWindow(_("Post Upgrade"), _("Performing post-upgrade configuration")) else: w = anaconda.intf.waitWindow(_("Post Installation"), _("Performing post-installation configuration")) packages.rpmSetupGraphicalSystem(anaconda) for repo in self.ayum.repos.listEnabled(): repo.dirCleanup() # expire yum caches on upgrade if (flags.cmdline.has_key("preupgrade") or anaconda.id.getUpgrade()) and os.path.exists("%s/var/cache/yum" %(anaconda.rootPath,)): log.info("Expiring yum caches") try: iutil.execWithRedirect("yum", ["clean", "all"], stdout="/dev/tty5", stderr="/dev/tty5", root = anaconda.rootPath) except: pass # nuke preupgrade if flags.cmdline.has_key("preupgrade") and os.path.exists("%s/var/cache/yum/anaconda-upgrade" %(anaconda.rootPath,)): try: shutil.rmtree("%s/var/cache/yum/anaconda-upgrade" %(anaconda.rootPath,)) except: pass # XXX: write proper lvm config AnacondaBackend.doPostInstall(self, anaconda) w.pop() def kernelVersionList(self, rootPath="/"): # FIXME: using rpm here is a little lame, but otherwise, we'd # be pulling in filelists return packages.rpmKernelVersionList(rootPath) def __getGroupId(self, group): """Get the groupid for the given name (english or translated).""" for g in self.ayum.comps.groups: if group == g.name: return g.groupid for trans in g.translated_name.values(): if group == trans: return g.groupid def isGroupSelected(self, group): try: grp = self.ayum.comps.return_group(group) if grp.selected: return True except yum.Errors.GroupsError, e: pass return False def selectGroup(self, group, *args): if not self.ayum.comps.has_group(group): log.debug("no such group %s" % group) raise NoSuchGroup, group types = ["mandatory"] if args: if args[0][0]: types.append("default") if args[0][1]: types.append("optional") else: types.append("default") try: mbrs = self.ayum.selectGroup(group, group_package_types=types) if len(mbrs) == 0 and self.isGroupSelected(group): return except yum.Errors.GroupsError, e: # try to find out if it's the name or translated name gid = self.__getGroupId(group) if gid is not None: mbrs = self.ayum.selectGroup(gid, group_package_types=types) if len(mbrs) == 0 and self.isGroupSelected(gid): return else: log.debug("no such group %s" %(group,)) raise NoSuchGroup, group def deselectGroup(self, group, *args): try: self.ayum.deselectGroup(group, force=True) except yum.Errors.GroupsError, e: # try to find out if it's the name or translated name gid = self.__getGroupId(group) if gid is not None: self.ayum.deselectGroup(gid, force=True) else: log.debug("no such group %s" %(group,)) def selectPackage(self, pkg, *args): try: mbrs = self.ayum.install(pattern=pkg) return len(mbrs) except yum.Errors.InstallError: log.debug("no package matching %s" %(pkg,)) return 0 def deselectPackage(self, pkg, *args): sp = pkg.rsplit(".", 2) txmbrs = [] if len(sp) == 2: txmbrs = self.ayum.tsInfo.matchNaevr(name=sp[0], arch=sp[1]) if len(txmbrs) == 0: exact, match, unmatch = yum.packages.parsePackages(self.ayum.pkgSack.returnPackages(), [pkg], casematch=1) for p in exact + match: txmbrs.append(p) if len(txmbrs) > 0: for x in txmbrs: self.ayum.tsInfo.remove(x.pkgtup) # we also need to remove from the conditionals # dict so that things don't get pulled back in as a result # of them. yes, this is ugly. conditionals should die. for req, pkgs in self.ayum.tsInfo.conditionals.iteritems(): if x in pkgs: pkgs.remove(x) self.ayum.tsInfo.conditionals[req] = pkgs return len(txmbrs) else: log.debug("no such package %s to remove" %(pkg,)) return 0 def groupListExists(self, grps): """Returns bool of whether all of the given groups exist.""" for gid in grps: g = self.ayum.comps.return_group(gid) if not g: log.debug("no such group %s" % (gid,)) return False return True def groupListDefault(self, grps): """Returns bool of whether all of the given groups are default""" rc = False for gid in grps: g = self.ayum.comps.return_group(gid) if g and not g.default: return False elif g: rc = True return rc def writeKS(self, f): for repo in self.ayum.repos.listEnabled(): if repo.name == "Installation Repo": continue if repo.name == "Red Hat Enterprise Linux": continue # ignore addon repos from media if repo.anacondaBaseURLs[0].startswith("file://"): continue line = "repo --name=\"%s\" " % (repo.name or repo.repoid) if repo.baseurl: line += " --baseurl=%s" % repo.anacondaBaseURLs[0] else: line += " --mirrorlist=%s" % repo.mirrorlist if repo.proxy: line += " --proxy=\"%s\"" % repo.proxy_dict['http'] if repo.cost: line += " --cost=%s" % repo.cost if repo.includepkgs: line += " --includepkgs=\"%s\"" % ",".join(repo.includepkgs) if repo.exclude: line += " --excludepkgs=\"%s\"" % ",".join(repo.exclude) if not repo.sslverify: line += " --noverifyssl" line += "\n" f.write(line) def writePackagesKS(self, f, anaconda): if anaconda.isKickstart: f.write(anaconda.id.ksdata.packages.__str__()) return groups = [] installed = [] removed = [] # Faster to grab all the package names up front rather than call # searchNevra in the loop below. allPkgNames = map(lambda pkg: pkg.name, self.ayum.pkgSack.returnPackages()) allPkgNames.sort() # On CD/DVD installs, we have one transaction per CD and will end up # checking allPkgNames against a very short list of packages. So we # have to reset to media #0, which is an all packages transaction. old = self.ayum.tsInfo.curmedia self.ayum.tsInfo.curmedia = 0 self.ayum.tsInfo.makelists() txmbrNames = map (lambda x: x.name, self.ayum.tsInfo.getMembers()) self.ayum.tsInfo.curmedia = old if len(self.ayum.tsInfo.instgroups) == 0 and len(txmbrNames) == 0: return for grp in filter(lambda x: x.selected, self.ayum.comps.groups): groups.append(grp.groupid) defaults = grp.default_packages.keys() + grp.mandatory_packages.keys() optionals = grp.optional_packages.keys() for pkg in filter(lambda x: x in defaults and (not x in txmbrNames and x in allPkgNames), grp.packages): removed.append(pkg) for pkg in filter(lambda x: x in txmbrNames, optionals): installed.append(pkg) if len(groups) == 1 and groups[0].lower() == "core": f.write("\n%packages --nobase\n") else: f.write("\n%packages\n") for grp in groups: f.write("@%s\n" % grp) for pkg in installed: f.write("%s\n" % pkg) for pkg in removed: f.write("-%s\n" % pkg) f.write("%end") def writeConfiguration(self): return def getRequiredMedia(self): return self.ayum.tsInfo.reqmedia.keys() class DownloadHeaderProgress: def __init__(self, intf, ayum=None): window = intf.progressWindow(_("Installation Starting"), _("Starting installation process"), 1.0, 0.01) self.window = window self.ayum = ayum self.current = self.loopstart = 0 self.incr = 1 if self.ayum is not None and self.ayum.tsInfo is not None: self.numpkgs = len(self.ayum.tsInfo.getMembers()) if self.numpkgs != 0: self.incr = (1.0 / self.numpkgs) * (1.0 - self.loopstart) else: self.numpkgs = 0 self.refresh() self.restartLoop = self.downloadHeader = self.transactionPopulation = self.refresh self.procReq = self.procConflict = self.unresolved = self.noop def noop(self, *args, **kwargs): pass def pkgAdded(self, *args): if self.numpkgs: self.set(self.current + self.incr) def pop(self): self.window.pop() def refresh(self, *args): self.window.refresh() def set(self, value): self.current = value self.window.set(self.current) class YumDepSolveProgress: def __init__(self, intf, ayum = None): window = intf.progressWindow(_("Dependency Check"), _("Checking dependencies in packages selected for installation"), 1.0, 0.01) self.window = window self.numpkgs = None self.loopstart = None self.incr = None self.ayum = ayum self.current = 0 self.restartLoop = self.downloadHeader = self.transactionPopulation = self.refresh self.procReq = self.procConflict = self.unresolved = self.noop def tscheck(self, num = None): self.refresh() if num is None and self.ayum is not None and self.ayum.tsInfo is not None: num = len(self.ayum.tsInfo.getMembers()) if num: self.numpkgs = num self.loopstart = self.current self.incr = (1.0 / num) * ((1.0 - self.loopstart) / 2) def pkgAdded(self, *args): if self.numpkgs: self.set(self.current + self.incr) def noop(self, *args, **kwargs): pass def refresh(self, *args): self.window.refresh() def set(self, value): self.current = value self.window.set(self.current) def start(self): self.set(0.0) self.refresh() def end(self): self.window.set(1.0) self.window.refresh() def pop(self): self.window.pop() # We don't have reasonable hook for sackSetup, and it # is fairly fast, so we use just waitWindow here class SackSetupProgress: def __init__(self, intf): self.intf = intf def connect(self, repo): if repo.name is None: txt = _("Retrieving installation information.") else: txt = _("Retrieving installation information for %s.")%(repo.name) self.window = self.intf.waitWindow(_("Installation Progress"), txt) def disconnect(self): self.window.pop() class RepoSetupPulseProgress: def __init__(self, intf): self.intf = intf self.repo = None def connect(self, repo): self.repo = repo if repo.name is None: txt = _("Retrieving installation information.") else: txt = _("Retrieving installation information for %s.")%(repo.name) self.window = self.intf.progressWindow(_("Installation Progress"), txt, 1.0, pulse=True) repo.setCallback(self) def disconnect(self): self.window.pop() self.repo.setCallback(None) def refresh(self, *args): self.window.refresh() def set(self): self.window.pulse() def start(self, filename, url, basename, size, text): log.debug("Grabbing %s" % url) self.set() self.refresh() def update(self, read): self.set() self.refresh() def end(self, read): self.set() self.window.refresh()