2N/A#! /usr/bin/python2.6
2N/A#
2N/A# CDDL HEADER START
2N/A#
2N/A# The contents of this file are subject to the terms of the
2N/A# Common Development and Distribution License (the "License").
2N/A# You may not use this file except in compliance with the License.
2N/A#
2N/A# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
2N/A# or http://www.opensolaris.org/os/licensing.
2N/A# See the License for the specific language governing permissions
2N/A# and limitations under the License.
2N/A#
2N/A# When distributing Covered Code, include this CDDL HEADER in each
2N/A# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
2N/A# If applicable, add the following below this CDDL HEADER, with the
2N/A# fields enclosed by brackets "[]" replaced with your own identifying
2N/A# information: Portions Copyright [yyyy] [name of copyright owner]
2N/A#
2N/A# CDDL HEADER END
2N/A#
2N/A# Copyright (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved.
2N/A#
2N/A
2N/A"""
2N/AGRUB2 boot loader backend for pybootmgmt
2N/A"""
2N/A
2N/A# pylint: disable=C0302
2N/A
2N/Aimport ConfigParser
2N/Aimport errno
2N/Aimport fcntl
2N/Aimport filecmp
2N/Aimport grp
2N/Aimport libbe
2N/Aimport libbe_py
2N/Aimport os
2N/Aimport pkg.pkgsubprocess as subprocess
2N/Aimport pwd
2N/Aimport shutil
2N/Aimport stat
2N/Aimport syslog
2N/Aimport sys
2N/Aimport tempfile
2N/Aimport time
2N/A
2N/Afrom bootmgmt import (BootmgmtError, BootmgmtArgumentError,
2N/A BootmgmtConfigReadError, BootmgmtConfigWriteError,
2N/A BootmgmtNotSupportedError,
2N/A BootmgmtUnsupportedOperationError,
2N/A BootmgmtUnsupportedPropertyError,
2N/A BootmgmtUnsupportedPlatformError,
2N/A BootmgmtInterfaceCodingError)
2N/Afrom bootmgmt.pysol import (libzfs_init, libzfs_fini, zpool_open, zpool_close,
2N/A zpool_get_prop, ZPOOL_PROP_GUID, is_gpt_disk,
2N/A mnttab_open, getmntany, mnttab_close)
2N/Afrom bootmgmt.bootloader import BootLoader, BootLoaderInstallError
2N/Afrom bootmgmt.bootconfig import (BootConfig, DiskBootConfig, ODDBootConfig,
2N/A NetBootConfig, SolarisDiskBootInstance)
2N/Afrom bootmgmt.bootutil import (get_current_arch_string, LoggerMixin,
2N/A bootfs_split, parse_bool,
2N/A find_efi_system_partition,
2N/A get_zpool_default_bootfs,
2N/A is_zfs_encrypted_dataset,
2N/A get_bootfs_list_from_libbe)
2N/Afrom bootmgmt.bootinfo import SystemFirmware
2N/A
2N/AEFISYS_PARTID = 0xEF
2N/AEFISYS_GUID = 'C12A7328-F81F-11D2-BA4B-00A0C93EC93B'
2N/A
2N/Adef timestamp():
2N/A """Returns a timestamp string with all significant digits
2N/A """
2N/A return '%f' % time.time()
2N/A
2N/A#############################################################################
2N/A# menu.conf interface classes
2N/A#############################################################################
2N/A# pylint: disable=R0903
2N/Aclass MenuConfigEntry(LoggerMixin):
2N/A """MenuConfigEntry represents one menu entry (section) from a
2N/A MenuConfigParser. Each entry is linked with the MenuConfigParser that
2N/A created it so that when it is modified, it can inform the parent
2N/A that state has been modified.
2N/A """
2N/A
2N/A def __init__(self, mcp, section, options=None):
2N/A """Initialize the MenuConfigEntry
2N/A """
2N/A super(MenuConfigEntry, self).__init__()
2N/A if not mcp:
2N/A raise BootmgmtArgumentError('Bad arguments to %s.__init__' %
2N/A self.__class__.__name__)
2N/A if not section or not type(section) is tuple or len(section) != 2:
2N/A raise BootmgmtArgumentError('Bad section argument to %s.__init__' %
2N/A self.__class__.__name__)
2N/A
2N/A self._mcp = mcp
2N/A # section is the section name tuple:
2N/A # (<class type>, <0-based index>)
2N/A self.section = section
2N/A # options is a dictionary of all options in the section
2N/A if options:
2N/A self._options = self._stringify_options(options)
2N/A else:
2N/A self._options = {}
2N/A if 'modified' not in self._options:
2N/A self._options['modified'] = timestamp()
2N/A
2N/A def update_options(self, newdict):
2N/A """Update the options dict in this instance with the newdict passed in.
2N/A Detects if there are changes, and if there are, marks the parent
2N/A MenuConfigParser as dirty.
2N/A """
2N/A dirty = False
2N/A
2N/A del_keylist = []
2N/A for key in self._options.keys():
2N/A if not newdict.has_key(key):
2N/A del_keylist.append(key)
2N/A
2N/A if del_keylist:
2N/A for key in del_keylist:
2N/A del self._options[key]
2N/A dirty = True
2N/A
2N/A for key, value in newdict.items():
2N/A if (not dirty and
2N/A (key not in self._options or self._options[key] != value)):
2N/A dirty = True
2N/A self._options[key] = value
2N/A
2N/A if dirty:
2N/A self._mcp.dirty = True
2N/A
2N/A def _stringify_options(self, options):
2N/A """Returns a dict with the options passed in, stringified.
2N/A """
2N/A options = dict(options) # Do not modify the original dict
2N/A for key, value in options.items():
2N/A if not isinstance(value, basestring):
2N/A self._debug('Stringifying %s', key)
2N/A options[key] = str(value)
2N/A return options
2N/A
2N/A @property
2N/A def options(self):
2N/A """Return a copy to the options dict so no one can modify it.
2N/A Ideally, it would be an immutable dict.
2N/A """
2N/A return dict(self._options)
2N/A
2N/A @property
2N/A def last_modified(self):
2N/A """Returns a floating point timestamp of the last modified time, or 0
2N/A if no timestamp exists.
2N/A """
2N/A ts_prop = self._options.get('modified')
2N/A if ts_prop:
2N/A try:
2N/A return float(ts_prop)
2N/A except ValueError:
2N/A self._debug('Error while converting %s to float', ts_prop)
2N/A return 0
2N/A
2N/A @property
2N/A def section_string(self):
2N/A """Return a string that represents the section name
2N/A """
2N/A if len(self.section) == 2:
2N/A return (('%s' + MenuConfigParser.SECTION_SEPARATOR + '%d') %
2N/A self.section)
2N/A else:
2N/A return None
2N/A
2N/A def match_section_by_string(self, sect_string):
2N/A """Match the section using the string passed.
2N/A """
2N/A split_section = sect_string.split(MenuConfigParser.SECTION_SEPARATOR)
2N/A if len(split_section) == 2:
2N/A classname = split_section[0]
2N/A try:
2N/A idx = int(split_section[1])
2N/A if self.section == (classname, idx):
2N/A return True
2N/A except ValueError:
2N/A self._debug('Error converting index to int for %s',
2N/A sect_string)
2N/A return False
2N/A
2N/A def __str__(self):
2N/A """Returns the string representation of this menu configuration entry
2N/A """
2N/A str_rep = ''
2N/A str_rep = '[%s]' % self.section_string
2N/A for key, value in self._options.items():
2N/A str_rep += '\n%s=%s' % (key, value)
2N/A return str_rep
2N/A
2N/A # pylint: disable=W0212
2N/A @staticmethod
2N/A def _cmp_sections(first, other):
2N/A """Compare two MenuConfigEntry instances based on their section names.
2N/A """
2N/A # Compare the section tuples directly (the integer comparison
2N/A # for the third tuple element happens automatically):
2N/A if first.section < other.section:
2N/A return -1
2N/A elif first.section > other.section:
2N/A return 1
2N/A
2N/A return 0
2N/A
2N/A def __cmp__(self, other):
2N/A """Compares two MenuConfigEntry instances. the entry with the
2N/A most-recent modified timestamp wins, otherwise, the entry with the
2N/A lexically-largest section tuple wins.
2N/A """
2N/A try:
2N/A self_modified = self._options['modified']
2N/A other_modified = other.options['modified']
2N/A if float(self_modified) < float(other_modified):
2N/A cmpval = -1
2N/A elif float(self_modified) == float(other_modified):
2N/A cmpval = MenuConfigEntry._cmp_sections(self, other)
2N/A else:
2N/A cmpval = 1
2N/A
2N/A return cmpval
2N/A except (ValueError, KeyError):
2N/A pass # Can't compare using the modified timestamps, so revert
2N/A # to testing the section names
2N/A return MenuConfigEntry._cmp_sections(self, other)
2N/A # pylint: enable=W0212
2N/A
2N/A# pylint: enable=R0903
2N/A# pylint: disable=R0902
2N/A
2N/Aclass MenuConfigParser(LoggerMixin):
2N/A """The MenuConfigParser class provides an interface to the menu.conf
2N/A file. The menu.conf file contains a set of sections, each (except for two)
2N/A of which describes a single Solaris boot entry in the
2N/A automatically-generated boot-loader menu. The syntax of the menu.conf is
2N/A that accepted by the ConfigParser class. The sections and properties
2N/A include:
2N/A
2N/A [meta]
2N/A order = <comma-separated list of section names>
2N/A
2N/A [global]
2N/A <Boot Loader Properties>
2N/A
2N/A Zero or More of:
2N/A [<classname>:<index>]
2N/A title=<Menu entry title> [Required]
2N/A default={True|False} # If this is the default boot entry
2N/A transient={True|False} # If this is a transient entry
2N/A
2N/A Other properties, depending on the <classname>:
2N/A bootfs=<bootfs value> [SolarisDiskBootInstance] [required]
2N/A kargs=<kernel arguments> [SolarisBootInstance] [optional]
2N/A kernel=<path to kernel file> [SolarisBootInstance] [optional]
2N/A boot_archive=<path to boot archive> [SolarisBootInstance] [optional]
2N/A """
2N/A
2N/A MENU_CONF_PREAMBLE = ('#\n# This file is modified without notice.\n'
2N/A '# DO NOT EDIT THIS FILE DIRECTLY. CHANGES MAY NOT '
2N/A 'BE PRESERVED.\n'
2N/A '# Changes to this file can be made indirectly via '
2N/A 'bootadm(1M) subcommands.\n#\n')
2N/A SECTION_SEPARATOR = '|'
2N/A
2N/A def __init__(self, filename='/rpool/boot/grub/menu.conf', new_conf=False):
2N/A """Creates or loads the menu configuration from the specified file.
2N/A If the file does not exist, an empty menu configuration is created,
2N/A but is not written to disk (see the write() method). If new_conf
2N/A is True, the file's contents are not loaded; instead, an empty menu
2N/A configuration is created (but not written yet).
2N/A
2N/A The following Exceptions may be generated:
2N/A BootmgmtConfigReadError -- If there's a problem reading config file.
2N/A """
2N/A super(MenuConfigParser, self).__init__()
2N/A self._filename = filename
2N/A self.last_written_path = None
2N/A self.entry_list = []
2N/A self._dirty = False
2N/A self._cfgparser = ConfigParser.RawConfigParser()
2N/A self._meta_options = {}
2N/A self.global_options = {}
2N/A if not new_conf:
2N/A self._load(filename)
2N/A
2N/A def _load(self, filename):
2N/A """Attempt to load the file specified. If it does not exist,
2N/A proceed with an empty SafeConfigParser.
2N/A
2N/A The following Exceptions may be generated:
2N/A BootmgmtConfigReadError -- If there's a problem reading config file.
2N/A """
2N/A
2N/A if not filename:
2N/A self._debug('No filename specified -- not loading menu '
2N/A 'configuration')
2N/A return
2N/A
2N/A self._debug('Loading menu.conf from %s', filename)
2N/A
2N/A try:
2N/A self._cfgparser.readfp(open(filename))
2N/A except IOError as ioerr:
2N/A if ioerr.errno == errno.ENOENT:
2N/A self._debug('[IGNORED] %s not found', filename)
2N/A return
2N/A raise BootmgmtConfigReadError('Error trying to read the '
2N/A 'menu configuration file ', ioerr)
2N/A
2N/A self._load_sections()
2N/A self.entry_list.sort()
2N/A
2N/A def _load_sections(self):
2N/A """Creates MenuConfigEntry objects from the ConfigParser's section
2N/A list and adds them to the entry list. Also loads the meta and
2N/A global sections, if found.
2N/A """
2N/A # Now create an MenuConfigEntry object for each section
2N/A for sect in self._cfgparser.sections():
2N/A
2N/A # Parse the section name into a tuple:
2N/A section_split = sect.split(MenuConfigParser.SECTION_SEPARATOR)
2N/A new_entry = None
2N/A # Handle 'normal' menu entries
2N/A if len(section_split) == 2:
2N/A try:
2N/A section_split = (section_split[0].strip(),
2N/A int(section_split[1]))
2N/A new_entry = MenuConfigEntry(self, section_split,
2N/A self._cfgparser.items(sect))
2N/A except ValueError: # Not an int, ignore entry
2N/A self._debug('Malformed section name: "%s"-- ignoring',
2N/A sect)
2N/A elif sect == 'meta': # Handle [meta] section
2N/A self._meta_options = dict(self._cfgparser.items('meta'))
2N/A elif sect == 'global': # Handle [global] section
2N/A self.global_options = dict(self._cfgparser.items('global'))
2N/A # Globals are sanity-checked by MenuConfigParser consumers
2N/A
2N/A if new_entry:
2N/A self._debug('Adding entry to list:\n%s', new_entry)
2N/A self.entry_list.append(new_entry)
2N/A
2N/A # pylint: disable=E0102,E0202,E1101
2N/A @property
2N/A def dirty(self):
2N/A """Returns the state of the internal dirty variable.
2N/A """
2N/A return self._dirty
2N/A
2N/A @dirty.setter
2N/A def dirty(self, val):
2N/A """Sets the state of the internal dirty variable. Emits a debug
2N/A message if the state of the internal dirty variable changed.
2N/A """
2N/A if self._dirty != val:
2N/A self._debug('dirty => %s', val)
2N/A self._dirty = val
2N/A # pylint: enable=E0102,E0202,E1101
2N/A
2N/A @property
2N/A def filename(self):
2N/A """Returns the filename of the menu.conf that this object was created
2N/A with.
2N/A """
2N/A return self._filename
2N/A
2N/A # pylint: disable=R0913
2N/A def add_entry(self, section_name, propdict=None):
2N/A """Add an entry to the menu configuration file.
2N/A The section_name parameter is used to derive the section
2N/A name.
2N/A If the propdict is not None, it contains
2N/A the properties used to initialize the new menu entry.
2N/A """
2N/A
2N/A new_section = self.gen_new_section(section_name)
2N/A newent = MenuConfigEntry(self, new_section, propdict)
2N/A self.entry_list.append(newent)
2N/A self.dirty = True
2N/A
2N/A self._debug('Added new entry:\n%s', newent)
2N/A return newent
2N/A # pylint: enable=R0913
2N/A
2N/A def delete_entry(self, entry):
2N/A """Delete an entry by matching the object address to one in the list.
2N/A """
2N/A self.delete_entries(cmp_fn=(lambda x: entry == x), match_all=False)
2N/A
2N/A def delete_entries(self, section_name=None, cmp_fn=None, match_all=True):
2N/A """Deletes one or more menu configuration entries. Entries are found
2N/A based by either matching the section_name argument (if cmp_fn is None)
2N/A or by invoking cmp_fn for each entry in the entry list. If cmp_fn
2N/A returns True, that entry is deleted. cmp_fn takes 1 argument--
2N/A the entry instance to consider for deletion).
2N/A If match_all is True, then all entries that match are deleted;
2N/A otherwise, only the first match is deleted.
2N/A
2N/A Returns a count of the number of deleted entries.
2N/A """
2N/A
2N/A if match_all:
2N/A orig_len = len(self.entry_list)
2N/A if cmp_fn:
2N/A self.entry_list = [x for x in self.entry_list if not cmp_fn(x)]
2N/A elif section_name:
2N/A self.entry_list = [x for x in self.entry_list
2N/A if section_name != x.section_name]
2N/A
2N/A count = orig_len - len(self.entry_list)
2N/A
2N/A self._debug('Deleted %d entr(y|ies)', count)
2N/A
2N/A if count > 0:
2N/A self.dirty = True
2N/A else:
2N/A count = 0
2N/A for idx, entry in enumerate(self.entry_list):
2N/A if ((section_name and section_name == entry.section[0]) or
2N/A (cmp_fn and cmp_fn(section_name, entry))):
2N/A self._debug('Deleting entry %s', str(entry.section))
2N/A del self.entry_list[idx]
2N/A self.dirty = True
2N/A count = 1
2N/A break
2N/A
2N/A return count
2N/A
2N/A
2N/A def _get_meta_cds_list(self, propname):
2N/A """CDS = comma-delimited string
2N/A """
2N/A stringlist = self._meta_options.get(propname)
2N/A if stringlist:
2N/A list_of_strings = stringlist.split(',')
2N/A list_of_strings = [x.strip() for x in list_of_strings]
2N/A return list_of_strings
2N/A return None
2N/A
2N/A def _set_meta_cds(self, propname, stringlist):
2N/A """Set the propname property to a comma-separated string composed
2N/A of strings from stringlist.
2N/A """
2N/A if stringlist:
2N/A newvalue = ', '.join(stringlist)
2N/A if (not propname in self._meta_options or
2N/A self._meta_options[propname] != newvalue):
2N/A self._debug('meta:%s: was %s now %s', (propname,
2N/A self._meta_options.get(propname, ''), newvalue))
2N/A self._meta_options[propname] = newvalue
2N/A self.dirty = True
2N/A
2N/A def get_order(self):
2N/A """Get a list of section headers that specify the order of menu
2N/A entries, if one exists.
2N/A """
2N/A return self._get_meta_cds_list('order')
2N/A
2N/A def set_order(self, order_list):
2N/A """Set the list of section headers that specifies the order of menu
2N/A entries.
2N/A """
2N/A self._set_meta_cds('order', order_list)
2N/A
2N/A def clear_order(self):
2N/A """Delete the order property from the meta section and mark this
2N/A instance dirty.
2N/A """
2N/A if 'order' in self._meta_options:
2N/A del self._meta_options['order']
2N/A self.dirty = True
2N/A
2N/A def gen_new_section(self, section_name):
2N/A """Generates a new section title based on the existing sections and
2N/A the name passed in. Note that until the section is a part of a
2N/A MenuConfigEntry object and that object is added to this
2N/A MenuConfigParser's entry list, it's possible for this function to
2N/A generate multiple identical section names.
2N/A """
2N/A # Now find the largest used index for the section_name
2N/A used_indices = [x.section[1] for x in self.entry_list
2N/A if x.section[0] == section_name]
2N/A
2N/A if not used_indices: # No other entries match, so index=0
2N/A section_tuple = (section_name, 0)
2N/A else: # Search for and return a tuple with a free index
2N/A unused_indices = list(set(range(len(used_indices) + 1)) -
2N/A set(used_indices))
2N/A if not unused_indices:
2N/A raise BootmgmtError('Could not find a free section index!')
2N/A section_tuple = (section_name, unused_indices[0])
2N/A
2N/A self._debug('Generated section: "%s"', str(section_tuple))
2N/A return section_tuple
2N/A
2N/A def _synch_to_config_parser(self):
2N/A """Generate a new ConfigParser using the data from the MenuConfigEntry
2N/A list (section and option data) and the meta/global dictionaries.
2N/A """
2N/A new_parser = ConfigParser.SafeConfigParser()
2N/A if self._meta_options:
2N/A new_parser.add_section('meta')
2N/A for key, value in self._meta_options.items():
2N/A new_parser.set('meta', key, value)
2N/A
2N/A if self.global_options:
2N/A new_parser.add_section('global')
2N/A for key, value in self.global_options.items():
2N/A new_parser.set('global', key, value)
2N/A
2N/A for entry in self.entry_list:
2N/A string_list = [str(x) for x in entry.section]
2N/A new_section = MenuConfigParser.SECTION_SEPARATOR.join(string_list)
2N/A
2N/A try:
2N/A new_parser.add_section(new_section)
2N/A except ConfigParser.DuplicateSectionError:
2N/A self._debug('Duplicate section error for "%s"', new_section)
2N/A else:
2N/A for key, value in entry.options.items():
2N/A if not isinstance(value, basestring):
2N/A new_parser.set(new_section, key, str(value))
2N/A else:
2N/A new_parser.set(new_section, key, value)
2N/A
2N/A return new_parser
2N/A
2N/A def write(self, alt_file=None, force=False):
2N/A """Writes the menu configuration data to the menu configuration file.
2N/A If no changes have been made since the configuration was loaded,
2N/A write() will return without writing the file (unless force is True).
2N/A If alt_file is not None, the menu configuration will be written there.
2N/A
2N/A Exceptions that may be generated:
2N/A BootmgmtConfigWriteError -- If the menu config file cannot be created
2N/A or if there's a problem writing to it.
2N/A """
2N/A
2N/A if not self._dirty and not force:
2N/A self._debug('avoiding write')
2N/A return
2N/A elif not self._dirty and force:
2N/A self._debug('forcing write')
2N/A
2N/A # First synch the MenuConfigParser state to a new ConfigParser:
2N/A self._cfgparser = self._synch_to_config_parser()
2N/A
2N/A # Use the alternate file, if specified; otherwise use the cached
2N/A # filename supplied to the constructor
2N/A write_file = alt_file if alt_file else self._filename
2N/A # We cannot proceed without a filename
2N/A if not write_file:
2N/A raise BootmgmtArgumentError('menu configuration filename not '
2N/A 'specified')
2N/A try:
2N/A oldumask = os.umask(022)
2N/A outfile = open(write_file, 'w')
2N/A os.umask(oldumask)
2N/A outfile.write(MenuConfigParser.MENU_CONF_PREAMBLE)
2N/A self._cfgparser.write(outfile)
2N/A self.last_written_path = write_file
2N/A self.dirty = False
2N/A except IOError as ioerr:
2N/A raise BootmgmtConfigWriteError('Error writing menu '
2N/A 'configuration file ', ioerr)
2N/A
2N/A# pylint: enable=R0902
2N/A
2N/A
2N/Aclass GRUB2BootLoader(BootLoader):
2N/A """Implementation of the BootLoader interface for the GRUB2 boot
2N/A loader.
2N/A """
2N/A
2N/A WEIGHT = 2
2N/A SUPPORTED_PLATFORMS = {'x86': ('bios', 'uefi64')}
2N/A SUPPORTED_PROPS = [
2N/A BootLoader.PROP_CONSOLE,
2N/A BootLoader.PROP_SERIAL_PARAMS,
2N/A BootLoader.PROP_TIMEOUT,
2N/A BootLoader.PROP_QUIET,
2N/A BootLoader.PROP_SPLASH,
2N/A BootLoader.PROP_FORECOLOR,
2N/A BootLoader.PROP_BACKCOLOR,
2N/A BootLoader.PROP_IDENT_FILE,
2N/A BootLoader.PROP_THEME
2N/A ]
2N/A
2N/A # These prefixes MUST NOT start with '/', or os.path.join() will not
2N/A # behave as we want below.
2N/A GRUB_PLATFORM_PREFIXES = {'bios' : 'usr/lib/grub2/bios',
2N/A 'uefi64': 'usr/lib/grub2/uefi64'}
2N/A GRUB_BOOT_SUBDIRS = {'bios' : 'i386-pc',
2N/A 'uefi64': 'x86_64-efi'}
2N/A GRUB_DATA_PATHS = {'bios' : os.path.join(GRUB_PLATFORM_PREFIXES['bios'],
2N/A 'lib/grub/' + GRUB_BOOT_SUBDIRS['bios']),
2N/A 'uefi64': os.path.join(GRUB_PLATFORM_PREFIXES['uefi64'],
2N/A 'lib/grub/' + GRUB_BOOT_SUBDIRS['uefi64'])}
2N/A GRUB_TARGET_STRINGS = {
2N/A 'bios' : GRUB_BOOT_SUBDIRS['bios'].replace('-', '_'),
2N/A 'uefi64': GRUB_BOOT_SUBDIRS['uefi64'].replace('-', '_')
2N/A }
2N/A
2N/A # List of modules that should be baked into the GRUB 2 image (i.e.
2N/A # available in rescue mode). Each key in PRELOADED_MODULES *must* be
2N/A # a list of strings (module names).
2N/A PRELOADED_MODULES_COMMON = ['minicmd', 'reboot']
2N/A PRELOADED_MODULES = {'bios' : PRELOADED_MODULES_COMMON,
2N/A 'uefi64': PRELOADED_MODULES_COMMON}
2N/A
2N/A # These may look like duplicates of the BOOT_SUBDIRS, but they're separate
2N/A # because they're actualy arguments to the grub-mkimage command.
2N/A MKIMAGE_FORMATS = {'bios' : 'i386-pc',
2N/A 'uefi64': 'x86_64-efi'}
2N/A VIDEO_BACKENDS = {'bios' : 'vbe',
2N/A 'uefi64': 'efi_gop'}
2N/A GFX_MODELIST = "1024x768x32;1024x768x16;800x600x16;640x480x16;" \
2N/A "640x480x15;640x480x32"
2N/A
2N/A GRUB_INSTALL = 'sbin/grub-install'
2N/A GRUB_MKCONFIG = 'sbin/grub-mkconfig'
2N/A GRUB_MKIMAGE = 'bin/grub-mkimage'
2N/A
2N/A # This exit code is returned by grub-install and grub-mkimage if it is
2N/A # incompatible with the GRUB2 modules on which it was asked to operate.
2N/A # In the case of ODDBootConfig, this is a fatal error. For DiskBootConfig,
2N/A # we log this fact and then proceed with deferred boot loader activation.
2N/A EXIT_CODE_INCOMPATIBLE = 5
2N/A DEFERRED_ACTIVATION_FILENAME = '.boot_loader_update_required'
2N/A
2N/A # GRUB_OTHERS stores lists of 3-tuples (source, destination, perms)
2N/A # (source is relative to the data root (i.e. the root directory of a boot
2N/A # instance) and destination is relative to the config data root (i.e. the
2N/A # zfs top level dataset for a root pool). The destination is relative to
2N/A # the grub configuration directory (boot/grub) and perms is a 3-tuple of
2N/A # (user, group, mode). If perms is None, GRUB_OTHERS_PERM_DEFAULTS is used.
2N/A GRUB_OTHERS_PERM_DEFAULTS = ('root', 'root', 0644)
2N/A BOOT_GRUB_SUBDIR = 'boot/grub'
2N/A THEME_SUBDIR = 'themes'
2N/A BOOT_GRUB_THEME_SUBDIR = BOOT_GRUB_SUBDIR + '/' + THEME_SUBDIR
2N/A THEME_DEFAULT = 'solaris'
2N/A THEME_TEMPLATE = os.path.join(GRUB_PLATFORM_PREFIXES['bios'],
2N/A 'share/grub/themes/%s/theme.txt')
2N/A THEME_DEFAULT_PATH = THEME_TEMPLATE % THEME_DEFAULT
2N/A
2N/A EXTN_TO_MODULE = {'jpg': 'jpeg', 'jpeg': 'jpeg',
2N/A 'png': 'png',
2N/A 'tga': 'tga'}
2N/A FONT_EXTN_LIST = [ 'pf2' ]
2N/A
2N/A GRUB_OTHERS = [(os.path.join(GRUB_PLATFORM_PREFIXES['bios'],
2N/A 'share/grub/unicode.pf2'),
2N/A 'unicode.pf2', None),
2N/A ('boot/grub/splash.jpg',
2N/A 'splash.jpg', None)]
2N/A
2N/A GRUB_NBP_PATHS = {'bios' : 'boot/grub/pxegrub2',
2N/A 'uefi64' : 'boot/grub/grub2netx64.efi'}
2N/A
2N/A CORE_IMG_BASENAME = 'core.img'
2N/A # Paths are relative to the zfs top level dataset:
2N/A CUSTOM_CFG_BASENAME = 'custom.cfg'
2N/A GRUB_CFG_BASENAME = 'grub.cfg'
2N/A GRUB_CFG_PERMS = 0644
2N/A MENU_CONF_BASENAME = 'menu.conf'
2N/A MENU_CONF_PERMS = 0644
2N/A GRUB_DEFS_PATH_FMT = 'grub2_defs.%s'
2N/A
2N/A EL_TORITO_IMAGENAMES = {'bios' : 'boot/bios.img',
2N/A 'uefi64': 'boot/uefi.img'}
2N/A
2N/A # Due to a limitation in Solaris's mkfs -F pcfs, we cannot make a
2N/A # fat filesystem with nofdisk of size < 4M.
2N/A UEFI_FS_MIN_SIZE = 4 * 1024 * 1024 # FAT filesystem min size
2N/A
2N/A DEFAULT_GRUB_SPLASH = 'splash.jpg' # relative to the config dir
2N/A DEFAULT_TIMEOUT = 30 # 30 seconds is the default timeout
2N/A DEFAULT_FORECOLOR = '343434'
2N/A DEFAULT_BACKCOLOR = 'F7FBFF'
2N/A
2N/A @classmethod
2N/A def probe(cls, **kwargs):
2N/A """Probe for GRUB2 on a system.
2N/A """
2N/A bootconfig = kwargs.get('bootconfig')
2N/A
2N/A if (bootconfig is None or bootconfig.boot_class is None):
2N/A return None
2N/A
2N/A if (bootconfig.boot_class != BootConfig.BOOT_CLASS_NET and
2N/A get_current_arch_string() != 'x86'):
2N/A cls._debug('GRUB2 boot loader not supported on this '
2N/A 'platform')
2N/A return None
2N/A
2N/A if (bootconfig.boot_class == BootConfig.BOOT_CLASS_DISK and
2N/A bootconfig.boot_fstype is not None):
2N/A return GRUB2BootLoader._probe_disk(**kwargs)
2N/A elif bootconfig.boot_class == BootConfig.BOOT_CLASS_ODD:
2N/A return GRUB2BootLoader._probe_odd(**kwargs)
2N/A elif bootconfig.boot_class == BootConfig.BOOT_CLASS_NET:
2N/A try:
2N/A cls._validate_platform_and_firmware(bootconfig,
2N/A kwargs.get('fwtype'))
2N/A except BootmgmtError as bmerr:
2N/A cls._debug('[PROBE ABORTED DUE TO EXCEPTION] %s', bmerr)
2N/A return None
2N/A return GRUB2BootLoader._probe_net(**kwargs)
2N/A else:
2N/A raise BootmgmtUnsupportedOperationError('Boot class %s not '
2N/A 'supported' % bootconfig.boot_class)
2N/A
2N/A @classmethod
2N/A def _probe_odd(cls, **kwargs):
2N/A """This GRUB2 probe function searches the ODD's root, looking for
2N/A the GRUB2 core modules"""
2N/A
2N/A bootconfig = kwargs.get('bootconfig')
2N/A
2N/A root = bootconfig.get_root()
2N/A
2N/A cls._debug('odd_image_root=%s', root)
2N/A
2N/A odd_data_probe_list = [os.path.join(
2N/A cls.GRUB_DATA_PATHS['bios'],
2N/A 'cdboot.img'),
2N/A os.path.join(
2N/A cls.GRUB_DATA_PATHS['bios'],
2N/A 'kernel.img'),
2N/A os.path.join(
2N/A cls.GRUB_DATA_PATHS['uefi64'],
2N/A 'kernel.img')]
2N/A try:
2N/A cls._probe_generic(root, odd_data_probe_list)
2N/A except BootmgmtNotSupportedError:
2N/A return None
2N/A
2N/A return GRUB2BootLoader(**kwargs)
2N/A
2N/A @classmethod
2N/A def _probe_net(cls, **kwargs):
2N/A """This GRUB2 probe function searches the net install image root,
2N/A looking for the GRUB2 NBP programs"""
2N/A
2N/A bootconfig = kwargs.get('bootconfig')
2N/A
2N/A artifacts = GRUB2BootLoader._probe_artifacts_net(bootconfig)
2N/A
2N/A if not artifacts:
2N/A return None
2N/A
2N/A kwargs['artifacts'] = artifacts
2N/A
2N/A return GRUB2BootLoader(**kwargs)
2N/A
2N/A @classmethod
2N/A def _probe_disk(cls, **kwargs):
2N/A """This GRUB2 probe function invokes probe_artifacts to look for
2N/A the menu.conf file at the ZFS top-level dataset (if this is a ZFS-based
2N/A DiskBootConfig), as well as the GRUB2 kernel and utilities.
2N/A """
2N/A bootconfig = kwargs.get('bootconfig')
2N/A
2N/A artifacts = cls._probe_artifacts_disk(bootconfig)
2N/A
2N/A if not artifacts:
2N/A return None
2N/A
2N/A kwargs['artifacts'] = artifacts
2N/A
2N/A return GRUB2BootLoader(**kwargs)
2N/A
2N/A @classmethod
2N/A def _probe_generic(cls, dataroot, datafiles):
2N/A """Generic probe function for GRUB2.
2N/A """
2N/A # Both the data root location must be specified
2N/A if dataroot is None:
2N/A raise BootmgmtNotSupportedError('dataroot is None')
2N/A
2N/A try:
2N/A for datafile in datafiles:
2N/A fpath = os.path.join(dataroot, datafile)
2N/A cls._debug('Probing for %s', fpath)
2N/A open(fpath).close()
2N/A except IOError as ioerr:
2N/A raise BootmgmtNotSupportedError('IOError when checking for '
2N/A 'datafiles', ioerr)
2N/A
2N/A @classmethod
2N/A def _probe_artifacts_net(cls, bootconfig):
2N/A """Looks for the menu.conf and the NBPs. There are no "tools"
2N/A needed for NetBootConfig. The images are preassembled.
2N/A """
2N/A artifacts = []
2N/A
2N/A # This is the OS image's root; we need to look for the config
2N/A # files in the net_data_root.
2N/A root = bootconfig.get_root()
2N/A
2N/A cls._debug('net_image_root=%s, net_data_root=%s', (root,
2N/A bootconfig.net_data_root))
2N/A
2N/A net_data_probe_list = [GRUB2BootLoader.GRUB_NBP_PATHS['bios'],
2N/A GRUB2BootLoader.GRUB_NBP_PATHS['uefi64']]
2N/A
2N/A menu_conf = os.path.join(bootconfig.net_data_root,
2N/A GRUB2BootLoader.MENU_CONF_BASENAME +
2N/A bootconfig.pxe_suffix())
2N/A try:
2N/A stat_info = os.stat(menu_conf)
2N/A if stat.S_ISREG(stat_info.st_mode) and stat_info.st_size > 0:
2N/A artifacts += [BootLoader.ARTIFACT_BOOTLOADER_CONFIGFILES]
2N/A except OSError:
2N/A pass
2N/A
2N/A try:
2N/A cls._probe_generic(root, net_data_probe_list)
2N/A artifacts += [BootLoader.ARTIFACT_BOOTLOADER_IMAGES]
2N/A artifacts += [BootLoader.ARTIFACT_BOOTLOADER_TOOLS]
2N/A except BootmgmtNotSupportedError:
2N/A pass
2N/A
2N/A return artifacts
2N/A
2N/A @classmethod
2N/A def _probe_artifacts_disk(cls, bootconfig):
2N/A """Probe for artifacts found when used with a DiskBootConfig
2N/A """
2N/A # probe_artifacts can either be called at BootLoader factory.get
2N/A # time or at runtime. If it's the latter, we need to get the
2N/A # most-recent data root in which to look for the artifacts.
2N/A bootloader = getattr(bootconfig, 'boot_loader', None)
2N/A
2N/A if bootloader:
2N/A # pylint: disable=W0212
2N/A dataroot = bootloader._get_boot_loader_data_root()
2N/A # pylint: enable=W0212
2N/A else:
2N/A dataroot = bootconfig.get_root()
2N/A
2N/A if bootconfig.boot_fstype == 'ufs':
2N/A cfgdataroot = dataroot
2N/A elif bootconfig.boot_fstype == 'zfs':
2N/A cfgdataroot = bootconfig.zfstop
2N/A else:
2N/A cls._debug('[PROBE FAILURE] Unknown filesystem %s',
2N/A bootconfig.boot_fstype)
2N/A return []
2N/A
2N/A if not os.path.isdir(dataroot):
2N/A cls._debug('dataroot is not a directory')
2N/A return []
2N/A
2N/A cls._debug('dataroot=%s cfgdataroot=%s', (dataroot, cfgdataroot))
2N/A
2N/A artifacts = []
2N/A
2N/A # First look for the menu.conf file:
2N/A try:
2N/A menuconf = os.path.join(cfgdataroot,
2N/A GRUB2BootLoader.BOOT_GRUB_SUBDIR,
2N/A GRUB2BootLoader.MENU_CONF_BASENAME)
2N/A stat_info = os.stat(menuconf)
2N/A if stat.S_ISREG(stat_info.st_mode) and stat_info.st_size > 0:
2N/A artifacts += [BootLoader.ARTIFACT_BOOTLOADER_CONFIGFILES]
2N/A except (OSError, AttributeError) as excpt:
2N/A cls._debug('Error while looking for %s: %s', (menuconf, str(excpt)))
2N/A
2N/A # List of essential data files need to pass the disk probe:
2N/A disk_data_probe_list = [os.path.join(
2N/A cls.GRUB_DATA_PATHS['bios'],
2N/A 'diskboot.img'),
2N/A os.path.join(
2N/A cls.GRUB_DATA_PATHS['bios'],
2N/A 'kernel.img'),
2N/A os.path.join(
2N/A cls.GRUB_DATA_PATHS['uefi64'],
2N/A 'kernel.img')]
2N/A try:
2N/A cls._probe_generic(dataroot, disk_data_probe_list)
2N/A artifacts += [BootLoader.ARTIFACT_BOOTLOADER_IMAGES]
2N/A except BootmgmtNotSupportedError as excpt:
2N/A cls._debug('Error while looking for GRUB2 kernel/loader files: %s',
2N/A str(excpt))
2N/A
2N/A # In addition to the loader files themselves, we need to ensure
2N/A # that we have access to the grub-install program (and its dependent
2N/A # executables) in the currently-running system (otherwise, we'll
2N/A # have no way to install GRUB2).
2N/A
2N/A fwname = SystemFirmware.get(None).fw_name
2N/A
2N/A if fwname == 'bios':
2N/A disk_execs_probe_list = [os.path.join(
2N/A cls.GRUB_PLATFORM_PREFIXES['bios'],
2N/A 'sbin/grub-setup'),
2N/A os.path.join(
2N/A cls.GRUB_PLATFORM_PREFIXES['bios'],
2N/A 'sbin/grub-probe'),
2N/A os.path.join(
2N/A cls.GRUB_PLATFORM_PREFIXES['bios'],
2N/A 'sbin/grub-install'),
2N/A os.path.join(
2N/A cls.GRUB_PLATFORM_PREFIXES['bios'],
2N/A 'bin/grub-mkimage')]
2N/A elif fwname == 'uefi64':
2N/A disk_execs_probe_list = [os.path.join(
2N/A cls.GRUB_PLATFORM_PREFIXES['uefi64'],
2N/A 'sbin/grub-probe'),
2N/A os.path.join(
2N/A cls.GRUB_PLATFORM_PREFIXES['uefi64'],
2N/A 'sbin/grub-install'),
2N/A os.path.join(
2N/A cls.GRUB_PLATFORM_PREFIXES['uefi64'],
2N/A 'bin/grub-mkimage')]
2N/A else:
2N/A cls._debug('Unsupported firmware type: %s', fwname)
2N/A return []
2N/A
2N/A missing_tools = False
2N/A for execname in disk_execs_probe_list:
2N/A try:
2N/A full_exec_path = os.path.join('/', execname)
2N/A if (not (os.path.exists(full_exec_path) and
2N/A os.access(full_exec_path, os.X_OK))):
2N/A cls._debug('%s not found or not executable',
2N/A full_exec_path)
2N/A missing_tools = True
2N/A break
2N/A except OSError as oserr:
2N/A cls._debug('Error while looking for GRUB2 tools: %s', oserr)
2N/A missing_tools = True
2N/A
2N/A if not missing_tools:
2N/A artifacts += [BootLoader.ARTIFACT_BOOTLOADER_TOOLS]
2N/A
2N/A return artifacts
2N/A
2N/A @classmethod
2N/A def probe_artifacts(cls, bootconfig):
2N/A """Returns a list of artifacts for this BootLoader, given a
2N/A bootconfig to use for directory information.
2N/A """
2N/A # Only supported for disk-based BootConfigs.
2N/A if bootconfig.boot_class == BootConfig.BOOT_CLASS_DISK:
2N/A return cls._probe_artifacts_disk(bootconfig)
2N/A elif bootconfig.boot_class == BootConfig.BOOT_CLASS_NET:
2N/A return cls._probe_artifacts_net(bootconfig)
2N/A return []
2N/A
2N/A @property
2N/A def config_dir(self):
2N/A if self._boot_config.boot_class == BootConfig.BOOT_CLASS_DISK:
2N/A return self._config_dir_disk()
2N/A elif self._boot_config.boot_class == BootConfig.BOOT_CLASS_NET:
2N/A # The config files are written to the root of the data dir.
2N/A return self._boot_config.net_data_root
2N/A else:
2N/A return '/' + GRUB2BootLoader.BOOT_GRUB_SUBDIR
2N/A
2N/A def __init__(self, **kwargs):
2N/A """Constructor for GRUB2 BootLoader
2N/A """
2N/A self.pkg_names = []
2N/A self.name = 'GRUB2'
2N/A super(GRUB2BootLoader, self).__init__(**kwargs)
2N/A
2N/A self.theme_os_path = None
2N/A self.theme_grub_path = None
2N/A self.theme_mods_set = None
2N/A self.theme_fonts_set = None
2N/A
2N/A if self._boot_config.boot_class == BootConfig.BOOT_CLASS_DISK:
2N/A self._menu_conf_path = os.path.join(self.config_dir,
2N/A GRUB2BootLoader.MENU_CONF_BASENAME)
2N/A elif self._boot_config.boot_class == BootConfig.BOOT_CLASS_NET:
2N/A self._menu_conf_path = os.path.join(self._boot_config.net_data_root,
2N/A GRUB2BootLoader.MENU_CONF_BASENAME +
2N/A self._boot_config.pxe_suffix())
2N/A
2N/A if self._boot_config.boot_class in [BootConfig.BOOT_CLASS_DISK,
2N/A BootConfig.BOOT_CLASS_NET]:
2N/A self._menu_org = GRUB2MenuOrganizer(self._boot_config,
2N/A self._menu_conf_path)
2N/A else:
2N/A self._menu_org = None
2N/A
2N/A
2N/A def _clear_theme_state(self):
2N/A """Uninitialized all theme attributes (resets to default theme)
2N/A """
2N/A self.theme_os_path = None
2N/A self.theme_grub_path = None
2N/A self.theme_mods_set = None
2N/A self.theme_fonts_set = None
2N/A
2N/A
2N/A def new_config(self):
2N/A """Reset configuration information by starting with a new menu.conf.
2N/A """
2N/A super(GRUB2BootLoader, self).new_config()
2N/A if self._boot_config.boot_class == BootConfig.BOOT_CLASS_NET:
2N/A self._bl_props[BootLoader.PROP_CONSOLE] = \
2N/A BootLoader.PROP_CONSOLE_TEXT
2N/A else:
2N/A self._bl_props[BootLoader.PROP_CONSOLE] = \
2N/A BootLoader.PROP_CONSOLE_GFX
2N/A if BootLoader.PROP_THEME in self._bl_props:
2N/A del self._bl_props[BootLoader.PROP_THEME]
2N/A self._clear_theme_state()
2N/A
2N/A if self._boot_config.boot_class in [BootConfig.BOOT_CLASS_DISK,
2N/A BootConfig.BOOT_CLASS_NET]:
2N/A self._menu_org.new_config()
2N/A else:
2N/A # menu.conf not supported on non-disk/net bootconfigs
2N/A self._menu_org = None
2N/A
2N/A def load_config(self, do_add=True):
2N/A """Loads the configuration, based on the type of BootConfig.
2N/A """
2N/A super(GRUB2BootLoader, self).load_config(do_add)
2N/A
2N/A bootcfg = self._boot_config
2N/A inst_list = []
2N/A
2N/A if bootcfg.boot_class in [BootConfig.BOOT_CLASS_DISK,
2N/A BootConfig.BOOT_CLASS_NET]:
2N/A self.dirty, inst_list = self._load_config_generic(do_add)
2N/A else:
2N/A raise BootmgmtUnsupportedOperationError('Stored configurations '
2N/A 'are not supported on non-disk/net BootConfigs')
2N/A
2N/A if inst_list:
2N/A return inst_list
2N/A return []
2N/A
2N/A def _config_dir_disk(self):
2N/A """Returns the directory where configuration files are stored.
2N/A """
2N/A if self._boot_config.boot_fstype == 'zfs':
2N/A cfgroot = self._boot_config.zfstop
2N/A elif self._boot_config.boot_fstype == 'ufs':
2N/A cfgroot = self._boot_config.get_root()
2N/A else:
2N/A raise BootmgmtUnsupportedOperationError('Unknown filesystem: %s'
2N/A % self._boot_config.boot_fstype)
2N/A return os.path.join(cfgroot, GRUB2BootLoader.BOOT_GRUB_SUBDIR)
2N/A
2N/A def _load_config_generic(self, do_add, menuconf_path=None):
2N/A """Load the menu configuration file and read bootloader properties
2N/A and boot instances from it. Autogenerate boot instances from the BEs
2N/A on the system. Returns True if boot instances were autogenerated (and
2N/A therefore the MenuConfigParser was updated).
2N/A """
2N/A self._menu_org.load(menuconf_path)
2N/A # merge in properties from the config file:
2N/A menu_conf_bl_props = self._menu_org.get_bootloader_props()
2N/A for key in menu_conf_bl_props:
2N/A try:
2N/A self.setprop(key, menu_conf_bl_props[key])
2N/A except BootmgmtUnsupportedPropertyError:
2N/A self._debug('Unsupported property found in menu config file: '
2N/A '%s=%s', (key, menu_conf_bl_props[key]))
2N/A
2N/A boot_instances = self._menu_org.generate_boot_instances()
2N/A if do_add:
2N/A if boot_instances:
2N/A # Add the boot instances to the BootConfig instance:
2N/A self._boot_config.add_boot_instance(boot_instances)
2N/A return self._menu_org.dirty, boot_instances
2N/A
2N/A def _prop_validate(self, key, value=None, validate_value=False):
2N/A """Part of upgrade support: Correct splash image extension if
2N/A it's .xpm.gz.
2N/A """
2N/A if key == BootLoader.PROP_SPLASH and isinstance(value, basestring):
2N/A if value.find('.xpm.gz') != -1:
2N/A return value.replace('.xpm.gz', '.jpg')
2N/A elif key == BootLoader.PROP_THEME:
2N/A self._clear_theme_state()
2N/A return super(GRUB2BootLoader, self)._prop_validate(key, value,
2N/A validate_value)
2N/A
2N/A # BootLoader install() hooks
2N/A
2N/A # For GRUB2, the menu.conf file is used to create the etc/default/grub
2N/A # (or temporary equivalent for non-Disk BootConfigs) file (from the global
2N/A # properties) and the rest of the menu.conf is used by the GRUB2
2N/A # autogeneration program in etc/grub.d/XX_solaris.
2N/A
2N/A def _write_config(self, basepath):
2N/A """Generates the grub.cfg file(s) for GRUB2. Different approaches are
2N/A taken depending on the type of BootConfig this BootLoader is associated
2N/A with. For DiskBootConfig, we update the
2N/A $PREFIX/etc/default/grub2_defs.$FW file and the menu.conf before
2N/A exec()ing grub-mkconfig to do the actual file generation. For
2N/A ODDBootConfig and NetBootConfig, we create the grub.cfg by hand (since
2N/A the autogeneration scripts put system-specific GRUB2 commands into the
2N/A generated configuration file.)
2N/A """
2N/A if self._boot_config is None:
2N/A msg = ('Cannot _write_config(%s) - _boot_config is None' %
2N/A str(basepath))
2N/A self._debug(msg)
2N/A raise BootmgmtInterfaceCodingError(msg)
2N/A
2N/A # Determine the type of boot configuration we're dealing with, then
2N/A # call the tailored _write_config_<bootconfig_type> method.
2N/A if self._boot_config.boot_class == BootConfig.BOOT_CLASS_DISK:
2N/A return self._write_config_disk(basepath)
2N/A elif self._boot_config.boot_class == BootConfig.BOOT_CLASS_ODD:
2N/A return self._write_config_odd(basepath)
2N/A elif self._boot_config.boot_class == BootConfig.BOOT_CLASS_NET:
2N/A return self._write_config_net(basepath)
2N/A else:
2N/A raise BootmgmtUnsupportedOperationError('Unsupported BootConfig '
2N/A 'class: %s' % self._boot_config.__class__.__name__)
2N/A
2N/A def _get_target_list(self):
2N/A """Returns a list of firmware targets that this BootLoader must
2N/A handle at install()-time.
2N/A """
2N/A targets = self.getprop(BootLoader.PROP_BOOT_TARGS)
2N/A if (targets and not isinstance(targets, basestring)
2N/A and len(targets) > 0):
2N/A return targets
2N/A else:
2N/A return [self.firmware.fw_name]
2N/A
2N/A def _write_config_disk(self, basepath):
2N/A """To generate the grub.cfg file, we invoke the grub-mkconfig
2N/A command. Before doing that, we need to create the grub2_defs.$FW
2N/A file, which consists of a number of environment variables whose
2N/A values are derived from the BootLoader properties. Once that file is
2N/A created, we must store the menu organizer's state (the menu.conf)
2N/A then we can invoke grub-mkconfig (since the Solaris autogenerator
2N/A plugin consumes the menu.conf file).
2N/A """
2N/A
2N/A menu_conf_tuple_list = self._write_menu_conf(basepath)
2N/A
2N/A target_list = self._get_target_list()
2N/A target = target_list[0]
2N/A if len(target_list) > 1:
2N/A self._debug('%s is %s: ONLY using %s as target value',
2N/A (BootLoader.PROP_BOOT_TARGS, target_list, target))
2N/A
2N/A # assemble_other_grub_files must be done before the grub.cfg is
2N/A # generated, as grub-mkconfig looks for specific files that
2N/A # assemble_other_grub_files copies.
2N/A other_tuple_list = self._assemble_other_grub_files(basepath)
2N/A grub_cfg_tuple_list = self._generate_grub_cfg(target, basepath)
2N/A if basepath:
2N/A menu_conf_tuple_list.extend(grub_cfg_tuple_list)
2N/A menu_conf_tuple_list.extend(other_tuple_list)
2N/A return menu_conf_tuple_list
2N/A else:
2N/A return None
2N/A
2N/A def _write_menu_conf(self, basepath):
2N/A """Write the menu.conf file to the system location if basepath is None.
2N/A Creates directories as needed.
2N/A
2N/A May raise BootmgmtConfigWriteError if there was a problem creating
2N/A directories.
2N/A """
2N/A if basepath:
2N/A # We need to return 2 tuples -- one for the menu.conf and one
2N/A # for the grub.cfg.
2N/A boot_class = self._boot_config.boot_class
2N/A # If final_path is None, do not generate a tuple for menu.conf
2N/A final_path = None
2N/A if boot_class == BootConfig.BOOT_CLASS_DISK:
2N/A dest = GRUB2BootLoader.BOOT_GRUB_SUBDIR + '/' + \
2N/A GRUB2BootLoader.MENU_CONF_BASENAME
2N/A menuconf_path = os.path.join(basepath,
2N/A GRUB2BootLoader.BOOT_GRUB_SUBDIR,
2N/A GRUB2BootLoader.MENU_CONF_BASENAME)
2N/A token = self._get_root_token()
2N/A if token:
2N/A final_path = '%(' + token + ')s/'
2N/A else:
2N/A final_path = '/'
2N/A final_path += dest
2N/A elif boot_class == BootConfig.BOOT_CLASS_NET:
2N/A menuconf_name = GRUB2BootLoader.MENU_CONF_BASENAME + \
2N/A self._boot_config.pxe_suffix()
2N/A # The menu.conf is stored in the root of the basepath
2N/A # provided, NOT in a /boot/grub subdirectory.
2N/A menuconf_path = os.path.join(basepath, menuconf_name)
2N/A final_path = os.path.join(self.config_dir, menuconf_name)
2N/A else:
2N/A raise BootmgmtConfigWriteError('Cannot find a place to write '
2N/A '%s for BootConfig class %s' %
2N/A (GRUB2BootLoader.MENU_CONF_BASENAME, boot_class))
2N/A
2N/A tuple_list = []
2N/A tuple_list.extend([(BootConfig.OUTPUT_TYPE_FILE,
2N/A menuconf_path,
2N/A None,
2N/A final_path,
2N/A 'root',
2N/A 'root',
2N/A GRUB2BootLoader.MENU_CONF_PERMS)])
2N/A force = True # always force when a basepath is specified
2N/A else:
2N/A force = False
2N/A tuple_list = None
2N/A menuconf_path = self._menu_conf_path
2N/A
2N/A self._make_basedir(menuconf_path)
2N/A self._debug('Writing menu.conf to %s', menuconf_path)
2N/A if self._boot_config and self._boot_config.boot_instances:
2N/A self._menu_org.synch_menu_config(self._boot_config.boot_instances,
2N/A self._bl_props)
2N/A self._menu_org.store(menuconf_path, force)
2N/A
2N/A return tuple_list
2N/A
2N/A # pylint: disable=R0913
2N/A def _generate_stub_cfg_file(self, target, cfg_basename, basepath,
2N/A contents=None, contents_only=False, on_zfs=False):
2N/A """Generates a stub file that sources the parent dir's file of the
2N/A same name. This is suitable ONLY for DiskBootConfig and ODDBootConfig.
2N/A Returns a tuple if basepath is not None.
2N/A """
2N/A subdir = GRUB2BootLoader.GRUB_BOOT_SUBDIRS[target]
2N/A if basepath:
2N/A cfgpath = basepath
2N/A subdir = GRUB2BootLoader.BOOT_GRUB_SUBDIR + '/' + subdir
2N/A else:
2N/A cfgpath = self.config_dir
2N/A
2N/A outfilename = os.path.join(cfgpath, subdir, cfg_basename)
2N/A
2N/A try:
2N/A self._make_basedir(outfilename)
2N/A outfile = file(outfilename, 'w')
2N/A except IOError as err:
2N/A raise BootmgmtConfigWriteError('Error during grub.cfg generation: '
2N/A "Couldn't create %s" % outfilename, err)
2N/A except BootmgmtConfigWriteError:
2N/A raise
2N/A
2N/A outfile.write('# Automatically generated -- do not edit\n')
2N/A if contents:
2N/A outfile.write(contents)
2N/A if not contents_only:
2N/A outfile.write('source %s/boot/grub/%s\n' %
2N/A ('/@' if on_zfs else '', cfg_basename))
2N/A outfile.close()
2N/A
2N/A if basepath:
2N/A token = self._get_root_token()
2N/A if token:
2N/A final_path = '%(' + token + ')s/'
2N/A else:
2N/A final_path = '/'
2N/A final_path += '%s/%s' % (subdir, cfg_basename)
2N/A return (BootConfig.OUTPUT_TYPE_FILE,
2N/A outfilename,
2N/A None,
2N/A final_path,
2N/A 'root',
2N/A 'root',
2N/A GRUB2BootLoader.GRUB_CFG_PERMS)
2N/A # pylint: enable=R0913
2N/A
2N/A @staticmethod
2N/A def _generate_grub2_load_video_func(target, def_dict):
2N/A """Generate the GRUB2 script code for the video driver load
2N/A function.
2N/A """
2N/A targname = GRUB2BootLoader.GRUB_TARGET_STRINGS[target]
2N/A vidbackend = def_dict.get('GRUB_VIDEO_BACKEND')
2N/A if vidbackend:
2N/A video_insmod = ('function load_video_%s {\n'
2N/A '\tinsmod %s\n'
2N/A '}') % (targname, vidbackend)
2N/A else:
2N/A video_insmod = 'function load_video_%s {\n\ttrue\n}'
2N/A return video_insmod
2N/A
2N/A def _generate_stub_files(self, target, basepath=None, def_dict=None):
2N/A """Generates the stub config files for the platform-specific config
2N/A directory.
2N/A """
2N/A if (isinstance(self._boot_config, DiskBootConfig) and
2N/A getattr(self._boot_config, 'boot_fstype', '') == 'zfs'):
2N/A on_zfs = True
2N/A else:
2N/A on_zfs = False
2N/A
2N/A targname = GRUB2BootLoader.GRUB_TARGET_STRINGS[target]
2N/A
2N/A video_insmod = ''
2N/A if def_dict:
2N/A video_insmod = self._generate_grub2_load_video_func(target,
2N/A def_dict)
2N/A
2N/A if target.startswith('uefi'):
2N/A multiboot = 'multiboot2'
2N/A module = 'module2'
2N/A else:
2N/A multiboot = 'multiboot'
2N/A module = 'module'
2N/A
2N/A grubscript = ('set target=%s\n'
2N/A 'set multiboot=%s\n'
2N/A 'set module=%s\n'
2N/A '%s\n') % (targname, multiboot, module,
2N/A video_insmod)
2N/A
2N/A stub_grub_cfg = self._generate_stub_cfg_file(target,
2N/A GRUB2BootLoader.GRUB_CFG_BASENAME, basepath, grubscript,
2N/A on_zfs=on_zfs)
2N/A custom_name = '%s/boot/grub/%s' % ('/@' if on_zfs else '',
2N/A GRUB2BootLoader.CUSTOM_CFG_BASENAME)
2N/A stub_custom_cfg = self._generate_stub_cfg_file(target,
2N/A GRUB2BootLoader.CUSTOM_CFG_BASENAME, basepath,
2N/A 'if [ -s %s ]; then\n'
2N/A '\tsource %s\n'
2N/A 'fi\n' % (custom_name, custom_name), True)
2N/A if basepath:
2N/A return [stub_grub_cfg, stub_custom_cfg]
2N/A
2N/A def _generate_grub_cfg_file(self, target, basepath, defs_dict, outfile):
2N/A """Create and populate the grub.cfg file"""
2N/A # Before we can invoke grub-mkconfig, we need to transform
2N/A # the boot loader properties into a grub defaults file,
2N/A # then set the BOOTMGMT_GRUB2_DEFAULTS environment
2N/A # variable to its path. (The grub-mkconfig program will source
2N/A # etc/default/grub, which will source the generated defaults
2N/A # file if the environment variable is set.)
2N/A grub_defs_path = None
2N/A try:
2N/A reset_env = False
2N/A if defs_dict:
2N/A # The resulting grub.cfg from _internal_mkconfig is designed
2N/A # to work on all targets
2N/A self._internal_mkconfig(target, defs_dict, outfile)
2N/A else:
2N/A grub_defs_path = self._generate_grub_defs(target, basepath)
2N/A os.environ['BOOTMGMT_GRUB2_DEFAULTS'] = grub_defs_path
2N/A reset_env = True
2N/A self._invoke_grub_mkconfig(target, outfile)
2N/A outfile.close()
2N/A except BootmgmtConfigWriteError:
2N/A try:
2N/A os.remove(outfile.name)
2N/A except OSError:
2N/A pass
2N/A raise
2N/A finally:
2N/A if reset_env:
2N/A del os.environ['BOOTMGMT_GRUB2_DEFAULTS']
2N/A if (basepath and grub_defs_path and
2N/A not getattr(self, 'preserve_defs', False)):
2N/A try:
2N/A os.remove(grub_defs_path)
2N/A except OSError:
2N/A pass
2N/A # Not a fatal error
2N/A
2N/A # pylint: disable=R0912
2N/A def _generate_grub_cfg(self, target, basepath, defs_dict=None,
2N/A filesuffix=''):
2N/A """Writes the grub.cfg file to the appropriate location. If
2N/A defs_dict is not None, internal grub-mkconfig is used.
2N/A """
2N/A dest_path = ''
2N/A if basepath:
2N/A boot_class = self._boot_config.boot_class
2N/A if boot_class == BootConfig.BOOT_CLASS_NET:
2N/A base_grub_cfg = GRUB2BootLoader.GRUB_CFG_BASENAME + filesuffix
2N/A dest_path = os.path.join(
2N/A self._boot_config.net_tftproot_subdir, base_grub_cfg)
2N/A outfilename = os.path.join(basepath, base_grub_cfg)
2N/A else:
2N/A dest_path = '/' + GRUB2BootLoader.BOOT_GRUB_SUBDIR + '/' + \
2N/A GRUB2BootLoader.GRUB_CFG_BASENAME
2N/A outfilename = os.path.join(basepath, dest_path[1:])
2N/A else:
2N/A final_filename = os.path.join(self.config_dir,
2N/A GRUB2BootLoader.GRUB_CFG_BASENAME)
2N/A outfilename = final_filename + '.new'
2N/A
2N/A try:
2N/A self._make_basedir(outfilename)
2N/A outfile = file(outfilename, 'w')
2N/A except IOError as err:
2N/A raise BootmgmtConfigWriteError('Error during grub.cfg generation: '
2N/A "Couldn't create %s" % outfilename, err)
2N/A except BootmgmtConfigWriteError:
2N/A raise
2N/A
2N/A self._generate_grub_cfg_file(target, basepath, defs_dict, outfile)
2N/A
2N/A if basepath:
2N/A token = self._get_root_token()
2N/A if token:
2N/A final_path = '%(' + token + ')s/'
2N/A else:
2N/A final_path = '/'
2N/A
2N/A final_path += dest_path.lstrip('/')
2N/A return [(BootConfig.OUTPUT_TYPE_FILE,
2N/A outfilename,
2N/A None,
2N/A final_path,
2N/A 'root',
2N/A 'root',
2N/A GRUB2BootLoader.GRUB_CFG_PERMS)]
2N/A else:
2N/A self._generate_stub_files(target)
2N/A self._move_temp_to_grub_cfg(outfilename, final_filename)
2N/A # pylint: enable=R0912
2N/A
2N/A def _get_root_token(self):
2N/A """Returns the token to use when constructing tuples returned from
2N/A install().
2N/A """
2N/A token = None
2N/A if isinstance(self._boot_config, DiskBootConfig):
2N/A fstype = getattr(self._boot_config, 'boot_fstype', None)
2N/A if fstype == 'zfs':
2N/A token = DiskBootConfig.TOKEN_ZFS_RPOOL_TOP_DATASET
2N/A elif fstype == 'ufs':
2N/A token = BootConfig.TOKEN_SYSTEMROOT
2N/A elif isinstance(self._boot_config, ODDBootConfig):
2N/A token = ODDBootConfig.TOKEN_ODD_ROOT
2N/A elif isinstance(self._boot_config, NetBootConfig):
2N/A token = NetBootConfig.TOKEN_TFTP_ROOT
2N/A else:
2N/A self._debug('Unknown BootConfig type -- not using a token')
2N/A
2N/A return token
2N/A
2N/A def _move_temp_to_grub_cfg(self, src, dest):
2N/A """Moves the temporary grub.cfg.new to grub.cfg.
2N/A """
2N/A try:
2N/A shutil.move(src, dest)
2N/A self._debug('grub.cfg moved into place as %s', dest)
2N/A except IOError as err:
2N/A try:
2N/A os.remove(src)
2N/A except OSError as oserr:
2N/A self._debug('Error while trying to remove %s: %s',
2N/A (src, oserr))
2N/A raise BootmgmtConfigWriteError("Couldn't move %s to %s" %
2N/A (src, dest), err)
2N/A
2N/A # Move was successful, so now set the owner and mode properly:
2N/A try:
2N/A os.chmod(dest, GRUB2BootLoader.GRUB_CFG_PERMS)
2N/A os.chown(dest, pwd.getpwnam('root').pw_uid,
2N/A grp.getgrnam('root').gr_gid)
2N/A except OSError as oserr:
2N/A raise BootmgmtConfigWriteError("Couldn't set mode/perms on %s"
2N/A % dest, oserr)
2N/A
2N/A # pylint: disable=R0912
2N/A def _invoke_grub_mkconfig(self, target, outfile):
2N/A """Invokes grub-mkconfig and directs its output to the outfile.
2N/A """
2N/A
2N/A grub_mkconfig = [os.path.join('/',
2N/A GRUB2BootLoader.GRUB_PLATFORM_PREFIXES[target],
2N/A GRUB2BootLoader.GRUB_MKCONFIG),
2N/A '--prefix=%s' % os.path.join(self.config_dir,
2N/A GRUB2BootLoader.GRUB_BOOT_SUBDIRS[target])]
2N/A
2N/A try:
2N/A self._execute_cmd(grub_mkconfig,
2N/A 'Generating %s boot loader configuration files' % target,
2N/A '%s grub.cfg generation' % target, outfile)
2N/A finally:
2N/A outfile.close()
2N/A
2N/A # pylint: enable=R0912
2N/A
2N/A # pylint: disable=R0914
2N/A def _generate_grub_defs(self, target, basepath):
2N/A """Generate the GRUB2 defaults file either in the specified
2N/A path or in the system location. Returns the path to the defaults
2N/A file. This is suitable only for DiskBootConfig.
2N/A
2N/A May raise BootmgmtConfigWriteError if there was a problem creating the
2N/A defaults file (or its parent directory).
2N/A """
2N/A if basepath:
2N/A dirpath = os.path.join(basepath, GRUB2BootLoader.BOOT_GRUB_SUBDIR)
2N/A else:
2N/A dirpath = self.config_dir
2N/A defs_path = os.path.join(dirpath,
2N/A GRUB2BootLoader.GRUB_DEFS_PATH_FMT % target)
2N/A self._make_basedir(defs_path)
2N/A try:
2N/A defs_file = open(defs_path, 'w')
2N/A except IOError as ioerr:
2N/A raise BootmgmtConfigWriteError('Error writing GRUB2 defaults',
2N/A ioerr)
2N/A
2N/A for key, value in self._generate_grub_defs_dict(target).items():
2N/A print >> defs_file, '%s=\'%s\'' % (key, value)
2N/A
2N/A defs_file.close()
2N/A return defs_path
2N/A # pylint: enable=R0914
2N/A
2N/A def _gen_grub_defs_dict_cons_gfx(self):
2N/A """Generate a dict with gfxterm defaults.
2N/A """
2N/A defdict = {}
2N/A
2N/A splash = self.getprop(BootLoader.PROP_SPLASH)
2N/A # If splash is None, the property does not exist, so use a
2N/A # default
2N/A if splash is None:
2N/A splash = GRUB2BootLoader.DEFAULT_GRUB_SPLASH
2N/A if len(splash) > 1:
2N/A if splash[0] == '/':
2N/A splash = splash[1:]
2N/A # We have to get the basename of the splashimage because
2N/A # we know it will be copied into the config_dir.
2N/A splash = os.path.join(self.config_dir, os.path.basename(splash))
2N/A defdict['GRUB_BACKGROUND'] = splash
2N/A
2N/A fore = self.getprop(BootLoader.PROP_FORECOLOR)
2N/A # If fore is None, the property does not exist, so use a default
2N/A if fore is None:
2N/A fore = GRUB2BootLoader.DEFAULT_FORECOLOR
2N/A if len(fore) > 0:
2N/A forecolor = self._grub2_color(fore)
2N/A defdict['GRUB_FORECOLOR'] = forecolor
2N/A
2N/A back = self.getprop(BootLoader.PROP_BACKCOLOR)
2N/A # If back is None, the property does not exist, so use a default
2N/A if back is None:
2N/A back = GRUB2BootLoader.DEFAULT_BACKCOLOR
2N/A if len(back) > 0:
2N/A backcolor = self._grub2_color(back)
2N/A # The background color must have a 0 alpha so the
2N/A # splash image comes through.
2N/A defdict['GRUB_BACKCOLOR'] = '%s,0' % backcolor
2N/A
2N/A # If a theme is to be used, it was previously copied into the
2N/A # proper place in _assemble_other_grub_files(). If theme_grub_path
2N/A # is non-None, use it to set the GRUB_THEME_DIRECT property; if
2N/A # theme_os_path is set, use it to set the GRUB_THEME property.
2N/A if self.theme_grub_path:
2N/A defdict['GRUB_THEME_DIRECT'] = self.theme_grub_path
2N/A if self.theme_os_path:
2N/A defdict['GRUB_THEME'] = self.theme_os_path
2N/A
2N/A return defdict
2N/A
2N/A
2N/A def _generate_grub_defs_dict_cons(self, target):
2N/A """Generate a dictionary with grub defaults pertaining to the console.
2N/A """
2N/A defdict = {}
2N/A
2N/A # Regardless of the type of the GRUB2 console, always set the video
2N/A # backend; Solaris uses a framebuffer console by default and GRUB2
2N/A # may be called upon to set the graphics mode.
2N/A video_backend = GRUB2BootLoader.VIDEO_BACKENDS.get(target)
2N/A if video_backend:
2N/A defdict['GRUB_VIDEO_BACKEND'] = video_backend
2N/A
2N/A # The gfxpayload modelist should be set regardless of the GRUB2
2N/A # console type also, since we may have Solaris instances that use
2N/A # framebuffer console that need this variable set in a specific
2N/A # part of the menu entry
2N/A defdict['GRUB_GFXPAYLOAD_MODE'] = GRUB2BootLoader.GFX_MODELIST
2N/A defdict['GRUB_GFXMODE'] = GRUB2BootLoader.GFX_MODELIST
2N/A
2N/A cons = self.getprop(BootLoader.PROP_CONSOLE)
2N/A if cons:
2N/A if cons == BootLoader.PROP_CONSOLE_TEXT:
2N/A # 'console' is the default GRUB2 input terminal type. gfxterm
2N/A # is the default output terminal type if none are specified.
2N/A interm = None
2N/A outterm = 'console'
2N/A elif cons == BootLoader.PROP_CONSOLE_GFX:
2N/A interm = None
2N/A outterm = 'gfxterm'
2N/A
2N/A defdict.update(self._gen_grub_defs_dict_cons_gfx())
2N/A
2N/A elif cons == BootLoader.PROP_CONSOLE_SERIAL:
2N/A interm = 'serial'
2N/A outterm = 'serial'
2N/A
2N/A serial_params = self.getprop(BootLoader.PROP_SERIAL_PARAMS)
2N/A serial_cmd = self._grub2_serial_cmd(serial_params)
2N/A defdict['GRUB_SERIAL_COMMAND'] = serial_cmd
2N/A else:
2N/A self._debug('Unknown console property: %s', cons)
2N/A interm = None
2N/A outterm = None
2N/A
2N/A if interm:
2N/A defdict['GRUB_TERMINAL_INPUT'] = interm
2N/A if outterm:
2N/A defdict['GRUB_TERMINAL_OUTPUT'] = outterm
2N/A
2N/A return defdict
2N/A
2N/A def _generate_grub_defs_dict(self, target):
2N/A """Generate a dictionary of grub default keys and their values.
2N/A """
2N/A defdict = {}
2N/A
2N/A defdict.update(self._generate_grub_defs_dict_cons(target))
2N/A
2N/A timeout = self.getprop(BootLoader.PROP_TIMEOUT)
2N/A if timeout == '':
2N/A timeout = '0'
2N/A elif timeout is None:
2N/A timeout = GRUB2BootLoader.DEFAULT_TIMEOUT
2N/A defdict['GRUB_TIMEOUT'] = timeout
2N/A
2N/A quiet = self.getprop(BootLoader.PROP_QUIET)
2N/A if (quiet and type(quiet) == bool) or parse_bool(quiet):
2N/A defdict['GRUB_HIDDEN_TIMEOUT'] = 'true' # Must be lower case
2N/A
2N/A # Set the default entry based on the list of default boot instances
2N/A if self._boot_config and self._boot_config.boot_instances:
2N/A default_idxs = [idx for idx, x in
2N/A enumerate(self._boot_config.boot_instances)
2N/A if getattr(x, 'default', False) == True]
2N/A if default_idxs:
2N/A defdict['GRUB_DEFAULT'] = str(default_idxs[0])
2N/A
2N/A return defdict
2N/A
2N/A @staticmethod
2N/A def _grub2_color(color):
2N/A """Convert a string of hex digits into a GRUB2 color specification.
2N/A """
2N/A if len(color) != 6:
2N/A color = ('0' * (6 - len(color))) + color
2N/A red = color[0:2]
2N/A green = color[2:4]
2N/A blue = color[4:6]
2N/A return '0x%s,0x%s,0x%s' % (red, green, blue)
2N/A
2N/A def _grub2_serial_cmd(self, params):
2N/A """Convert the serial parameters property tuple into a GRUB2 serial
2N/A command. If params is None, the default serial command is used.
2N/A
2N/A The form of the serial_params property is:
2N/A
2N/A serial_params | A tuple containing (<portspec>,<speed>,<d>,
2N/A | <p>,<s>,<f>).
2N/A | <portspec> is currently defined to be a
2N/A | number (valid values depend on the platform,
2N/A | but '0' is ttya (com1) and '1' is ttyb (com2)).
2N/A | Serial console parameters (<d>=data bits,
2N/A | <p>=parity ('N','E','O'),<s>=stop bits ('0','1'),
2N/A | <f>=flow control ('H','S',None) for hardware,
2N/A | software, or none). The default is:
2N/A | ('0',None,None,None,None,None).
2N/A """
2N/A
2N/A parity_dict = {'N': 'no', 'E': 'even', 'O': 'odd'}
2N/A cmd = 'serial'
2N/A
2N/A if params is None:
2N/A self._debug('Serial parameters are None: Using defaults')
2N/A return cmd + ' --unit 0'
2N/A
2N/A if not params or type(params) != tuple:
2N/A self._debug('Invalid serial parameters: %s', str(params))
2N/A return cmd
2N/A
2N/A if len(params) > 0 and params[0] is not None:
2N/A # If the port number is larger than 0xFF, assume it's an
2N/A # IO port number and use --port instead of --unit:
2N/A if int(params[0]) > 0xFF:
2N/A cmd += ' --port %s' % hex(int(params[0]))
2N/A else:
2N/A cmd += ' --unit %s' % params[0]
2N/A if len(params) > 1 and params[1] is not None:
2N/A cmd += ' --speed %s' % params[1]
2N/A if len(params) > 2 and params[2] is not None:
2N/A cmd += ' --word %s' % params[2]
2N/A if len(params) > 3 and params[3] is not None:
2N/A cmd += ' --parity %s' % parity_dict.get(params[3], 'no')
2N/A if len(params) > 4 and params[4] is not None:
2N/A cmd += ' --stop %s' % params[4]
2N/A
2N/A return cmd
2N/A
2N/A
2N/A def _assemble_grub_theme_files(self, basepath, theme_file):
2N/A """Copy all files associated with the theme whose theme
2N/A file is specified into the proper location. Returns a 3-tuple of
2N/A (1) tuples (if basepath is set) (or None if not), and (2)
2N/A the *GRUB path* to the theme_file (the GRUB path will be used
2N/A directly in the grub.cfg file) and (3) The OS path to the
2N/A theme_file.
2N/A """
2N/A
2N/A tuple_list = []
2N/A source_dir = os.path.dirname(os.path.join(
2N/A self._get_boot_loader_data_root(), theme_file))
2N/A
2N/A theme_name = os.path.basename(source_dir)
2N/A
2N/A theme_file_basename = os.path.basename(theme_file)
2N/A
2N/A grub_path = '/' + GRUB2BootLoader.BOOT_GRUB_THEME_SUBDIR + '/' + \
2N/A theme_name + '/' + theme_file_basename
2N/A
2N/A if (self._boot_config.boot_class == BootConfig.BOOT_CLASS_DISK and
2N/A self._boot_config.boot_fstype == 'zfs'):
2N/A grub_path_prefix = '/@'
2N/A grub_path = grub_path_prefix + grub_path
2N/A else:
2N/A grub_path_prefix = ''
2N/A
2N/A self._debug('THEME: src=%s, theme=%s, theme_bn=%s, grub_path=%s ',
2N/A (source_dir, theme_name, theme_file_basename, grub_path))
2N/A
2N/A if basepath:
2N/A dest_dir = os.path.join(basepath,
2N/A GRUB2BootLoader.BOOT_GRUB_THEME_SUBDIR)
2N/A
2N/A dest_theme_dir = os.path.join(dest_dir, theme_name)
2N/A
2N/A self._make_dirtree(dest_dir)
2N/A
2N/A theme_fonts_set = set()
2N/A theme_mods_set = set()
2N/A
2N/A def collect_tuples(dirnm, listing):
2N/A "dirnm is the source theme's directory"
2N/A for entry in listing:
2N/A source_file = os.path.join(dirnm, entry)
2N/A if not os.path.isfile(source_file):
2N/A continue
2N/A
2N/A destcomponent = source_file.replace(source_dir + '/', '', 1)
2N/A root_rel_path = os.path.join('/',
2N/A GRUB2BootLoader.BOOT_GRUB_THEME_SUBDIR, theme_name,
2N/A destcomponent)
2N/A
2N/A newtup = (BootConfig.OUTPUT_TYPE_FILE,
2N/A os.path.join(dest_theme_dir, destcomponent),
2N/A None,
2N/A '%(' + self._get_root_token() + ')s' +
2N/A root_rel_path,
2N/A 'root', 'bin', 0644)
2N/A self._debug('THEME: New tuple: %s', str(newtup))
2N/A tuple_list.append(newtup)
2N/A
2N/A extension = source_file.rsplit('.', 1)
2N/A if len(extension) > 1:
2N/A extn = extension[1]
2N/A modname = GRUB2BootLoader.EXTN_TO_MODULE.get(extn)
2N/A if modname:
2N/A theme_mods_set.add(modname)
2N/A elif extn in GRUB2BootLoader.FONT_EXTN_LIST:
2N/A fontpath = os.path.join(grub_path_prefix,
2N/A root_rel_path)
2N/A theme_fonts_set.add(fontpath)
2N/A
2N/A return []
2N/A
2N/A # We're doing a directory copy of an entire theme. If the
2N/A # destination exists, blow it away first, or the copytree will fail:
2N/A shutil.rmtree(dest_theme_dir, ignore_errors=True)
2N/A
2N/A # Iterate through the source directory, using collect_tuples to
2N/A # collect the list of file tuples returned to the caller.
2N/A try:
2N/A self._debug('THEME: Copying %s -> %s', (source_dir,
2N/A dest_theme_dir))
2N/A shutil.copytree(source_dir, dest_theme_dir,
2N/A ignore=collect_tuples)
2N/A except (shutil.Error, OSError) as copyerr:
2N/A self._debug('Error during theme copy: %s', copyerr)
2N/A shutil.rmtree(dest_theme_dir, ignore_errors=True)
2N/A raise
2N/A
2N/A # The OS path to the theme file is the destination path
2N/A os_path = os.path.join(dest_theme_dir, theme_file_basename)
2N/A
2N/A self.theme_mods_set = theme_mods_set
2N/A self.theme_fonts_set = theme_fonts_set
2N/A
2N/A return tuple_list, grub_path, os_path
2N/A
2N/A if self._boot_config.boot_class != BootConfig.BOOT_CLASS_DISK:
2N/A return None, None, None
2N/A
2N/A dest_dir = os.path.join(self._config_dir_disk(),
2N/A GRUB2BootLoader.THEME_SUBDIR)
2N/A
2N/A self._make_dirtree(dest_dir)
2N/A
2N/A dest_theme_dir = os.path.join(dest_dir, theme_name)
2N/A
2N/A
2N/A # If the source directory does not exist, but the destination DOES,
2N/A # check to see if the theme file exists, and if it does, just use the
2N/A # existing theme in the destination. This covers the case where
2N/A # pybootmgmt is used in conjunction with a data source that does not
2N/A # include the specified theme.
2N/A
2N/A # The OS path to the theme file is the destination path
2N/A os_path = os.path.join(dest_theme_dir, theme_file_basename)
2N/A
2N/A if os.path.exists(source_dir):
2N/A # We're doing a directory copy of an entire theme. If the
2N/A # destination exists, blow it away first, or the copytree will fail:
2N/A shutil.rmtree(dest_theme_dir, ignore_errors=True)
2N/A
2N/A try:
2N/A self._debug('THEME: Copying %s -> %s', (source_dir,
2N/A dest_theme_dir))
2N/A shutil.copytree(source_dir, dest_theme_dir)
2N/A except (shutil.Error, OSError) as copyerr:
2N/A self._debug('Error during theme copy: %s', copyerr)
2N/A shutil.rmtree(dest_theme_dir, ignore_errors=True)
2N/A raise
2N/A elif not os.path.exists(os_path):
2N/A self._debug('Could not fall back to existing theme in %s',
2N/A os_path)
2N/A return None, None, None
2N/A else:
2N/A self._debug('Falling back to existing theme in %s',
2N/A os_path)
2N/A
2N/A return None, grub_path, os_path
2N/A
2N/A
2N/A def _assemble_other_grub_files(self, basepath):
2N/A """Copy other needed GRUB2 files into the proper location.
2N/A Return the appropriate tuples (if basepath is set).
2N/A """
2N/A tuple_list = self._assemble_grub_files_generic(basepath,
2N/A GRUB2BootLoader.GRUB_OTHERS)
2N/A
2N/A # Assemble the theme files, if necessary
2N/A constype = self.getprop(BootLoader.PROP_CONSOLE)
2N/A theme_tuples = None
2N/A if constype == BootLoader.PROP_CONSOLE_GFX:
2N/A default_theme = False
2N/A if BootLoader.PROP_THEME in self._bl_props:
2N/A themename = self.getprop(BootLoader.PROP_THEME)
2N/A # theme name alphanumeric validation was done in BootLoader
2N/A if themename:
2N/A theme_file = GRUB2BootLoader.THEME_TEMPLATE % themename
2N/A else:
2N/A theme_file = None
2N/A else: # No theme property, so default to enable themeing with
2N/A # the default theme.
2N/A theme_file = GRUB2BootLoader.THEME_DEFAULT_PATH
2N/A default_theme = True
2N/A
2N/A try:
2N/A if theme_file:
2N/A theme_tuples, self.theme_grub_path, self.theme_os_path = \
2N/A self._assemble_grub_theme_files(basepath, theme_file)
2N/A except (IOError, OSError) as copyerr:
2N/A self._debug('Exception while assembling theme files: %s',
2N/A copyerr)
2N/A # If this is the default theme and we hit an error, quietly
2N/A # revert to non-themed graphical menus.
2N/A if not default_theme:
2N/A raise
2N/A
2N/A if tuple_list:
2N/A if theme_tuples:
2N/A tuple_list.extend(theme_tuples)
2N/A return tuple_list
2N/A if theme_tuples:
2N/A return theme_tuples
2N/A
2N/A def _assemble_grub_files_generic(self, basepath, file_tuples):
2N/A """Using the list of 3-tuples passed in file_tuples, copy those files
2N/A to proper destination with the proper modes. This is suitable for
2N/A DiskBootConfig and ODDBootConfig only.
2N/A """
2N/A tuple_list = []
2N/A for src, dest, perms in file_tuples:
2N/A src = os.path.join(self._get_boot_loader_data_root(), src)
2N/A
2N/A if basepath:
2N/A destdir = os.path.join(basepath,
2N/A GRUB2BootLoader.BOOT_GRUB_SUBDIR)
2N/A else:
2N/A destdir = self.config_dir
2N/A
2N/A fulldest = os.path.join(destdir, dest)
2N/A self._debug('src = %s, dest = %s', (src, fulldest))
2N/A self._make_basedir(fulldest)
2N/A # If perms is None in the 3-tuple
2N/A if not perms:
2N/A perms = GRUB2BootLoader.GRUB_OTHERS_PERM_DEFAULTS
2N/A try:
2N/A shutil.copy(src, fulldest)
2N/A except (IOError, shutil.Error) as copyerr:
2N/A self._debug('Could not copy %s for GRUB2: %s', (src, copyerr))
2N/A continue
2N/A
2N/A # Skip the chmod/chown if we're writing to an intermediate
2N/A # location
2N/A if not basepath:
2N/A try:
2N/A os.chmod(fulldest, perms[2])
2N/A os.chown(fulldest, pwd.getpwnam(perms[0]).pw_uid,
2N/A grp.getgrnam(perms[1]).gr_gid)
2N/A except OSError as oserr:
2N/A raise BootmgmtConfigWriteError("Couldn't set mode/perms on"
2N/A " %s" % fulldest, oserr)
2N/A else:
2N/A token = self._get_root_token()
2N/A if token:
2N/A final_path = '%(' + token + ')s/'
2N/A else:
2N/A final_path = '/'
2N/A final_path += GRUB2BootLoader.BOOT_GRUB_SUBDIR + '/' + dest
2N/A
2N/A tuple_list.append((BootConfig.OUTPUT_TYPE_FILE, fulldest, None,
2N/A final_path, perms[0], perms[1], perms[2]))
2N/A
2N/A if tuple_list:
2N/A return tuple_list
2N/A
2N/A def _internal_mkconfig(self, target, defs_dict, outfile):
2N/A """A stripped-down barebones implementation of a GRUB2 menu generator.
2N/A First, a set of header script code is emitted (its contents depends on
2N/A the values in the defs_dict (a dict representation of the
2N/A $prefix/etc/default/grub file), followed by the menuentries that
2N/A correspond to the boot configuration's boot_instances list.
2N/A """
2N/A outfile.write('# GRUB2 configuration file\n\n')
2N/A
2N/A emitter = GRUB2MenuEntryEmitter(outfile, defs_dict)
2N/A
2N/A self._internal_mkconfig_header(defs_dict, outfile)
2N/A
2N/A for bootinst in self._boot_config.boot_instances:
2N/A emitter.write_menuentry(bootinst, target)
2N/A outfile.write('\n')
2N/A
2N/A if 'NO_CUSTOM_CFG' not in defs_dict:
2N/A outfile.write('\n'
2N/A 'if [ -f /boot/grub/custom.cfg ]; then\n'
2N/A '\tsource /boot/grub/custom.cfg\n'
2N/A 'fi\n')
2N/A
2N/A @staticmethod
2N/A def _grub_cfg_preamble(defs_dict):
2N/A """Returns the list of lines in the script preamble stored in
2N/A the definitions dictionary passed in.
2N/A """
2N/A preamble = defs_dict.get('__SCRIPT_PREAMBLE')
2N/A if preamble:
2N/A if isinstance(preamble, (list, tuple)):
2N/A return preamble
2N/A elif isinstance(preamble, basestring):
2N/A return preamble.split('\n')
2N/A return []
2N/A
2N/A # pylint: disable=R0912
2N/A def _internal_mkconfig_header(self, defs_dict, outfile):
2N/A """Output the grub.cfg header for this target based on the defs_dict.
2N/A """
2N/A lines = []
2N/A
2N/A # If there is a preamble specified in the defs_dict, add that first:
2N/A lines.extend(self._grub_cfg_preamble(defs_dict))
2N/A
2N/A # First, output the script for initializing the console device(s):
2N/A # load_video_$target is defined in the stub grub.cfg files (or the
2N/A # grub.cfg directly for NetBootConfig).
2N/A # NOTE: We always load video drivers because Solaris may need it
2N/A # for framebuffer console.
2N/A lines.append('load_video_$target')
2N/A
2N/A outdev = defs_dict.get('GRUB_TERMINAL_OUTPUT')
2N/A indev = defs_dict.get('GRUB_TERMINAL_INPUT')
2N/A initted_devs = []
2N/A if outdev:
2N/A outdevs = outdev.split()
2N/A # Initting gfxterm must be done after all other console devices
2N/A # are initted because the terminal_output statement is embedded
2N/A # in the gfxterm init script block.
2N/A if 'gfxterm' in outdevs:
2N/A outdevs.remove('gfxterm')
2N/A do_gfxterm = True
2N/A else:
2N/A do_gfxterm = False
2N/A
2N/A for dev in outdevs:
2N/A conslines = self._mkcfg_consdev(dev, outdev, defs_dict)
2N/A lines.extend(conslines)
2N/A initted_devs += [dev]
2N/A
2N/A if do_gfxterm:
2N/A conslines = self._mkcfg_consdev('gfxterm', outdev, defs_dict)
2N/A lines.extend(conslines)
2N/A initted_devs += 'gfxterm'
2N/A else:
2N/A lines.append('terminal_output %s' % outdev)
2N/A
2N/A if indev:
2N/A for dev in indev.split():
2N/A if dev in initted_devs:
2N/A continue
2N/A conslines = self._mkcfg_consdev(dev, indev, defs_dict, False)
2N/A lines.extend(conslines)
2N/A initted_devs += dev
2N/A lines.append('terminal_input %s' % indev)
2N/A
2N/A if lines[-1] != '':
2N/A lines.append('')
2N/A
2N/A # Now add the timeout and quiet script:
2N/A quiet = defs_dict.get('GRUB_HIDDEN_TIMEOUT')
2N/A timeout = defs_dict.get('GRUB_TIMEOUT')
2N/A if quiet:
2N/A lines.append('if sleep --verbose --interruptible %s; then' %
2N/A timeout)
2N/A lines.append('\tset timeout=0')
2N/A lines.append('fi')
2N/A else:
2N/A lines.append('set timeout=%s' % timeout)
2N/A
2N/A lines.append('')
2N/A
2N/A # finally, the default:
2N/A default = defs_dict.get('GRUB_DEFAULT')
2N/A if default:
2N/A lines.append('set default="%s"' % default)
2N/A lines.append('')
2N/A
2N/A for line in lines:
2N/A outfile.write(line)
2N/A outfile.write('\n')
2N/A
2N/A def _mkcfg_consdev(self, dev, alldevs, defs_dict, outdev=True):
2N/A """Return a list of lines that init the specified terminal device
2N/A """
2N/A lines = []
2N/A
2N/A if dev == 'gfxterm' and outdev:
2N/A # The caller knows that gfxterm must be the last console device
2N/A # processed (due to the use of terminal_output statements)
2N/A
2N/A # We have several potential properties to handle here:
2N/A # GRUB_VIDEO_BACKEND, GRUB_BACKGROUND, GRUB_FORECOLOR, and
2N/A # GRUB_BACKCOLOR
2N/A modes = defs_dict.get('GRUB_GFXMODE')
2N/A backimg = defs_dict.get('GRUB_BACKGROUND')
2N/A forecolor = defs_dict.get('GRUB_FORECOLOR')
2N/A backcolor = defs_dict.get('GRUB_BACKCOLOR')
2N/A
2N/A lines.append('insmod gfxterm')
2N/A if modes:
2N/A lines.append("set gfxmode='%s'" % modes)
2N/A
2N/A loaded_mods = []
2N/A
2N/A # Add GRUB theme dependencies here:
2N/A themefile = defs_dict.get('GRUB_THEME_DIRECT')
2N/A if themefile:
2N/A # Emit module load lines for all required modules:
2N/A if self.theme_mods_set:
2N/A for modname in self.theme_mods_set:
2N/A lines.append('insmod %s' % modname)
2N/A loaded_mods.append(modname)
2N/A if self.theme_fonts_set:
2N/A for fontpath in self.theme_fonts_set:
2N/A lines.append('if ! loadfont %s ; then' % fontpath)
2N/A lines.append('\tset theme_dep_load_failed=1')
2N/A lines.append('fi')
2N/A # If loading the default font fails, it's not fatal
2N/A lines.append('if [ x$theme_dep_load_failed = x ] ; then')
2N/A lines.append('\tloadfont /boot/grub/unicode.pf2')
2N/A lines.append('\tset theme=%s' % themefile)
2N/A lines.append('\tterminal_output %s' % alldevs)
2N/A lines.append('else')
2N/A indent = '\t'
2N/A need_fi = True
2N/A else:
2N/A indent = ''
2N/A need_fi = False
2N/A
2N/A # Add the font-load conditional statement:
2N/A # XXX - Revisit the hard-coded font path here?
2N/A lines.append(indent + 'if loadfont /boot/grub/unicode.pf2 ; then')
2N/A lines.append(indent + '\tterminal_output %s' % alldevs)
2N/A # fore/back color must be set BEFORE the background image
2N/A if forecolor:
2N/A lines.append(indent + '\tforeground_color %s' % forecolor)
2N/A if backcolor:
2N/A lines.append(indent + '\tbackground_color %s' % backcolor)
2N/A if backimg:
2N/A extn = backimg.rsplit('.', 1)
2N/A if len(extn) > 1:
2N/A imgmod = GRUB2BootLoader.EXTN_TO_MODULE.get(extn[1])
2N/A if imgmod and imgmod not in loaded_mods:
2N/A lines.append(indent + '\tinsmod %s' % imgmod)
2N/A # Loading the background image requires us to first load
2N/A # modules needed to access the filesystem on which it's stored
2N/A lines.append(indent + '\tbackground_image -m stretch %s' %
2N/A backimg)
2N/A # Close the conditional
2N/A alldevs_minus_gfxterm = alldevs.replace('gfxterm', '').strip()
2N/A if alldevs_minus_gfxterm:
2N/A lines.append(indent + 'else')
2N/A lines.append(indent + '\tterminal_output %s' %
2N/A alldevs_minus_gfxterm)
2N/A lines.append(indent + 'fi')
2N/A if need_fi: # If we need a fi (if theme code was emitted above)
2N/A lines.append('fi')
2N/A
2N/A elif dev == 'serial':
2N/A serialcmd = defs_dict.get('GRUB_SERIAL_COMMAND')
2N/A if serialcmd:
2N/A lines.append(serialcmd)
2N/A else:
2N/A self._debug('No serial command specified -- using default')
2N/A lines.append('serial') # default serial command
2N/A
2N/A return lines
2N/A # pylint: enable=R0912
2N/A
2N/A def _write_config_odd(self, basepath):
2N/A """To generate the grub.cfg file for an ODDBootConfig, we create the
2N/A menu by hand (we do NOT use the grub-mkconfig program).
2N/A """
2N/A if not basepath:
2N/A raise BootmgmtInterfaceCodingError('basepath must be specified for '
2N/A 'ODDBootConfig')
2N/A target_list = self._get_target_list()
2N/A tuple_list = []
2N/A
2N/A if target_list:
2N/A of_tuples = self._assemble_other_grub_files(basepath)
2N/A if of_tuples:
2N/A tuple_list.extend(of_tuples)
2N/A
2N/A generated_grub_cfg = False
2N/A for target in target_list:
2N/A loader_tuples = self._create_grub2_image(target, basepath)
2N/A
2N/A defs_dict = self._generate_grub_defs_dict(target)
2N/A if not generated_grub_cfg:
2N/A # It doesn't matter which target's defs_dict we use here
2N/A # because _generate_grub_cfg is guaranteed to generate a
2N/A # config file without direct target-specific code (there may
2N/A # be references to things defined in the target-specific stubs
2N/A # though)
2N/A gcfg_tuples = self._generate_grub_cfg(target, basepath,
2N/A defs_dict)
2N/A if gcfg_tuples:
2N/A tuple_list.extend(gcfg_tuples)
2N/A generated_grub_cfg = True
2N/A # The stub files are the small script files that do target-specific
2N/A # things, then source the main config file
2N/A stub_list = self._generate_stub_files(target, basepath,
2N/A def_dict=defs_dict)
2N/A if stub_list:
2N/A tuple_list.extend(stub_list)
2N/A
2N/A if loader_tuples:
2N/A tuple_list.extend(loader_tuples)
2N/A
2N/A return tuple_list
2N/A
2N/A
2N/A def _write_config_net(self, basepath):
2N/A """To generate the grub.cfg file for a NetBootConfig, we create the
2N/A menu by hand (we do NOT use the grub-mkconfig program). Since we will
2N/A not use a graphical console while network booting, we do not need
2N/A to assemble other files (i.e. GRUB2 font files and splash screen image).
2N/A """
2N/A if not basepath:
2N/A raise BootmgmtInterfaceCodingError('basepath must be specified for '
2N/A 'NetBootConfig')
2N/A target_list = self._get_target_list()
2N/A tuple_list = []
2N/A
2N/A menu_conf_tuple_list = self._write_menu_conf(basepath)
2N/A if menu_conf_tuple_list:
2N/A tuple_list.extend(menu_conf_tuple_list)
2N/A
2N/A preamble = \
2N/A"""# begin preamble
2N/Aregexp ".*/(.*)" $prefix -s 1:target
2N/Atr -s target - _ "$target"
2N/Aif [ -z "$target" -o "$target" = "%(bios)s" ]; then
2N/A set multiboot=multiboot
2N/A set module=module
2N/Aelif [ "$target" = "%(uefi64)s" ]; then
2N/A set multiboot=multiboot2
2N/A set module=module2
2N/Afi
2N/A""" % GRUB2BootLoader.GRUB_TARGET_STRINGS
2N/A target_dicts = {}
2N/A
2N/A # Generate the target def dictionaries and the video driver load
2N/A # code so we can use them in the loop below.
2N/A for target in target_list:
2N/A target_dicts[target] = self._generate_grub_defs_dict(target)
2N/A preamble += self._generate_grub2_load_video_func(target,
2N/A target_dicts[target]) + "\n"
2N/A
2N/A preamble += "# end preamble"
2N/A
2N/A generated_grub_cfg = False
2N/A for target in target_list:
2N/A loader_tuples = self._create_grub2_image(target, basepath)
2N/A if loader_tuples:
2N/A tuple_list.extend(loader_tuples)
2N/A
2N/A if not generated_grub_cfg:
2N/A # It doesn't matter which target's defs_dict we use here
2N/A # because _generate_grub_cfg is guaranteed to generate a
2N/A # config file without direct target-specific code.
2N/A defs_dict = target_dicts[target]
2N/A # Elide the final stanza that searches for the custom.cfg --
2N/A # we have no use for it in a network boot scenario.
2N/A defs_dict['NO_CUSTOM_CFG'] = True
2N/A # Add the script preamble, which establishes the platform
2N/A # on which the script is executing and sets some key variables
2N/A # that the generated grub configuration file uses.
2N/A defs_dict['__SCRIPT_PREAMBLE'] = preamble
2N/A gcfg_tuples = self._generate_grub_cfg(target, basepath,
2N/A defs_dict, self._boot_config.pxe_suffix())
2N/A if gcfg_tuples:
2N/A # The grub_cfg tuples must be marked specially, so that
2N/A # the consumer knows that they can be placed in the
2N/A # tftp boot loader search path
2N/A config_tuples = []
2N/A for tupl in gcfg_tuples:
2N/A lst = list(tupl)
2N/A if lst[BootConfig.IDX_FILETYPE] == \
2N/A BootConfig.OUTPUT_TYPE_FILE:
2N/A lst[BootConfig.IDX_FILETYPE] = \
2N/A BootConfig.OUTPUT_TYPE_NETCONFIG
2N/A config_tuples.append(tuple(lst))
2N/A tuple_list.extend(config_tuples)
2N/A generated_grub_cfg = True
2N/A
2N/A return tuple_list
2N/A
2N/A # Override install so we can update the UEFI boot variables with the
2N/A # device list passed in. Failure to set the boot variables is NOT a
2N/A # fatal error; it will, however, be logged to syslog.
2N/A def install(self, location, force=False, verbose_file=None, platdict=None):
2N/A """
2N/A """
2N/A tuple_list = super(GRUB2BootLoader, self).install(location, force,
2N/A verbose_file, platdict)
2N/A
2N/A # No need to go further if we have tuples to return (no install
2N/A # was done to an actual device) or if location is empty
2N/A if tuple_list or not location:
2N/A return tuple_list
2N/A
2N/A # If this is not a disk-based boot configuration, or if there were
2N/A # tuples returned from the main install() method (which implies we
2N/A # did not install onto actual physical devices) or if the system
2N/A # firmware is not UEFI, just return the tuple_list, if any.
2N/A if (self._boot_config.boot_class != BootConfig.BOOT_CLASS_DISK or
2N/A self.firmware is None or 'uefi' not in self.firmware.fw_name):
2N/A self._debug('No attempt will be made to set the boot order')
2N/A return None
2N/A
2N/A try:
2N/A self.firmware.setprop(SystemFirmware.PROP_BOOT_DEVICE, location)
2N/A self._debug('UEFI boot variables successfully updated')
2N/A except BootmgmtError as bmerr:
2N/A self._debug('Error updating system boot device list: %s',
2N/A str(bmerr))
2N/A syslog.openlog('pybootmgmt-GRUB2', 0, syslog.LOG_DAEMON)
2N/A syslog.syslog(syslog.LOG_WARNING, 'Failed to set the UEFI boot '
2N/A 'device to %s. Manual intervention may be required '
2N/A 'to boot Solaris when the system restarts.' % str(location))
2N/A syslog.closelog()
2N/A
2N/A return None
2N/A
2N/A def _write_loader(self, devname, data_root, force, verbose_file, platdict):
2N/A """Write the GRUB2 boot loader to disk. We support 4 scenarios:
2N/A (1) BIOS systems, installation onto a DOS-partitioned disk with
2N/A a Solaris partition -- boot loader is embedded in the Solaris
2N/A boot slice (usually slice 8, but really it's any slice that
2N/A includes cylinder 0). The devname passed in can be directly
2N/A passed to grub-install, as grub-setup has been modified to write
2N/A the loader to the appropriate place in the Solaris partition;
2N/A (2) BIOS systems, installation onto a GPT-partitioned disk. A
2N/A BIOS Boot Partition (BBP) is required. The devname passed in
2N/A refers to the partition that includes the ZFS, not the BBP, so
2N/A we need to figure out if we're on a GPT-partitioned disk, and
2N/A pass the whole-disk node (p0) to grub-install so *IT* can
2N/A locate the BBP and install GRUB2 there;
2N/A (3) UEFI systems, installation onto a DOS-partitioned disk. The
2N/A device node refers to a VTOC slice, so we need to get the device
2N/A node for the EFI System Partition, mount it (if it's not already
2N/A mounted), and invoke grub-install, telling it to install the EFI
2N/A boot application under EFI/ORACLE/ on the ESP;
2N/A (4) UEFI systems, installation onto a GPT-partitioned disk.
2N/A Similar to (3).
2N/A """
2N/A
2N/A if not isinstance(self._boot_config, DiskBootConfig):
2N/A self._debug('Ignoring _write_loader() for non-DiskBootConfig')
2N/A return
2N/A
2N/A if not ((len(devname) > 2 and devname[-2] == 's' and
2N/A devname[-1].isdigit())
2N/A or
2N/A (len(devname) > 3 and devname[-3] == 's' and
2N/A devname[-2:].isdigit())):
2N/A raise BootLoaderInstallError('Device node is not a slice: ' +
2N/A devname)
2N/A
2N/A target = self.firmware.fw_name
2N/A prefixdir = os.path.join(self.config_dir,
2N/A GRUB2BootLoader.GRUB_BOOT_SUBDIRS[target])
2N/A fulldatadir = os.path.join(data_root,
2N/A GRUB2BootLoader.GRUB_DATA_PATHS[target])
2N/A
2N/A # The prefixdir MUST exist, otherwise grub-install will fail:
2N/A self._make_dirtree(prefixdir)
2N/A
2N/A grub_inst_cmd = [os.path.join('/',
2N/A GRUB2BootLoader.GRUB_PLATFORM_PREFIXES[target],
2N/A GRUB2BootLoader.GRUB_INSTALL), '--no-floppy',
2N/A '--grub-directory=%s' % prefixdir,
2N/A '--pkglibdir=%s' % fulldatadir]
2N/A
2N/A preloads = GRUB2BootLoader.PRELOADED_MODULES[target]
2N/A if preloads:
2N/A grub_inst_cmd.append('--modules=%s' % ' '.join(preloads))
2N/A
2N/A if force:
2N/A grub_inst_cmd.append('--no-check-versioning')
2N/A
2N/A if verbose_file:
2N/A grub_inst_cmd.append('--debug')
2N/A
2N/A if (isinstance(platdict, dict) and platdict.get(BootLoader.PLATOPT_MBR)
2N/A and target == 'bios'):
2N/A grub_inst_cmd.append('--force-mbr')
2N/A
2N/A base_devname = devname[:devname.rfind('s')]
2N/A wholedisk = base_devname + 'p0'
2N/A is_gpt = is_gpt_disk(wholedisk)
2N/A
2N/A if target == 'bios':
2N/A if is_gpt:
2N/A instdev = wholedisk
2N/A else:
2N/A instdev = devname
2N/A elif target == 'uefi64':
2N/A # We need to derive the EFI System Partition from the device
2N/A # passed in, mount it (if it's not already mounted), and pass
2N/A # the mountpoint as the instdev
2N/A esp_dev = find_efi_system_partition(devname, is_gpt)
2N/A if not esp_dev:
2N/A raise BootLoaderInstallError('Could not determine the EFI '
2N/A 'System Partition (ESP) for device %s' % devname)
2N/A
2N/A esp_mountpt = None
2N/A esp_tempdir = None
2N/A try:
2N/A mnttab_open()
2N/A mntentdict = getmntany(mnt_special=esp_dev)
2N/A if mntentdict:
2N/A esp_mountpt = mntentdict['mnt_mountp']
2N/A mnttab_close()
2N/A except IOError as ioerr:
2N/A # If there was a problem trying to determine if the ESP is
2N/A # already mounted, proceed anyway -- we'll fail during the
2N/A # mount if it was already mounted.
2N/A pass
2N/A
2N/A if not esp_mountpt:
2N/A try:
2N/A esp_tempdir = tempfile.mkdtemp(dir='/system/volatile')
2N/A except IOError as ioerr:
2N/A raise BootLoaderInstallError('Error while trying to create '
2N/A 'a temporary dir for mounting the ESP', ioerr)
2N/A
2N/A esp_mountpt = esp_tempdir
2N/A mount_cmd = ['/usr/lib/fs/pcfs/mount', esp_dev, esp_mountpt]
2N/A try:
2N/A self._execute_cmd(mount_cmd)
2N/A except BootmgmtError:
2N/A try:
2N/A os.rmdir(esp_tempdir)
2N/A except OSError:
2N/A pass
2N/A raise BootLoaderInstallError('Error while trying to mount '
2N/A 'the EFI System Partition (%s)' % esp_dev)
2N/A need_unmount = True
2N/A else:
2N/A need_unmount = False
2N/A instdev = esp_mountpt + '/EFI/ORACLE'
2N/A
2N/A default_bootapp = esp_mountpt + '/efi/boot/bootx64.efi'
2N/A oracle_bootapp = instdev + '/grubx64.efi'
2N/A try:
2N/A default_statinfo = os.stat(default_bootapp)
2N/A except OSError:
2N/A default_statinfo = None
2N/A
2N/A if default_statinfo is None:
2N/A copy_efi_boot_to_default = True
2N/A else:
2N/A copy_efi_boot_to_default = False
2N/A try:
2N/A if filecmp.cmp(default_bootapp, oracle_bootapp,
2N/A shallow=False):
2N/A copy_efi_boot_to_default = True
2N/A except OSError:
2N/A pass
2N/A else:
2N/A raise BootmgmtUnsupportedPlatformError(target +
2N/A ' is not supported')
2N/A
2N/A grub_inst_cmd.append(instdev)
2N/A
2N/A cmd_succeeded = True # Optimism
2N/A
2N/A try:
2N/A if verbose_file:
2N/A print >> verbose_file, 'Output from boot loader ' \
2N/A 'installation command "%s" is:' % ' '.join(grub_inst_cmd)
2N/A cmdout, cmderr = self._execute_cmd(grub_inst_cmd,
2N/A return_stdout=True, return_stderr=True)
2N/A print >> verbose_file, '<STDOUT>:\n%s' % cmdout,
2N/A print >> verbose_file, '<STDERR>:\n%s' % cmderr,
2N/A print >> verbose_file, '<END OF OUTPUT>'
2N/A else:
2N/A self._execute_cmd(grub_inst_cmd, 'Installing boot loader',
2N/A 'Boot loader installation')
2N/A
2N/A except BootmgmtConfigWriteError as bmwerr:
2N/A cmd_succeeded = False
2N/A # If we're not able to install the bootloader due to
2N/A # incompatibility between the GRUB2 utilities and the modules
2N/A # from the data directory, begin the deferred boot loader
2N/A # activation process by creating the required file in the
2N/A # root directory of the data_root.
2N/A if bmwerr.retcode == GRUB2BootLoader.EXIT_CODE_INCOMPATIBLE:
2N/A defer_file = os.path.join(data_root,
2N/A GRUB2BootLoader.DEFERRED_ACTIVATION_FILENAME)
2N/A try:
2N/A open(defer_file, 'w').close()
2N/A except IOError as ioerr:
2N/A raise BootLoaderInstallError('Unable to install '
2N/A 'GRUB2 boot loader and unable to create required '
2N/A 'deferred boot loader activation file %s' % defer_file,
2N/A ioerr)
2N/A syslog.openlog('pybootmgmt-GRUB2', 0, syslog.LOG_DAEMON)
2N/A syslog.syslog(syslog.LOG_NOTICE,
2N/A 'GRUB2 could not be installed at this time. The GRUB2 '
2N/A 'utilities on the root filesystem are older than (and '
2N/A 'incompatible with) the GRUB2 modules in %s. '
2N/A 'The new version of GRUB2 will be installed automatically '
2N/A 'when booting the Solaris instance in which the new GRUB2 '
2N/A 'modules are located.' % data_root)
2N/A syslog.closelog()
2N/A else:
2N/A raise BootLoaderInstallError('GRUB2 installation failed',
2N/A bmwerr)
2N/A
2N/A finally:
2N/A if target == 'uefi64':
2N/A try:
2N/A try:
2N/A statinfo = os.stat(oracle_bootapp)
2N/A except OSError:
2N/A statinfo = None
2N/A
2N/A if (cmd_succeeded and statinfo and statinfo.st_size > 0 and
2N/A copy_efi_boot_to_default):
2N/A try:
2N/A self._make_dirtree(esp_mountpt + '/efi/boot/')
2N/A shutil.copyfile(oracle_bootapp, default_bootapp)
2N/A except (IOError, OSError, BootmgmtConfigWriteError):
2N/A self._debug('Failed to copy boot app to '
2N/A 'default boot location -- ignoring')
2N/A
2N/A if need_unmount:
2N/A umount_cmd = ['/sbin/umount', esp_mountpt]
2N/A self._execute_cmd(umount_cmd)
2N/A
2N/A # Final sanity check
2N/A if cmd_succeeded and (statinfo is None or
2N/A statinfo.st_size == 0):
2N/A raise BootLoaderInstallError('Error while creating '
2N/A 'the UEFI boot loader (size=0) on %s' % esp_dev)
2N/A finally:
2N/A if esp_tempdir:
2N/A try:
2N/A os.rmdir(esp_tempdir)
2N/A except OSError:
2N/A pass
2N/A
2N/A def _copy_grub2_mods(self, target, basepath, exclusion_list):
2N/A """Copies GRUB2 modules (and config files) to a directory under
2N/A basepath. Returns a list of tuples, each of which describes one of the
2N/A files copied. This is suitable for DiskBootConfig and ODDBootConfig
2N/A only.
2N/A """
2N/A if isinstance(self._boot_config, NetBootConfig):
2N/A # No additional modules for netboot images -- everything is baked
2N/A # in.
2N/A return []
2N/A
2N/A if not isinstance(self._boot_config, ODDBootConfig):
2N/A return []
2N/A
2N/A modpath = os.path.join(self._get_boot_loader_data_root(),
2N/A GRUB2BootLoader.GRUB_DATA_PATHS[target])
2N/A
2N/A tuple_list = []
2N/A # Iterate through all files in the modpath directory, excluding ones
2N/A # that are in the exclusion_list:
2N/A boot_subdir = GRUB2BootLoader.BOOT_GRUB_SUBDIR + '/' + \
2N/A GRUB2BootLoader.GRUB_BOOT_SUBDIRS[target]
2N/A self._make_dirtree(os.path.join(basepath, boot_subdir))
2N/A for name in os.listdir(modpath):
2N/A # We're only interested in *.img, *.mod, *.lst and efiemu*
2N/A if not (name.endswith('.mod') or name.endswith('.lst') or
2N/A name.startswith('efiemu') or name.endswith('.img')):
2N/A continue
2N/A if name.split('.')[0] in exclusion_list:
2N/A self._debug('Skipping copying file %s', name)
2N/A continue
2N/A
2N/A srcpath = os.path.join(modpath, name)
2N/A destpath = os.path.join(basepath, boot_subdir, name)
2N/A try:
2N/A shutil.copy(srcpath, destpath)
2N/A except (OSError, shutil.Error) as copyerr:
2N/A self._debug('Error while copying %s -> %s: %s',
2N/A (srcpath, destpath, copyerr))
2N/A for tup in tuple_list:
2N/A try:
2N/A os.remove(tup[1])
2N/A except OSError:
2N/A pass
2N/A raise BootmgmtConfigWriteError('Error while copying GRUB2'
2N/A ' file %s to %s' % (srcpath, destpath), copyerr)
2N/A else:
2N/A tuple_list.append((BootConfig.OUTPUT_TYPE_FILE,
2N/A destpath,
2N/A None,
2N/A '%(' + ODDBootConfig.TOKEN_ODD_ROOT + ')s' +
2N/A os.path.join('/', boot_subdir, name),
2N/A 'root', 'root', 0644))
2N/A return tuple_list
2N/A
2N/A def _create_grub2_image(self, target, basepath):
2N/A """Construct the bootable grub2 image for the specified target. The
2N/A boot image is BootConfig-specific (BootLoaders associated with
2N/A ODDBootConfig instances get BIOS-targeted El Torito images and UEFI64-
2N/A targeted FAT filesystem images with embedded UEFI64-targeted boot
2N/A applications (suitable for direct inclusion in an ODD image);
2N/A NetBootConfig boot loaders get BIOS PXE images and UEFI64 net boot
2N/A application images).
2N/A """
2N/A
2N/A # microconfigs are needed even on BIOS targets, since the resulting
2N/A # image is used to build a USB image, where we will need to search.
2N/A if isinstance(self._boot_config, ODDBootConfig):
2N/A microconfig = self._create_microconfig(target)
2N/A else:
2N/A microconfig = None
2N/A
2N/A tuples = None
2N/A if isinstance(self._boot_config, ODDBootConfig):
2N/A if target == 'bios':
2N/A tuples = self._create_bios_odd_image(basepath, microconfig)
2N/A elif target == 'uefi64':
2N/A tuples = self._create_uefi64_odd_image(basepath, microconfig)
2N/A elif isinstance(self._boot_config, NetBootConfig):
2N/A if target == 'bios':
2N/A tuples = self._create_bios_pxe_image(basepath)
2N/A elif target == 'uefi64':
2N/A tuples = self._create_uefi64_net_image(basepath)
2N/A
2N/A if microconfig:
2N/A shutil.rmtree(microconfig)
2N/A
2N/A if tuples is not None:
2N/A return tuples
2N/A
2N/A raise BootmgmtUnsupportedPlatformError('%s and target=%s not supported'
2N/A % (self._boot_config.__class__.__name__, target))
2N/A
2N/A def _create_bios_odd_image(self, basepath, microconfig):
2N/A """Create an El Torito image by invoking grub-mkimage with the proper
2N/A parameters, then prepending the result with cdboot.img.
2N/A """
2N/A
2N/A boot_subdir = GRUB2BootLoader.BOOT_GRUB_SUBDIR + '/' + \
2N/A GRUB2BootLoader.GRUB_BOOT_SUBDIRS['bios']
2N/A # Write the image to 'core.img', as that's where we'll look for it
2N/A # if we want to repurpose the ISO to make other bootable media
2N/A imagename = os.path.join(basepath, boot_subdir,
2N/A GRUB2BootLoader.CORE_IMG_BASENAME)
2N/A self._make_basedir(imagename)
2N/A
2N/A wholeimagename = os.path.join(basepath,
2N/A GRUB2BootLoader.EL_TORITO_IMAGENAMES['bios'])
2N/A self._make_basedir(wholeimagename)
2N/A
2N/A modlist = ['biosdisk', 'iso9660', 'part_msdos', 'part_sunpc',
2N/A 'part_gpt', 'ufs1']
2N/A modlist.extend(GRUB2BootLoader.PRELOADED_MODULES['bios'])
2N/A
2N/A self._grub2_mkimage_generic('bios', imagename, modlist, microconfig)
2N/A
2N/A # Finally, create the final file with cdboot.img prepended to the
2N/A # image we just created.
2N/A try:
2N/A modpath = os.path.join(self._get_boot_loader_data_root(),
2N/A GRUB2BootLoader.GRUB_DATA_PATHS['bios'])
2N/A cdboot_img = os.path.join(modpath, 'cdboot.img')
2N/A wholeimg = open(wholeimagename, 'wb')
2N/A for fname in [cdboot_img, imagename]:
2N/A shutil.copyfileobj(open(fname), wholeimg)
2N/A wholeimg.close()
2N/A except (OSError, IOError) as copyerr:
2N/A raise BootmgmtConfigWriteError('Could not finish construction of '
2N/A ' %s' % wholeimagename, copyerr)
2N/A
2N/A tuple_list = [(BootConfig.OUTPUT_TYPE_BIOS_ELTORITO, wholeimagename,
2N/A None, None, None, None, None),
2N/A (BootConfig.OUTPUT_TYPE_FILE, imagename, None,
2N/A '%(' + ODDBootConfig.TOKEN_ODD_ROOT + ')s' +
2N/A os.path.join('/', boot_subdir,
2N/A GRUB2BootLoader.CORE_IMG_BASENAME),
2N/A 'root', 'root', 0644)]
2N/A
2N/A mod_tuples = self._copy_grub2_mods('bios', basepath, modlist)
2N/A if mod_tuples:
2N/A tuple_list.extend(mod_tuples)
2N/A return tuple_list
2N/A
2N/A def _grub2_mkimage_generic(self, target, imagename, modlist,
2N/A microconfig=None):
2N/A """Create a GRUB2 boot image for the specified target, named imagename,
2N/A with modlist modules embedded and preloaded. If microconfig is not
2N/A None, it must be the path to a directory containing a boot/grub/grub.cfg
2N/A file.
2N/A """
2N/A modpath = os.path.join(self._get_boot_loader_data_root(),
2N/A GRUB2BootLoader.GRUB_DATA_PATHS[target])
2N/A
2N/A base_cmd = os.path.join('/',
2N/A GRUB2BootLoader.GRUB_PLATFORM_PREFIXES[target],
2N/A GRUB2BootLoader.GRUB_MKIMAGE)
2N/A
2N/A if microconfig:
2N/A memdisk_path = self._make_memdisk_image(microconfig, target)
2N/A modlist.extend(['memdisk', 'tar', 'search', 'gzio', 'regexp',
2N/A 'configfile'])
2N/A img_cmd = [base_cmd, '-o', imagename, '-d', modpath,
2N/A '-O', GRUB2BootLoader.MKIMAGE_FORMATS[target],
2N/A '-m', memdisk_path,
2N/A '--prefix=(memdisk)/boot/grub'] + modlist
2N/A else:
2N/A memdisk_path = None
2N/A img_cmd = [base_cmd, '-o', imagename, '-d', modpath,
2N/A '-O', GRUB2BootLoader.MKIMAGE_FORMATS[target],
2N/A '--prefix=%s' % os.path.join('/',
2N/A GRUB2BootLoader.BOOT_GRUB_SUBDIR,
2N/A GRUB2BootLoader.GRUB_BOOT_SUBDIRS[target])] + modlist
2N/A
2N/A self._execute_cmd(img_cmd, 'Generating %s boot loader image' % target,
2N/A '%s boot image generation' % target)
2N/A
2N/A if memdisk_path:
2N/A # Now we can remove the temporary memdisk image file:
2N/A try:
2N/A os.remove(memdisk_path)
2N/A except OSError:
2N/A pass
2N/A
2N/A def _make_memdisk_image(self, microconfig, target):
2N/A """Create a "tarfs" (tar file) with the microconfig specified. The
2N/A microconfig specified is the parent directory -- there must be a
2N/A boot/grub/grub.cfg under that directory.
2N/A """
2N/A memdisk_img = None
2N/A orig_dir = None
2N/A try:
2N/A memdisk_img = tempfile.NamedTemporaryFile(dir='/system/volatile',
2N/A delete=False)
2N/A # Change into the directory that includes microconfig. This is
2N/A # required so that we can generate the tarfile that includes it.
2N/A orig_dir = os.getcwd()
2N/A os.chdir(microconfig)
2N/A self._debug('cwd => %s', microconfig)
2N/A
2N/A tar_cmd = ['/bin/tar', 'cf', '-', 'boot']
2N/A
2N/A self._execute_cmd(tar_cmd, 'Generating %s memdisk image' % target,
2N/A '%s memdisk image creation' % target, memdisk_img)
2N/A
2N/A memdisk_img.close()
2N/A # We change back to the original directory in the finally clause,
2N/A # below
2N/A return memdisk_img.name
2N/A except OSError as oserr:
2N/A if memdisk_img:
2N/A memdisk_img.close()
2N/A try:
2N/A os.remove(memdisk_img.name)
2N/A except OSError:
2N/A pass
2N/A raise BootmgmtConfigWriteError('Error during GRUB2 memdisk '
2N/A 'image construction', oserr)
2N/A finally:
2N/A if orig_dir:
2N/A try:
2N/A os.chdir(orig_dir)
2N/A self._debug('cwd => %s', orig_dir)
2N/A except OSError:
2N/A pass
2N/A
2N/A # pylint: disable=R0912,R0913,R0914,R0915
2N/A def _execute_cmd(self, cmd, msg=None, whatisthis=None, stdout_file=None,
2N/A stdin_file=None, return_stdout=False, return_stderr=False):
2N/A """Spawn a command.
2N/A """
2N/A fullcmd = ' '.join(cmd)
2N/A self._debug('Invoking %s', fullcmd)
2N/A
2N/A try:
2N/A if msg:
2N/A self._debug(msg)
2N/A if return_stdout or not stdout_file:
2N/A stdout_val = subprocess.PIPE
2N/A else:
2N/A stdout_val = stdout_file
2N/A
2N/A if return_stderr or stdout_file:
2N/A stderr_val = subprocess.PIPE
2N/A else:
2N/A stderr_val = stdout_val
2N/A
2N/A proc = subprocess.Popen(cmd, stdin=stdin_file, stdout=stdout_val,
2N/A stderr=stderr_val)
2N/A
2N/A stdout_output = ''
2N/A stderr_output = ''
2N/A # Set up the stderr/out pipes to be non-blocking so we can read
2N/A # from both without blocking.
2N/A filedescs = []
2N/A if stderr_val == subprocess.PIPE:
2N/A filedescs.append(proc.stderr.fileno())
2N/A if stdout_val == subprocess.PIPE:
2N/A filedescs.append(proc.stdout.fileno())
2N/A for fdesc in filedescs:
2N/A fcinfo = fcntl.fcntl(fdesc, fcntl.F_GETFL)
2N/A fcntl.fcntl(fdesc, fcntl.F_SETFL, fcinfo | os.O_NONBLOCK)
2N/A while proc.poll() is None:
2N/A try:
2N/A if stderr_val == subprocess.PIPE:
2N/A stderr_output += proc.stderr.read()
2N/A except IOError as ioerr: # EWOULDBLOCK
2N/A if ioerr.errno != errno.EWOULDBLOCK:
2N/A self._debug('Error while reading from stderr'
2N/A ' pipe: %s', ioerr)
2N/A try:
2N/A if stdout_val == subprocess.PIPE:
2N/A stdout_output += proc.stdout.read()
2N/A except IOError as ioerr: # EWOULDBLOCK
2N/A if ioerr.errno != errno.EWOULDBLOCK:
2N/A self._debug('Error while reading from stderr'
2N/A ' pipe: %s', ioerr)
2N/A time.sleep(0.5)
2N/A if msg:
2N/A self._debug('.')
2N/A retcode = proc.returncode
2N/A
2N/A try:
2N/A if stderr_val == subprocess.PIPE:
2N/A stderr_output += proc.stderr.read()
2N/A except IOError as ioerr:
2N/A self._debug('Error reading final stderr data: %s',
2N/A ioerr)
2N/A try:
2N/A if stdout_val == subprocess.PIPE:
2N/A stdout_output += proc.stdout.read()
2N/A except IOError as ioerr:
2N/A self._debug('Error reading final stderr data: %s',
2N/A ioerr)
2N/A
2N/A if retcode != 0:
2N/A self._debug('.failed (exit code %d). '
2N/A 'stderr was: %s\nstdout was: %s' % (retcode,
2N/A stderr_output, stdout_output))
2N/A if not whatisthis:
2N/A whatisthis = 'command execution'
2N/A raise BootmgmtConfigWriteError('Error during %s: '
2N/A '%s returned error code %d. stderr was:\n%s' %
2N/A (whatisthis, cmd[0], retcode, stderr_output),
2N/A retcode=retcode)
2N/A if msg:
2N/A self._debug('.completed successfully.')
2N/A if return_stdout or return_stderr:
2N/A return (stdout_output, stderr_output)
2N/A else:
2N/A return None
2N/A except OSError as oserr:
2N/A self._debug('Error while invoking %s: %s', (fullcmd, oserr))
2N/A raise BootmgmtConfigWriteError('Error invoking %s' % fullcmd, oserr)
2N/A # pylint: enable=R0913
2N/A
2N/A def _create_uefi64_odd_image(self, basepath, microconfig):
2N/A """Create a FAT filesystem image that contains a GRUB2 boot application
2N/A (constructed by grub-mkimage) suitable for 64-bit UEFI systems. If a
2N/A microconfig file is specified, it is embedded into the image via the
2N/A -c argument to grub-mkimage.
2N/A """
2N/A cleanup_list = []
2N/A
2N/A def _uefi64_odd_cleanup():
2N/A """Perform a cleanup of activities in LIFO order
2N/A """
2N/A for func in cleanup_list.reverse():
2N/A # pylint: disable=W0702
2N/A try:
2N/A func()
2N/A except:
2N/A pass
2N/A # pylint: enable=W0702
2N/A
2N/A # Make a temporary directory that will be used to hold the image:
2N/A try:
2N/A tmpf = tempfile.NamedTemporaryFile(dir='/system/volatile',
2N/A delete=False)
2N/A tmpf.close()
2N/A imagetempname = tmpf.name
2N/A cleanup_list.append(lambda : os.remove(imagetempname))
2N/A except OSError as oserr:
2N/A raise BootmgmtConfigWriteError('Error during 64-bit UEFI El Torito '
2N/A 'image construction', oserr)
2N/A
2N/A # gzio must be in the list before normal because normal attempts
2N/A # to grub_dl_load(gzio) very early in GRUB startup (before $prefix
2N/A # is set) so the net result would be an error message from
2N/A # grub_dl_load() that $prefix is not set. We use gzio for the
2N/A # Solaris boot archive, so we might as well load it now anyway.
2N/A modlist = ['iso9660', 'part_msdos', 'part_sunpc', 'part_gpt', 'ufs1']
2N/A modlist.extend(GRUB2BootLoader.PRELOADED_MODULES['uefi64'])
2N/A
2N/A self._grub2_mkimage_generic('uefi64', imagetempname, modlist,
2N/A microconfig)
2N/A
2N/A imagename = os.path.join(basepath,
2N/A GRUB2BootLoader.EL_TORITO_IMAGENAMES['uefi64'])
2N/A try:
2N/A self._make_basedir(imagename)
2N/A except BootmgmtError as bmerr:
2N/A _uefi64_odd_cleanup()
2N/A raise BootmgmtConfigWriteError('Error during 64-bit UEFI El Torito '
2N/A 'image construction', bmerr)
2N/A
2N/A # Create the image with a size that will fit the boot image and
2N/A # filesystem metadata
2N/A try:
2N/A imagesize = os.path.getsize(imagetempname)
2N/A if imagesize < GRUB2BootLoader.UEFI_FS_MIN_SIZE:
2N/A imagesize = GRUB2BootLoader.UEFI_FS_MIN_SIZE
2N/A imagefile = open(imagename, 'w')
2N/A imagefile.seek(imagesize - 1)
2N/A imagefile.write('\0')
2N/A imagefile.close()
2N/A cleanup_list.append(lambda : os.remove(imagename))
2N/A except (IOError, OSError) as un_err:
2N/A _uefi64_odd_cleanup()
2N/A raise BootmgmtConfigWriteError('Error during 64-bit UEFI El Torito '
2N/A 'image construction', un_err)
2N/A
2N/A # Associate the image with the lofi device
2N/A lofiadm_cmd = ['/sbin/lofiadm', '-a', imagename]
2N/A lofiadm_d_cmd = list(lofiadm_cmd) # Reuse the command list from above
2N/A lofiadm_d_cmd[1] = '-d'
2N/A try:
2N/A lofidev = self._execute_cmd(lofiadm_cmd, return_stdout=True)[0]
2N/A cleanup_list.append(lambda : self._execute_cmd(lofiadm_d_cmd))
2N/A except BootmgmtError as bmerr:
2N/A _uefi64_odd_cleanup()
2N/A raise BootmgmtConfigWriteError('Error during 64-bit UEFI El Torito '
2N/A 'image construction', bmerr)
2N/A
2N/A lofidev = lofidev.strip()
2N/A raw_lofidev = lofidev.replace('/lofi/', '/rlofi/')
2N/A
2N/A self._debug('lofi association: %s => %s', (imagename, lofidev))
2N/A self._debug('Creating pcfs on image size %dK', (imagesize / 1024))
2N/A
2N/A # Create a pcfs filesystem on the lofi device
2N/A mkfs_cmd = ['/usr/lib/fs/pcfs/mkfs', '-o', 'nofdisk,size=%d' %
2N/A (imagesize / 1024), raw_lofidev]
2N/A try:
2N/A self._execute_cmd(mkfs_cmd, stdin_file=open('/dev/zero', 'r'))
2N/A except BootmgmtError as bmerr:
2N/A _uefi64_odd_cleanup()
2N/A raise BootmgmtConfigWriteError('Error during 64-bit UEFI El Torito '
2N/A 'image construction', bmerr)
2N/A
2N/A # Create a temp dir to use as a mountpoint
2N/A try:
2N/A mount_tempdir = tempfile.mkdtemp(dir='/system/volatile')
2N/A cleanup_list.append(lambda : os.rmdir(mount_tempdir))
2N/A # Mount the pcfs filesystem just created
2N/A mount_cmd = ['/usr/lib/fs/pcfs/mount', lofidev, mount_tempdir]
2N/A umount_cmd = ['/sbin/umount', mount_tempdir]
2N/A self._execute_cmd(mount_cmd)
2N/A cleanup_list.append(lambda : self._execute_cmd(umount_cmd))
2N/A except (IOError, BootmgmtError) as un_err:
2N/A _uefi64_odd_cleanup()
2N/A raise BootmgmtConfigWriteError('Error during 64-bit UEFI El Torito '
2N/A 'image construction', un_err)
2N/A
2N/A
2N/A # Create the EFI/BOOT directory that will hold the boot image
2N/A image_destdir = os.path.join(mount_tempdir, 'EFI/BOOT')
2N/A try:
2N/A self._make_dirtree(image_destdir)
2N/A except BootmgmtError as bmerr:
2N/A _uefi64_odd_cleanup()
2N/A raise BootmgmtConfigWriteError('Error during 64-bit UEFI El Torito '
2N/A 'image construction', bmerr)
2N/A
2N/A # Copy the image onto the mounted pcfs filesystem
2N/A try:
2N/A shutil.copyfile(imagetempname,
2N/A os.path.join(image_destdir, 'BOOTx64.EFI'))
2N/A except (OSError, IOError) as copyerr:
2N/A _uefi64_odd_cleanup()
2N/A raise BootmgmtConfigWriteError('Error during 64-bit UEFI El Torito '
2N/A 'image construction', copyerr)
2N/A
2N/A # Unmount the pcfs filesystem
2N/A try:
2N/A self._execute_cmd(umount_cmd)
2N/A except BootmgmtError as bmerr:
2N/A self._debug('Unmounting the uefi64 image failed: %s', bmerr)
2N/A
2N/A try:
2N/A os.rmdir(mount_tempdir)
2N/A except OSError as oserr:
2N/A self._debug('Removing temp mount dir for uefi64 image failed: %s',
2N/A oserr)
2N/A
2N/A # Remove the lofi association
2N/A try:
2N/A self._execute_cmd(lofiadm_d_cmd)
2N/A except BootmgmtError as bmerr:
2N/A self._debug('Removing lofi associated failed: %s', bmerr)
2N/A
2N/A # Remove the boot image temporary file and temp dir
2N/A try:
2N/A os.remove(imagetempname)
2N/A except OSError as oserr:
2N/A self._debug('Removing temp uefi64 image failed: %s', oserr)
2N/A
2N/A tuple_list = [(BootConfig.OUTPUT_TYPE_UEFI_ELTORITO, imagename,
2N/A None, None, None, None, None)]
2N/A mod_tuples = self._copy_grub2_mods('uefi64', basepath, modlist)
2N/A if mod_tuples:
2N/A tuple_list.extend(mod_tuples)
2N/A return tuple_list
2N/A # pylint: enable=R0912,R0914,R0915
2N/A
2N/A def _nbp_path(self, target):
2N/A """Copies the NBP program for the specified target to the specified
2N/A path. Raises BootmgmtConfigWriteError if the copy failed.
2N/A """
2N/A return os.path.join(self._boot_config.net_osimage_root,
2N/A GRUB2BootLoader.GRUB_NBP_PATHS[target])
2N/A
2N/A # pylint: disable=W0613
2N/A def _create_bios_pxe_image(self, basepath):
2N/A """Create a GRUB2 image bootable via the PXE on systems with BIOS
2N/A firmware.
2N/A """
2N/A # For now, do not create the image-- just use the one delivered
2N/A # into the installation image.
2N/A return [(BootConfig.OUTPUT_TYPE_BIOS_NBP,
2N/A self._nbp_path('bios'),
2N/A None,
2N/A None,
2N/A None,
2N/A None,
2N/A None)]
2N/A
2N/A def _create_uefi64_net_image(self, basepath):
2N/A """Create a GRUB2 UEFI boot application image bootable via the native
2N/A UEFI network boot mechanism.
2N/A """
2N/A # For now, do not create the image-- just use the one delivered
2N/A # into the installation image.
2N/A return [(BootConfig.OUTPUT_TYPE_UEFI64_NBP,
2N/A self._nbp_path('uefi64'),
2N/A None,
2N/A None,
2N/A None,
2N/A None,
2N/A None)]
2N/A # pylint: enable=W0613
2N/A
2N/A def _create_microconfig(self, target):
2N/A """Create a small configuration file whose purpose is one:
2N/A (a) to locate the REAL root device (since the microconfig file will
2N/A be loaded from a memdisk baked inside the GRUB2 image), or (b) to
2N/A source the REAL configuration file (i.e. when booting from the network,
2N/A the microconfig contains the GRUB2 script that searches the tftp
2N/A hierarchy, in accordance with the Solaris-defined search order for
2N/A the GRUB2 configuration file.)
2N/A """
2N/A
2N/A lines = []
2N/A
2N/A if isinstance(self._boot_config, ODDBootConfig):
2N/A emitter = GRUB2MenuEntryEmitter()
2N/A root_search = emitter.search_cmd_generic(None, self)
2N/A if root_search:
2N/A lines.append(root_search)
2N/A lines.append('set prefix=($root)%s' %
2N/A os.path.join('/', GRUB2BootLoader.BOOT_GRUB_SUBDIR,
2N/A GRUB2BootLoader.GRUB_BOOT_SUBDIRS[target]))
2N/A lines.append('source $prefix/grub.cfg')
2N/A self._debug('Microconfig:\n%s', '\n'.join(lines))
2N/A else:
2N/A # Future enhancement: Generate the GRUB2 NetBootConfig microconfig
2N/A # here by using the script delivered to the install image, making
2N/A # sure to set prefix to a directory whose last component is the
2N/A # platform name (that means that _grub2_mkimage_generic must be
2N/A # modified also to parameterize the --prefix argument given to
2N/A # grub-mkimage)
2N/A raise BootmgmtUnsupportedOperationError('Microconfig creation '
2N/A 'not supported for class %s' %
2N/A self._boot_config.__class__.__name__)
2N/A
2N/A if not lines:
2N/A return None
2N/A
2N/A tmpdir = None
2N/A try:
2N/A tmpdir = tempfile.mkdtemp(dir='/system/volatile')
2N/A # The microconfig file must be stored in a temp dir, under
2N/A # boot/grub, since that's where the image construction commands
2N/A # will be told to find it (prefix will be set to
2N/A # (memdisk)/boot/grub).
2N/A tmpfiledir = os.path.join(tmpdir, 'boot/grub')
2N/A self._make_dirtree(tmpfiledir, 0700)
2N/A cfgfile = open(os.path.join(tmpfiledir,
2N/A GRUB2BootLoader.GRUB_CFG_BASENAME), 'w')
2N/A for line in lines:
2N/A cfgfile.write(line)
2N/A cfgfile.write('\n')
2N/A cfgfile.close()
2N/A # Return the parent dir holding the config file
2N/A return tmpdir
2N/A except IOError as err:
2N/A if tmpdir:
2N/A shutil.rmtree(tmpdir, True)
2N/A raise BootmgmtConfigWriteError('Error while trying to write '
2N/A 'GRUB2 microconfig', err)
2N/A
2N/A def _make_basedir(self, basepath, mode=0755):
2N/A """Create directories for dirname(basepath).
2N/A
2N/A May raise BootmgmtConfigWriteError if there was a problem creating the
2N/A directories.
2N/A """
2N/A self._make_dirtree(os.path.dirname(basepath), mode)
2N/A
2N/A @staticmethod
2N/A def _make_dirtree(path, mode=0755):
2N/A """Create directories for path.
2N/A
2N/A May raise BootmgmtConfigWriteError if there was a problem creating the
2N/A directories.
2N/A """
2N/A old_umask = os.umask(0)
2N/A try:
2N/A os.makedirs(path, mode)
2N/A except OSError as oserr:
2N/A if oserr.errno != errno.EEXIST:
2N/A raise BootmgmtConfigWriteError('Could not create '
2N/A 'directory', oserr)
2N/A finally:
2N/A os.umask(old_umask)
2N/A
2N/A
2N/Aclass GRUB2MenuOrganizer(LoggerMixin):
2N/A """GRUB2MenuOrganizer includes shared code between the GRUB2 pybootmgmt
2N/A backend and the GRUB2 menu generator program in $PREFIX/etc/grub.d.
2N/A Methods are provided to get an ordered list of boot instances from the
2N/A system (via the autogen backend (which gets the list of BEs and
2N/A generates a boot instance for each BE) if none exist in the menu.conf) and
2N/A the rest from the menu.conf, which has sections for each customized boot
2N/A instance (and, therefore, menu entry)) and to synch a list of boot
2N/A instances with the menu.conf.
2N/A """
2N/A
2N/A def __init__(self, boot_config, menu_conf_path):
2N/A """Constructor for GRUB2MenuOrganizer
2N/A """
2N/A super(GRUB2MenuOrganizer, self).__init__()
2N/A self._mcp = None # menu.conf parser instance
2N/A self._menu_conf_path = menu_conf_path
2N/A self._boot_config = boot_config
2N/A
2N/A @property
2N/A def dirty(self):
2N/A """Pseudoproperty returns True of the underlying MenuConfigParser is
2N/A dirty.
2N/A """
2N/A if self._mcp:
2N/A return self._mcp.dirty
2N/A return False
2N/A
2N/A @property
2N/A def menu_conf_source(self):
2N/A """Accessor for the path to the last menu.conf written.
2N/A """
2N/A if self._mcp.last_written_path:
2N/A return self._mcp.last_written_path
2N/A elif self._mcp.filename:
2N/A return self._mcp.filename
2N/A else:
2N/A return None
2N/A
2N/A def new_config(self):
2N/A """Start over with a new (empty) menu configuration
2N/A """
2N/A self._mcp = MenuConfigParser(self._menu_conf_path, True)
2N/A
2N/A def get_bootloader_props(self):
2N/A """Returns a dictionary of properties from the global section of the
2N/A menu.conf.
2N/A """
2N/A global_props = self._mcp.global_options
2N/A blprops = dict(global_props)
2N/A
2N/A self._debug('Serial properties: %s',
2N/A global_props.get(BootLoader.PROP_SERIAL_PARAMS))
2N/A
2N/A return blprops
2N/A
2N/A def generate_boot_instances(self):
2N/A """Load the menu.conf file and processes the sections, generating
2N/A BootInstance objects and returning the list to the caller.
2N/A """
2N/A # The total number of boot instances added to the BootConfig instance
2N/A # stored in self._boot_config is equal to the number of BE sections
2N/A # in the menu.conf file (including the transient entry, if it exists).
2N/A # If there are no BE sections, the autogenerator is used to populate
2N/A # the list.
2N/A
2N/A # Get the list of instances from the menu.conf file:
2N/A inst_list = self._build_custom_inst_list()
2N/A if inst_list:
2N/A # If an explicit ordering is specified in the menu configuration
2N/A # file, rearrange inst_list and return it.
2N/A explicit_order = self._mcp.get_order()
2N/A if explicit_order:
2N/A do_update = self._rearrange_inst_list(inst_list, explicit_order)
2N/A if do_update:
2N/A self._mcp.clear_order()
2N/A elif self._boot_config.boot_class == BootConfig.BOOT_CLASS_DISK:
2N/A # If no instances are present, generate instances for the BEs
2N/A # on the system
2N/A from bootmgmt.backend.autogen import BootInstanceAutogenFactory
2N/A inst_list = BootInstanceAutogenFactory.autogen(self._boot_config,
2N/A ['solaris'])
2N/A for inst in inst_list: # Add a menu.conf entry for each generated BE
2N/A classname = inst.__class__.__name__
2N/A propdict = self._boot_instance_to_propdict(inst)
2N/A self._mcp.add_entry(classname, propdict)
2N/A
2N/A # Now inst_list has the full set of boot instances
2N/A return inst_list
2N/A
2N/A def synch_menu_config(self, bi_list, bl_props):
2N/A """Given a list of boot instances and a boot loader properties dict,
2N/A synchronize the underlying MenuConfigParser in preparation for writing
2N/A the menu.conf.
2N/A """
2N/A
2N/A # First update the properties in the [global] section
2N/A for key, value in bl_props.items():
2N/A if key in GRUB2BootLoader.SUPPORTED_PROPS:
2N/A if key == BootLoader.PROP_SERIAL_PARAMS:
2N/A try:
2N/A strlist = []
2N/A for item in value:
2N/A strlist.append(str(item))
2N/A value = ', '.join(strlist)
2N/A except TypeError:
2N/A self._debug('Failed to set serial props')
2N/A continue
2N/A self._mcp.global_options[key] = str(value)
2N/A
2N/A if (self._boot_config and
2N/A self._boot_config.boot_class == BootConfig.BOOT_CLASS_DISK):
2N/A valid_bootfses = get_bootfs_list_from_libbe()
2N/A else:
2N/A valid_bootfses = None
2N/A
2N/A # Loop through all boot instances, keeping track of the order
2N/A # for the order property, and synchronizing the menu entry with the
2N/A # boot instance.
2N/A order_list = []
2N/A for inst in bi_list:
2N/A # Filter out Solaris boot instances that refer to deleted BEs
2N/A # (this is only possible if the boot instance has a bootfs
2N/A # member)
2N/A if valid_bootfses and isinstance(inst, SolarisDiskBootInstance):
2N/A inst_bootfs = getattr(inst, 'bootfs', None)
2N/A if inst_bootfs and inst_bootfs not in valid_bootfses:
2N/A self._debug('Removing boot instance %s from menu.conf',
2N/A getattr(inst, 'title', '<Title Unavailable>'))
2N/A continue
2N/A
2N/A custom_entry = getattr(inst, '_menu_conf_entry', None)
2N/A
2N/A if custom_entry:
2N/A # Synch boot instance to custom entry:
2N/A self._synch_boot_instance_to_entry(inst, custom_entry)
2N/A else:
2N/A classname = inst.__class__.__name__
2N/A # Check if this class has a corresponding emitter in
2N/A # the GRUB2MenuEntryEmitter class. If it does not,
2N/A # do not add it to menu.conf.
2N/A emitter = getattr(GRUB2MenuEntryEmitter,
2N/A '_write_menuentry_' + inst.__class__.__name__,
2N/A None)
2N/A if emitter:
2N/A propdict = self._boot_instance_to_propdict(inst)
2N/A custom_entry = self._mcp.add_entry(classname, propdict)
2N/A inst._menu_conf_entry = custom_entry
2N/A else:
2N/A custom_entry = None
2N/A
2N/A if custom_entry:
2N/A order_list.append(custom_entry.section_string)
2N/A
2N/A self._mcp.set_order(order_list)
2N/A
2N/A # Remove sections in the menu.conf file that correspond to deleted boot
2N/A # instances (i.e. all those not in the order_list)
2N/A self._mcp.delete_entries(cmp_fn=
2N/A (lambda x: x.section_string not in order_list))
2N/A
2N/A def load(self, new_conf_path=None):
2N/A """Loads the menu configuration file.
2N/A """
2N/A # This will succeeds either if the menu.conf is loaded successfully,
2N/A # or if the menu.conf is not present. In the latter case, an empty
2N/A # menu.conf is created (but not written, yet).
2N/A if new_conf_path:
2N/A self._menu_conf_path = new_conf_path
2N/A
2N/A self._mcp = MenuConfigParser(self._menu_conf_path)
2N/A
2N/A def store(self, new_conf_path=None, force=False):
2N/A """Save the menu configuration to stable storage.
2N/A """
2N/A self._mcp.write(new_conf_path, force)
2N/A
2N/A @staticmethod
2N/A def _boot_instance_to_propdict(inst):
2N/A """Convert boot instance to a dictionary suitable for serialization
2N/A in the menu.conf file. The bulk of the work is performed by methods
2N/A tailored for each type of BootInstance.
2N/A """
2N/A if inst and hasattr(inst, 'serialization_dict'):
2N/A return inst.serialization_dict()
2N/A else:
2N/A return {}
2N/A
2N/A def _propdict_to_boot_instance(self, section_type, propdict):
2N/A """Convert a propdict from a menu.conf section into a boot instance.
2N/A The type of BootInstance is given by the string section_type.
2N/A """
2N/A import bootmgmt.bootconfig
2N/A classtype = getattr(bootmgmt.bootconfig, section_type, None)
2N/A if classtype:
2N/A # Make a copy of the properties from the section and remove the
2N/A # 'modified' attribute, since it has nothing to do with creation
2N/A # of a boot instance:
2N/A attributes = dict(propdict)
2N/A if 'modified' in attributes:
2N/A del attributes['modified']
2N/A # The default property must be a bool, else we get an exception
2N/A # at BootInstance construction time.
2N/A if 'default' in attributes and type(attributes['default']) != bool:
2N/A attributes['default'] = parse_bool(attributes['default'])
2N/A self._debug('Instantiating type %s with %s', (section_type,
2N/A str(attributes)))
2N/A # Add the platform value from the associated BootConfig so that
2N/A # the BootInstance is properly initialized.
2N/A plat = self._boot_config.platform_requested
2N/A if plat:
2N/A firmw = self._boot_config.firmware_requested
2N/A attributes['platform'] = (plat, firmw)
2N/A # BootInstance instantiation takes a path as the first parameter
2N/A # and **kwargs for all others.
2N/A # pylint: disable=W0142
2N/A return classtype(None, **attributes)
2N/A # pylint: enable=W0142
2N/A self._debug('Could not find type %s', section_type)
2N/A return None
2N/A
2N/A def _synch_boot_instance_to_entry(self, boot_inst, mc_entry):
2N/A """Copy properties from the boot instance to the MenuConfigEntry
2N/A """
2N/A propdict = self._boot_instance_to_propdict(boot_inst)
2N/A self._debug('Updating %s with dict %s', (mc_entry.section_string,
2N/A propdict))
2N/A mc_entry.update_options(propdict)
2N/A
2N/A def _rearrange_inst_list(self, boot_instances, explicit_order):
2N/A """Rearranges the boot instance list passed in to match the order
2N/A specified. The boot_instances list is modified in place.
2N/A """
2N/A # The explicit_order list contains strings that are of the forms:
2N/A # '<classtype>|<index>'. Note that if the explicit order list is
2N/A # incomplete, order is undefined for other entries. If a boot
2N/A # instance is referenced in the explicit order list, but it doesn't
2N/A # exist on the system, that order entry is ignored
2N/A need_update = False
2N/A boot_insts = list(boot_instances)
2N/A ordered_list = []
2N/A for current in explicit_order:
2N/A found = False
2N/A for instidx, inst in enumerate(boot_insts):
2N/A if inst is None: # Skip instance slots marked with None
2N/A continue
2N/A
2N/A # Each instance in the instance list is guaranteed to have
2N/A # a _menu_conf_entry, so there is no need to use getattr here.
2N/A # pylint: disable=W0212
2N/A if inst._menu_conf_entry.match_section_by_string(current):
2N/A # Found a match -- add the instance to the temp list
2N/A ordered_list.append(inst)
2N/A # and mark the slot in boot_insts so we don't duplicate
2N/A # effort
2N/A boot_insts[instidx] = None
2N/A found = True
2N/A break
2N/A # pylint: enable=W0212
2N/A if not found: # the order property is out of date!
2N/A need_update = True
2N/A
2N/A # Now update boot_instances with the ordered_list first:
2N/A cur_inst = 0
2N/A self._debug('%s', ordered_list)
2N/A for inst in ordered_list:
2N/A boot_instances[cur_inst] = inst
2N/A cur_inst += 1
2N/A # Then any leftover instances that were not specified explicitly.
2N/A # Note that the relative order of these unmatched instances is
2N/A # not disturbed.
2N/A for inst in boot_insts:
2N/A if inst:
2N/A self._debug('Remaining[%d]: %s', (cur_inst, inst))
2N/A boot_instances[cur_inst] = inst
2N/A cur_inst += 1
2N/A
2N/A return need_update
2N/A
2N/A def _build_custom_inst_list(self):
2N/A """Builds a list of boot instances, each of which corresponds to a
2N/A single entry-type section in the menu.conf file.
2N/A """
2N/A
2N/A custom_inst_list = []
2N/A
2N/A for entry in self._mcp.entry_list:
2N/A # Ignore entries with sections whos names are tuples with <> 2
2N/A # items:
2N/A if len(entry.section) != 2:
2N/A continue
2N/A
2N/A boot_inst = self._propdict_to_boot_instance(entry.section[0],
2N/A entry.options)
2N/A # Store the menu.conf entry this boot instance was created from
2N/A # for later use (i.e. sorting, etc.)
2N/A boot_inst._menu_conf_entry = entry
2N/A self._debug('Created boot instance:\n%s', boot_inst)
2N/A custom_inst_list.append(boot_inst)
2N/A
2N/A return custom_inst_list
2N/A
2N/A @staticmethod
2N/A def escape_title_str_for_grub_cfg(rawstr):
2N/A """Escape a menuentry title. In addition to the regular escapes,
2N/A '>' must be escaped (replaced with '>>').
2N/A """
2N/A escstr = GRUB2MenuOrganizer.escape_str_for_grub_cfg(rawstr)
2N/A if escstr:
2N/A escstr = escstr.replace('>', '>>')
2N/A return escstr
2N/A
2N/A @staticmethod
2N/A def escape_str_for_grub_cfg(rawstr):
2N/A """Escape a string so it evaluates to its raw value in grub.cfg.
2N/A """
2N/A # Quotation marks must be escaped, as must backslashes
2N/A cookedstr = rawstr.replace('\\', '\\\\')
2N/A cookedstr = cookedstr.replace('"', '\\"')
2N/A return cookedstr
2N/A
2N/A
2N/Aclass GRUB2MenuEntryEmitter(LoggerMixin):
2N/A """Emits GRUB menuentry blocks for the grub.cfg file.
2N/A """
2N/A
2N/A def __init__(self, fileobj=None, vardict=None, force_grub2=False):
2N/A """If fileobj is None, STDOUT is used. vardict is the dictionary
2N/A that holds variables that may be used by the menu entry
2N/A generators. If it's None, os.environ is used. If force_grub2 is
2N/A True, we instantiate the BootConfigs with a loaderclass keyword arg
2N/A to ensure we get the BootInstances from GRUB2 and not a different
2N/A (perhaps higher-ranked) BootLoader, whose BootInstances may be in
2N/A the BootConfig's boot_instances list.
2N/A """
2N/A super(GRUB2MenuEntryEmitter, self).__init__()
2N/A if not fileobj:
2N/A self._fileobj = sys.stdout
2N/A else:
2N/A self._fileobj = fileobj
2N/A self.menuentry_theme_string = ''
2N/A self.submenu_theme_string = ''
2N/A self._rpool_props = {}
2N/A self.lzfsh = None
2N/A self.force_grub2 = force_grub2
2N/A if not vardict:
2N/A self._vardict = os.environ
2N/A else:
2N/A self._vardict = dict(vardict)
2N/A
2N/A def cache_zpool_props(self, lzfsh, rpool):
2N/A """Caches zpool properties used by the menu generator methods.
2N/A """
2N/A zph = zpool_open(lzfsh, rpool)
2N/A if self._rpool_props.get(rpool) is None:
2N/A self._rpool_props[rpool] = {}
2N/A rawguid = zpool_get_prop(lzfsh, zph, ZPOOL_PROP_GUID)
2N/A try:
2N/A # The GRUB2 guid for a zfs pool is a straight hex conversion--
2N/A # there are NO dashes, but there ARE leading zeroes!
2N/A hexguid = '%016x' % int(rawguid)
2N/A self._rpool_props[rpool][ZPOOL_PROP_GUID] = hexguid
2N/A except ValueError as valerr:
2N/A self._debug("Couldn't convert guid `%s' to hex: %s",
2N/A (rawguid, valerr))
2N/A zpool_close(zph)
2N/A
2N/A def emit_entries(self):
2N/A """Emit entries to the fileobj specified in __init__. This should only
2N/A be invoked when this class is instantiated by the GRUB2 autogen script.
2N/A """
2N/A
2N/A all_root_pools = libbe_py.beGetRootPoolList()
2N/A
2N/A valid_bootfses = get_bootfs_list_from_libbe()
2N/A
2N/A self.lzfsh = libzfs_init()
2N/A for rpool in all_root_pools:
2N/A if self.force_grub2:
2N/A argdict = { 'loaderclass' : GRUB2BootLoader }
2N/A self._debug('Forcing loaderclass = GRUB2BootLoader')
2N/A else:
2N/A argdict = None
2N/A with libbe.get_boot_config(rpool, init_argdict=argdict) as bootconf:
2N/A self.cache_zpool_props(self.lzfsh, rpool)
2N/A
2N/A for bootinst in bootconf.boot_instances:
2N/A # Filter out entries that managed to exist despite
2N/A # referring to a nonexistant BE:
2N/A if (valid_bootfses and
2N/A isinstance(bootinst, SolarisDiskBootInstance)):
2N/A inst_bootfs = getattr(bootinst, 'bootfs', None)
2N/A if inst_bootfs and inst_bootfs not in valid_bootfses:
2N/A continue
2N/A
2N/A self.write_menuentry(bootinst)
2N/A self._fileobj.write('\n')
2N/A
2N/A # Emit code to check for the presence and nonzero size of
2N/A # a GRUB Legacy configuration file. If it exists, extract
2N/A # the entries into a submenu.
2N/A self._fileobj.write('if [ "$target" = "%(bios)s" ]; then\n' %
2N/A GRUB2BootLoader.GRUB_TARGET_STRINGS)
2N/A rootvar = 'root_' + ''.join([x for x in rpool if x.isalnum()])
2N/A search_cmd = self._search_cmd_for_pool(rpool, rootvar)
2N/A self._fileobj.write('\t%s\n' % search_cmd)
2N/A legacy_cfg_file = '($%s)/@/boot/grub/menu.lst' % rootvar
2N/A self._fileobj.write('\tif [ -s "%s" ]; then\n' %
2N/A legacy_cfg_file)
2N/A title = 'Legacy GRUB Menu (from root pool %s)' % rpool
2N/A self._fileobj.write('\t\tsubmenu "%s" "%s" %s{\n' % (title,
2N/A legacy_cfg_file, self.submenu_theme_string))
2N/A self._fileobj.write(
2N/A '\t\t\textract_legacy_entries_source "$2"\n')
2N/A self._fileobj.write('\t\t}\n')
2N/A self._fileobj.write('\tfi\n')
2N/A self._fileobj.write('fi\n\n')
2N/A
2N/A libzfs_fini(self.lzfsh)
2N/A self.lzfsh = None
2N/A
2N/A def write_menuentry(self, bootinst, target=None):
2N/A """Call the BootInstance-specific function to emit a menuentry block
2N/A """
2N/A func = getattr(self, '_write_menuentry_' + bootinst.__class__.__name__,
2N/A None)
2N/A if func and getattr(bootinst, 'title', None):
2N/A clean_title = GRUB2MenuOrganizer.escape_str_for_grub_cfg(
2N/A bootinst.title)
2N/A tabs = ''
2N/A # If the boot instance is target-specific, emit code that causes
2N/A # GRUB2 to only process the menu entry when running on that target.
2N/A if bootinst.TARGET and bootinst.TARGET[1]:
2N/A targdict = GRUB2BootLoader.GRUB_TARGET_STRINGS
2N/A self._fileobj.write('if [ "$target" = "%s"' %
2N/A targdict[bootinst.TARGET[1][0]])
2N/A if len(bootinst.TARGET[1]) > 1:
2N/A for targstring in bootinst.TARGET[1][1:]:
2N/A self._fileobj.write(' -o "$target" = "%s"' %
2N/A targdict[targstring])
2N/A self._fileobj.write(' ]; then\n')
2N/A tabs = '\t'
2N/A needclose = True
2N/A else:
2N/A needclose = False
2N/A menu_start = tabs + 'menuentry "%s" %s{\n' % \
2N/A (clean_title, self.menuentry_theme_string)
2N/A self._fileobj.write(menu_start)
2N/A entry = func(bootinst, target)
2N/A for line in entry:
2N/A if line == '':
2N/A self._fileobj.write('\n')
2N/A else:
2N/A self._fileobj.write(tabs + '\t' + line + '\n')
2N/A self._fileobj.write(tabs + '}\n')
2N/A if needclose:
2N/A self._fileobj.write('else\n')
2N/A self._fileobj.write('\tmenuentry "Entry [%s] not supported '
2N/A 'on this firmware" {\n' % clean_title)
2N/A self._fileobj.write('\t\techo "Not supported"\n')
2N/A self._fileobj.write('\t}\n')
2N/A self._fileobj.write('fi\n')
2N/A else:
2N/A self._debug('Not emitting entry for instance type %s (title=%s)',
2N/A (bootinst.__class__.__name__,
2N/A str(getattr(bootinst, 'title', None))))
2N/A
2N/A def _search_cmd_for_pool(self, rpool, varname='root'):
2N/A """Returns a string containing the search command to use for the
2N/A specified pool.
2N/A """
2N/A if (self._rpool_props.get(rpool) and
2N/A self._rpool_props[rpool].get(ZPOOL_PROP_GUID)):
2N/A guid = self._rpool_props[rpool][ZPOOL_PROP_GUID]
2N/A # XXX Add hints:
2N/A return 'search --no-floppy --fs-uuid --set=%s %s' % (varname, guid)
2N/A else:
2N/A # XXX Add hints:
2N/A return 'search --no-floppy --label --set=%s %s' % (varname, rpool)
2N/A
2N/A # pylint: disable=C0103
2N/A def _write_menuentry_SolarisNetBootInstance(self, bootinst, target=None):
2N/A """Emits a menuentry for a Solaris network boot instance.
2N/A """
2N/A entry = []
2N/A
2N/A bootconfig = getattr(bootinst, '_bootconfig', None)
2N/A
2N/A # If the parent boot configuration isn't a NetBootConfig, we'll need an
2N/A # explicit load of the network driver.
2N/A if not isinstance(bootconfig, NetBootConfig):
2N/A if target.startswith('uefi'):
2N/A entry.append('insmod efinet')
2N/A else:
2N/A entry.append('insmod pxe')
2N/A
2N/A kargs = bootinst.kargs if bootinst.kargs else ''
2N/A
2N/A # Net boot instances frequently have $ strings embedded in the
2N/A # kernel arguments. If any are found, escape them from the grub2
2N/A # scripting parser
2N/A if kargs:
2N/A kargs = kargs.replace('$', '\\$')
2N/A
2N/A return self._write_menuentry_generic(bootinst, entry, None, kargs)
2N/A
2N/A # pylint: disable=W0613
2N/A def _write_menuentry_SolarisODDBootInstance(self, bootinst, target=None):
2N/A """Emits a menuentry for a ODD-based Solaris instance.
2N/A """
2N/A entry = []
2N/A
2N/A bootconfig = getattr(bootinst, '_bootconfig', None)
2N/A boot_loader = getattr(bootconfig, 'boot_loader', None)
2N/A
2N/A # If the parent boot configuration isn't on an ODD, we'll need an
2N/A # explicit load of iso9660.
2N/A if not isinstance(bootconfig, ODDBootConfig):
2N/A entry.append('insmod iso9660')
2N/A
2N/A kargs = bootinst.kargs if bootinst.kargs else ''
2N/A
2N/A search = self.search_cmd_generic(bootinst, boot_loader)
2N/A if search:
2N/A entry.append(search)
2N/A
2N/A return self._write_menuentry_generic(bootinst, entry, None, kargs)
2N/A
2N/A # pylint: disable=W0212
2N/A def _write_menuentry_SolarisDiskBootInstance(self, bootinst, target=None):
2N/A """Custom emitter for a SolarisDiskBootInstance
2N/A """
2N/A entry = []
2N/A
2N/A # Cover all bases by loading part_msdos, part_sunpc, and part_gpt
2N/A entry.append('insmod part_msdos')
2N/A entry.append('insmod part_sunpc')
2N/A entry.append('insmod part_gpt')
2N/A entry.append('')
2N/A
2N/A bootconfig = getattr(bootinst, '_bootconfig', None)
2N/A boot_loader = getattr(bootconfig, 'boot_loader', None)
2N/A
2N/A kargs = bootinst.kargs if bootinst.kargs else ''
2N/A
2N/A if bootinst.fstype == 'ufs':
2N/A rootpath = None
2N/A search = self.search_cmd_generic(bootinst, boot_loader)
2N/A if search:
2N/A entry.append(search)
2N/A is_encrypted = False
2N/A else:
2N/A # else, assume ZFS
2N/A (kargs, rootpath,
2N/A is_encrypted) = self._handle_zfs_SolarisDiskBootInstance(
2N/A bootinst, entry, kargs)
2N/A if not kargs:
2N/A return
2N/A
2N/A # If the BootConfig that this boot instance is a member of is a
2N/A # disk boot config (i.e. we invoked grub-mkconfig to generate the
2N/A # grub.cfg file) AND the console type of this BootConfig is NOT
2N/A # graphical, then we need to manually add a call to load_video
2N/A # (the body of which is emitted by the 00_header script). This is
2N/A # essential so that GRUB can find and set graphics modes for Solaris
2N/A # instances that use framebuffer console.
2N/A if bootconfig and boot_loader:
2N/A try:
2N/A constype = boot_loader.getprop(BootLoader.PROP_CONSOLE)
2N/A except BootmgmtUnsupportedPropertyError:
2N/A pass
2N/A else:
2N/A if (bootconfig.boot_class == BootConfig.BOOT_CLASS_DISK and
2N/A constype != BootLoader.PROP_CONSOLE_GFX):
2N/A entry.append('load_video')
2N/A
2N/A # Common code for the remainder of the entry:
2N/A return self._write_menuentry_generic(bootinst, entry, rootpath, kargs,
2N/A is_encrypted)
2N/A # pylint: enable=W0613
2N/A
2N/A def search_cmd_generic(self, bootinst, boot_loader):
2N/A """Returns a search command string that includes the identifying file
2N/A from either the bootinst (tried first) or boot_loader.
2N/A """
2N/A # If the boot instance specifies a identifying file, use it to
2N/A # find $root
2N/A if getattr(bootinst, 'identfile', None):
2N/A ident_file = bootinst.identfile
2N/A else:
2N/A ident_file = None
2N/A
2N/A # Fall back on the boot loader's identifying file to find $root
2N/A if not ident_file and boot_loader:
2N/A ident_file = boot_loader.getprop(BootLoader.PROP_IDENT_FILE)
2N/A
2N/A if ident_file:
2N/A return 'search --no-floppy --file --set=root ' + ident_file
2N/A else:
2N/A self._debug('no identifying file -- search omitted')
2N/A return None
2N/A
2N/A def _write_menuentry_generic(self, bootinst, entry, rootpath, kargs,
2N/A is_encrypted=False):
2N/A """Generic code for finishing a Solaris boot instance's menuentry
2N/A """
2N/A kargs = GRUB2MenuOrganizer.escape_str_for_grub_cfg(kargs)
2N/A
2N/A gfxpayload_modes = self._vardict.get('GRUB_GFXPAYLOAD_MODE')
2N/A if gfxpayload_modes:
2N/A if gfxpayload_modes[0] != '"': # Add quotes if needed
2N/A gfxpayload_modes = '"%s"' % gfxpayload_modes
2N/A gfxpayload_modes = 'set gfxpayload=%s' % gfxpayload_modes
2N/A
2N/A # Only 64-bit is supported, so replace the format-string here:
2N/A try:
2N/A kernel = bootinst.kernel % { 'karch' : 'amd64' }
2N/A except KeyError:
2N/A kernel = bootinst.kernel
2N/A
2N/A try:
2N/A boot_archive = bootinst.boot_archive % { 'karch' : 'amd64' }
2N/A except KeyError:
2N/A boot_archive = bootinst.boot_archive
2N/A
2N/A if (kernel.find('$ISADIR') != -1 or
2N/A boot_archive.find('$ISADIR') != -1):
2N/A entry.append('if cpuid -l; then')
2N/A entry.append('\tset ISADIR=amd64')
2N/A entry.append('else')
2N/A entry.append('\tset ISADIR=')
2N/A entry.append('fi')
2N/A
2N/A if is_encrypted:
2N/A multiboot = 'multiboot2'
2N/A module = 'module2'
2N/A else:
2N/A multiboot = '$multiboot'
2N/A module = '$module'
2N/A
2N/A entry.append('set kern=%s' % kernel)
2N/A if rootpath:
2N/A entry.append('echo -n "Loading ${root}%s$kern: "' % rootpath)
2N/A entry.append('%s %s/$kern $kern %s' % (multiboot, rootpath, kargs))
2N/A if gfxpayload_modes:
2N/A entry.append(gfxpayload_modes)
2N/A entry.append('insmod gzio')
2N/A bootarch_string = '%s%s' % (rootpath, boot_archive)
2N/A entry.append('echo -n "Loading ${root}%s: "' % bootarch_string)
2N/A entry.append('%s %s' % (module, bootarch_string))
2N/A else:
2N/A # If the kernel specified has another path prepended, ensure
2N/A # that the second parameter to multiboot[2] is the boot-archive-
2N/A # relative kernel path.
2N/A idx = kernel.find('/platform')
2N/A if idx > 0:
2N/A ba_rel_kern = kernel[idx:]
2N/A else:
2N/A ba_rel_kern = '$kern'
2N/A entry.append('echo -n "Loading ${root}$kern: "')
2N/A entry.append('%s $kern %s %s' % (multiboot, ba_rel_kern, kargs))
2N/A if gfxpayload_modes:
2N/A entry.append(gfxpayload_modes)
2N/A entry.append('insmod gzio')
2N/A entry.append('echo -n "Loading ${root}%s: "' % boot_archive)
2N/A entry.append('%s %s' % (module, boot_archive))
2N/A
2N/A return entry
2N/A
2N/A def _handle_zfs_encrypted_dataset(self, rpool, bootfs, entry):
2N/A """Checks if the specified dataset (or implied dataset) has encryption
2N/A enabled, and if so, emits the proper commands to prompt for and set
2N/A the zfs key. Returns True if the dataset is encrypted.
2N/A """
2N/A if bootfs:
2N/A dataset_name = bootfs
2N/A elif rpool:
2N/A dataset_name = get_zpool_default_bootfs(self.lzfsh, rpool)
2N/A else:
2N/A dataset_name = None
2N/A
2N/A if dataset_name and is_zfs_encrypted_dataset(self.lzfsh, dataset_name):
2N/A entry.append('insmod zfscrypt')
2N/A entry.append('echo -n "[Encrypted dataset %s] " ' % dataset_name)
2N/A entry.append('zfskey')
2N/A return True
2N/A
2N/A return False
2N/A
2N/A def _handle_zfs_SolarisDiskBootInstance(self, bootinst, entry, kargs):
2N/A """Handle the ZFS-specific part of menuentry generation. Returns the
2N/A kargs to use in the entry.
2N/A """
2N/A bootfs = getattr(bootinst, 'bootfs', None)
2N/A if bootfs:
2N/A rpool, bename = bootfs_split(bootfs)
2N/A else: # No bootfs -- check for rpool
2N/A rpool = getattr(bootinst, 'rpool', None)
2N/A
2N/A if not rpool:
2N/A entry.append('# Cannot determine the root pool for this entry.')
2N/A return None
2N/A
2N/A entry.append('insmod zfs')
2N/A
2N/A entry.append('')
2N/A search_cmd = self._search_cmd_for_pool(rpool)
2N/A if search_cmd:
2N/A entry.append(search_cmd)
2N/A
2N/A # If this is an encrypted dataset, prompt user for the password
2N/A is_encrypted = self._handle_zfs_encrypted_dataset(rpool, bootfs, entry)
2N/A
2N/A if bootfs:
2N/A rootpath = '/ROOT/%s/@' % bename
2N/A entry.append('zfs-bootfs %s/ zfs_bootfs' % rootpath)
2N/A else:
2N/A entry.append('zfs-defaultbootfs $root zfs_rootpath zfs_bootfs')
2N/A rootpath = '/${zfs_rootpath}/@'
2N/A
2N/A if kargs:
2N/A kargs = kargs.strip() + ' -B $zfs_bootfs'
2N/A else:
2N/A kargs = '-B $zfs_bootfs'
2N/A
2N/A return (kargs, rootpath, is_encrypted)
2N/A
2N/A # pylint: disable=W0613
2N/A def _write_menuentry_ChainDiskBootInstance(self, inst, target=None):
2N/A """Custom emitter for a ChainDiskBootInstance
2N/A """
2N/A entry = []
2N/A if ((inst.chaininfo is None) or
2N/A (type(inst.chaininfo) is not tuple or len(inst.chaininfo) == 0) or
2N/A (type(inst.chaininfo[0]) is not int) or
2N/A ((len(inst.chaininfo) > 1 and type(inst.chaininfo[1]) is not
2N/A int and type(inst.chaininfo[1]) is not tuple)) or
2N/A (len(inst.chaininfo) > 1 and type(inst.chaininfo[1]) is tuple and
2N/A len(inst.chaininfo[1]) > 2)):
2N/A self._debug('Invalid chainloader info in boot instance')
2N/A entry.append('# Could not process this chainload entry')
2N/A return entry
2N/A
2N/A diskstr = '(hd' + str(inst.chaininfo[0])
2N/A
2N/A if len(inst.chaininfo) > 1:
2N/A if type(inst.chaininfo[1]) is tuple:
2N/A for tuple_item in inst.chaininfo[1]:
2N/A try:
2N/A int_item = int(tuple_item)
2N/A # GRUB2 uses 1-based partition indices
2N/A diskstr += ',' + str(int_item + 1)
2N/A except ValueError:
2N/A # Special case -- use of letters: GRUB2 does not
2N/A # support using letters to access VTOC-labelled drives
2N/A # so convert the letter into a numerical index
2N/A if (isinstance(tuple_item, basestring) and
2N/A len(tuple_item) == 1 and tuple_item >= 'a' and
2N/A tuple_item <= 'z'):
2N/A part = (ord(tuple_item) - ord('a')) + 1
2N/A diskstr += ',%d' % part
2N/A else:
2N/A try:
2N/A int_item = int(inst.chaininfo[1])
2N/A # GRUB2 uses 1-based partition indices
2N/A diskstr += ',' + str(int_item + 1)
2N/A except ValueError:
2N/A diskstr += ',' + inst.chaininfo[1]
2N/A
2N/A diskstr += ')'
2N/A
2N/A entry.append('set root=%s' % diskstr)
2N/A
2N/A if inst.forceactive:
2N/A entry.append('parttool %s boot+' % diskstr)
2N/A
2N/A if int(inst.chainstart) != 0:
2N/A entry.append('chainloader --force %s+%s' %
2N/A (inst.chainstart, inst.chaincount))
2N/A else:
2N/A entry.append('chainloader --force +%s' % inst.chaincount)
2N/A
2N/A return entry
2N/A # pylint: enable=C0103,W0212,W0613
2N/A
2N/A
2N/Adef bootloader_classes():
2N/A """Returns a list of boot loader classes, consumed by the boot loader
2N/A factory.
2N/A """
2N/A return [GRUB2BootLoader]
2N/A