This also removes a lot of the "magic" from BootLoader. The drive list must now be set explicitly using the set_drive_list method. Similarly, the stage1 device is only set by a call to the set_stage1_device method when the storage configuration is settled. Additionally, the default image/target/stanza is no longer generated on demand if it does not exist when access is requested. --- pyanaconda/bootloader.py | 283 ++++++++---------------------------- pyanaconda/storage/__init__.py | 34 ++++- pyanaconda/storage/partitioning.py | 4 - 3 files changed, 91 insertions(+), 230 deletions(-) diff --git a/pyanaconda/bootloader.py b/pyanaconda/bootloader.py index cb48382..ecc9eba 100644 --- a/pyanaconda/bootloader.py +++ b/pyanaconda/bootloader.py @@ -178,15 +178,6 @@ class TbootLinuxBootLoaderImage(LinuxBootLoaderImage): return self._args class BootLoader(object): - """TODO: - - iSeries bootloader? - - same as pSeries, except optional, I think - - upgrade of non-grub bootloaders - - detection of existing bootloaders - - improve password encryption for grub - - fix handling of kickstart-provided already-encrypted - password - """ name = "Generic Bootloader" packages = [] obsoletes = [] @@ -219,14 +210,14 @@ class BootLoader(object): _trusted_boot = False - def __init__(self, storage=None): - # pyanaconda.storage.Storage instance - self.storage = storage + def __init__(self, platform=None): + # pyanaconda.platform.Platform instance + self.platform = platform self.boot_args = Arguments() self.dracut_args = Arguments() - self._drives = [] + self.drives = [] self._drive_order = [] # timeout in seconds @@ -246,7 +237,7 @@ class BootLoader(object): self._default_image = None # the device the bootloader will be installed on - self._stage1_device = None + self.stage1_device = None # the "boot drive", meaning the drive stage1 _will_ go on self.stage1_drive = None @@ -260,45 +251,6 @@ class BootLoader(object): self.warnings = [] # - # stage1 device access - # - # pylint: disable-msg=E0202 - @property - def stage1_device(self): - """ Stage1 target device. """ - if not self._stage1_device: - log.debug("no stage1 device: %s" - % [d.name for d in self.stage1_devices]) - if self.stage2_is_preferred_stage1: - log.debug("using stage2 device as stage1") - self.stage1_device = self.stage2_device - else: - log.debug("stage1_drive is %s" % self.stage1_drive) - devices = self.stage1_devices - if self.stage1_drive: - devices = [d for d in devices if - self.stage1_drive in d.disks] - try: - self.stage1_device = devices[0] - except IndexError: - pass - - return self._stage1_device - - # pylint: disable-msg=E0102,E0202,E1101 - @stage1_device.setter - def stage1_device(self, device): - log.debug("new bootloader stage1 device: %s" % getattr(device, - "name", None)) - self._stage1_device = device - - # pylint: disable-msg=E0202 - @property - def stage2_device(self): - """ Stage2 target device. """ - return self.storage.mountpoints.get("/boot", self.storage.rootDevice) - - # # drive list access # # pylint: disable-msg=E0202 @@ -312,42 +264,23 @@ class BootLoader(object): def drive_order(self, order): log.debug("new drive order: %s" % order) self._drive_order = order - self.clear_drive_list() # this will get regenerated on next access + if self.drives: + self._sort_drives() - def _sort_drives(self, drives): - """Sort drives based on the drive order.""" - _drives = sorted(drives, - cmp=self.storage.compareDisks, key=lambda d: d.name) - for name in reversed(self._drive_order): + def _sort_drives(self): + """Sort the internal drive list. """ + for name in reversed(self.drive_order): try: - idx = [d.name for d in _drives].index(name) + idx = [d.name for d in self.drives].index(name) except ValueError: log.error("bios order specified unknown drive %s" % name) continue - first = _drives.pop(idx) - _drives.insert(0, first) - - return _drives - - def clear_drive_list(self): - """ Clear the drive list to force re-populate on next access. """ - self._drives = [] - - @property - def drives(self): - """Sorted list of available drives.""" - if self._drives: - # only generate the list if it is empty - return self._drives - - drives = [d for d in self.storage.disks if not d.format.hidden] - self._drives = self._sort_drives(drives) + self.drives.insert(0, self.drives.pop(idx)) - # set "boot drive" - self.stage1_drive = self._drives[0] - - return self._drives + def set_drive_list(self, drives): + self.drives = drives[:] + self._sort_drives() # # image list access @@ -356,15 +289,8 @@ class BootLoader(object): @property def default(self): """The default image.""" - if not self._default_image: - if self.linux_images: - _default = self.linux_images[0] - else: - _default = LinuxBootLoaderImage(device=self.storage.rootDevice, - label=productName, - short="linux") - - self._default_image = _default + if not self._default_image and self.linux_images: + self._default_image = self.linux_images[0] return self._default_image @@ -380,9 +306,6 @@ class BootLoader(object): @property def images(self): """ List of OS images that will be included in the configuration. """ - if not self.linux_images: - self.linux_images.append(self.default) - all_images = self.linux_images all_images.extend([i for i in self.chain_images if i.label]) return all_images @@ -403,24 +326,10 @@ class BootLoader(object): """Return the appropriate image label for this bootloader.""" return getattr(image, self.image_label_attr) - def _find_chain_images(self): - """ Collect a list of potential non-linux OS installations. """ - # XXX not used -- do we want to pre-populate the image list for the ui? - self.chain_images = [] - if not self.can_dual_boot: - return - - for device in [d for d in self.bootable_chain_devices if d.exists]: - self.chain_images.append(BootLoaderImage(device=device)) - # # platform-specific data access # @property - def platform(self): - return self.storage.platform - - @property def disklabel_types(self): return self.platform._disklabel_types @@ -660,40 +569,25 @@ class BootLoader(object): valid)) return valid - @property - def stage1_devices(self): - """ A list of valid stage1 target devices. + def set_stage1_device(self, devices): + if not self.stage1_drive: + raise BootLoaderError("need stage1 drive to set stage1 device") - The list self.stage1_device_types is ordered, so we return a list - of all valid target devices, sorted by device type, then sorted - according to our drive ordering. - """ - device_types = self.platform.bootStage1ConstraintDict["device_types"] - slots = [[] for t in device_types] - for device in self.storage.devices: - idx = self._device_type_index(device, device_types) - if idx is None: + if self.stage2_is_preferred_stage1: + self.stage1_device = self.stage2_device + return + + self.stage1_device = None + for device in devices: + if self.stage1_drive not in device.disks: continue if self.is_valid_stage1_device(device): - slots[idx].append(device) - - devices = [] - for slot in slots: - devices.extend(slot) - - devices = self._sort_drives(devices) - - # if a boot drive has been chosen put it, and devices on it, first - # XXX should this be done in _sort_drives instead? - if self.stage1_drive: - boot_devs = [d for d in devices if self.stage1_drive in d.disks] - if len(boot_devs) != len(devices): - for dev in reversed(boot_devs): - idx = devices.index(dev) - devices.insert(0, devices.pop(idx)) + self.stage1_device = device + break - return devices + if not self.stage1_device: + raise BootLoaderError("failed to find a suitable stage1 device") # # boot/stage2 device access @@ -761,36 +655,11 @@ class BootLoader(object): valid)) return valid - @property - def bootable_chain_devices(self): - """ Potential boot devices containing non-linux operating systems. """ - # make sure we don't clobber error/warning lists - errors = self.errors[:] - warnings = self.warnings[:] - ret = [d for d in self.storage.devices - if self.is_valid_stage2_device(d, linux=False, non_linux=True)] - self.errors = errors - self.warnings = warnings - return ret - - @property - def bootable_devices(self): - """ Potential boot devices containing linux operating systems. """ - # make sure we don't clobber error/warning lists - errors = self.errors[:] - warnings = self.warnings[:] - ret = [d for d in self.storage.devices - if self.is_valid_stage2_device(d)] - self.errors = errors - self.warnings = warnings - return ret - # # miscellaneous # - @property - def has_windows(self): + def has_windows(self, devices): return False # pylint: disable-msg=E0202 @@ -827,12 +696,14 @@ class BootLoader(object): Keyword Arguments: + storage - a pyanaconda.storage.Storage instance network - a pyanaconda.network.Network instance (for network storage devices' boot arguments) All other arguments are expected to have a dracutSetupArgs() method. """ + storage = kwargs.pop("storage", None) network = kwargs.pop("network", None) # @@ -847,11 +718,11 @@ class BootLoader(object): # storage from pyanaconda.storage.devices import NetworkStorageDevice - dracut_devices = [self.storage.rootDevice] - if self.stage2_device != self.storage.rootDevice: + dracut_devices = [storage.rootDevice] + if self.stage2_device != storage.rootDevice: dracut_devices.append(self.stage2_device) - dracut_devices.extend(self.storage.fsset.swapDevices) + dracut_devices.extend(storage.fsset.swapDevices) done = [] # When we see a device whose setup string starts with a key in this @@ -862,7 +733,7 @@ class BootLoader(object): "rd.md.uuid": "rd.md=0", "rd.dm.uuid": "rd.dm=0"} for device in dracut_devices: - for dep in self.storage.devices: + for dep in storage.devices: if dep in done: continue @@ -930,7 +801,7 @@ class BootLoader(object): @property def boot_prefix(self): """ Prefix, if any, to paths in /boot. """ - if self.stage2_device == self.storage.rootDevice: + if self.stage2_device.format.mountpoint == "/": prefix = "/boot" else: prefix = "" @@ -1381,9 +1252,15 @@ class GRUB(BootLoader): # miscellaneous # - @property - def has_windows(self): - return len(self.bootable_chain_devices) != 0 + def has_windows(self, devices): + """ Potential boot devices containing non-linux operating systems. """ + # make sure we don't clobber error/warning lists + errors = self.errors[:] + warnings = self.warnings[:] + ret = [d for d in devices if self.is_valid_stage2_device(d, linux=False, non_linux=True)] + self.errors = errors + self.warnings = warnings + return ret class EFIGRUB(GRUB): @@ -1450,7 +1327,7 @@ class EFIGRUB(GRUB): raise BootLoaderError("failed to remove old efi boot entry") def add_efi_boot_target(self): - boot_efi = self.storage.mountpoints["/boot/efi"] + boot_efi = self.stage1_device if boot_efi.type == "partition": boot_disk = boot_efi.disk boot_part_num = boot_efi.partedPartition.number @@ -1531,50 +1408,6 @@ class GRUB2(GRUB): # since it's unlikely there'll be a bios boot partition on each disk # - # constraints for target devices - # - def _gpt_disk_has_bios_boot(self, device): - """ Return False if device is gpt-labeled disk w/o bios boot part. """ - ret = True - - if device is None: - return ret - - if not self.platform.weight(fstype="biosboot"): - # this platform does not need bios boot - return ret - - # check that a bios boot partition is present if the stage1 device - # is a gpt-labeled disk - if device.isDisk and getattr(device.format, "labelType", None) == "gpt": - ret = False - partitions = [p for p in self.storage.partitions - if p.disk == device] - for p in partitions: - if p.format.type == "biosboot": - ret = True - break - - if not ret: - self.errors.append(_("Your BIOS-based system needs a special " - "partition to boot with Fedora's new " - "disk label format (GPT). To continue, " - "please create a 1MB 'BIOS Boot' type " - "partition.")) - - log.debug("_gpt_disk_has_bios_boot(%s) returning %s" % (device.name, - ret)) - return ret - - def is_valid_stage1_device(self, device): - ret = super(GRUB2, self).is_valid_stage1_device(device) - ret = ret and self._gpt_disk_has_bios_boot(device) - - log.debug("is_valid_stage1_device(%s) returning %s" % (device.name, - ret)) - return ret - - # # grub-related conveniences # @@ -1751,7 +1584,7 @@ class YabootSILOBase(BootLoader): else: initrd_line = "" - root_device_spec = self.storage.rootDevice.fstabSpec + root_device_spec = image.device.fstabSpec if root_device_spec.startswith("/"): root_line = "\troot=%s\n" % root_device_spec else: @@ -1951,6 +1784,7 @@ class ZIPL(BootLoader): # def install(self): + # DWL FIXME: resolve the boot device to a StorageDevice from storage buf = iutil.execWithCapture("zipl", [], stderr="/dev/tty5", root=ROOT_PATH) @@ -2107,14 +1941,12 @@ def writeBootloader(anaconda): # The first one is the default kernel. Update the bootloader's default # entry to reflect the details of the default kernel. (version, arch, nick) = kernel_versions.pop(0) - default_image = anaconda.bootloader.default - if not default_image: - log.error("unable to find default image, bailing") - if w: - w.pop() - return - - default_image.version = version + default_image = LinuxBootLoaderImage(device=anaconda.storage.rootDevice, + version=version, + label=productName, + short="linux") + anaconda.bootloader.add_image(default_image) + anaconda.bootloader.default = default_image # all the linux images' labels are based on the default image's base_label = default_image.label @@ -2151,6 +1983,7 @@ def writeBootloader(anaconda): # set up dracut/fips boot args anaconda.bootloader.set_boot_args(keyboard=anaconda.keyboard, + storage=anaconda.storage, language=anaconda.instLanguage, network=anaconda.network) diff --git a/pyanaconda/storage/__init__.py b/pyanaconda/storage/__init__.py index e4f0b17..6a87789 100644 --- a/pyanaconda/storage/__init__.py +++ b/pyanaconda/storage/__init__.py @@ -445,9 +445,12 @@ class Storage(object): if self.bootloader: # clear out bootloader attributes that refer to devices that are # no longer in the tree - self.bootloader.clear_drive_list() + boot_disks = [d for d in self.disks if d.partitioned] + boot_disks.sort(cmp=self.compareDisks, key=lambda d: d.name) + self.bootloader.set_drive_list(boot_disks) self.bootloader.stage1_drive = None self.bootloader.stage1_device = None + self.bootloader.stage2_device = None self.dumpState("initial") @@ -1119,6 +1122,25 @@ class Storage(object): errors.extend(self.bootloader.errors) warnings.extend(self.bootloader.warnings) + # + # check that GPT boot disk on BIOS system has a BIOS boot partition + # + if self.platform.weight(fstype="biosboot") and \ + stage1 and stage1.isDisk and \ + getattr(device.format, "labelType", None) == "gpt": + missing = True + for part in [p for p in self.partitions if p.disk == stage1]: + if part.format.type == "biosboot": + missing = False + break + + if missing: + errors.append(_("Your BIOS-based system needs a special " + "partition to boot with %s's new " + "disk label format (GPT). To continue, " + "please create a 1MB 'BIOS Boot' type " + "partition.") % productName) + if not swaps: from pyanaconda.storage.size import Size @@ -1269,6 +1291,16 @@ class Storage(object): self._bootloader = self.platform.bootloaderClass(self.platform) return self._bootloader + def setUpBootLoader(self): + """ Propagate ksdata into BootLoader. """ + if not self.bootloader or not self.data: + log.warning("either ksdata or bootloader data missing") + return + + self.bootloader.stage1_drive = self.data.bootDrive + self.bootloader.stage2_device = self.bootDevice + self.bootloader.set_stage1_device(self.devices) + @property def bootDisk(self): disk = None diff --git a/pyanaconda/storage/partitioning.py b/pyanaconda/storage/partitioning.py index cbcb43a..5c61117 100644 --- a/pyanaconda/storage/partitioning.py +++ b/pyanaconda/storage/partitioning.py @@ -268,10 +268,6 @@ def doAutoPartition(storage, data, errorcb=None, warningcb=None): if storage.doAutoPart: scheduleShrinkActions(storage) clearPartitions(storage) - # update the bootloader's drive list to add disks which have their - # whole disk format replaced by a disklabel. Make sure to keep any - # previous boot order selection from clearpart_gui or kickstart - anaconda.bootloader.clear_drive_list() disks = _getCandidateDisks(storage) devs = _schedulePVs(storage, disks) -- 1.7.8.4 _______________________________________________ Anaconda-devel-list mailing list Anaconda-devel-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/anaconda-devel-list