# # platform.py: Architecture-specific information # # Copyright (C) 2009 # 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 . # # Authors: Chris Lumens # import iutil import parted import storage from storage.errors import * from storage.formats import * from storage.partspec import * import gettext _ = lambda x: gettext.ldgettext("anaconda", x) N_ = lambda x: x class Platform(object): """Platform A class containing platform-specific information and methods for use during installation. The intent is to eventually encapsulate all the architecture quirks in one place to avoid lots of platform checks throughout anaconda.""" _bootFSType = "ext3" _bootloaderPackage = None _diskType = parted.diskType["msdos"] _isEfi = iutil.isEfi() _minimumSector = 0 _supportsMdRaidBoot = False _minBootPartSize = 50 _maxBootPartSize = 0 def __init__(self, anaconda): """Creates a new Platform object. This is basically an abstract class. You should instead use one of the platform-specific classes as returned by getPlatform below. Not all subclasses need to provide all the methods in this class.""" self.anaconda = anaconda def _mntDict(self): """Return a dictionary mapping mount points to devices.""" ret = {} for device in [d for d in self.anaconda.id.storage.devices if d.format.mountable]: ret[device.format.mountpoint] = device return ret def bootDevice(self): """Return the device where /boot is mounted.""" if self.__class__ is Platform: raise NotImplementedError("bootDevice not implemented for this platform") mntDict = self._mntDict() return mntDict.get("/boot", mntDict.get("/")) @property def bootFSType(self): """Return the default filesystem type for the boot partition.""" return self._bootFSType def bootloaderChoices(self, bl): """Return the default list of places to install the bootloader. This is returned as a dictionary of locations to (device, identifier) tuples. If there is no boot device, an empty dictionary is returned.""" if self.__class__ is Platform: raise NotImplementedError("bootloaderChoices not implemented for this platform") bootDev = self.bootDevice() ret = {} if not bootDev: return ret if bootDev.type == "mdarray": ret["boot"] = (bootDev.name, N_("RAID Device")) ret["mbr"] = (bl.drivelist[0], N_("Master Boot Record (MBR)")) else: ret["boot"] = (bootDev.name, N_("First sector of boot partition")) ret["mbr"] = (bl.drivelist[0], N_("Master Boot Record (MBR)")) return ret @property def bootloaderPackage(self): return self._bootloaderPackage def checkBootRequest(self, req): """Perform an architecture-specific check on the boot device. Not all platforms may need to do any checks. Returns a list of errors if there is a problem, or [] otherwise.""" errors = [] if not req: return [_("You have not created a bootable partition.")] if req.type == "mdarray" and req.level != 1: errors.append(_("Bootable partitions can only be on RAID1 devices.")) # can't have bootable partition on LV if req.type == "lvmlv": errors.append(_("Bootable partitions cannot be on a logical volume.")) # most arches can't have boot on RAID if req.type == "mdarray" and not self.supportsMdRaidBoot: errors.append(_("Bootable partitions cannot be on a RAID device.")) # Lots of filesystems types don't support /boot. if not req.format.bootable: errors.append(_("Bootable partitions cannot be on an %s filesystem.") % req.format.name) # vfat /boot is insane. if req == self.anaconda.id.storage.fsset.rootDevice and req.format.type == "vfat": errors.append(_("Bootable partitions cannot be on an %s filesystem.") % req.format.type) if req.type == "luks/dm-crypt": # Handle encrypted boot on a partition. errors.append(_("Bootable partitions cannot be on an encrypted block device")) else: # Handle encrypted boot on more complicated devices. for dev in map(lambda d: d.type == "luks/dm-crypt", self.anaconda.id.storage.devices): if req in self.anaconda.id.storage.deviceDeps(dev): errors.append(_("Bootable partitions cannot be on an encrypted block device")) return errors @property def diskType(self): """Return the disk label type as a parted.DiskType.""" return self._diskType @diskType.setter def diskType(self, value): """Sets the disk label type.""" self._diskType = value @property def isEfi(self): return self._isEfi @property def minimumSector(self, disk): """Return the minimum starting sector for the provided disk.""" return self._minimumSector def setDefaultPartitioning(self): """Return the default platform-specific partitioning information.""" return [PartSpec(mountpoint="/boot", fstype=self.bootFSType, size=200, weight=self.weight(mountpoint="/boot"))] @property def supportsMdRaidBoot(self): """Does the platform support /boot on MD RAID?""" return self._supportsMdRaidBoot @property def minBootPartSize(self): return self._minBootPartSize @property def maxBootPartSize(self): return self._maxBootPartSize def validBootPartSize(self, size): """ Is the given size (in MB) acceptable for a boot device? """ if not isinstance(size, int) and not isinstance(size, float): return False return ((not self.minBootPartSize or size >= self.minBootPartSize) and (not self.maxBootPartSize or size <= self.maxBootPartSize)) def weight(self, fstype=None, mountpoint=None): """ Given an fstype (as a string) or a mountpoint, return an integer for the base sorting weight. This is used to modify the sort algorithm for partition requests, mainly to make sure bootable partitions and /boot are placed where they need to be.""" return 0 class EFI(Platform): _diskType = parted.diskType["gpt"] _minBootPartSize = 50 _maxBootPartSize = 256 def bootDevice(self): mntDict = self._mntDict() return mntDict.get("/boot/efi") def bootloaderChoices(self, bl): bootDev = self.bootDevice() ret = {} if not bootDev: return ret ret["boot"] = (bootDev.name, N_("EFI System Partition")) return ret def checkBootRequest(self, req): if not req: return [_("You have not created a /boot/efi partition.")] errors = Platform.checkBootRequest(self, req) if req.format.mountpoint == "/boot": if not req.format.type.startswith("ext"): errors.append(_("/boot is not on an ext2 filesystem.")) elif req.format.mountpoint == "/boot/efi": if req.format.type != "efi": errors.append(_("/boot/efi is not EFI.")) return errors def setDefaultPartitioning(self): ret = Platform.setDefaultPartitioning(self) # Only add the EFI partition to the default set if there's not already # one on the system. if len(filter(lambda dev: dev.format.type == "efi" and self.validBootPartSize(dev.size), self.anaconda.id.storage.partitions)) == 0: ret.append(PartSpec(mountpoint="/boot/efi", fstype="efi", size=20, maxSize=200, grow=True, weight=self.weight(fstype="efi"))) return ret def weight(self, fstype=None, mountpoint=None): if fstype and fstype == "efi" or mountpoint and mountpoint == "/boot/efi": return 5000 elif mountpoint and mountpoint == "/boot": return 2000 else: return 0 class Alpha(Platform): _diskType = parted.diskType["bsd"] def checkBootRequest(self, req): errors = Platform.checkBootRequest(self, req) disk = req.disk if not disk: raise DeviceError("Boot partition has no disk") disk = disk.partedDisk # Check that we're a BSD disk label if not disk.type == self.diskType.name: errors.append(_("%s must have a bsd disk label.") % req.disk.name) # The first free space should start at the beginning of the drive and # span for a megabyte or more. free = disk.getFirstPartition() while free: if free.type & parted.PARTITION_FREESPACE: break free = free.nextPartition() if not free or free.geoemtry.start != 1L or free.getSize(unit="MB") < 1: errors.append(_("The disk %s requires at least 1MB of free space at the beginning.") % req.disk.name) return errors class IA64(EFI): _bootloaderPackage = "elilo" def __init__(self, anaconda): EFI.__init__(self, anaconda) class PPC(Platform): _bootloaderPackage = "yaboot" _ppcMachine = iutil.getPPCMachine() _supportsMdRaidBoot = True @property def ppcMachine(self): return self._ppcMachine class IPSeriesPPC(PPC): _minBootPartSize = 4 _maxBootPartSize = 10 def bootDevice(self): bootDev = None # We want the first PReP partition. for device in self.anaconda.id.storage.partitions: if device.format.type == "prepboot": bootDev = device break return bootDev def bootloaderChoices(self, bl): ret = {} bootDev = self.bootDevice() if not bootDev: return ret if bootDev.type == "mdarray": ret["boot"] = (bootDev.name, N_("RAID Device")) ret["mbr"] = (bl.drivelist[0], N_("Master Boot Record (MBR)")) else: ret["boot"] = (bootDev.name, N_("PPC PReP Boot")) return ret def checkBootRequest(self, req): errors = PPC.checkBootRequest(self, req) bootPart = getattr(req, "partedPartition", None) if not bootPart: raise DeviceError("Boot partition has no partedPartition") if bootPart.geometry.end * bootPart.geometry.device.sectorSize / (1024.0 * 1024) > 4096: errors.append(_("The boot partition must be within the first 4MB of the disk.")) return errors def setDefaultPartitioning(self): ret = PPC.setDefaultPartitioning(self) ret.append(PartSpec(fstype="PPC PReP Boot", size=4, weight=self.weight(fstype="prepboot"))) return ret def weight(self, fstype=None, mountpoint=None): if fstype and fstype == "prepboot": return 5000 elif mountpoint and mountpoint == "/boot": return 2000 else: return 0 class NewWorldPPC(PPC): _diskType = parted.diskType["mac"] _minBootPartSize = (800.00 / 1024.00) _maxBootPartSize = 1 def bootDevice(self): bootDev = None for part in self.anaconda.id.storage.partitions: if part.format.type == "appleboot" and self.validBootPartSize(part.size): bootDev = part # if we're only picking one, it might as well be the first break return bootDev def bootloaderChoices(self, bl): ret = {} bootDev = self.bootDevice() if not bootDev: return ret if bootDev.type == "mdarray": ret["boot"] = (bootDev.name, N_("RAID Device")) ret["mbr"] = (bl.drivelist[0], N_("Master Boot Record (MBR)")) else: ret["boot"] = (bootDev.name, N_("Apple Bootstrap")) for (n, device) in enumerate(self.anaconda.id.storage.partitions): if device.format.type == "appleboot" and device.path != bootDev.path: ret["boot%d" % n] = (device.path, N_("Apple Bootstrap")) return ret def checkBootRequest(self, req): errors = PPC.checkBootRequest(self, req) disk = req.disk if not disk: raise DeviceError("Boot partition has no disk") disk = disk.partedDisk # Check that we're a Mac disk label if not disk.type == self.diskType.name: errors.append(_("%s must have a mac disk label.") % req.disk.name) return errors def setDefaultPartitioning(self): ret = Platform.setDefaultPartitioning(self) ret.append(PartSpec(fstype="Apple Bootstrap", size=1, maxSize=1, weight=self.weight(fstype="appleboot"))) return ret def weight(self, fstype=None, mountpoint=None): if fstype and fstype == "appleboot": return 5000 elif mountpoint and mountpoint == "/boot": return 2000 else: return 0 class PS3(PPC): _diskType = parted.diskType["msdos"] def __init__(self, anaconda): PPC.__init__(self, anaconda) class S390(Platform): _bootloaderPackage = "s390utils" def __init__(self, anaconda): Platform.__init__(self, anaconda) class Sparc(Platform): _diskType = parted.diskType["sun"] @property def minimumSector(self, disk): (cylinders, heads, sector) = disk.device.biosGeometry start = long(sectors * heads) start /= long(1024 / disk.device.sectorSize) return start+1 class X86(EFI): _bootloaderPackage = "grub" _supportsMdRaidBoot = True def __init__(self, anaconda): EFI.__init__(self, anaconda) if self.isEfi: self.diskType = parted.diskType["gpt"] else: self.diskType = parted.diskType["msdos"] def bootDevice(self): if self.isEfi: return EFI.bootDevice(self) else: return Platform.bootDevice(self) def bootloaderChoices(self, bl): if self.isEfi: return EFI.bootloaderChoices(self, bl) bootDev = self.bootDevice() ret = {} if not bootDev: return {} if bootDev.type == "mdarray": ret["boot"] = (bootDev.name, N_("RAID Device")) ret["mbr"] = (bl.drivelist[0], N_("Master Boot Record (MBR)")) else: ret["boot"] = (bootDev.name, N_("First sector of boot partition")) ret["mbr"] = (bl.drivelist[0], N_("Master Boot Record (MBR)")) return ret @property def maxBootPartSize(self): if self.isEfi: return EFI._maxBootPartSize else: return Platform._maxBootPartSize @property def minBootPartSize(self): if self.isEfi: return EFI._minBootPartSize else: return Platform._minBootPartSize def setDefaultPartitioning(self): if self.isEfi: return EFI.setDefaultPartitioning(self) else: return Platform.setDefaultPartitioning(self) def getPlatform(anaconda): """Check the architecture of the system and return an instance of a Platform subclass to match. If the architecture could not be determined, raise an exception.""" if iutil.isAlpha(): return Alpha(anaconda) elif iutil.isIA64(): return IA64(anaconda) elif iutil.isPPC(): ppcMachine = iutil.getPPCMachine() if (ppcMachine == "PMac" and iutil.getPPCMacGen() == "NewWorld"): return NewWorldPPC(anaconda) elif ppcMachine in ["iSeries", "pSeries"]: return IPSeriesPPC(anaconda) elif ppcMachine == "PS3": return PS3(anaconda) else: raise SystemError, "Unsupported PPC machine type" elif iutil.isS390(): return S390(anaconda) elif iutil.isSparc(): return Sparc(anaconda) elif iutil.isX86(): return X86(anaconda) else: raise SystemError, "Could not determine system architecture."