# # 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 . # import ConfigParser import sys import os import os.path import shutil import time import types import locale import glob import tempfile import itertools import re class NoSuchGroup(Exception): pass from flags import flags 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 product import isFinal, productName, productVersion, productStamp from constants import * from image import * import packages from backend import AnacondaBackend 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 class NoSuchGroup(Exception): pass 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.anaconda = anaconda self.repos = ayum.repos self.ts = ayum.ts self.ayum = ayum self.messageWindow = anaconda.intf.messageWindow self.progress = anaconda.intf.instProgress self.progressWindowClass = anaconda.intf.progressWindow self.rootPath = ROOT_PATH 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 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: # Old-style (hdr, path) callback if isinstance(h, types.TupleType): (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 # New-style callback, h is our txmbr else: po = h.po repo = self.repos.getRepo(po.repoid) pkgStr = "%s-%s-%s.%s" % (po.name, po.version, po.release, po.arch) if self.anaconda.upgrade: s = to_unicode(_("Upgrading %(pkgStr)s (%(size)s)\n")) \ % {'pkgStr': pkgStr, 'size': size_string(po.installedsize)} else: s = to_unicode(_("Installing %(pkgStr)s (%(size)s)\n")) \ % {'pkgStr': pkgStr, 'size': size_string(po.installedsize)} summary = to_unicode(gettext.ldgettext("redhat-dist", po.summary) or "") s += summary.strip() self.progress.set_label(s) self.instLog.write(self.modeText % (time.strftime("%H:%M:%S"), str(pkgStr))) self.instLog.flush() self.openfile = None while self.openfile is None: try: fn = repo.getPackage(po) f = open(fn, 'r') self.openfile = f except (yum.Errors.NoMoreMirrorsRepoError, IOError): self.ayum._handleFailure(po) except yum.Errors.RepoError: continue self.inProgressPo = po return self.openfile.fileno() elif what == rpm.RPMCALLBACK_INST_CLOSE_FILE: if self.initWindow: self.initWindow.pop() self.initWindow = None 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 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}) self.progress.set_fraction(float(self.doneSize / self.totalSize)) self.progress.processEvents() self.inProgressPo = None elif what == rpm.RPMCALLBACK_UNINST_START: self.progress.set_text("") self.progress.set_label(_("Cleaning up %s" % h)) elif what in (rpm.RPMCALLBACK_CPIO_ERROR, rpm.RPMCALLBACK_UNPACK_ERROR, rpm.RPMCALLBACK_SCRIPT_ERROR): # If this is a cleanup/remove, then h is just a string. # A tuple h means old-style (hdr, path) yum callback, # otherwise it's a new-style txmbr callback. if isinstance(h, basestring): name = h elif isinstance(h, types.TupleType): name = h[0]['name'] else: name = h.name # 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: if what == rpm.RPMCALLBACK_CPIO_ERROR: error_type = _("cpio") elif what == rpm.RPMCALLBACK_UNPACK_ERROR: error_type = _("unpack") else: error_type = _("script") self.messageWindow(_("Error Installing Package"), _("A %s error occurred when installing the %s " "package. This could indicate errors when reading " "the installation media. Installation cannot " "continue.") % (error_type, name), 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): cachedir = self.getAttribute('cachedir') if os.path.isdir(cachedir): if not self.needsNetwork() or self.name == "Installation Repo" or self.id.startswith("anaconda-"): shutil.rmtree(cachedir) else: 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 AnacondaYum(yum.YumBase): def __init__(self, anaconda): yum.YumBase.__init__(self) self.anaconda = anaconda self._timestamp = None self.repoIDcounter = itertools.count() # Only needed for hard drive and nfsiso installs. self.isodir = None # Only needed for media installs. self.mediagrabber = None # Where is the source media mounted? This is the directory # where Packages/ is located. self.tree = "/mnt/install/source" if hasattr(self, "use_txmbr_in_callback"): log.debug("enabling new callback mode") self.use_txmbr_in_callback = True 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=ROOT_PATH) if not self.anaconda.bootloader.update_only: self.conf.installonlypkgs = [] def _mountInstallCD(self): 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.storage.devicetree.getDeviceByName(self.anaconda.mediaDevice) dev.format.mountpoint = self.tree # See if there's any media mounted on self.tree before continuing. # This saves a useless eject and insert if the user has already put # the disc in the drive. if not self.anaconda.storage.fsset.mountpoints.has_key(self.tree): try: dev.format.mount() if verifyMedia(self.tree, None): return dev.format.unmount() except Exception: pass else: if verifyMedia(self.tree, None): return dev.format.unmount() log.error("Wrong disc found on %s" % (self.tree)) if self.anaconda.intf: self.anaconda.intf.beep() self.messageWindow(_("Wrong Disc"), _("That's not the correct %s disc.") % (productName), type="custom", custom_icon="error", custom_buttons=[_("_Exit installer")]) sys.exit(1) def _mountInstallImage(self): umountImage(self.tree) # mountDirectory checks before doing anything, so it's safe to # call this repeatedly. mountDirectory(self.anaconda.methodstr, self.anaconda.intf.messageWindow) mountImage(self.isodir, self.tree, self.anaconda.intf.messageWindow) 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 initramfs only mounts the source # for the stage2 image. We need to set up the source mount # now. # methodstr == cdrom is a special case, meaning the first cdrom found # by scanning or previously mounted as the install source. 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 and self.anaconda.methodstr != "cdrom": m = self.anaconda.methodstr if m.startswith("hd:"): if m.count(":") == 2: (device, path) = m[3:].split(":") else: (device, fstype, path) = m[3:].split(":") self.isodir = "/mnt/install/isodir/%s" % path # This takes care of mounting /mnt/install/isodir first. self._mountInstallImage() self.mediagrabber = self.mediaHandler elif m.startswith("nfsiso:"): self.isodir = "/mnt/install/isodir" # Calling _mountInstallImage takes care of mounting /mnt/install/isodir first. if not network.hasActiveNetDev(): if not self.anaconda.intf.enableNetwork(): self._baseRepoURL = None return self._mountInstallImage() 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 (opts, server, path) = iutil.parseNfsUrl(m) isys.mount(server+":"+path, self.tree, "nfs", options=opts) # This really should be fixed in initrd instead but for now see # if there's images and if so go with this being an NFSISO # install instead. image = findFirstIsoImage(self.tree, self.anaconda.intf.messageWindow) if image: isys.umount(self.tree, removeDir=False) self.anaconda.methodstr = "nfsiso:%s" % m[4:] self.configBaseURL() return elif m.startswith("cdrom:"): self._mountInstallCD() self.mediagrabber = self.mediaHandler self._baseRepoURL = "file://%s" % self.tree elif os.path.isdir("/run/initramfs/live/repodata"): # No methodstr was given. In order to find an installation source # we first check to see if dracut has already mounted the source # on /run/initramfs/live/ and if not we check to see if there's a # CD/DVD with packages on it. If both those fail we default to the # mirrorlist URL. The user can always change the repo with the # repo editor later. isys.mount("/run/initramfs/live/", self.tree, bindMount=True) self.mediagrabber = self.mediaHandler self._baseRepoURL = "file://%s" % self.tree elif os.path.isdir("/run/install/repo/repodata"): # Same hack as above. FIXME: make scanForMedia do this, dammit isys.mount("/run/install/repo", self.tree, bindMount=True) 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. cdrs = opticalInstallMedia(self.anaconda.storage.devicetree, self.tree) if cdrs: self.mediagrabber = self.mediaHandler self.anaconda.mediaDevice = cdrs[0].name log.info("found installation media on %s" % cdrs[0].name) 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): # 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.instClass.getPackagePaths(anacondabaseurl) for (name, uri) in self.anaconda.instClass.getPackagePaths(self._baseRepoURL).items(): repo = AnacondaYumRepo("anaconda-%s" % self.repoIDcounter.next()) 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" % (repo.name, 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): relative = kwargs["relative"] 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 # this is a little hard-coded, but it's effective if isFinal 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 isFinal 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 """ # 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, baseurl, proxy_url, sslverify): """ 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). """ retval = [] c = ConfigParser.ConfigParser() # If there's no .treeinfo for this repo, don't bother looking for addons. treeinfo = self._getTreeinfo(baseurl, proxy_url, 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.ConfigParser.read(c, treeinfo) variant = c.get("general", "variant") except ConfigParser.Error: 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 ug = URLGrabber() ugopts = { "ssl_verify_peer" : sslverify, "ssl_verify_host" : sslverify } if proxy_url: proxies = { 'http' : proxy_url, 'https' : 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.info("Error downloading treeinfo: %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.ConfigParser() treeinfo = self._getTreeinfo(self._baseRepoURL, self.proxy_url, not flags.noverifyssl) if not treeinfo: return productVersion.split('-')[0] ConfigParser.ConfigParser.read(c, treeinfo) try: ver = c.get("general", "version") # Trim off any -Alpha or -Beta return ver.split('-')[0] except ConfigParser.Error: 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(anaconda_log.TTY_FORMAT, anaconda_log.DATE_FORMAT) 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.tty_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 doFileLogSetup(self, uid, logfile): # don't do the file log as it can lead to open fds # being left and an inability to clean up after ourself pass 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"] yum.YumBase._getConfig(self) else: yum.YumBase._getConfig(self, fn=fn, root=root, enabled_plugins=["whiteout", "blacklist"]) self.configBaseRepo() extraRepos = [] ddArch = os.uname()[4] #Add the Driver disc repos to Yum for d in glob.glob(DD_RPMS): dirname = os.path.basename(d) repo = AnacondaYumRepo("anaconda-%s" % self.repoIDcounter.next()) repo.baseurl = [ "file://%s" % d ] repo.name = "Driver Disk %s" % dirname.split("-")[1] repo.enable() extraRepos.append(repo) if self.anaconda.ksdata: for ksrepo in self.anaconda.ksdata.repo.repoList: # If no location was given, this must be a repo pre-configured # through /etc/yum.repos.d that we just want to enable. if not ksrepo.baseurl and not ksrepo.mirrorlist: try: repo = self.repos.getRepo(ksrepo.name) repo.enable() log.info("enabled repository %s with URL %s" % (repo.name, repo.mirrorlist or repo.baseurl[0])) except RepoError: log.error("Could not find the pre-configured repo %s, skipping" % ksrepo.name) continue 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:"): if not network.hasActiveNetDev() and not self.anaconda.intf.enableNetwork(): self.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) 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.yumvar.update(self.conf.yumvar) 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) repo.enable() extraRepos.append(repo) initialRepos = self.repos.repos.values() + extraRepos for repo in filter(lambda r: r.isEnabled(), initialRepos): addons = self._getAddons(repo.mirrorlist or repo.baseurl[0], repo.proxy_url or self.proxy_url, repo.sslverify) for addon in addons: addonRepo = AnacondaYumRepo(addon[0]) addonRepo.name = addon[1] addonRepo.baseurl = [ addon[2] ] addonRepo.enable() 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 yum.Errors.DuplicateRepoError: log.warning("ignoring duplicate repository %s with URL %s" % (repo.name, repo.mirrorlist or repo.baseurl[0])) self.repos.setCacheDir(self.conf.cachedir) def downloadHeader(self, po): while True: # retrying version of download header try: yum.YumBase.downloadHeader(self, po) break except (yum.Errors.NoMoreMirrorsRepoError, IOError): self._handleFailure(po) except yum.Errors.RepoError: continue def _handleFailure(self, package): if package.repo.anacondaBaseURLs[0].startswith("cdrom:"): buttons = [_("Re_boot"), _("_Eject")] else: buttons = [_("Re_boot"), _("_Retry")] pkgFile = to_unicode(os.path.basename(package.remote_path)) 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 package.repo.anacondaBaseURLs[0].startswith("cdrom:"): self._mountInstallCD() 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. repo = self.repos.getRepo(kwargs["repo"]) log.warning("Failed to get %s from mirror %d/%d, " "or downloaded file is corrupt" % (obj.url, repo.grab._next + 1, len(repo.grab.mirrors))) if repo.anacondaBaseURLs[0].startswith("cdrom:"): dev = self.anaconda.storage.devicetree.getDeviceByName(self.anaconda.mediaDevice) dev.format.mountpoint = self.tree unmountCD(dev, self.anaconda.intf.messageWindow) 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): self.initActionTs() if self.anaconda.upgrade: self.ts.ts.setProbFilter(~rpm.RPMPROB_FILTER_DISKSPACE) self.setColor() try: self.dsCallback = DownloadHeaderProgress(intf, self) self.populateTs(keepold=0) self.dsCallback.pop() self.dsCallback = None except RepoError as e: msg = _("There was an error running your transaction for " "the following reason: %s\n") % str(e) if self.anaconda.upgrade or self.anaconda.ksdata: 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: return DISPATCH_BACK self.ts.check() self.ts.order() self.ts.clean() self.anaconda.bootloader.trusted_boot = self.isPackageInstalled(name="tboot") if self._run(instLog, cb, intf) == DISPATCH_BACK: 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 PackageSackError as e: log.error("AnacondaYum._run: PackageSackError: %s" % e) msg = _("There was an error running your transaction for " "the following reason: %s.\n") % str(e) intf.messageWindow(_("Error Running Transaction"), msg, type="custom", custom_icon="error", custom_buttons=[_("_Exit installer")]) sys.exit(1) except YumBaseError as 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 self.anaconda.upgrade or self.anaconda.ksdata: 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/$basearch/$releasever 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 """ % (ROOT_PATH) 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) # clean up rpmdb locks so that kickstart %post scripts aren't # unhappy (#496961) iutil.resetRpmDb() if os.access(ROOT_PATH + "/tmp/yum.log", os.R_OK): os.unlink(ROOT_PATH + "/tmp/yum.log") self.ayum.history.close() def doBackendSetup(self, anaconda): if anaconda.dir == DISPATCH_BACK: return DISPATCH_BACK if anaconda.upgrade: # FIXME: make sure that the rpmdb doesn't have stale locks :/ iutil.resetRpmDb() 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. for repo in self.ayum.repos.listEnabled(): if repo.needsNetwork() 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) break self.doRepoSetup(anaconda) self.doSackSetup(anaconda) self.doGroupSetup(anaconda) self.ayum.doMacros() def doGroupSetup(self, anaconda): while True: try: self.ayum.doGroupSetup() except (GroupsError, NoSuchGroup, RepoError) as e: buttons = [_("_Exit installer"), _("_Retry")] log.error("Unable to read group information: %s" % e) 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 as e: if callback: callback.disconnect() buttons = [_("_Exit installer"), _("Edit"), _("_Retry")] else: break # success if anaconda.ksdata: 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.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.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 foundkernel = False if not foundkernel and isys.isPaeAvailable(): if selectKernel("kernel-PAE"): foundkernel = True if not foundkernel and iutil.isARM(): if anaconda.platform.armMachine is not None: selectKernel("kernel-%s" % anaconda.platform.armMachine) foundkernel = True if not foundkernel: selectKernel("kernel") def selectFSPackages(self, storage): for device in storage.fsset.devices: # this takes care of device and filesystem packages map(self.selectPackage, device.packages) # anaconda requires several programs on the installed system to complete # installation, but we have no guarantees that some of these will be # installed (they could have been removed in kickstart). So we'll force # it. def selectAnacondaNeeds(self): for pkg in ['authconfig', 'chkconfig', 'system-config-firewall-base']: self.selectPackage(pkg) 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.upgrade: # New installs only - upgrades will already have all this stuff. self.selectBestKernel(anaconda) map(self.selectPackage, anaconda.platform.packages) map(self.selectPackage, anaconda.bootloader.packages) self.selectFSPackages(anaconda.storage) self.selectAnacondaNeeds() else: if not anaconda.bootloader.skip_bootloader: map(self.deselectPackage, anaconda.bootloader.obsoletes) map(self.selectPackage, anaconda.bootloader.packages) 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.ksdata and anaconda.ksdata.packages.handleMissing == KS_MISSING_IGNORE: break if code == 1 and not anaconda.upgrade: # resolveDeps returns 0 if empty transaction, 1 if error, # 2 if success depprob = "\n".join(msgs) for m in msgs: log.warning(m) custom_buttons = [_("_Exit installer"), _("_Continue")] if not anaconda.ksdata: custom_buttons.insert(1, _("_Back")) rc = anaconda.intf.detailedMessageWindow(_("Warning"), _("Some of the packages you have selected for " "install are missing dependencies. You can " "exit the installation, go back and change " "your package selections, or continue " "installing these packages without their " "dependencies. If you continue, these packages " "may not work correctly due to missing components."), 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.ksdata: self.ayum._undoDepInstalls() return DISPATCH_BACK break except RepoError as e: log.critical(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.upgrade: largePart = anaconda.storage.mountpoints.get("/usr", anaconda.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() self.ayum.dsCallback = None def doPreInstall(self, anaconda): 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.storage.protectedDevices: if getattr(protected.format, "mountpoint", None): dirList.append(protected.format.mountpoint) for i in dirList: try: os.mkdir(ROOT_PATH + i) except OSError: pass # log.error("Error making directory %s: %s" % (i, msg)) self.initLog(ROOT_PATH) # write out the fstab if not anaconda.upgrade: anaconda.storage.fsset.write() if os.access("/etc/modprobe.d/anaconda.conf", os.R_OK): shutil.copyfile("/etc/modprobe.d/anaconda.conf", ROOT_PATH + "/etc/modprobe.d/anaconda.conf") network.write_sysconfig_network() network.disableIPV6() network.copyConfigToPath(ROOT_PATH) if not anaconda.ksdata: anaconda.instClass.setNetworkOnbootDefault() anaconda.storage.write() else: # ensure that /etc/mtab is a symlink to /proc/self/mounts anaconda.storage.makeMtab() def doInstall(self, anaconda): log.info("Preparing to install packages") if not anaconda.upgrade: rpm.addMacro("__dbi_htconfig", "hash nofsync %{__dbi_other} %{__dbi_perms}") if anaconda.ksdata and anaconda.ksdata.packages.excludeDocs: rpm.addMacro("_excludedocs", "1") 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) if cb.initWindow is not None: cb.initWindow.pop() self.instLog.write("*** FINISHED INSTALLING PACKAGES ***") self.instLog.close () anaconda.intf.setInstallProgressClass(None) if rc == DISPATCH_BACK: return DISPATCH_BACK def doPostInstall(self, anaconda): if anaconda.upgrade: 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 anaconda.upgrade and os.path.exists("%s/var/cache/yum" %(ROOT_PATH,)): log.info("Expiring yum caches") try: iutil.execWithRedirect("yum", ["clean", "all"], stdout="/dev/tty5", stderr="/dev/tty5", root = ROOT_PATH) except RuntimeError: pass # nuke preupgrade if flags.cmdline.has_key("preupgrade") and os.path.exists("%s/var/cache/yum/anaconda-upgrade" %(ROOT_PATH,)): try: shutil.rmtree("%s/var/cache/yum/anaconda-upgrade" %(ROOT_PATH,)) except (OSError, IOError): pass # XXX: write proper lvm config AnacondaBackend.doPostInstall(self, anaconda) w.pop() def kernelVersionList(self): # FIXME: using rpm here is a little lame, but otherwise, we'd # be pulling in filelists return packages.rpmKernelVersionList() 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: 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: # 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: # 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): if self.ayum.tsInfo.matchNaevr(name=pkg): return 0 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 writeConfiguration(self): return def postAction(self, anaconda): self.ayum.close() self.ayum.closeRpmDB() iutil.resetRpmDb() 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()