# # 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 * 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"] _minimumSector = 0 _supportsMdRaidBoot = False 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, diskset): """Perform an architecture-specific check on the boot device. Not all platforms may need to do any checks. Raises an exception if there is a problem, or returns True otherwise.""" return @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 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 [("/boot", self.bootFSType, 200, None, 0, 0)] @property def supportsMdRaidBoot(self): """Does the platform support /boot on MD RAID?""" return self._supportsMdRaidBoot class EFI(Platform): _diskType = parted.diskType["gpt"] 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, diskset): if not req.device or not hasattr(req, "drive"): return bootPart = None for drive in req.drive: bootPart = diskset.disks[drive].getPartitionByPath("/dev/%s" % req.device) if bootPart: break if not bootPart: return if req.mountpoint == "/boot": if not bootPart.fileSystem.type.startswith("ext"): raise FSError("/boot is not ext2") elif req.mountpoint == "/boot/efi": if not bootPart.fileSystem.type.startswith("fat"): raise FSError("/boot/efi is not vfat") 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 dev.size < 256 and dev.bootable, self.anaconda.id.storage.partitions)) == 0: ret.append(("/boot/efi", "efi", 50, 200, 1, 0)) return ret class Alpha(Platform): _diskType = parted.diskType["bsd"] def checkBootRequest(self, req, diskset): if not req.device or not hasattr(req, "drive"): return bootPart = None for drive in req.drive: bootPart = diskset.disks[drive].getPartitionByPath("/dev/%s" % req.device) if bootPart: break if not bootPart: return disk = bootPart.disk # Check that we're a BSD disk label if not disk.type == self.diskType: raise DeviceError("Disk label is not %s" % self.diskType) # 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: raise DeviceError("Disk does not have enough free space at the beginning") return 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): def bootDevice(self): bootDev = None # We want the first PReP partition. for device in storage.partitions: if device.partedPartition.getFlag(parted.PARTITION_PREP): 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, diskset): if not req.device or not hasattr(req, "drive"): return bootPart = None for drive in req.drive: bootPart = diskset.disks[drive].getPartitionByPath("/dev/%s" % req.device) if bootPart and (bootPart.geometry.end * bootPart.geometry.device.sectorSize / (1024.0 * 1024)) > 4096: raise DeviceError("Boot partition is located too high") return def setDefaultPartitioning(self): ret = PPC.setDefaultPartitioning(self) ret.insert(0, (None, "PPC PReP Boot", 4, None, 0, 0)) return ret class NewWorldPPC(PPC): _diskType = parted.diskType["mac"] def bootDevice(self): bootDev = None for device in self.anaconda.id.storage.devices.values(): # XXX do we need to also check the size? if device.format.type == "hfs" and device.bootable: bootDev = device 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 == "hfs" and device.bootable and device.path != bootDev.path: ret["boot%d" % n] = (device.path, N_("Apple Bootstrap")) return ret def setDefaultPartitioning(self): ret = Platform.setDefaultPartitioning(self) ret.insert(0, (None, "Apple Bootstrap", 1, 1, 0, 0)) return ret 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" _isEfi = iutil.isEfi() _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 isEfi(self): return self._isEfi 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") or ppcMachine == "PS3": return NewWorldPPC(anaconda) elif ppcMachine in ["iSeries", "pSeries"]: return IPSeriesPPC(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."