# # kickstart.py: kickstart install support # # Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 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 storage.deviceaction import * from storage.devices import LUKSDevice from storage.devicelibs.lvm import getPossiblePhysicalExtents from storage.formats import getFormat from storage.partitioning import clearPartitions from storage.partitioning import shouldClear import storage.iscsi import storage.fcoe import storage.zfcp from errors import * import iutil import isys import os import tempfile from flags import flags from constants import * import sys import string import urlgrabber import warnings import network import upgrade import pykickstart.commands as commands from storage.devices import * from scdate.core import zonetab from pykickstart.base import KickstartCommand, BaseData from pykickstart.constants import * from pykickstart.errors import * from pykickstart.parser import * from pykickstart.version import * import gettext _ = lambda x: gettext.ldgettext("anaconda", x) import logging log = logging.getLogger("anaconda") stderrLog = logging.getLogger("anaconda.stderr") from anaconda_log import logger, logLevelMap, setHandlersLevel,\ DEFAULT_TTY_LEVEL class AnacondaKSScript(Script): def run(self, chroot, serial, intf = None): import os.path if self.inChroot: scriptRoot = chroot else: scriptRoot = "/" (fd, path) = tempfile.mkstemp("", "ks-script-", scriptRoot + "/tmp") os.write(fd, self.script) os.close(fd) os.chmod(path, 0700) # Always log stdout/stderr from scripts. Using --logfile just lets you # pick where it goes. The script will also be logged to program.log # because of execWithRedirect, and to anaconda.log if the script fails. if self.logfile: if self.inChroot: messages = "%s/%s" % (scriptRoot, self.logfile) else: messages = self.logfile d = os.path.basename(messages) if not os.path.exists(d): os.makedirs(d) else: messages = "%s.log" % path if intf: intf.suspend() rc = iutil.execWithRedirect(self.interp, ["/tmp/%s" % os.path.basename(path)], stdin = messages, stdout = messages, stderr = messages, root = scriptRoot) if intf: intf.resume() # Always log an error. Only fail if we have a handle on the # windowing system and the kickstart file included --erroronfail. if rc != 0: log.error("Error code %s running the kickstart script at line %s" % (rc, self.lineno)) try: f = open(messages, "r") err = f.readlines() f.close() for l in err: log.error("\t%s" % l) except: err = None if self.errorOnFail: if intf != None: msg = _("There was an error running the kickstart " "script at line %(lineno)s. You may examine the " "output in %(msgs)s. This is a fatal error and " "installation will be aborted. Press the " "OK button to exit the installer.") \ % {'lineno': self.lineno, 'msgs': messages} if err: intf.detailedMessageWindow(_("Scriptlet Failure"), msg, err) else: intf.messageWindow(_("Scriptlet Failure"), msg) sys.exit(0) if serial or self.logfile is not None: os.chmod("%s" % messages, 0600) class AnacondaKSPackages(Packages): def __init__(self): Packages.__init__(self) # Has the %packages section been seen at all? self.seen = False def getEscrowCertificate(anaconda, url): if not url: return None if url in anaconda.storage.escrowCertificates: return anaconda.storage.escrowCertificates[url] needs_net = not url.startswith("/") and not url.startswith("file:") if needs_net and not network.hasActiveNetDev(): if not anaconda.intf.enableNetwork(anaconda): anaconda.intf.messageWindow(_("No Network Available"), _("Encryption key escrow requires " "networking, but there was an error " "enabling the network on your " "system."), type="custom", custom_icon="error", custom_buttons=[_("_Exit installer")]) sys.exit(1) log.info("escrow: downloading %s" % (url,)) f = urlgrabber.urlopen(url) try: anaconda.storage.escrowCertificates[url] = f.read() finally: f.close() return anaconda.storage.escrowCertificates[url] def deviceMatches(spec): matches = udev_resolve_glob(spec) dev = udev_resolve_devspec(spec) # udev_resolve_devspec returns None if there's no match, but we don't # want that ending up in the list. if dev and dev not in matches: matches.append(dev) return matches ### ### SUBCLASSES OF PYKICKSTART COMMAND HANDLERS ### class Authconfig(commands.authconfig.FC3_Authconfig): def execute(self, anaconda): anaconda.security.auth = self.authconfig class AutoPart(commands.autopart.F12_AutoPart): def execute(self, anaconda): # sets up default autopartitioning. use clearpart separately # if you want it anaconda.instClass.setDefaultPartitioning(anaconda.storage, anaconda.platform) anaconda.storage.doAutoPart = True if self.encrypted: anaconda.storage.encryptedAutoPart = True anaconda.storage.encryptionPassphrase = self.passphrase anaconda.storage.autoPartEscrowCert = \ getEscrowCertificate(anaconda, self.escrowcert) anaconda.storage.autoPartAddBackupPassphrase = \ self.backuppassphrase anaconda.ksdata.skipSteps.extend(["partition", "zfcpconfig", "parttype"]) class AutoStep(commands.autostep.FC3_AutoStep): def execute(self, anaconda): flags.autostep = 1 flags.autoscreenshot = self.autoscreenshot class Bootloader(commands.bootloader.F12_Bootloader): def execute(self, anaconda): if self.location == "none": location = None elif self.location == "partition": location = "boot" else: location = self.location if self.upgrade and not anaconda.upgrade: raise KickstartValueError, formatErrorMsg(self.lineno, msg="Selected upgrade mode for bootloader but not doing an upgrade") if self.upgrade: anaconda.bootloader.kickstart = 1 anaconda.bootloader.doUpgradeOnly = 1 if location is None: anaconda.ksdata.permanentSkipSteps.extend(["bootloadersetup", "instbootloader"]) else: anaconda.ksdata.showSteps.append("bootloader") if self.appendLine: anaconda.bootloader.args.append(self.appendLine) if self.password: anaconda.bootloader.setPassword(self.password, isCrypted = 0) if self.md5pass: anaconda.bootloader.setPassword(self.md5pass) if location != None: anaconda.bootloader.defaultDevice = location else: anaconda.bootloader.defaultDevice = -1 if self.timeout: anaconda.bootloader.timeout = self.timeout # add unpartitioned devices that will get partitioned into # bootloader.drivelist disks = anaconda.storage.disks partitioned = anaconda.storage.partitioned for disk in [d for d in disks if not d.partitioned]: if shouldClear(disk, anaconda.storage.clearPartType, anaconda.storage.clearPartDisks): # add newly partitioned disks to the drivelist anaconda.bootloader.drivelist.append(disk.name) elif disk.name in anaconda.bootloader.drivelist: # remove unpartitioned disks from the drivelist anaconda.bootloader.drivelist.remove(disk.name) anaconda.bootloader.drivelist.sort( cmp=anaconda.storage.compareDisks) # Throw out drives specified that don't exist. if self.driveorder and len(self.driveorder) > 0: new = [] for drive in self.driveorder: if drive in anaconda.bootloader.drivelist: new.append(drive) else: log.warning("requested drive %s in boot drive order " "doesn't exist" %(drive,)) anaconda.bootloader.updateDriveList(new) anaconda.ksdata.permanentSkipSteps.extend(["upgbootloader", "bootloader"]) class ClearPart(commands.clearpart.FC3_ClearPart): def parse(self, args): retval = commands.clearpart.FC3_ClearPart.parse(self, args) if self.type is None: self.type = CLEARPART_TYPE_NONE # Do any glob expansion now, since we need to have the real list of # disks available before the execute methods run. drives = [] for spec in self.drives: matched = deviceMatches(spec) if matched: drives.extend(matched) else: raise KickstartValueError, formatErrorMsg(self.lineno, msg="Specified nonexistent disk %s in clearpart command" % spec) self.drives = drives return retval def execute(self, anaconda): anaconda.storage.clearPartType = self.type anaconda.storage.clearPartDisks = self.drives if self.initAll: anaconda.storage.reinitializeDisks = self.initAll clearPartitions(anaconda.storage) anaconda.ksdata.skipSteps.append("cleardiskssel") class Fcoe(commands.fcoe.F13_Fcoe): def parse(self, args): fc = commands.fcoe.F13_Fcoe.parse(self, args) if fc.nic not in isys.getDeviceProperties(): raise KickstartValueError, formatErrorMsg(self.lineno, msg="Specified nonexistent nic %s in fcoe command" % fc.nic) storage.fcoe.fcoe().addSan(nic=fc.nic, dcb=fc.dcb) return fc class Firewall(commands.firewall.F10_Firewall): def execute(self, anaconda): anaconda.firewall.enabled = self.enabled anaconda.firewall.trustdevs = self.trusts for port in self.ports: anaconda.firewall.portlist.append (port) for svc in self.services: anaconda.firewall.servicelist.append (svc) class IgnoreDisk(commands.ignoredisk.F8_IgnoreDisk): def parse(self, args): retval = commands.ignoredisk.F8_IgnoreDisk.parse(self, args) # See comment in ClearPart.parse drives = [] for spec in self.ignoredisk: matched = deviceMatches(spec) if matched: drives.extend(matched) else: raise KickstartValueError, formatErrorMsg(self.lineno, msg="Specified nonexistent disk %s in ignoredisk command" % spec) self.ignoredisk = drives drives = [] for spec in self.onlyuse: matched = deviceMatches(spec) if matched: drives.extend(matched) else: raise KickstartValueError, formatErrorMsg(self.lineno, msg="Specified nonexistent disk %s in ignoredisk command" % spec) self.onlyuse = drives return retval def execute(self, anaconda): anaconda.storage.ignoredDisks = self.ignoredisk anaconda.storage.exclusiveDisks = self.onlyuse anaconda.ksdata.skipSteps.extend(["filter", "filtertype"]) class Iscsi(commands.iscsi.F10_Iscsi): def parse(self, args): tg = commands.iscsi.F10_Iscsi.parse(self, args) try: storage.iscsi.iscsi().addTarget(tg.ipaddr, tg.port, tg.user, tg.password, tg.user_in, tg.password_in) log.info("added iscsi target: %s" %(tg.ipaddr,)) except (IOError, ValueError), e: raise KickstartValueError, formatErrorMsg(self.lineno, msg=str(e)) return tg class IscsiName(commands.iscsiname.FC6_IscsiName): def parse(self, args): retval = commands.iscsiname.FC6_IscsiName.parse(self, args) storage.iscsi.iscsi().initiator = self.iscsiname return retval class Keyboard(commands.keyboard.FC3_Keyboard): def execute(self, anaconda): anaconda.keyboard.set(self.keyboard) anaconda.keyboard.beenset = 1 anaconda.ksdata.skipSteps.append("keyboard") class Lang(commands.lang.FC3_Lang): def execute(self, anaconda): anaconda.instLanguage.instLang = self.lang anaconda.instLanguage.systemLang = self.lang anaconda.ksdata.skipSteps.append("language") class LogVolData(commands.logvol.F12_LogVolData): def execute(self, anaconda): storage = anaconda.storage devicetree = storage.devicetree storage.doAutoPart = False if self.mountpoint == "swap": type = "swap" self.mountpoint = "" if self.recommended: (self.size, self.maxSizeMB) = iutil.swapSuggestion() self.grow = True else: if self.fstype != "": type = self.fstype else: type = storage.defaultFSType # Sanity check mountpoint if self.mountpoint != "" and self.mountpoint[0] != '/': raise KickstartValueError, formatErrorMsg(self.lineno, msg="The mount point \"%s\" is not valid." % (self.mountpoint,)) # Check that the VG this LV is a member of has already been specified. vg = devicetree.getDeviceByName(self.vgname) if not vg: raise KickstartValueError, formatErrorMsg(self.lineno, msg="No volume group exists with the name \"%s\". Specify volume groups before logical volumes." % self.vgname) # If this specifies an existing request that we should not format, # quit here after setting up enough information to mount it later. if not self.format: if not self.name: raise KickstartValueError, formatErrorMsg(self.lineno, msg="--noformat used without --name") dev = devicetree.getDeviceByName("%s-%s" % (vg.name, self.name)) if not dev: raise KickstartValueError, formatErrorMsg(self.lineno, msg="No preexisting logical volume with the name \"%s\" was found." % self.name) dev.format.mountpoint = self.mountpoint dev.format.mountopts = self.fsopts anaconda.ksdata.skipSteps.extend(["partition", "zfcpconfig", "parttype"]) return # Make sure this LV name is not already used in the requested VG. if not self.preexist: tmp = devicetree.getDeviceByName("%s-%s" % (vg.name, self.name)) if tmp: raise KickstartValueError, formatErrorMsg(self.lineno, msg="Logical volume name already used in volume group %s" % vg.name) # Size specification checks if not self.percent: if not self.size: raise KickstartValueError, formatErrorMsg(self.lineno, msg="Size required") elif not self.grow and self.size*1024 < vg.peSize: raise KickstartValueError, formatErrorMsg(self.lineno, msg="Logical volume size must be larger than the volume group physical extent size.") elif self.percent <= 0 or self.percent > 100: raise KickstartValueError, formatErrorMsg(self.lineno, msg="Percentage must be between 0 and 100") # Now get a format to hold a lot of these extra values. format = getFormat(type, mountpoint=self.mountpoint, mountopts=self.fsopts) if not format: raise KickstartValueError, formatErrorMsg(self.lineno, msg="The \"%s\" filesystem type is not supported." % type) # If we were given a pre-existing LV to create a filesystem on, we need # to verify it and its VG exists and then schedule a new format action # to take place there. Also, we only support a subset of all the # options on pre-existing LVs. if self.preexist: device = devicetree.getDeviceByName("%s-%s" % (vg.name, self.name)) if not device: raise KickstartValueError, formatErrorMsg(self.lineno, msg="Specified nonexistent LV %s in logvol command" % self.name) devicetree.registerAction(ActionCreateFormat(device, format)) else: # If a previous device has claimed this mount point, delete the # old one. try: if self.mountpoint: device = storage.mountpoints[self.mountpoint] storage.destroyDevice(device) except KeyError: pass request = storage.newLV(format=format, name=self.name, vg=vg, size=self.size, grow=self.grow, maxsize=self.maxSizeMB, percent=self.percent) if self.fsprofile and hasattr(request.format, "fsprofile"): request.format.fsprofile = self.fsprofile storage.createDevice(request) if self.encrypted: if self.passphrase and not storage.encryptionPassphrase: storage.encryptionPassphrase = self.passphrase cert = getEscrowCertificate(anaconda, self.escrowcert) if self.preexist: luksformat = format device.format = getFormat("luks", passphrase=self.passphrase, device=device.path, escrow_cert=cert, add_backup_passphrase=self.backuppassphrase) luksdev = LUKSDevice("luks%d" % storage.nextID, format=luksformat, parents=device) else: luksformat = request.format request.format = getFormat("luks", passphrase=self.passphrase, escrow_cert=cert, add_backup_passphrase=self.backuppassphrase) luksdev = LUKSDevice("luks%d" % storage.nextID, format=luksformat, parents=request) storage.createDevice(luksdev) anaconda.ksdata.skipSteps.extend(["partition", "zfcpconfig", "parttype"]) class Logging(commands.logging.FC6_Logging): def execute(self, anaconda): if logger.tty_loglevel == DEFAULT_TTY_LEVEL: # not set from the command line level = logLevelMap[self.level] logger.tty_loglevel = level storage_log = logging.getLogger("storage") setHandlersLevel(log, level) setHandlersLevel(storage_log, level) if logger.remote_syslog == None and len(self.host) > 0: # not set from the command line, ok to use kickstart remote_server = self.host if self.port: remote_server = "%s:%s" %(self.host, self.port) logger.updateRemote(remote_server) class NetworkData(commands.network.F8_NetworkData): def execute(self, anaconda): if self.bootProto: devices = anaconda.network.netdevices if (devices and self.bootProto): if not self.device: list = devices.keys () list.sort() device = list[0] else: device = self.device try: dev = devices[device] except KeyError: raise KickstartValueError, formatErrorMsg(self.lineno, msg="The provided network interface %s does not exist" % device) dev.set (("bootproto", self.bootProto)) dev.set (("dhcpclass", self.dhcpclass)) if self.onboot: dev.set (("onboot", "yes")) else: dev.set (("onboot", "no")) if self.bootProto == "static": if (self.ip): dev.set (("ipaddr", self.ip)) if (self.netmask): dev.set (("netmask", self.netmask)) if self.ethtool: dev.set (("ethtool_opts", self.ethtool)) if isys.isWireless(device): if self.essid: dev.set(("essid", self.essid)) if self.wepkey: dev.set(("wepkey", self.wepkey)) if self.hostname != "": anaconda.network.setHostname(self.hostname) anaconda.network.overrideDHCPhostname = True if self.nameserver != "": anaconda.network.setDNS(self.nameserver, device) if self.gateway != "": anaconda.network.setGateway(self.gateway, device) needs_net = (anaconda.methodstr and (anaconda.methodstr.startswith("http:") or anaconda.methodstr.startswith("ftp:") or anaconda.methodstr.startswith("nfs:"))) if needs_net and not network.hasActiveNetDev(): log.info("Bringing up network in stage2 kickstart ...") rc = anaconda.network.bringUp() log.info("Network setup %s" % (rc and 'succeeded' or 'failed',)) class MultiPath(commands.multipath.FC6_MultiPath): def parse(self, args): raise NotImplementedError("The multipath kickstart command is not currently supported") class DmRaid(commands.dmraid.FC6_DmRaid): def parse(self, args): raise NotImplementedError("The dmraid kickstart command is not currently supported") class PartitionData(commands.partition.F12_PartData): def execute(self, anaconda): storage = anaconda.storage devicetree = storage.devicetree kwargs = {} storage.doAutoPart = False if self.onbiosdisk != "": for (disk, biosdisk) in storage.eddDict.iteritems(): if str(biosdisk) == self.onbiosdisk: self.disk = disk break if self.disk == "": raise KickstartValueError, formatErrorMsg(self.lineno, msg="Specified BIOS disk %s cannot be determined" % self.onbiosdisk) if self.mountpoint == "swap": type = "swap" self.mountpoint = "" if self.recommended: (self.size, self.maxSizeMB) = iutil.swapSuggestion() self.grow = True # if people want to specify no mountpoint for some reason, let them # this is really needed for pSeries boot partitions :( elif self.mountpoint == "None": self.mountpoint = "" if self.fstype: type = self.fstype else: type = storage.defaultFSType elif self.mountpoint == 'appleboot': type = "Apple Bootstrap" self.mountpoint = "" kwargs["weight"] = anaconda.platform.weight(fstype="appleboot") elif self.mountpoint == 'prepboot': type = "PPC PReP Boot" self.mountpoint = "" kwargs["weight"] = anaconda.platform.weight(fstype="prepboot") elif self.mountpoint.startswith("raid."): type = "mdmember" kwargs["name"] = self.mountpoint if devicetree.getDeviceByName(kwargs["name"]): raise KickstartValueError, formatErrorMsg(self.lineno, msg="RAID partition defined multiple times") # store "raid." alias for other ks partitioning commands if self.onPart: anaconda.ksdata.onPart[kwargs["name"]] = self.onPart self.mountpoint = "" elif self.mountpoint.startswith("pv."): type = "lvmpv" kwargs["name"] = self.mountpoint if devicetree.getDeviceByName(kwargs["name"]): raise KickstartValueError, formatErrorMsg(self.lineno, msg="PV partition defined multiple times") # store "pv." alias for other ks partitioning commands if self.onPart: anaconda.ksdata.onPart[kwargs["name"]] = self.onPart self.mountpoint = "" elif self.mountpoint == "/boot/efi": type = "EFI System Partition" self.fsopts = "defaults,uid=0,gid=0,umask=0077,shortname=winnt" kwargs["weight"] = anaconda.platform.weight(fstype="efi") else: if self.fstype != "": type = self.fstype elif self.mountpoint == "/boot": type = anaconda.platform.defaultBootFSType else: type = storage.defaultFSType # If this specified an existing request that we should not format, # quit here after setting up enough information to mount it later. if not self.format: if not self.onPart: raise KickstartValueError, formatErrorMsg(self.lineno, msg="--noformat used without --onpart") dev = devicetree.getDeviceByName(udev_resolve_devspec(self.onPart)) if not dev: raise KickstartValueError, formatErrorMsg(self.lineno, msg="No preexisting partition with the name \"%s\" was found." % self.onPart) dev.format.mountpoint = self.mountpoint dev.format.mountopts = self.fsopts anaconda.ksdata.skipSteps.extend(["partition", "zfcpconfig", "parttype"]) return # Size specification checks. if not self.size and not self.onPart: raise KickstartValueError, formatErrorMsg(self.lineno, msg="Partition requires a size specification") # Now get a format to hold a lot of these extra values. kwargs["format"] = getFormat(type, mountpoint=self.mountpoint, label=self.label, mountopts=self.fsopts) if not kwargs["format"]: raise KickstartValueError, formatErrorMsg(self.lineno, msg="The \"%s\" filesystem type is not supported." % type) # If we were given a specific disk to create the partition on, verify # that it exists first. If it doesn't exist, see if it exists with # mapper/ on the front. If that doesn't exist either, it's an error. if self.disk: names = [self.disk, "mapper/" + self.disk] for n in names: disk = devicetree.getDeviceByName(udev_resolve_devspec(n)) should_clear = shouldClear(disk, storage.clearPartType, storage.clearPartDisks) if disk and (disk.partitioned or should_clear): kwargs["disks"] = [disk] break elif disk: raise KickstartValueError, formatErrorMsg(self.lineno, msg="Specified unpartitioned disk %s in partition command" % self.disk) if not kwargs["disks"]: raise KickstartValueError, formatErrorMsg(self.lineno, msg="Specified nonexistent disk %s in partition command" % self.disk) kwargs["grow"] = self.grow kwargs["size"] = self.size kwargs["maxsize"] = self.maxSizeMB kwargs["primary"] = self.primOnly # If we were given a pre-existing partition to create a filesystem on, # we need to verify it exists and then schedule a new format action to # take place there. Also, we only support a subset of all the options # on pre-existing partitions. if self.onPart: device = devicetree.getDeviceByName(udev_resolve_devspec(self.onPart)) if not device: raise KickstartValueError, formatErrorMsg(self.lineno, msg="Specified nonexistent partition %s in partition command" % self.onPart) devicetree.registerAction(ActionCreateFormat(device, kwargs["format"])) else: # If a previous device has claimed this mount point, delete the # old one. try: if self.mountpoint: device = storage.mountpoints[self.mountpoint] storage.destroyDevice(device) except KeyError: pass request = storage.newPartition(**kwargs) if self.fsprofile and hasattr(request.format, "fsprofile"): request.format.fsprofile = self.fsprofile storage.createDevice(request) if self.encrypted: if self.passphrase and not storage.encryptionPassphrase: storage.encryptionPassphrase = self.passphrase cert = getEscrowCertificate(anaconda, self.escrowcert) if self.onPart: luksformat = format device.format = getFormat("luks", passphrase=self.passphrase, device=device.path, escrow_cert=cert, add_backup_passphrase=self.backuppassphrase) luksdev = LUKSDevice("luks%d" % storage.nextID, format=luksformat, parents=device) else: luksformat = request.format request.format = getFormat("luks", passphrase=self.passphrase, escrow_cert=cert, add_backup_passphrase=self.backuppassphrase) luksdev = LUKSDevice("luks%d" % storage.nextID, format=luksformat, parents=request) storage.createDevice(luksdev) anaconda.ksdata.skipSteps.extend(["partition", "zfcpconfig", "parttype"]) class Reboot(commands.reboot.FC6_Reboot): def execute(self, anaconda): anaconda.ksdata.skipSteps.append("complete") class RaidData(commands.raid.F12_RaidData): def execute(self, anaconda): raidmems = [] devicename = "md%d" % self.device storage = anaconda.storage devicetree = storage.devicetree kwargs = {} storage.doAutoPart = False if self.mountpoint == "swap": type = "swap" self.mountpoint = "" elif self.mountpoint.startswith("pv."): type = "lvmpv" kwargs["name"] = self.mountpoint anaconda.ksdata.onPart[kwargs["name"]] = devicename if devicetree.getDeviceByName(kwargs["name"]): raise KickstartValueError, formatErrorMsg(self.lineno, msg="PV partition defined multiple times") self.mountpoint = "" else: if self.fstype != "": type = self.fstype elif self.mountpoint == "/boot" and anaconda.platform.supportsMdRaidBoot: type = anaconda.platform.defaultBootFSType else: type = storage.defaultFSType # Sanity check mountpoint if self.mountpoint != "" and self.mountpoint[0] != '/': raise KickstartValueError, formatErrorMsg(self.lineno, msg="The mount point is not valid.") # If this specifies an existing request that we should not format, # quit here after setting up enough information to mount it later. if not self.format: if not devicename: raise KickstartValueError, formatErrorMsg(self.lineno, msg="--noformat used without --device") dev = devicetree.getDeviceByName(devicename) if not dev: raise KickstartValueError, formatErrorMsg(self.lineno, msg="No preexisting RAID device with the name \"%s\" was found." % devicename) dev.format.mountpoint = self.mountpoint dev.format.mountopts = self.fsopts anaconda.ksdata.skipSteps.extend(["partition", "zfcpconfig", "parttype"]) return # Get a list of all the RAID members. for member in self.members: # if member is using --onpart, use original device member = anaconda.ksdata.onPart.get(member, member) dev = devicetree.getDeviceByName(member) if dev and dev.format.type == "luks": try: dev = devicetree.getChildren(dev)[0] except IndexError: dev = None if not dev: raise KickstartValueError, formatErrorMsg(self.lineno, msg="Tried to use undefined partition %s in RAID specification" % member) raidmems.append(dev) if not self.preexist: if len(raidmems) == 0: raise KickstartValueError, formatErrorMsg(self.lineno, msg="RAID Partition defined without any RAID members") if self.level == "": raise KickstartValueError, formatErrorMsg(self.lineno, msg="RAID Partition defined without RAID level") # Now get a format to hold a lot of these extra values. kwargs["format"] = getFormat(type, mountpoint=self.mountpoint, mountopts=self.fsopts) if not kwargs["format"]: raise KickstartValueError, formatErrorMsg(self.lineno, msg="The \"%s\" filesystem type is not supported." % type) kwargs["name"] = devicename kwargs["level"] = self.level kwargs["parents"] = raidmems kwargs["memberDevices"] = len(raidmems) kwargs["totalDevices"] = kwargs["memberDevices"]+self.spares # If we were given a pre-existing RAID to create a filesystem on, # we need to verify it exists and then schedule a new format action # to take place there. Also, we only support a subset of all the # options on pre-existing RAIDs. if self.preexist: device = devicetree.getDeviceByName(devicename) if not device: raise KickstartValueError, formatErrorMsg(self.lineno, msg="Specifeid nonexistent RAID %s in raid command" % devicename) devicetree.registerAction(ActionCreateFormat(device, kwargs["format"])) else: # If a previous device has claimed this mount point, delete the # old one. try: if self.mountpoint: device = storage.mountpoints[self.mountpoint] storage.destroyDevice(device) except KeyError: pass try: request = storage.newMDArray(**kwargs) except ValueError, e: raise KickstartValueError, formatErrorMsg(self.lineno, msg=str(e)) if self.fsprofile and hasattr(request.format, "fsprofile"): request.format.fsprofile = self.fsprofile storage.createDevice(request) if self.encrypted: if self.passphrase and not storage.encryptionPassphrase: storage.encryptionPassphrase = self.passphrase cert = getEscrowCertificate(anaconda, self.escrowcert) if self.preexist: luksformat = format device.format = getFormat("luks", passphrase=self.passphrase, device=device.path, escrow_cert=cert, add_backup_passphrase=self.backuppassphrase) luksdev = LUKSDevice("luks%d" % storage.nextID, format=luksformat, parents=device) else: luksformat = request.format request.format = getFormat("luks", passphrase=self.passphrase, escrow_cert=cert, add_backup_passphrase=self.backuppassphrase) luksdev = LUKSDevice("luks%d" % storage.nextID, format=luksformat, parents=request) storage.createDevice(luksdev) anaconda.ksdata.skipSteps.extend(["partition", "zfcpconfig", "parttype"]) class RootPw(commands.rootpw.F8_RootPw): def execute(self, anaconda): anaconda.users.rootPassword["password"] = self.password anaconda.users.rootPassword["isCrypted"] = self.isCrypted anaconda.users.rootPassword["lock"] = self.lock anaconda.ksdata.skipSteps.append("accounts") class SELinux(commands.selinux.FC3_SELinux): def execute(self, anaconda): anaconda.security.setSELinux(self.selinux) class SkipX(commands.skipx.FC3_SkipX): def execute(self, anaconda): anaconda.ksdata.skipSteps.extend(["setsanex", "videocard", "xcustom"]) if anaconda.desktop is not None: anaconda.desktop.setDefaultRunLevel(3) class Timezone(commands.timezone.FC6_Timezone): def execute(self, anaconda): # check validity tab = zonetab.ZoneTab() if self.timezone not in (entry.tz.replace(' ','_') for entry in tab.getEntries()): log.warning("Timezone %s set in kickstart is not valid." % (self.timezone,)) anaconda.timezone.setTimezoneInfo(self.timezone, self.isUtc) anaconda.ksdata.skipSteps.append("timezone") class Upgrade(commands.upgrade.F11_Upgrade): def execute(self, anaconda): anaconda.upgrade = self.upgrade class VolGroupData(commands.volgroup.FC3_VolGroupData): def execute(self, anaconda): pvs = [] storage = anaconda.storage devicetree = storage.devicetree storage.doAutoPart = False # Get a list of all the physical volume devices that make up this VG. for pv in self.physvols: # if pv is using --onpart, use original device pv = anaconda.ksdata.onPart.get(pv, pv) dev = devicetree.getDeviceByName(pv) if dev and dev.format.type == "luks": try: dev = devicetree.getChildren(dev)[0] except IndexError: dev = None if not dev: raise KickstartValueError, formatErrorMsg(self.lineno, msg="Tried to use undefined partition %s in Volume Group specification" % pv) pvs.append(dev) if len(pvs) == 0 and not self.preexist: raise KickstartValueError, formatErrorMsg(self.lineno, msg="Volume group defined without any physical volumes. Either specify physical volumes or use --useexisting.") if self.pesize not in getPossiblePhysicalExtents(floor=1024): raise KickstartValueError, formatErrorMsg(self.lineno, msg="Volume group specified invalid pesize") # If --noformat or --useexisting was given, there's really nothing to do. if not self.format or self.preexist: if not self.vgname: raise KickstartValueError, formatErrorMsg(self.lineno, msg="--noformat or --useexisting used without giving a name") dev = devicetree.getDeviceByName(self.vgname) if not dev: raise KickstartValueError, formatErrorMsg(self.lineno, msg="No preexisting VG with the name \"%s\" was found." % self.vgname) else: request = storage.newVG(pvs=pvs, name=self.vgname, peSize=self.pesize/1024.0) storage.createDevice(request) class XConfig(commands.xconfig.F10_XConfig): def execute(self, anaconda): if self.startX: anaconda.desktop.setDefaultRunLevel(5) if self.defaultdesktop: anaconda.desktop.setDefaultDesktop(self.defaultdesktop) class ZeroMbr(commands.zerombr.FC3_ZeroMbr): def execute(self, anaconda): anaconda.storage.zeroMbr = 1 class ZFCP(commands.zfcp.FC3_ZFCP): def parse(self, args): fcp = commands.zfcp.FC3_ZFCP.parse(self, args) try: storage.zfcp.ZFCP().addFCP(fcp.devnum, fcp.wwpn, fcp.fcplun) except ValueError, e: log.warning(str(e)) return fcp ### ### HANDLERS ### # This is just the latest entry from pykickstart.handlers.control with all the # classes we're overriding in place of the defaults. commandMap = { "auth": Authconfig, "authconfig": Authconfig, "autopart": AutoPart, "autostep": AutoStep, "bootloader": Bootloader, "clearpart": ClearPart, "dmraid": DmRaid, "fcoe": Fcoe, "firewall": Firewall, "halt": Reboot, "ignoredisk": IgnoreDisk, "install": Upgrade, "iscsi": Iscsi, "iscsiname": IscsiName, "keyboard": Keyboard, "lang": Lang, "logging": Logging, "multipath": MultiPath, "poweroff": Reboot, "reboot": Reboot, "rootpw": RootPw, "selinux": SELinux, "shutdown": Reboot, "skipx": SkipX, "timezone": Timezone, "upgrade": Upgrade, "xconfig": XConfig, "zerombr": ZeroMbr, "zfcp": ZFCP, } dataMap = { "LogVolData": LogVolData, "NetworkData": NetworkData, "PartData": PartitionData, "RaidData": RaidData, "VolGroupData": VolGroupData, } superclass = returnClassForVersion() class AnacondaKSHandler(superclass): def __init__ (self, anaconda): superclass.__init__(self, commandUpdates=commandMap, dataUpdates=dataMap) self.packages = AnacondaKSPackages() self.permanentSkipSteps = [] self.skipSteps = [] self.showSteps = [] self.anaconda = anaconda self.id = self.anaconda.id self.onPart = {} # All the KickstartCommand and KickstartData objects that # handleCommand returns, so we can later iterate over them and run # the execute methods. These really should be stored in the order # they're seen in the kickstart file. self._dataObjs = [] def add(self, obj): if isinstance(obj, KickstartCommand): # Commands can only be run once, and the latest one seen takes # precedence over any earlier ones. i = 0 while i < len(self._dataObjs): if self._dataObjs[i].__class__ == obj.__class__: self._dataObjs.pop(i) break i += 1 self._dataObjs.append(obj) else: # Data objects can be seen over and over again. self._dataObjs.append(obj) def execute(self): for obj in filter(lambda o: hasattr(o, "execute"), self._dataObjs): obj.execute(self.anaconda) class AnacondaPreParser(KickstartParser): # A subclass of KickstartParser that only looks for %pre scripts and # sets them up to be run. All other scripts and commands are ignored. def __init__ (self, handler, followIncludes=True, errorsAreFatal=True, missingIncludeIsFatal=True): KickstartParser.__init__(self, handler, missingIncludeIsFatal=False) def addScript (self): if self._script["type"] != KS_SCRIPT_PRE: return s = AnacondaKSScript (self._script["body"], type=self._script["type"], interp=self._script["interp"], lineno=self._script["lineno"], inChroot=self._script["chroot"], logfile=self._script["log"], errorOnFail=self._script["errorOnFail"]) self.handler.scripts.append(s) def addPackages (self, line): pass def handleCommand (self, lineno, args): pass def handlePackageHdr (self, lineno, args): pass def handleScriptHdr (self, lineno, args): if not args[0] == "%pre": return KickstartParser.handleScriptHdr(self, lineno, args) class AnacondaKSParser(KickstartParser): def __init__ (self, handler, followIncludes=True, errorsAreFatal=True, missingIncludeIsFatal=True): KickstartParser.__init__(self, handler) def addScript (self): if string.join(self._script["body"]).strip() == "": return s = AnacondaKSScript (self._script["body"], type=self._script["type"], interp=self._script["interp"], lineno=self._script["lineno"], inChroot=self._script["chroot"], logfile=self._script["log"], errorOnFail=self._script["errorOnFail"]) self.handler.scripts.append(s) def handlePackageHdr (self, lineno, args): KickstartParser.handlePackageHdr (self, lineno, args) self.handler.packages.seen = True def handleCommand (self, lineno, args): if not self.handler: return retval = KickstartParser.handleCommand(self, lineno, args) self.handler.add(retval) return retval def preScriptPass(anaconda, file): # The first pass through kickstart file processing - look for %pre scripts # and run them. This must come in a separate pass in case a script # generates an included file that has commands for later. ksparser = AnacondaPreParser(AnacondaKSHandler(anaconda)) try: ksparser.readKickstart(file) except IOError, e: if anaconda.intf: anaconda.intf.kickstartErrorWindow("Could not open kickstart file or included file named %s" % e.filename) sys.exit(1) else: stderrLog.critical(_("The following error was found while parsing the kickstart " "configuration file:\n\n%s") % e) sys.exit(1) except KickstartError, e: if anaconda.intf: anaconda.intf.kickstartErrorWindow(e.__str__()) sys.exit(1) else: stderrLog.critical(_("The following error was found while parsing the kickstart " "configuration file:\n\n%s") % e) sys.exit(1) # run %pre scripts runPreScripts(anaconda, ksparser.handler.scripts) def parseKickstart(anaconda, file): try: file = preprocessKickstart(file) except KickstartError, msg: stderrLog.critical(_("Error processing %%ksappend lines: %s") % msg) sys.exit(1) except Exception, e: stderrLog.critical(_("Unknown error processing %%ksappend lines: %s") % e) sys.exit(1) handler = AnacondaKSHandler(anaconda) ksparser = AnacondaKSParser(handler) # We need this so all the /dev/disk/* stuff is set up before parsing. udev_trigger(subsystem="block", action="change") # So that drives onlined by these can be used in the ks file storage.iscsi.iscsi().startup() storage.fcoe.fcoe().startup() storage.zfcp.ZFCP().startup() # Note we do NOT call dasd.startup() here, that does not online drives, but # only checks if they need formatting, which requires zerombr to be known try: ksparser.readKickstart(file) except IOError, e: # We may not have an intf now, but we can do better than just raising # the exception. if anaconda.intf: anaconda.intf.kickstartErrorWindow("Could not open kickstart file or included file named %s" % e.filename) sys.exit(1) else: stderrLog.critical(_("The following error was found while parsing the kickstart " "configuration file:\n\n%s") % e) sys.exit(1) except KickstartError, e: if anaconda.intf: anaconda.intf.kickstartErrorWindow(e.__str__()) sys.exit(1) else: stderrLog.critical(_("The following error was found while parsing the kickstart " "configuration file:\n\n%s") % e) sys.exit(1) return handler def runPostScripts(anaconda): if not anaconda.ksdata: return postScripts = filter (lambda s: s.type == KS_SCRIPT_POST, anaconda.ksdata.scripts) if len(postScripts) == 0: return # Remove environment variables that cause problems for %post scripts. for var in ["LIBUSER_CONF"]: if os.environ.has_key(var): del(os.environ[var]) log.info("Running kickstart %%post script(s)") if anaconda.intf is not None: w = anaconda.intf.waitWindow(_("Post-Installation"), _("Running post-installation scripts")) map (lambda s: s.run(anaconda.rootPath, flags.serial, anaconda.intf), postScripts) log.info("All kickstart %%post script(s) have been run") if anaconda.intf is not None: w.pop() def runPreScripts(anaconda, scripts): preScripts = filter (lambda s: s.type == KS_SCRIPT_PRE, scripts) if len(preScripts) == 0: return log.info("Running kickstart %%pre script(s)") if anaconda.intf is not None: w = anaconda.intf.waitWindow(_("Pre-Installation"), _("Running pre-installation scripts")) map (lambda s: s.run("/", flags.serial, anaconda.intf), preScripts) log.info("All kickstart %%pre script(s) have been run") if anaconda.intf is not None: w.pop() def runTracebackScripts(anaconda): log.info("Running kickstart %%traceback script(s)") for script in filter (lambda s: s.type == KS_SCRIPT_TRACEBACK, anaconda.ksdata.scripts): script.run("/", flags.serial) log.info("All kickstart %%traceback script(s) have been run") def selectPackages(anaconda): ksdata = anaconda.ksdata ignoreAll = False # If no %packages header was seen, use the installclass's default group # selections. This can also be explicitly specified with %packages # --default. Otherwise, select whatever was given (even if it's nothing). if not ksdata.packages.seen or ksdata.packages.default: anaconda.instClass.setGroupSelection(anaconda) return for pkg in ksdata.packages.packageList: num = anaconda.backend.selectPackage(pkg) if ksdata.packages.handleMissing == KS_MISSING_IGNORE or ignoreAll: continue if num > 0: continue rc = anaconda.intf.messageWindow(_("Missing Package"), _("You have specified that the " "package '%s' should be installed. " "This package does not exist. " "Would you like to continue or " "abort this installation?") %(pkg,), type="custom", custom_buttons=[_("_Abort"), _("_Ignore All"), _("_Continue")]) if rc == 0: sys.exit(1) elif rc == 1: ignoreAll = True ksdata.packages.groupList.insert(0, Group("Core")) if ksdata.packages.addBase: ksdata.packages.groupList.insert(1, Group("Base")) else: log.warning("not adding Base group") for grp in ksdata.packages.groupList: default = False optional = False if grp.include == GROUP_DEFAULT: default = True elif grp.include == GROUP_ALL: default = True optional = True try: anaconda.backend.selectGroup(grp.name, (default, optional)) except NoSuchGroup, e: if ksdata.packages.handleMissing == KS_MISSING_IGNORE or ignoreAll: pass else: rc = anaconda.intf.messageWindow(_("Missing Group"), _("You have specified that the " "group '%s' should be installed. " "This group does not exist. " "Would you like to continue or " "abort this installation?") %(grp.name,), type="custom", custom_buttons=[_("_Abort"), _("_Ignore All"), _("_Continue")]) if rc == 0: sys.exit(1) elif rc == 1: ignoreAll = True map(anaconda.backend.deselectPackage, ksdata.packages.excludedList) map(lambda g: anaconda.backend.deselectGroup(g.name), ksdata.packages.excludedGroupList) def setSteps(anaconda): def havePackages(packages): return len(packages.groupList) > 0 or len(packages.packageList) > 0 or \ len(packages.excludedList) > 0 or len(packages.excludedGroupList) > 0 dispatch = anaconda.dispatch ksdata = anaconda.ksdata interactive = ksdata.interactive.interactive if ksdata.upgrade.upgrade: upgrade.setSteps(anaconda) # we have no way to specify migrating yet dispatch.skipStep("upgrademigfind") dispatch.skipStep("upgrademigratefs") dispatch.skipStep("upgradecontinue") dispatch.skipStep("findinstall", permanent = 1) dispatch.skipStep("language") dispatch.skipStep("keyboard") dispatch.skipStep("betanag") dispatch.skipStep("installtype") else: anaconda.instClass.setSteps(anaconda) dispatch.skipStep("findrootparts") if interactive or flags.autostep: dispatch.skipStep("installtype") dispatch.skipStep("bootdisk") dispatch.skipStep("bootdisk") dispatch.skipStep("betanag") dispatch.skipStep("installtype") dispatch.skipStep("network") # Storage is initialized for us right when kickstart processing starts. dispatch.skipStep("storageinit") if not interactive: # Don't show confirmation screens on non-interactive installs. dispatch.skipStep("confirminstall") dispatch.skipStep("confirmupgrade") dispatch.skipStep("welcome") # Since ignoredisk is optional and not specifying it means you want to # consider all possible disks, we should not stop on the filter steps # unless it's an interactive install. dispatch.skipStep("filter") dispatch.skipStep("filtertype") # Make sure to automatically reboot even in interactive if told to. if interactive and ksdata.reboot.action in [KS_REBOOT, KS_SHUTDOWN]: dispatch.skipStep("complete") # If the package section included anything, skip group selection unless # they're in interactive. if ksdata.upgrade.upgrade: ksdata.skipSteps.extend(["tasksel", "group-selection"]) # Special check for this, since it doesn't make any sense. if ksdata.packages.seen: warnings.warn("Ignoring contents of %packages section due to upgrade.") elif havePackages(ksdata.packages): if interactive: ksdata.showSteps.extend(["tasksel", "group-selection"]) else: ksdata.skipSteps.extend(["tasksel", "group-selection"]) else: if ksdata.packages.seen: ksdata.skipSteps.extend(["tasksel", "group-selection"]) else: ksdata.showSteps.extend(["tasksel", "group-selection"]) if not interactive: for n in ksdata.skipSteps: dispatch.skipStep(n) for n in ksdata.permanentSkipSteps: dispatch.skipStep(n, permanent=1) for n in ksdata.showSteps: dispatch.skipStep(n, skip = 0) # Text mode doesn't have all the steps that graphical mode does, so we # can't stop and prompt for missing information. Make sure we've got # everything that would be provided by a missing section now and error # out if we don't. if anaconda.displayMode == "t": missingSteps = [("bootloader", "Bootloader configuration"), ("filter", "Disks to use in installation"), ("cleardiskssel", "Disks to clear"), ("group-selection", "Package selection")] errors = [] for (step, msg) in missingSteps: if not dispatch.stepInSkipList(step): errors.append(msg) if len(errors) > 0: anaconda.intf.kickstartErrorWindow(_("The kickstart configuration " "file is missing required information that anaconda cannot " "prompt for. Please add the following sections and try " "again:\n%s") % ", ".join(errors)) sys.exit(0)