FAQ Search Today's Posts Mark Forums Read
» Video Reviews

» Linux Archive

Linux-archive is a website aiming to archive linux email lists and to make them easily accessible for linux users/developers.


» Sponsor

» Partners

» Sponsor

Go Back   Linux Archive > ArchLinux > ArchLinux Development

 
 
LinkBack Thread Tools
 
Old 04-01-2011, 07:57 PM
David Lehman
 
Default Replace booty with a new bootloader module.

Moves requirements for and detection of bootloader stage1/stage2
target devices into the bootloader classes themselves.

Separates the concepts of bios drive ordering and boot drive selection.

Removes support for LILO and ABOOT.

Removes chandev code from ZIPL.

Moves target/stage1 device type descriptions into BootLoader classes.

Adds prevention of duplicate labels on upgrades.
---
configure.ac | 1 -
pyanaconda/Makefile.am | 2 +-
pyanaconda/bootloader.py | 1823 +++++++++++++++++++++++++++++++----
pyanaconda/booty/Makefile.am | 24 -
pyanaconda/booty/__init__.py | 52 -
pyanaconda/booty/alpha.py | 140 ---
pyanaconda/booty/bootloaderInfo.py | 714 --------------
pyanaconda/booty/checkbootloader.py | 176 ----
pyanaconda/booty/ia64.py | 38 -
pyanaconda/booty/lilo.py | 308 ------
pyanaconda/booty/ppc.py | 184 ----
pyanaconda/booty/s390.py | 186 ----
pyanaconda/booty/sparc.py | 128 ---
pyanaconda/booty/util.py | 9 -
pyanaconda/booty/x86.py | 562 -----------
15 files changed, 1630 insertions(+), 2717 deletions(-)
delete mode 100644 pyanaconda/booty/Makefile.am
delete mode 100644 pyanaconda/booty/__init__.py
delete mode 100644 pyanaconda/booty/alpha.py
delete mode 100644 pyanaconda/booty/bootloaderInfo.py
delete mode 100644 pyanaconda/booty/checkbootloader.py
delete mode 100644 pyanaconda/booty/ia64.py
delete mode 100644 pyanaconda/booty/lilo.py
delete mode 100644 pyanaconda/booty/ppc.py
delete mode 100644 pyanaconda/booty/s390.py
delete mode 100644 pyanaconda/booty/sparc.py
delete mode 100644 pyanaconda/booty/util.py
delete mode 100644 pyanaconda/booty/x86.py

diff --git a/configure.ac b/configure.ac
index ed7350e..e466d52 100644
--- a/configure.ac
+++ b/configure.ac
@@ -286,7 +286,6 @@ AC_CONFIG_FILES([Makefile
po/Makefile.in
scripts/Makefile
pyanaconda/Makefile
- pyanaconda/booty/Makefile
pyanaconda/isys/Makefile
pyanaconda/storage/Makefile
pyanaconda/storage/devicelibs/Makefile
diff --git a/pyanaconda/Makefile.am b/pyanaconda/Makefile.am
index dda42d0..1269502 100644
--- a/pyanaconda/Makefile.am
+++ b/pyanaconda/Makefile.am
@@ -17,7 +17,7 @@
#
# Author: Martin Sivak <msivak@redhat.com>

-SUBDIRS = booty installclasses isys iw textw storage
+SUBDIRS = installclasses isys iw textw storage

MAINTAINERCLEANFILES = Makefile.in

diff --git a/pyanaconda/bootloader.py b/pyanaconda/bootloader.py
index ce27f69..c99620a 100644
--- a/pyanaconda/bootloader.py
+++ b/pyanaconda/bootloader.py
@@ -1,242 +1,1677 @@
+# bootloader.py
+# Anaconda's bootloader configuration module.
#
-# bootloader.py: anaconda bootloader shims
+# Copyright (C) 2011 Red Hat, Inc.
#
-# Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006 Red Hat, Inc.
-# All rights reserved.
+# This copyrighted material is made available to anyone wishing to use,
+# modify, copy, or redistribute it subject to the terms and conditions of
+# the GNU General Public License v.2, or (at your option) any later version.
+# This program is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY expressed or implied, including the implied warranties 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, write to the
+# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA. Any Red Hat trademarks that are incorporated in the
+# source code or documentation are not subject to the GNU General Public
+# License and may only be used or replicated with the express permission of
+# Red Hat, Inc.
#
-# 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 <http://www.gnu.org/licenses/>.
-#
-# Author(s): Erik Troan <ewt@redhat.com>
-# Jeremy Katz <katzj@redhat.com>
+# Red Hat Author(s): David Lehman <dlehman@redhat.com>
#

-import isys
-import parted
-import os, sys
-import iutil
-import string
-from flags import flags
-from constants import *
-from storage.devices import devicePathToName
-from storage import getReleaseString
-from booty.util import getDiskPart
+import sys
+import os
+import re
+import struct
+
+from pyanaconda import iutil
+from pyanaconda.storage.devicelibs import mdraid
+from pyanaconda.isys import sync
+from pyanaconda.product import productName
+from pyanaconda.flags import flags
+from pyanaconda.constants import *
+from pyanaconda.storage.errors import StorageError

import gettext
_ = lambda x: gettext.ldgettext("anaconda", x)
+N_ = lambda x: x

import logging
log = logging.getLogger("anaconda")

-import booty
-from booty import bootloaderInfo, checkbootloader

-def isEfiSystemPartition(part):
- if not part.active:
+def get_boot_block(device, seek_blocks=0):
+ block = " " * 512
+ status = device.status
+ if not status:
+ try:
+ device.setup()
+ except StorageError:
+ return block
+ fd = os.open(device.path, os.O_RDONLY)
+ if seek_blocks:
+ os.lseek(fd, seek_blocks * 512, 0)
+ block = os.read(fd, 512)
+ os.close(fd)
+ if not status:
+ try:
+ device.teardown(recursive=True)
+ except StorageError:
+ pass
+
+ return block
+
+def is_windows_boot_block(block):
+ try:
+ windows = (len(block) >= 512 and
+ struct.unpack("H", block[0x1fe: 0x200]) == (0xaa55,))
+ except struct.error:
+ windows = False
+ return windows
+
+def has_windows_boot_block(device):
+ return is_windows_boot_block(get_boot_block(device))
+
+
+class BootLoaderError(Exception):
+ pass
+
+
+class ArgumentList(list):
+ def _is_match(self, item, value):
+ try:
+ _item = item.split("=")[0]
+ except (ValueError, AttributeError):
+ _item = item
+
+ try:
+ _value = value.split("=")[0]
+ except (ValueError, AttributeError):
+ _value = value
+
+ return _item == _value
+
+ def __contains__(self, value):
+ for arg in self:
+ if self._is_match(arg, value):
+ return True
+
return False
- return (part.disk.type == "gpt" and
- part.name == "EFI System Partition" and
- part.getFlag(parted.PARTITION_BOOT) and
- part.fileSystem.type in ("fat16", "fat32") and
- isys.readFSLabel(part.getDeviceNodeName()) != "ANACONDA")

-def bootloaderSetupChoices(anaconda):
- if anaconda.dir == DISPATCH_BACK:
- rc = anaconda.intf.messageWindow(_("Warning"),
- _("Filesystems have already been activated. You "
- "cannot go back past this point.

Would you like to "
- "continue with the installation?"),
- type="custom", custom_icon=["error","error"],
- custom_buttons=[_("_Exit installer"), _("_Continue")])
+ def count(self, value):
+ return len([x for x in self if self._is_match(x, value)])

- if rc == 0:
- sys.exit(0)
- return DISPATCH_FORWARD
+ def index(self, value):
+ for i in range(len(self)):
+ if self._is_match(self[i], value):
+ return i

- if anaconda.ksdata and anaconda.ksdata.bootloader.driveorder:
- anaconda.bootloader.updateDriveList(anaconda.ksdat a.bootloader.driveorder)
- else:
- #We want the selected bootloader drive to be preferred
- pref = anaconda.bootloader.drivelist[:1]
- anaconda.bootloader.updateDriveList(pref)
-
- if iutil.isEfi() and not anaconda.bootloader.device:
- bootPart = None
- partitions = anaconda.storage.partitions
- for part in partitions:
- if part.partedPartition.active and isEfiSystemPartition(part.partedPartition):
- bootPart = part.name
- break
- if bootPart:
- anaconda.bootloader.setDevice(bootPart)
-
-# iSeries bootloader on upgrades
- if iutil.getPPCMachine() == "iSeries" and not anaconda.bootloader.device:
- bootPart = None
- partitions = anaconda.storage.partitions
- for part in partitions:
- if part.partedPartition.active and
- part.partedPartition.getFlag(parted.PARTITION_PREP ):
- bootPart = part.name
- break
- if bootPart:
- anaconda.bootloader.setDevice(bootPart)
-
- choices = anaconda.platform.bootloaderChoices(anaconda.bootl oader)
- if not choices and iutil.getPPCMachine() != "iSeries":
- anaconda.dispatch.skipStep("instbootloader")
- else:
- anaconda.dispatch.skipStep("instbootloader", skip = 0)
-
- # FIXME: ...
- anaconda.bootloader.images.setup(anaconda.storage)
-
- if anaconda.bootloader.defaultDevice != None and choices:
- keys = choices.keys()
- # there are only two possible things that can be in the keys
- # mbr and boot. boot is ALWAYS present. so if the dev isn't
- # listed, it was mbr and we should nicely fall back to boot
- if anaconda.bootloader.defaultDevice not in keys:
- log.warning("MBR not suitable as boot device; installing to partition")
- anaconda.bootloader.defaultDevice = "boot"
- anaconda.bootloader.setDevice(choices[anaconda.bootloader.defaultDevice][0])
- elif choices and iutil.isMactel() and choices.has_key("boot"): # haccckkkk
- anaconda.bootloader.setDevice(choices["boot"][0])
- elif choices and choices.has_key("mbr"):
- anaconda.bootloader.setDevice(choices["mbr"][0])
- elif choices and choices.has_key("boot"):
- anaconda.bootloader.setDevice(choices["boot"][0])
-
-def fixedMdraidGrubTarget(anaconda, grubTarget):
- # handle change made in F12 - before F12 mdX used to mean installation
- # into mbrs of mdX members' disks
- fixedGrubTarget = grubTarget
- (product, version) = getReleaseString(anaconda.rootPath)
- try:
- if float(version) < 12:
- stage1Devs = anaconda.bootloader.getPhysicalDevices(grubTarget)
- fixedGrubTarget = getDiskPart(stage1Devs[0])[0]
- log.info("Mdraid grub upgrade: %s -> %s" % (grubTarget.name,
- fixedGrubTarget.name))
- except ValueError:
- log.warning("Can't decide mdraid grub upgrade fix, product: %s, version: %s" % (product, version))
+ raise ValueError("'%s' is not in list" % value)

- return fixedGrubTarget
+ def rindex(self, value):
+ for i in reversed(range(len(self))):
+ if self._is_match(self[i], value):
+ return i

-def writeBootloader(anaconda):
- def dosync():
- isys.sync()
- isys.sync()
- isys.sync()
+ raise ValueError("'%s' is not in list" % value)

- if anaconda.bootloader.defaultDevice == -1:
- return
+ def append(self, value):
+ """ Append a new argument to the list.
+
+ If the value is an exact match to an existing argument it will
+ not be added again. Also, empty strings will not be added.
+ """
+ if value == "":
+ return

- if anaconda.bootloader.doUpgradeOnly:
- (bootType, theDev) = checkbootloader.getBootloaderTypeAndBoot(anaconda. rootPath, storage=anaconda.storage)
-
- anaconda.bootloader.doUpgradeonly = 1
- if bootType == "GRUB":
- grubTarget = anaconda.storage.devicetree.getDeviceByPath(theDev )
- if grubTarget.type == "mdarray":
- grubTarget = fixedMdraidGrubTarget(anaconda, grubTarget)
- anaconda.bootloader.useGrubVal = 1
- anaconda.bootloader.setDevice(grubTarget.name)
+ try:
+ idx = self.index(value)
+ except ValueError:
+ pass
else:
- anaconda.bootloader.doUpgradeOnly = 0
+ if self[idx] == value:
+ return

- w = anaconda.intf.waitWindow(_("Bootloader"), _("Installing bootloader."))
+ super(ArgumentList, self).append(value)

- kernelList = []
- otherList = []
- # getDefault needs to return a device, but that's too invasive for now.
- rootDev = anaconda.storage.rootDevice
+ def extend(self, values):
+ map(self.append, values)

- if not anaconda.bootloader.images.getDefault():
- defaultDev = None
- else:
- defaultDev = anaconda.storage.devicetree.getDeviceByName(anacon da.bootloader.images.getDefault())
+ def __str__(self):
+ return " ".join(map(str, self))

- kernelLabel = None
- kernelLongLabel = None

- for (dev, (label, longlabel, type)) in anaconda.bootloader.images.getImages().items():
- if (rootDev is None and kernelLabel is None) or (dev == rootDev.name):
- kernelLabel = label
- kernelLongLabel = longlabel
- elif (not defaultDev and not dev) or (defaultDev and dev == defaultDev.name):
- otherList = [(label, longlabel, dev)] + otherList
- else:
- otherList.append((label, longlabel, dev))
+class BootLoaderImage(object):
+ """ Base class for bootloader images. Suitable for non-linux OS images. """
+ def __init__(self, device=None, label=None):
+ self.label = label
+ self.device = device

- if kernelLabel is None:
- log.error("unable to find default image, bailing")
- w.pop()
- return

- defkern = "kernel"
- for (version, arch, nick) in
- anaconda.backend.kernelVersionList(anaconda.rootPa th):
- if nick != 'base':
- defkern = "kernel-%s" %(nick,)
- kernelList.append(("%s-%s" %(kernelLabel, nick),
- "%s-%s" %(kernelLongLabel, nick),
- version))
+class LinuxBootLoaderImage(BootLoaderImage):
+ def __init__(self, device=None, label=None, short=None, version=None):
+ super(LinuxBootLoaderImage, self).__init__(device=device, label=label)
+ self.label = label # label string
+ self.short_label = short # shorter label string
+ self.device = device # StorageDevice instance
+ self.version = version # kernel version string
+ self._kernel = None # filename string
+ self._initrd = None # filename string
+
+ @property
+ def kernel(self):
+ filename = self._kernel
+ if self.version and not filename:
+ filename = "vmlinuz-%s" % self.version
+ return filename
+
+ @property
+ def initrd(self):
+ filename = self._initrd
+ if self.version and not filename:
+ filename = "initramfs-%s.img" % self.version
+ return filename
+
+
+class BootLoader(object):
+ """TODO:
+ - iSeries bootloader?
+ - same as pSeries, except optional, I think
+ - upgrade of non-grub bootloaders
+ - detection of existing bootloaders
+ - resolve overlap between Platform checkFoo methods and
+ _is_valid_target_device and _is_valid_boot_device
+ - split certain parts of _is_valid_target_device and
+ _is_valid_boot_device into specific bootloader classes
+ """
+ name = "Generic Bootloader"
+ packages = []
+ config_file = None
+ config_file_mode = 0600
+ can_dual_boot = False
+ can_update = False
+ image_label_attr = "label"
+
+ # requirements for bootloader target devices
+ target_device_types = ["partition"]
+ target_device_raid_levels = []
+ target_device_format_types = []
+ target_device_disklabel_types = ["msdos"]
+ target_device_mountpoints = []
+ target_device_min_size = None
+ target_device_max_size = None
+
+ target_descriptions = {"disk": N_("Master Boot Record"),
+ "partition": N_("First sector of boot partition"),
+ "mdarray": N_("RAID Device")}
+
+ # requirements for boot devices
+ boot_device_types = ["partition"]
+ boot_device_raid_levels = []
+ boot_device_format_types = []
+
+ # linux-specific requirements for boot devices
+ linux_boot_device_format_types = ["ext4", "ext3", "ext2"]
+ linux_boot_device_mountpoints = ["/boot", "/"]
+ linux_boot_device_min_size = 50
+ linux_boot_device_max_size = None
+
+ # this is so stupid...
+ global_preserve_args = ["speakup_synth", "apic", "noapic", "apm", "ide",
+ "noht", "acpi", "video", "pci", "nodmraid",
+ "nompath", "nomodeset", "noiswmd", "fips"]
+ preserve_args = []
+
+ def __init__(self, storage=None):
+ # pyanaconda.storage.Storage instance
+ self.storage = storage
+
+ self.boot_args = ArgumentList()
+ self.dracut_args = []
+
+ self._drives = []
+ self._drive_order = []
+
+ # timeout in seconds
+ self._timeout = None
+ self.password = None
+
+ # console/serial stuff
+ self.console = ""
+ self.console_options = ""
+ self._set_console()
+
+ # list of BootLoaderImage instances representing bootable OSs
+ self.linux_images = []
+ self.chain_images = []
+
+ # default image
+ self._default_image = None
+
+ # the device the bootloader will be installed on
+ self._target_device = None
+
+ self._update_only = False
+
+ #
+ # target device access
+ #
+ @property
+ def stage1_device(self):
+ """ Stage1 target device. """
+ if not self._target_device:
+ self.stage1_device = self.target_devices[0]
+
+ return self._target_device
+
+ @stage1_device.setter
+ def stage1_device(self, device):
+ if not self._is_valid_target_device(device):
+ raise ValueError("%s is not a valid target device" % device.name)
+
+ log.debug("new bootloader stage1 device: %s" % device.name)
+ self._target_device = device
+
+ @property
+ def stage2_device(self):
+ """ Stage2 target device. """
+ return self.storage.mountpoints.get("/boot", self.storage.rootDevice)
+
+ #
+ # drive list access
+ #
+ @property
+ def drive_order(self):
+ """Potentially partial order for drives."""
+ return self._drive_order
+
+ @drive_order.setter
+ 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
+
+ def _sort_drives(self, drives):
+ """Sort drives based on the drive order."""
+ _drives = drives[:]
+ for name in reversed(self._drive_order):
+ try:
+ idx = [d.name for d in _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
+
+ # XXX requiring partitioned may break clearpart
+ drives = [d for d in self.storage.disks if d.partitioned]
+ self._drives = self._sort_drives(drives)
+ return self._drives
+
+ #
+ # image list access
+ #
+ @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.rootDevic e,
+ label=productName,
+ short="linux")
+
+ self._default_image = _default
+
+ return self._default_image
+
+ @default.setter
+ def default(self, image):
+ if image not in self.images:
+ raise ValueError("new default image not in image list")
+
+ log.debug("new default image: %s" % image)
+ self._default_image = image
+
+ @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
+
+ def clear_images(self):
+ """Empty out the image list."""
+ self.linux_images = []
+ self.chain_images = []
+
+ def add_image(self, image):
+ """Add a BootLoaderImage instance to the image list."""
+ if isinstance(image, LinuxBootLoaderImage):
+ self.linux_images.append(image)
+ else:
+ self.chain_images.append(image)
+
+ def image_label(self, image):
+ """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=de vice))
+
+ #
+ # target/stage1 device access
+ #
+ def _device_type_index(self, device, types):
+ """ Return the index of the matching type in types to device's type.
+
+ Return None if no match is found. """
+ index = None
+ try:
+ index = types.index(device.type)
+ except ValueError:
+ if "disk" in types and device.isDisk:
+ index = types.index("disk")
+
+ return index
+
+ def _device_type_match(self, device, types):
+ """ Return True if device is of one of the types in the list types. """
+ return self._device_type_index(device, types) is not None
+
+ def device_description(self, device):
+ idx = self._device_type_index(device, self.target_device_types)
+ if idx is None:
+ raise ValueError("'%s' not a valid stage1 type" % device.type)
+
+ return self.target_descriptions[self.target_device_types[idx]]
+
+ def set_preferred_stage2_type(self, preferred):
+ """ Set a preferred type of stage1 device.
+
+ XXX should this reorder the list or remove everything else? """
+ if preferred == "mbr":
+ preferred = "disk"
+
+ try:
+ index = self.target_device_types.index(preferred)
+ except ValueError:
+ raise ValueError("'%s' not a valid stage1 device type" % preferred)
+
+ self.target_device_types.insert(0, self.target_device_types.pop(index))
+
+ def _is_valid_target_device(self, device):
+ """ Return True if the device is a valid stage1 target device.
+
+ The criteria for being a valid stage1 target device vary from
+ platform to platform. On some platforms a disk with an msdos
+ disklabel is a valid stage1 target, while some platforms require
+ a special device. Some examples of these special devices are EFI
+ system partitions on EFI machines, PReP boot partitions on
+ iSeries, and Apple bootstrap partitions on Mac. """
+ if not self._device_type_match(device, self.target_device_types):
+ return False
+
+ if (self.target_device_min_size is not None and
+ device.size < self.target_device_min_size):
+ return False
+
+ if (self.target_device_max_size is not None and
+ device.size > self.target_device_max_size):
+ return False
+
+ if not getattr(device, "bootable", True) or
+ (hasattr(device, "partedPartition") and
+ not device.partedPartition.active):
+ return False
+
+ if getattr(device.format, "label", None) == "ANACONDA":
+ return False
+
+ if self.target_device_format_types and
+ device.format.type not in self.target_device_format_types:
+ return False
+
+ if self.target_device_disklabel_types:
+ for disk in device.disks:
+ label_type = disk.format.labelType
+ if label_type not in self.target_device_disklabel_types:
+ return False
+
+ if self.target_device_mountpoints and
+ hasattr(device.format, "mountpoint") and
+ device.format.mountpoint not in self.target_device_mountpoints:
+ return False
+
+ return True
+
+ @property
+ def target_devices(self):
+ """ A list of valid stage1 target devices.
+
+ The list self.target_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.
+ """
+ slots = [[] for t in self.target_device_types]
+ for device in self.storage.devices:
+ idx = self._device_type_index(device, self.target_device_types)
+ if idx is None:
+ continue
+
+ if self._is_valid_target_device(device):
+ slots[idx].append(device)
+
+ devices = []
+ for slot in slots:
+ devices.extend(slot)
+
+ return self._sort_drives(devices)
+
+ #
+ # boot/stage2 device access
+ #
+
+ def _is_valid_boot_device(self, device, linux=False):
+ """ Return True if the specified device might contain an OS image. """
+ if not self._device_type_match(device, self.boot_device_types):
+ return False
+
+ if device.type == "mdarray" and
+ device.level not in self.boot_device_raid_levels:
+ # TODO: also check metadata version, as ridiculous as that is
+ return False
+
+ if not self.target_devices:
+ # XXX is this really a dealbreaker?
+ return False
+
+ # FIXME: the windows boot block part belongs in GRUB
+ if hasattr(device, "partedPartition") and
+ (not device.bootable or not device.partedPartition.active) and
+ not has_windows_boot_block(device):
+ return False
+
+ if linux:
+ format_types = self.linux_boot_device_format_types
+ mountpoint = getattr(device.format, "mountpoint", None)
+ if mountpoint not in self.linux_boot_device_mountpoints:
+ return False
else:
- kernelList.append((kernelLabel, kernelLongLabel, version))
+ format_types = self.boot_device_format_types
+
+ return device.format.type in format_types
+
+ @property
+ def bootable_chain_devices(self):
+ """ Potential boot devices containing non-linux operating systems. """
+ return [d for d in self.storage.devices if self._is_valid_boot_device(d)]
+
+ @property
+ def bootable_linux_devices(self):
+ """ Potential boot devices containing linux operating systems. """
+ return [d for d in self.storage.devices
+ if self._is_valid_boot_device(d, linux=True)]
+
+ #
+ # miscellaneous
+ #
+
+ @property
+ def has_windows(self):
+ return False
+
+ @property
+ def timeout(self):
+ """Bootloader timeout in seconds."""
+ if self._timeout is not None:
+ t = self._timeout
+ elif self.console and self.console.startswith("ttyS"):
+ t = 5
+ else:
+ t = 20
+
+ return t
+
+ @timeout.setter
+ def timeout(self, seconds):
+ self._timeout = seconds
+
+ @property
+ def update_only(self):
+ return self._update_only
+
+ @update_only.setter
+ def update_only(self, value):
+ if value and not self.can_update:
+ raise ValueError("this bootloader does not support updates")
+ elif self.can_update:
+ self._update_only = value
+
+ def set_boot_args(self, *args, **kwargs):
+ """ Set up the boot command line.
+
+ Keyword Arguments:
+
+ network - a pyanaconda.network.Network instance (for network
+ storage devices' boot arguments)
+
+ All other arguments are expected to have a dracutSetupString()
+ method.
+ """
+ network = kwargs.pop("network", None)
+
+ #
+ # FIPS
+ #
+ if flags.cmdline.get("fips") == "1":
+ self.boot_args.append("boot=%s" % self.stage2_device.fstabSpec)
+
+ #
+ # dracut
+ #
+
+ # storage
+ from pyanaconda.storage.devices import NetworkStorageDevice
+ dracut_devices = [self.storage.rootDevice]
+ if self.stage2_device != self.storage.rootDevice:
+ dracut_devices.append(self.stage2_device)
+
+ dracut_devices.extend(self.storage.fsset.swapDevic es)
+
+ done = []
+ # When we see a device whose setup string starts with a key in this
+ # dict we pop that pair from the dict. When we're done looking at
+ # devices we are left with the values that belong in the boot args.
+ dracut_storage = {"rd_LUKS_UUID": "rd_NO_LUKS",
+ "rd_LVM_LV": "rd_NO_LVM",
+ "rd_MD_UUID": "rd_NO_MD",
+ "rd_DM_UUID": "rd_NO_DM"}
+ for device in dracut_devices:
+ for dep in self.storage.devices:
+ if device in done:
+ continue
+
+ if device != dep and not device.dependsOn(dep):
+ continue
+
+ setup_string = dep.dracutSetupString().strip()
+ if not setup_string:
+ continue
+
+ self.boot_args.append(setup_string)
+ self.dracut_args.append(setup_string)
+ done.append(dep)
+ dracut_storage.pop(setup_string.split("=")[0], None)
+
+ # network storage
+ # XXX this is nothing to be proud of
+ if isinstance(dep, NetworkStorageDevice):
+ if network is None:
+ log.error("missing network instance for setup of boot "
+ "command line for network storage device %s"
+ % dep.name)
+ raise BootLoaderError("missing network instance when "
+ "setting boot args for network "
+ "storage device")
+
+ setup_string = network.dracutSetupString(dep).strip()
+ self.boot_args.append(setup_string)
+ self.dracut_args.append(setup_string)
+
+ self.boot_args.extend(dracut_storage.values())
+ self.dracut_args.extend(dracut_storage.values())
+
+ # passed-in objects
+ for cfg_obj in list(args) + kwargs.values():
+ setup_string = cfg_obj.dracutSetupString().strip()
+ self.boot_args.append(setup_string)
+ self.dracut_args.append(setup_string)
+
+ #
+ # preservation of some of our boot args
+ # FIXME: this is stupid.
+ #
+ for opt in self.global_preserve_args + self.preserve_args:
+ if opt not in flags.cmdline:
+ continue
+
+ arg = flags.cmdline.get(opt)
+ new_arg = opt
+ if arg:
+ new_arg += "=%s" % arg
+
+ self.boot_args.append(new_arg)
+
+ #
+ # configuration
+ #
+
+ @property
+ def boot_prefix(self):
+ """ Prefix, if any, to paths in /boot. """
+ if self.stage2_device == self.storage.rootDevice:
+ prefix = "/boot"
+ else:
+ prefix = ""
+
+ return prefix
+
+ def _set_console(self):
+ """ Set console options based on boot arguments. """
+ if flags.serial:
+ console = flags.cmdline.get("console", "ttyS0").split(",", 1)
+ self.console = console[0]
+ if len(console) > 1:
+ self.console_options = console[1]
+ elif flags.virtpconsole:
+ self.console = re.sub("^/dev/", "", flags.virtpconsole)
+
+ def write_config_console(self, config):
+ """Write console-related configuration lines."""
+ pass
+
+ def write_config_password(self, config):
+ """Write password-related configuration lines."""
+ pass
+
+ def write_config_header(self, config):
+ """Write global configuration lines."""
+ self.write_config_console(config)
+ self.write_config_password(config)
+
+ def write_config_images(self, config):
+ """Write image configuration entries."""
+ # XXX might this be identical for yaboot and silo?
+ raise NotImplementedError()
+
+ def write_config_post(self, install_root=""):
+ try:
+ os.chmod(install_root + self.config_file, self.config_file_mode)
+ except OSError as e:
+ log.error("failed to set config file permissions: %s" % e)
+
+ def write_config(self, install_root=""):
+ """ Write the bootloader configuration. """
+ if not self.config_file:
+ raise BootLoaderError("no config file defined for this bootloader")
+
+ config_path = os.path.normpath(install_root + self.config_file)
+ if os.access(config_path, os.R_OK):
+ os.rename(config_path, config_path + ".rpmsave")
+
+ config = open(config_path, "w")
+ self.write_config_header(config, install_root=install_root)
+ self.write_config_images(config)
+ config.close()
+ self.write_config_post(install_root=install_root)
+
+ def writeKS(self, f):
+ """ Write bootloader section of kickstart configuration. """
+ if self.stage1_device.isDisk:
+ location = "mbr"
+ elif self.stage1_device:
+ location = "partition"
+ else:
+ location = "none
"
+
+ f.write("bootloader --location=%s" % location)
+
+ if not self.stage1_device:
+ return
+
+ if self.drive_order:
+ f.write(" --driveorder=%s" % ",".join(self.drive_order))
+
+ append = [a for a in self.boot_args if a not in self.dracut_args]
+ if append:
+ f.write(" --append="%s"" % " ".join(append))
+
+ f.write("
")
+
+ def read(self):
+ """ Read an existing bootloader configuration. """
+ raise NotImplementedError()
+
+ #
+ # installation
+ #
+ def write(self, install_root=""):
+ """ Write the bootloader configuration and install the bootloader. """
+ if self.update_only:
+ self.update(install_root=install_root)
+ return
+
+ self.write_config(install_root=install_root)
+ sync()
+ sync()
+ sync()
+ sync()
+ self.install(install_root=install_root)
+
+ def update(self, install_root=""):
+ """ Update an existing bootloader configuration. """
+ pass
+
+
+class GRUB(BootLoader):
+ name = "GRUB"
+ _config_dir = "grub"
+ _config_file = "grub.conf"
+ _device_map_file = "device.map"
+ can_dual_boot = True
+ can_update = True
+
+ # list of strings representing options for bootloader target device types
+ target_device_types = ["disk", "partition", "mdarray"]
+ target_device_raid_levels = [mdraid.RAID1]
+ target_device_format_types = []
+ target_device_format_mountpoints = ["/boot", "/"]
+ target_device_disklabel_types = ["msdos", "gpt"] # gpt?
+
+ # list of strings representing options for boot device types
+ boot_device_types = ["partition", "mdarray"]
+ boot_device_raid_levels = [mdraid.RAID1]
+ # XXX hpfs, if reported by blkid/udev, will end up with a type of None
+ boot_device_format_types = ["vfat", "ntfs", "hpfs"]
+
+ packages = ["grub"]
+
+ def __init__(self, storage):
+ super(GRUB, self).__init__(storage)
+ self.encrypt_password = False
+
+ #
+ # grub-related conveniences
+ #
+
+ def grub_device_name(self, device):
+ """ Return a grub-friendly representation of device. """
+ drive = getattr(device, "disk", device)
+ name = "(hd%d" % self.drives.index(drive)
+ if hasattr(device, "disk"):
+ name += ",%d" % (device.partedPartition.number - 1,)
+ name += ")"
+ return name

+ @property
+ def grub_config_dir(self):
+ """ Config dir, adjusted for grub's view of the world. """
+ return self.boot_prefix + self._config_dir
+
+ #
+ # configuration
+ #
+
+ @property
+ def config_dir(self):
+ """ Full path to configuration directory. """
+ return "/boot/" + self._config_dir
+
+ @property
+ def config_file(self):
+ """ Full path to configuration file. """
+ return "%s/%s" % (self.config_dir, self._config_file)
+
+ @property
+ def device_map_file(self):
+ """ Full path to device.map file. """
+ return "%s/%s" % (self.config_dir, self._device_map_file)
+
+ @property
+ def grub_conf_device_line(self):
+ return ""
+
+ def write_config_console(self, config):
+ """ Write console-related configuration. """
+ if not self.console:
+ return
+
+ if self.console.startswith("ttyS"):
+ unit = self.console[-1]
+ speed = "9600"
+ for opt in self.console_options.split(","):
+ if opt.isdigit:
+ speed = opt
+ break
+
+ config.write("serial --unit=%s --speed=%s
" % (unit, speed))
+ config.write("terminal --timeout=%s serial console
"
+ % self.timeout)
+
+ console_arg = "console=%s" % self.console
+ if self.console_options:
+ console_arg += ",%s" % self.console_options
+ self.boot_args.append(console_arg)
+
+ @property
+ def encrypted_password(self):
+ import string
+ import crypt
+ import random
+ salt = "$6$"
+ salt_len = 16
+ salt_chars = string.letters + string.digits + './'
+
+ rand_gen = random.SystemRandom()
+ salt += "".join([rand_gen.choice(salt_chars) for i in range(salt_len)])
+ password = crypt.crypt(self.password, salt)
+ return password
+
+ def write_config_password(self, config):
+ """ Write password-related configuration. """
+ if self.password:
+ if self.encrypt_password:
+ password = "--encrypted " + self.encrypted_password
+ else:
+ password = self.password
+
+ config.write("password %s
" % password)
+
+ def write_config_header(self, config, install_root=""):
+ """Write global configuration information. """
+ if self.boot_prefix:
+ have_boot = "do not "
+ else:
+ have_boot = ""
+
+ s = """# grub.conf generated by anaconda
"
+# Note that you do not have to rerun grub after making changes to this file.
+# NOTICE: You %(do)shave a /boot partition. This means that all kernel and
+# initrd paths are relative to %(boot)s, eg.
+# root %(grub_target)s
+# kernel %(prefix)s/vmlinuz-version ro root=%(root_device)s
+# initrd %(prefix)s/initrd-[generic-]version.img
+""" % {"do": have_boot, "boot": self.stage2_device.format.mountpoint,
+ "root_device": self.stage2_device.path,
+ "grub_target": self.grub_device_name(self.stage1_device),
+ "prefix": self.boot_prefix}
+
+ config.write(s)
+ config.write("boot=%s
" % self.stage1_device.path)
+ config.write(self.grub_conf_device_line)
+
+ # find the index of the default image
+ try:
+ default_index = self.images.index(self.default)
+ except ValueError:
+ e = "Failed to find default image (%s)" % self.default.label
+ raise BootLoaderError(e)
+
+ config.write("default=%d
" % default_index)
+ config.write("timeout=%d
" % self.timeout)
+
+ self.write_config_console(config)
+
+ if not flags.serial:
+ splash = "splash.xpm.gz"
+ splash_path = os.path.normpath("%s%s/%s" % (install_root,
+ self.config_dir,
+ splash))
+ if os.access(splash_path, os.R_OK):
+ grub_root_grub_name = self.grub_device_name(self.stage2_device)
+ config.write("splashimage=%s/%s/%s
" % (grub_root_grub_name,
+ self.grub_config_dir,
+ splash))
+ config.write("hiddenmenu
")
+
+ self.write_config_password(config)
+
+ def write_config_images(self, config):
+ """ Write image entries into configuration file. """
+ for image in self.images:
+ if isinstance(image, LinuxBootLoaderImage):
+ args = ArgumentList()
+ grub_root = self.grub_device_name(self.stage2_device)
+ args.extend(["ro", "root=%s" % image.device.fstabSpec])
+ args.extend(self.boot_args)
+ stanza = ("title %(label)s (%(version)s)
"
+ " root %(grub_root)s
"
+ " kernel %(prefix)s/%(kernel)s %(args)s
"
+ " initrd %(prefix)s/%(initrd)s
"
+ % {"label": image.label, "version": image.version,
+ "grub_root": grub_root,
+ "kernel": image.kernel, "initrd": image.initrd,
+ "args": args,
+ "prefix": self.boot_prefix})
+ else:
+ stanza = ("title %(label)s
"
+ " rootnoverify %(grub_root)s
"
+ " chainloader +1
"
+ % {"label": image.label,
+ "grub_root": self.grub_device_name(image.device)})
+
+ config.write(stanza)
+
+ def write_device_map(self, install_root=""):
+ """ Write out a device map containing all supported devices. """
+ map_path = os.path.normpath(install_root + self.device_map_file)
+ if os.access(map_path, os.R_OK):
+ os.rename(map_path, map_path + ".rpmsave")
+
+ dev_map = open(map_path, "w")
+ dev_map.write("# this device map was generated by anaconda
")
+ for drive in self.drives:
+ dev_map.write("%s %s
" % (self.grub_device_name(drive),
+ drive.path))
+ dev_map.close()
+
+ def write_config_post(self, install_root=""):
+ """ Perform additional configuration after writing config file(s). """
+ super(GRUB, self).write_config_post(install_root=install_root)
+
+ # make symlink for menu.lst (grub's default config file name)
+ menu_lst = "%s%s/menu.lst" % (install_root, self.config_dir)
+ if os.access(menu_lst, os.R_OK):
+ try:
+ os.rename(menu_lst, menu_lst + '.rpmsave')
+ except OSError as e:
+ log.error("failed to back up %s: %s" % (menu_lst, e))
+
+ try:
+ os.symlink(self._config_file, menu_lst)
+ except OSError as e:
+ log.error("failed to create grub menu.lst symlink: %s" % e)
+
+ # make symlink to grub.conf in /etc since that's where configs belong
+ etc_grub = "%s/etc/%s" % (install_root, self._config_file)
+ if os.access(etc_grub, os.R_OK):
+ try:
+ os.rename(etc_grub, etc_grub + '.rpmsave')
+ except OSError as e:
+ log.error("failed to back up %s: %s" % (etc_grub, e))
+
+ try:
+ os.symlink("..%s" % self.config_file, etc_grub)
+ except OSError as e:
+ log.error("failed to create /etc/grub.conf symlink: %s" % e)
+
+ def write_config(self, install_root=""):
+ """ Write bootloader configuration to disk. """
+ # write device.map
+ self.write_device_map(install_root=install_root)
+
+ # this writes the actual configuration file
+ super(GRUB, self).write_config(install_root=install_root)
+
+ #
+ # installation
+ #
+
+ def install(self, install_root=""):
+ rc = iutil.execWithRedirect("grub-install", ["--just-copy"],
+ stdout="/dev/tty5", stderr="/dev/tty5",
+ root=install_root)
+ if rc:
+ raise BootLoaderError("bootloader install failed")
+
+ boot = self.stage2_device
+ targets = []
+ if boot.type != "mdarray":
+ targets.append((self.stage1_device, self.stage2_device))
+ else:
+ if [d for d in self.stage2_device.parents if d.type != "partition"]:
+ raise BootLoaderError("boot array member devices must be "
+ "partitions")
+
+ # make sure we have stage1 and stage2 installed with redundancy
+ # so that boot can succeed even in the event of failure or removal
+ # of some of the disks containing the member partitions of the
+ # /boot array
+ for stage2dev in self.stage2_device.parents:
+ if self.stage1_device.isDisk:
+ # install to mbr
+ if self.stage2_device.dependsOn(self.stage1_device):
+ # if target disk contains any of /boot array's member
+ # partitions, set up stage1 on each member's disk
+ # and stage2 on each member partition
+ stage1dev = stage2dev.disk
+ else:
+ # if target disk does not contain any of /boot array's
+ # member partitions, install stage1 to the target disk
+ # and stage2 to each of the member partitions
+ stage1dev = self.stage1_device
+ else:
+ # target is /boot device and /boot is raid, so install
+ # grub to each of /boot member partitions
+ stage1dev = stage2dev
+
+ targets.append((stage1dev, stage2dev))
+
+ for (stage1dev, stage2dev) in targets:
+ cmd = ("root %(stage2dev)s
"
+ "install --stage2=%(config_dir)s/stage2"
+ " /%(grub_config_dir)s/stage1 d %(stage1dev)s"
+ " /%(grub_config_dir)s/stage2 p"
+ " %(stage2dev)s/%(grub_config_dir)s/%(config_basename)s
"
+ % {"grub_config_dir": self.grub_config_dir,
+ "config_dir": self.config_dir,
+ "config_basename": self._config_file,
+ "stage1dev": self.grub_device_name(self.stage1_device),
+ "stage2dev": self.grub_device_name(self.stage2_device)})
+ (pread, pwrite) = os.pipe()
+ os.write(pwrite, cmd)
+ os.close(pwrite)
+ args = ["--batch", "--no-floppy",
+ "--device-map=%s" % self.device_map_file]
+ rc = iutil.execWithRedirect("grub", args,
+ stdout="/dev/tty5", stderr="/dev/tty5",
+ stdin=pread, root=install_root)
+ os.close(pread)
+ if rc:
+ raise BootLoaderError("bootloader install failed")
+
+ def update(self, install_root=""):
+ self.install(install_root=install_root)
+
+ #
+ # miscellaneous
+ #
+
+ @property
+ def has_windows(self):
+ return len(self.bootable_chain_devices) != 0
+
+
+class EFIGRUB(GRUB):
+ name = "GRUB (EFI)"
+ can_dual_boot = False
+ _config_dir = "efi/EFI/redhat"
+
+ # bootloader target device types
+ target_device_types = ["partition", "mdarray"]
+ target_device_raid_levels = [mdraid.RAID1]
+ target_device_format_types = ["efi"]
+ target_device_mountpoints = ["/boot/efi"]
+ target_device_disklabel_types = ["gpt"]
+ target_device_min_size = 50
+ target_device_max_size = 256
+
+ target_descriptions = {"partition": N_("EFI System Partition"),
+ "mdarray": N_("RAID Device")}
+
+ # boot device types
+ boot_device_format_types = []
+
+ def efibootmgr(self, *args, **kwargs):
+ if kwargs.pop("capture", False):
+ exec_func = iutil.execWithCapture
+ else:
+ exec_func = iutil.execWithRedirect
+
+ return exec_func("efibootmgr", list(args), **kwargs)
+
+ #
+ # configuration
+ #
+
+ @property
+ def efi_product_path(self):
+ """ The EFI product path.
+
+ eg: HD(1,800,64000,faacb4ef-e361-455e-bd97-ca33632550c3)
+ """
+ path = ""
+ buf = self.efibootmgr("-v", stderr="/dev/tty5", capture=True)
+ matches = re.search(productName + r's+HD(.+?)', buf)
+ if matches:
+ path = re.sub(productName + r's+',
+ ',
+ buf[matches[0].start():matches[0].end()])
+
+ return path
+
+ @property
+ def grub_conf_device_line(self):
+ return "device %s %s
" % (self.grub_device_name(self.stage2_device),
+ self.efi_product_path)
+
+ #
+ # installation
+ #
+
+ def remove_efi_boot_target(self, install_root=""):
+ buf = self.efibootmgr(capture=True)
+ for line in buf.splitlines():
+ try:
+ (slot, _product) = line.split(None, 1)
+ except ValueError:
+ continue
+
+ if _product == productName:
+ slot_id = slot[4:8]
+ if not slot_id.isdigit():
+ log.warning("failed to parse efi boot slot (%s)" % slot)
+ continue
+
+ rc = self.efibootmgr("-b", slot_id, "-B",
+ root=install_root,
+ stdout="/dev/tty5", stderr="/dev/tty5")
+ if rc:
+ raise BootLoaderError("failed to remove old efi boot entry")
+
+ def add_efi_boot_target(self, install_root=""):
+ boot_efi = self.storage.mountpoints["/boot/efi"]
+ if boot_efi.type == "partition":
+ boot_disk = boot_efi.disk
+ boot_part_num = boot_efi.partedPartition.number
+ elif boot_efi.type == "mdarray":
+ # FIXME: I'm just guessing here. This probably needs the full
+ # treatment, ie: multiple targets for each member.
+ boot_disk = boot_efi.parents[0].disk
+ boot_part_num = boot_efi.parents[0].partedPartition.number
+
+ rc = self.efibootmgr("-c", "-w", "-L", productName,
+ "-d", boot_disk.path, "-p", boot_part_num,
+ "-l", "EFI
edhatgrub.efi",
+ root=install_root,
+ stdout="/dev/tty5", stderr="/dev/tty5")
+ if rc:
+ raise BootLoaderError("failed to set new efi boot target")
+
+ def install(self, install_root=""):
+ self.remove_efi_boot_target(install_root=install_r oot)
+ self.add_efi_boot_target(install_root=install_root )
+
+ def update(self, install_root=""):
+ self.write(install_root=install_root)
+
+
+class YabootSILOBase(BootLoader):
+ def write_config_password(self, config):
+ if self.password:
+ config.write("password=%s
" % self.password)
+ config.write("restricted
")
+
+ def write_config_images(self, config):
+ for image in self.images:
+ if not isinstance(image, LinuxBootLoaderImage):
+ # mac os images are handled specially in the header on mac
+ continue
+
+ args = ArgumentList()
+ if image.initrd:
+ initrd_line = " initrd=%s/%s
" % (self.boot_prefix,
+ image.initrd)
+ else:
+ initrd_line = ""
+
+ root_device_spec = self.storage.rootDevice.fstabSpec
+ if root_device_spec.startswith("/"):
+ root_line = " root=%s
" % root_device_spec
+ else:
+ args.append("root=%s" % root_device_spec)
+ root_line = ""
+
+ args.extend(self.boot_args)
+
+ stanza = ("image=%(boot_prefix)s%(kernel)s
"
+ " label=%(label)s
"
+ " read-only
"
+ "%(initrd_line)s"
+ "%(root_line)s"
+ " append="%(args)s"

"
+ % {"kernel": image.kernel, "initrd_line": initrd_line,
+ "label": self.image_label(image),
+ "root_line": root_line, "args": args,
+ "boot_prefix": self.boot_prefix})
+ config.write(stanza)
+
+
+class Yaboot(YabootSILOBase):
+ name = "Yaboot"
+ _config_file = "yaboot.conf"
+ prog = "ybin"
+
+ # list of strings representing options for bootloader target device types
+ target_device_types = ["partition", "mdarray"]
+ target_device_raid_levels = [mdraid.RAID1]
+ target_device_format_types = ["appleboot", "prepboot"]
+
+ # list of strings representing options for boot device types
+ boot_device_types = ["partition", "mdarray"]
+ boot_device_raid_levels = [mdraid.RAID1]
+ boot_device_format_types = ["hfs", "hfs+"]
+
+ packages = ["yaboot"]
+
+ image_label_attr = "short_label"
+
+ def __init__(self, storage):
+ BootLoader.__init__(self, storage)
+
+ #
+ # configuration
+ #
+
+ @property
+ def config_dir(self):
+ conf_dir = "/etc"
+ if self.stage2_device.format.mountpoint == "/boot":
+ conf_dir = "/boot/etc"
+ return conf_dir
+
+ @property
+ def config_file(self):
+ return "%s/%s" % (self.config_dir, self._config_file)
+
+ def write_config_header(self, config):
+ if self.stage2_device.type == "mdarray":
+ boot_part_num = self.stage2_device.parents[0].partedPartition.number
+ else:
+ boot_part_num = self.stage2_device.partedPartition.number
+
+ # yaboot.conf timeout is in tenths of a second. Brilliant.
+ header = ("# yaboot.conf generated by anaconda

"
+ "boot=%(stage1dev)s
"
+ "init-message="Welcome to %(product)s!
Hit <TAB> for "
+ "boot options"

"
+ "partition=%(part_num)d
"
+ "timeout=%(timeout)d
"
+ "install=/usr/lib/yaboot/yaboot
"
+ "delay=5
"
+ "enablecdboot
"
+ "enableofboot
"
+ "enablenetboot
"
+ % {"stage1dev": self.stage1_device.path,
+ "product": productName, "part_num": boot_part_num,
+ "timeout": self.timeout * 10})
+ config.write(header)
+ self.write_config_variant_header(config)
+ self.write_config_password(config)
+ config.write("
")
+
+ def write_config_variant_header(self, config):
+ config.write("nonvram
")
+ config.write("mntpoint=/boot/yaboot
")
+ config.write("usemount
")
+
+ def write_config_post(self, install_root=""):
+ super(Yaboot, self).write_config_post(install_root=install_root)
+
+ # make symlink in /etc to yaboot.conf if config is in /boot/etc
+ etc_yaboot_conf = install_root + "/etc/yaboot.conf"
+ if not os.access(etc_yaboot_conf, os.R_OK):
+ try:
+ os.symlink("../boot/etc/yaboot.conf", etc_yaboot_conf)
+ except OSError as e:
+ log.error("failed to create /etc/yaboot.conf symlink: %s" % e)
+
+ def write_config(self, install_root=""):
+ if not os.path.isdir(install_root + self.config_dir):
+ os.mkdir(install_root + self.config_dir)
+
+ # this writes the config
+ super(Yaboot, self).write_config(install_root=install_root)
+
+ #
+ # installation
+ #
+
+ def install(self, install_root=""):
+ args = ["-f", "-C", self.config_file]
+ rc = iutil.execWithRedirect(self.prog, args,
+ stdout="/dev/tty5", stderr="/dev/tty5",
+ root=install_root)
+ if rc:
+ raise BootLoaderError("bootloader installation failed")
+
+
+class IPSeriesYaboot(Yaboot):
+ # XXX is this just for pSeries?
+ name = "Yaboot (IPSeries)"
+ prog = "mkofboot"
+
+ target_device_format_types = ["prepboot"]
+ target_device_min_size = 4
+ target_device_max_size = 10
+
+ target_descriptions = {"partition": N_("PReP Boot Partition"),
+ "mdarray": N_("RAID Device")}
+
+ #
+ # configuration
+ #
+
+ def write_config_variant_header(self, config):
+ config.write("nonvram
") # only on pSeries?
+ config.write("fstype=raw
")
+
+
+class MacYaboot(Yaboot):
+ name = "Yaboot (Mac)"
+ prog = "mkofboot"
+
+ can_dual_boot = True
+ target_device_format_types = ["appleboot"]
+ target_device_disklabel_types = ["mac"]
+ target_device_min_size = 800.00 / 1024.00
+ target_device_max_size = 1
+
+ target_descriptions = {"partition": N_("Apple Bootstrap Partition"),
+ "mdarray": N_("RAID Device")}
+
+ #
+ # configuration
+ #
+
+ def write_config_variant_header(self, config):
+ try:
+ mac_os = [i for i in self.chain_images if i.label][0]
+ except IndexError:
+ pass
+ else:
+ config.write("macosx=%s
" % mac_os.device.path)
+
+ config.write("magicboot=/usr/lib/yaboot/ofboot
")
+
+
+class ZIPL(BootLoader):
+ name = "ZIPL"
+ config_file = "/etc/zipl.conf"
+
+ # list of strings representing options for bootloader target device types
+ target_device_types = ["disk", "partition"]
+ target_device_disklabel_types = ["msdos", "dasd"]
+
+ # list of strings representing options for boot device types
+ boot_device_types = ["partition", "mdarray", "lvmlv"]
+ boot_device_raid_levels = [mdraid.RAID1]
+
+ packages = ["s390utils"] # is this bootloader or platform?
+ image_label_attr = "short_label"
+ preserve_args = ["cio_ignore"]
+
+ #
+ # configuration
+ #
+
+ @property
+ def boot_dir(self):
+ return "/boot"
+
+ def write_config_images(self, config):
+ for image in self.images:
+ args = ArgumentList()
+ if image.initrd:
+ initrd_line = " ramdisk=%s/%s
" % (self.boot_dir,
+ image.initrd)
+ else:
+ initrd_line = ""
+ args.append("root=%s/%s" % (self.boot_dir, image.kernel))
+ args.extend(self.boot_args)
+ stanza = ("[%(label)s]
"
+ " image=%(boot_dir)s/%(kernel)s
"
+ "%(initrd_line)s"
+ " parameters="%(args)s"
"
+ % {"label": self.image_label(image),
+ "kernel": image.kernel, "initrd_line": initrd_line,
+ "args": args,
+ "boot_dir": self.boot_dir})
+ config.write(stanza)
+
+ def write_config_header(self, config):
+ header = ("[defaultboot]
"
+ "timeout=%{timeout}d
"
+ "default=%{default}
"
+ "target=/boot
"
+ % {"timeout": self.timeout,
+ "default": self.image_label(self.default)})
+ config.write(header)
+
+ #
+ # installation
+ #
+
+ def install(self, install_root=""):
+ buf = iutil.execWithCapture("zipl", [],
+ stderr="/dev/tty5",
+ root=install_root)
+ for line in buf.splitlines():
+ if line.startswith("Preparing boot device: "):
+ # Output here may look like:
+ # Preparing boot device: dasdb (0200).
+ # Preparing boot device: dasdl.
+ # We want to extract the device name and pass that.
+ name = re.sub(".+?: ", "", line)
+ name = re.sub("(s(.+))?.$", "", name)
+ device = self.storage.devicetree.getDeviceByName(name)
+ if not device:
+ raise BootLoaderError("could not find IPL device")
+
+ self.stage1_device = device
+
+
+class SILO(YabootSILOBase):
+ name = "SILO"
+ _config_file = "silo.conf"
+ message_file = "/etc/silo.message"
+
+ # list of strings representing options for bootloader target device types
+ target_device_types = ["partition"]
+ target_device_disklabel_types = ["sun"]
+
+ # list of strings representing options for boot device types
+ boot_device_types = ["partition"]
+
+ packages = ["silo"]
+
+ image_label_attr = "short_label"
+
+ #
+ # configuration
+ #
+
+ @property
+ def config_dir(self):
+ if self.stage2_device.format.mountpoint == "/boot":
+ return "/boot"
+ else:
+ return "/etc"
+
+ @property
+ def config_file(self):
+ return "%s/%s" % (self.config_dir, self._config_file)
+
+ def write_message_file(self, install_root=""):
+ message_file = os.path.normpath(install_root + self.message_file)
+ f = open(message_file, "w")
+ f.write("Welcome to %s!
Hit <TAB> for boot options

" % productName)
+ f.close()
+ os.chmod(message_file, 0600)
+
+ def write_config_header(self, config):
+ header = ("# silo.conf generated by anaconda

"
+ "#boot=%(stage1dev)s
"
+ "message=%(message)s
"
+ "timeout=%(timeout)d
"
+ "partition=%(boot_part_num)d
"
+ "default=%(default)s
"
+ % {"stage1dev": self.stage1_device.path,
+ "message": self.message_file, "timeout": self.timeout,
+ "boot_part_num": self.stage1_device.partedPartition.number,
+ "default": self.image_label(self.default)})
+ config.write(header)
+ self.write_config_password(config)
+
+ def write_config_post(self, install_root=""):
+ etc_silo = os.path.normpath(install_root + "/etc/" + self._config_file)
+ if not os.access(etc_silo, os.R_OK):
+ try:
+ os.symlink("../boot/%s" % self._config_file, etc_silo)
+ except OSError as e:
+ log.warning("failed to create /etc/silo.conf symlink: %s" % e)
+
+ def write_config(self, install_root=""):
+ self.write_message_file(install_root=install_root)
+ super(SILO, self).write_config(install_root=install_root)
+
+ #
+ # installation
+ #
+
+ def install(self, install_root=""):
+ backup = "%s/backup.b" % self.config_dir
+ args = ["-f", "-C", self.config_file, "-S", backup]
+ variant = iutil.getSparcMachine()
+ if variant in ("sun4u", "sun4v"):
+ args.append("-u")
+ else:
+ args.append("-U")
+
+ rc = iutil.execWithRedirect("silo", args,
+ stdout="/dev/tty5", stderr="/dev/tty5",
+ root=install_root)
+
+ if rc:
+ raise BootLoaderError("bootloader install failed")
+
+
+""" anaconda-specific functions """
+
+# this doesn't need to exist anymore, but the messageWindow probably needs to
+# go somewhere
+def bootloaderSetupChoi
 
Old 04-01-2011, 09:22 PM
Peter Jones
 
Default Replace booty with a new bootloader module.

On 04/01/2011 03:57 PM, David Lehman wrote:
> +def get_boot_block(device, seek_blocks=0):
> + block = " " * 512
> + status = device.status
> + if not status:
> + try:
> + device.setup()
> + except StorageError:
> + return block
> + fd = os.open(device.path, os.O_RDONLY)
> + if seek_blocks:
> + os.lseek(fd, seek_blocks * 512, 0)
> + block = os.read(fd, 512)
> + os.close(fd)
> + if not status:
> + try:
> + device.teardown(recursive=True)
> + except StorageError:
> + pass
> +
> + return block

This probably ought to query and use the block size for the device rather than
hard-code 512, though I'm not 100% sure it'll actually make any difference.

...

> +class BootLoaderImage(object):
> + """ Base class for bootloader images. Suitable for non-linux OS images. """
> + def __init__(self, device=None, label=None):
> + self.label = label
> + self.device = device

I'm really not sure I understand the concept of what an "image" is here.


> +class LinuxBootLoaderImage(BootLoaderImage):
> + def __init__(self, device=None, label=None, short=None, version=None):
> + super(LinuxBootLoaderImage, self).__init__(device=device, label=label)
> + self.label = label # label string
> + self.short_label = short # shorter label string
> + self.device = device # StorageDevice instance
> + self.version = version # kernel version string
> + self._kernel = None # filename string
> + self._initrd = None # filename string

Is it a config stanza then?

> +class BootLoader(object):
> + """TODO:
> + - iSeries bootloader?
> + - same as pSeries, except optional, I think
> + - upgrade of non-grub bootloaders
> + - detection of existing bootloaders
> + - resolve overlap between Platform checkFoo methods and
> + _is_valid_target_device and _is_valid_boot_device
> + - split certain parts of _is_valid_target_device and
> + _is_valid_boot_device into specific bootloader classes
> + """
> + name = "Generic Bootloader"
> + packages = []
> + config_file = None
> + config_file_mode = 0600
> + can_dual_boot = False
> + can_update = False
> + image_label_attr = "label"
> +
> + # requirements for bootloader target devices
> + target_device_types = ["partition"]
> + target_device_raid_levels = []
> + target_device_format_types = []
> + target_device_disklabel_types = ["msdos"]

So the base class is really sort of abstract i386, then, and just doesn't say
that? Why aren't the x86-ish bits in either an x86-only base class or in GRUB?

> + def _is_valid_boot_device(self, device, linux=False):
...
> + # FIXME: the windows boot block part belongs in GRUB
> + if hasattr(device, "partedPartition") and
> + (not device.bootable or not device.partedPartition.active) and
> + not has_windows_boot_block(device):
> + return False

Doesn't this mean that this workflow won't work:

1) Use existing device label with windows installed
2) remove all partitions
3) create new partitions...

Since at that point we'll still have a NTLDR installed.

> + #
> + # FIPS
> + #
> + if flags.cmdline.get("fips") == "1":
> + self.boot_args.append("boot=%s" % self.stage2_device.fstabSpec)

Wait, what? boot= is for FIPS?

I realize this same code is in booty almost exactly like this, but a comment
to mention what the hell it's doing might be helpful.
...
> + #
> + # installation
> + #
> + def write(self, install_root=""):
> + """ Write the bootloader configuration and install the bootloader. """
> + if self.update_only:
> + self.update(install_root=install_root)
> + return
> +
> + self.write_config(install_root=install_root)
> + sync()
> + sync()
> + sync()
> + sync()

Three of these are probably unnecessary paranoia held over from booty. But
We also do need fs checking for e.g. xfs_freeze :/

> +class GRUB(BootLoader):
...
> + boot_device_format_types = ["vfat", "ntfs", "hpfs"]

I was really confused by this for a minute until I figured out that this was
specifically *not* linux. With that in mind, it seems like I'd assume that
unprefixed was the common case (e.g. linux) and the non-linux version should
be the labelled ones. But that's really pretty nitpicky.

...
> + @property
> + def encrypted_password(self):
> + import string
> + import crypt
> + import random
> + salt = "$6$"
> + salt_len = 16
> + salt_chars = string.letters + string.digits + './'
> +
> + rand_gen = random.SystemRandom()
> + salt += "".join([rand_gen.choice(salt_chars) for i in range(salt_len)])
> + password = crypt.crypt(self.password, salt)
> + return password

It almost seems as if password creation should be entirely separate.

> + def write_device_map(self, install_root=""):
> + """ Write out a device map containing all supported devices. """
> + map_path = os.path.normpath(install_root + self.device_map_file)
> + if os.access(map_path, os.R_OK):
> + os.rename(map_path, map_path + ".rpmsave")

I'm not entirely convinced there's any point in keeping a .rpmsave here,
but I guess that's something we can do.

> + # make symlink to grub.conf in /etc since that's where configs belong
> + etc_grub = "%s/etc/%s" % (install_root, self._config_file)
> + if os.access(etc_grub, os.R_OK):
> + try:
> + os.rename(etc_grub, etc_grub + '.rpmsave')
> + except OSError as e:
> + log.error("failed to back up %s: %s" % (etc_grub, e))

Keeping a .rpmsave of the symlink seems *really* odd. It's always a symlink.
Aren't we just going to wind up with 2 symlinks to the same thing? At least
replace the old symlink with a symlink to the new config file.
...

> anaconda.intf.messageWindow(_("Warning"),
> - _("No kernel packages were installed on the "
> - "system. Bootloader configuration "
> - "will not be changed."))
> + _("No kernel packages were installed on the system "
> + "Bootloader configuration will not be changed."))

Minor nit - you've changed the grammar and translation of this message,
probably not on purpose since the new way makes less sense.

--
Peter

_______________________________________________
Anaconda-devel-list mailing list
Anaconda-devel-list@redhat.com
https://www.redhat.com/mailman/listinfo/anaconda-devel-list
 

Thread Tools




All times are GMT. The time now is 07:07 PM.

VBulletin, Copyright ©2000 - 2014, Jelsoft Enterprises Ltd.
Content Relevant URLs by vBSEO ©2007, Crawlability, Inc.
Copyright 2007 - 2008, www.linux-archive.org