#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License (the "License").
# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#
#
"""
Legacy GRUB BootLoader Implementation for pybootmgmt
"""
import tempfile
import shutil
import os
import stat
import re
"""Implementation of a Legacy GRUB (GRUB 0.97) BootLoader. Handles parsing
the menu.lst file (reading and writing), though reading it and creating
BootInstance objects is rather fragile"""
# MENU_LST_PATH is defined as a property-decorated method, below.
# This dict is applied to the menu.lst template (below)
DEFAULT_PROPDICT = {
'default': 'default 0',
'serial': '# serial --unit=0 --speed=9600',
'terminal': '# terminal serial',
'min_mem64': '#',
'hiddenmenu': '#'}
# Supported properties for setprop()
SUPPORTED_PROPS = [
]
}
# Template for NEW menu.lst files:
r"""# default menu entry to boot
%(default)s
#
# menu timeout in second before default OS is booted
# set to -1 to wait for user input
%(timeout)s
#
# To enable grub serial console to ttya uncomment the following lines
# and comment out the splashimage line below
# WARNING: do not enable grub serial console when BIOS console serial
# redirection is active.
%(serial)s
%(terminal)s
#
# Uncomment the following line to enable GRUB splashimage on console
%(splashimage)s
%(foreground)s
%(background)s
#
# To chainload another OS
#
# title Another OS
# root (hd<disk no>,<partition no>)
# chainloader +1
#
# To chainload a Solaris release not based on GRUB:
#
# title Solaris 9
# root (hd<disk no>,<partition no>)
# chainloader +1
# makeactive
#
# To load a Solaris instance based on GRUB:
#
# title Solaris <version>
# bootfs <poolname>/ROOT/<BE_name>
# module /platform/i86pc/amd64/boot_archive
#
# To override Solaris boot args (see kernel(1M)), console device and
# properties set via eeprom(1M) edit the "kernel" line to:
#
#
%(hiddenmenu)s
%(min_mem64)s
""")
"""Probe for Legacy GRUB files for use with the BootConfig passed
in"""
return None
cur_arch != 'x86'):
'platform')
return None
bootconfig.boot_fstype is not None):
try:
except BootmgmtError as bmerr:
return None
else:
raise BootmgmtUnsupportedOperationError('Boot class %s not '
"""This Legacy GRUB probe function searches the ODD's root, looking for
the Legacy GRUB menu.lst and stage1 and stage2 files"""
try:
except BootmgmtNotSupportedError:
return None
return LegacyGRUBBootLoader(**kwargs)
"""Creates a LegacyGRUBBootLoader if the required files are present.
"""
if not artifacts:
return None
return LegacyGRUBBootLoader(**kwargs)
"""This Legacy GRUB probe function searches the ZFS top-level dataset
for a menu.lst file. If that's not present, we search the system root
should exist if Legacy GRUB is the active boot loader.
"""
if not artifacts:
return None
return LegacyGRUBBootLoader(**kwargs)
"""Attempt to open each file in datafiles. Raises
BootmgmtNotSupportedError if any files cannot be opened.
"""
# The data root location must be specified
if dataroot is None:
raise BootmgmtNotSupportedError('dataroot is None')
try:
raise BootmgmtNotSupportedError('IOError when checking for '
"""Probe for pxegrub and menu.lst[.<client>]
"""
artifacts = []
try:
except BootmgmtNotSupportedError:
return None
try:
except OSError:
pass
return artifacts
"""Probe for menu.lst, stage1, stage2, and installgrub.
"""
if bootloader:
else:
if fstype == 'ufs':
elif fstype == 'zfs':
else:
return []
return []
artifacts = []
# Look for the menu.lst file
try:
except BootmgmtNotSupportedError as bmnse:
try:
except BootmgmtNotSupportedError as bmnse:
try:
full_exec_path = '/usr/sbin/installgrub'
else:
return artifacts
"""Returns a list of artifacts for this BootLoader, given a
bootconfig to use for directory information.
"""
return []
"""The configuration for Legacy GRUB consists solely of the menu.lst
file. The default new configuration is an empty menu.lst file,
# Set boot loader default properties after the config reset:
else:
"""Load boot instances and GRUB properties from the menu.lst file"""
else:
raise BootmgmtUnsupportedOperationError('load_config() is not '
'supported for class %s' %
if boot_instances and do_add:
# Add the boot instances to the BootConfig instance:
if not do_add:
return boot_instances
"""The only file that needs to be written is the menu.lst file, using
information from the BootConfig instance to which we have a reference.
Information from the boot loader's properties is also used to
determine which commands are emitted at the global level"""
if self._boot_config is None:
msg = ('Cannot _write_config(%s) - _boot_config is None' %
raise BootmgmtInterfaceCodingError(msg)
# Determine the type of boot configuration we're dealing with, then
# determine the filesystem type that will hold the menu.lst. Only
# then can we tell the caller the appropriate path to copy it into.
else:
raise BootmgmtUnsupportedOperationError('XXX - Fix Me')
"""Returns the ultimate destination of menu.lst, depending on the
type of BootConfig stored in self._boot_config
"""
if not self._boot_config:
return self._MENU_LST_PATH
# If the BootConfig is class net, we need to change the menu.lst
else:
raise BootmgmtUnsupportedOperationError('Unknown BootConfig '
'class %s' % boot_class)
return menu_lst_path
return self._menu_lst_dir_disk()
else:
# Legacy GRUB methods for dealing with a disk-based BootConfig
raise BootmgmtUnsupportedOperationError('Unknown filesystem: %s'
% fstype)
if fstype == 'zfs':
elif fstype == 'ufs':
"""
"""
try:
raise BootmgmtConfigReadError('Error while processing the %s \
file' % menu_lst, err)
except MenuLstError as err:
raise BootmgmtConfigReadError('Error while processing the %s '
default_index = None
boot_instances = []
# Extract the properties and entries from the parsed file:
# Make sure we have everything first:
# XXX - Recognize non-disk boot instances in the menu.lst
argdict = {}
# Skip non-commands
continue
# XXX - Handle "BE_XXXX" and other hints
if subs > 0:
else:
# This is either an erroneous or older use;
# in that case, ignore it.
'directive: %s -- ignoring',
# Skip non-commands
continue
# Skip non-commands
continue
else: # OSes we don't know about
# Skip non-commands
continue
# Add the platform value from the associated BootConfig so that
# the BootInstance is properly initialized.
if plat:
if 'chainload' in argdict:
else:
# This is a bit of a kludge. To detect a network
# boot instance, we depend on the parent BootConfig
# type from which this configuration was loaded. We
# also assume that kernel lines with the Solaris
# identify a Solaris OS instance.
else:
else:
boot_instances += [inst]
# Add the command as a property
else:
argstring = ''
if entity_cmd == 'default':
elif entity_cmd == 'hiddenmenu':
elif entity_cmd == 'serial':
else:
try:
# Note that the property names in BootLoader were
# chosen to overlap the menu.lst keywords used to
# express them, so that we could just call setprop()
# and not special-case each of them. When the
# menu.lst is reconsitituted, the BootLoader instance
# is interrogated as each command is encountered,
# enabling in-place "editing" of those lines from
# a loaded menu.lst file.
except BootmgmtUnsupportedPropertyError:
# Just ignore unsupported properties
if default_index is not None:
try:
except ValueError:
"an int -- setting to 0", default_index)
default_index = 0
# XXX What to do when we have boot instances from both the menu.lst
# is to mark the default boot instance as read from the menu.lst
# file.
# Figure out the console type. If console wasn't set to serial,
# and any of (splashimage, foreground, background) properties are set,
# we can deduce that the console type is graphics, otherwise, it's text.
return boot_instances
"""This is a disk-based configuration. We support ZFS or UFS root,
so figure out which filesystem we're dealing with, and write the
menu.lst file to that location."""
if tuples is None:
return None
and
# Make a copy of the tuple so we can change it:
if fstype == 'zfs':
elif fstype == 'ufs':
# The BootInstance included in the 6-tuple will be the first
# SolarisDiskBootInstance in the list held in the associated
# BootConfig's boot_instances list (this may need to be
# revisited).
inst = None
break
# Update tuple in the list:
return tuples
# _write_config_generic is taken from the MenuLstBootLoaderMixIn
"""
"""
if basepath is None:
raise BootmgmtInterfaceCodingError('basepath must not be None for '
'ODDBootConfig boot configs')
and
# Make a copy of the tuple so we can change it:
# Update tuple in the list:
# Now add the stage2_eltorito file to the tuples list:
try:
raise BootmgmtConfigWriteError('Error while trying to copy '
'Legacy GRUB El Torito stage2', err)
None,
None,
None,
None,
None)]
return tuples
"""
"""
if basepath is None:
raise BootmgmtInterfaceCodingError('basepath must not be None for '
'NetBootConfig boot configs')
continue
# If this is the menu.lst file, we must mark it specially, so the
# consumer knows they can place it anywhere in the search path.
# Add pxegrub to the tuple list
None,
None,
None,
None,
None)]
return tuples
# Generic support methods for all BootConfig classes
"""Invoked by the MenuLstBootLoaderMixIn.
These are two cases that _write_menu_lst has to handle. The
first is when there was a menu.lst that was previously loaded.
In that case, portions of that file may change, depending on
modifications made to the BootLoader properties or to the
individual BootInstance objects that comprise the BootConfig.
The second case is a new menu.lst file. For this case, we
just fill the propdict with defaults, then update those
defaults with values from BootLoader properties and then
apply the propdict to the menu.lst template (above).
Additional entries not previously present in the menu.lst
file are appended. In this way, the ordering of the file
and user comments can be preserved.
"""
if min_mem64 is not None:
propdict['min_mem64'] = None
if timeout is not None:
propdict['timeout'] = None
propdict['hiddenmenu'] = None
# XXX - If there is no splashimage/forecolor/backcolor in
# XXX - the existing menu.lst, don't add it here if no
# XXX - splashimage / forecolor / backcolor was specified
# XXX - in the BootLoader properties. If there was no
# XXX - menu.lst previously loaded, DO add the defaults.
else:
default_grub_splash = None
default_forecolor = None
default_backcolor = None
# The structure of each if statement below is intentional--
# The else branch is for the case when we have a menufile
# already and we want to support REMOVING properties that
# were previously set (i.e. removing commands).
if splash is not None:
else:
propdict['splashimage'] = None
if forecolor is not None:
else:
propdict['foreground'] = None
if backcolor is not None:
else:
propdict['background'] = None
try:
except ValueError:
port = 0
sercmd = 'serial'
if port > 0xFF:
else:
# Delete any reference to the serial console if it exists,
# but only if we previously loaded a menu:
propdict['serial'] = None
propdict['terminal'] = None
# Figure out the default boot index first by iterating through
# all BootInstances
# Now write the global section
else:
# Filter out values in propdict that are just comment lines
# Use the values in propdict to update any global commands in
# the existing menu.lst entity list.
# Write the menu.lst's global section first by going through
# the entity list, making sure to ignore deleted boot
# instances:
# iterate through the list of boot instances in the BootConfig
# instance, adding an entry (or updating an existing entry) for
# each one:
# XXX - Do validation of kernel against kernel under
# self.rootpath (if specified)
# XXX - Also: should we verify that a 64-bit kernel is being
# used with a 64-bit boot archive and similarly for 32-bit?
# XXX - Use the signature attribute
# _generate_entry() returns a string only if inst._menulst_entity
# is None.
if entry_lines is not None:
else:
# Make sure we ignore deleted boot instances
if entry_lines is not None:
'unexpected (%s)', entry_lines)
"Update top-level commands in the menu.lst file associated with self"
deleted_cmds = set()
# MenuLstCommand instances at the top level of the entity list
# are global
continue
if entity_cmd in propdict:
if cmd_and_args is None:
# Delete this entity!
continue
# Update the entity in-place with the arguments from
# propdict
if deleted_cmds:
# Now figure out the set of globals that do not exist in the file
# and add them
if missing_globals:
for missing_cmd in missing_globals:
# Menu-entry generator infrastructure
"""Use the BootInstance's class name to find the entry-generator
method. Entry generator functions are responsible for producing a
string with the rest of the entry (the title is printed by the
caller)"""
if entry_generator is not None:
# Do the generic work of updating the title if an instance
# is associated with an existing menu.lst entity:
if instance._menulst_entity is not None:
# It's a method, so self must be passed explicitly
else:
if instance._menulst_entity is not None:
return None
else:
return ''
# If the BootInstance specifies a splashimage, foreground color
if inst._menulst_entity is None:
if splash:
else:
splash_cmd = None
if forecolor:
else:
fore_cmd = None
if backcolor:
else:
back_cmd = None
else:
splash_cmd = 'splashimage'
if splash:
fore_cmd = 'foreground'
if forecolor:
back_cmd = 'background'
if backcolor:
# XXX Check to ensure that splashimage/foreground/background is not
# XXX specified when this boot instance is using a non-graphics
# XXX console.
try:
except KeyError:
# If somehow another Python conversion specifier snuck in,
# raise an exception
else:
# kargs already has a leading space from the initialization, above
try:
except KeyError:
# If somehow another Python conversion specifier snuck in,
# raise an exception
raise BootmgmtMalformedPropertyValueError('boot_archive',
else:
# If this instance is associated with an existing menu.lst entity,
# update that entity
if inst._menulst_entity is not None:
return None
ostr = ''
if splash_cmd is not None:
if fore_cmd is not None:
if back_cmd is not None:
ostr += module_cmd
return ostr
"""
"""
if inst._menulst_entity is None:
return ostr
"Menu-entry generator function for SolarisDiskBootInstance instances"
ostr = ''
kargs = ''
bootfs_cmd = 'bootfs'
findroot_cmd = 'findroot'
raise BootmgmtIncompleteBootConfigError('bootfs '
'and rpool properties are missing')
# bootfs takes priority over the rpool property
# If there's a findroot command that's already part of the
# menu entry, use its partition hint
if inst._menulst_entity is not None:
# findroot's arguments come in two forms:
# SIGNATURE | (SIGNATURE,partition1[,slice])
findroot_orig = \
if findroot_orig is not None:
findroot_components[1:])
findroot_components[1:])
if findroot_cmd == 'findroot':
kargs = '-B $ZFS-BOOTFS'
# XXX - This is very simplistic and should be revisited
else:
if inst._menulst_entity is not None:
else:
return None
prepend_cmds = ''
if findroot_cmd != 'findroot':
if bootfs_cmd != 'bootfs':
return ostr
"""Menu-entry generator for ChainDiskBootInstance instances. This is a
VERY simple use of the rootnoverify and chainloader Legacy GRUB
commands. Chainloading is only supported to a specific, numbered
raise BootmgmtIncompleteBootConfigError('chaininfo property is '
'missing')
raise BootmgmtArgumentError('chaininfo must be a non-zero-length '
'tuple')
raise BootmgmtArgumentError('chaininfo[0] must be an int')
raise BootmgmtArgumentError('chaininfo[1] must be an int or tuple')
raise BootmgmtArgumentError('chaininfo[1] must not have > 2 items')
else:
diskstr += ')'
# if the user had a root command, use that, otherwise,
# use rootnoverify
else:
active_cmd = None
if inst.forceactive:
if inst._menulst_entity:
else:
active_cmd = 'makeactive'
else:
chainloader_cmd = 'chainloader '
if inst._menulst_entity is not None:
return None
if active_cmd:
return ostr
# Property-related methods
"""Parses the GRUB serial command argument string and returns a tuple
that can be used to set the SERIAL_PARAMS BootLoader property.
"""
# The serial command's default is port 0 / all other defaults
if not argstring:
return ('0', None, None, None, None, None)
# argstring should consist of a set of long options separated by
# spaces:
continue
else:
continue
str(serial_params)))
return serial_params
"""Parse serial parameters and return a tuple of (serial port number,
serial port speed, data bits, parity (one of 'no', 'odd' or 'even'),
stop bits (0 or 1), flow control) (all tuple members must be strings).
The form of the serial_params property is:
serial_params | A tuple containing (<portspec>,<speed>,<d>,
| <p>,<s>,<f>).
| <portspec> is currently defined to be a
| number (valid valid depend on the platform,
| but '0' is ttya (com1) and '1' is ttyb (com2)).
| Serial console parameters (<d>=data bits,
| <p>=parity ('N','E','O'),<s>=stop bits ('0','1'),
| <f>=flow control ('H','S',None) for hardware,
| software, or none). The default is:
| ('0',None,None,None,None,None).
"""
try:
'E': 'even',
except KeyError:
parity = None
else:
parity = None
try:
except KeyError:
flowc = None
else:
flowc = None
# if port is not indicated, force use of port 0.
if ret_params[0] is None:
return ret_params
return ('0', None, None, None, None, None)
# BootLoader installation methods
# Iterate through the list of boot instances, adding _menulst_entity
# attributes to those that do not have them. This prevents us from
# having to pepper lower-level code with getattr() calls.
# _write_loader performs the real guts of boot loader installation
"""Invoke installgrub to write stage1 and stage2 to disk. Slice
nodes (and only slice nodes) are required."""
return
or
raise BootLoaderInstallError('Device node is not a slice: ' +
args = ['/sbin/installgrub']
# installgrub will autodetect when to use the -m switch
# If a version is present, try to use it during installation.
if force:
args += ['-F']
args += ['-m']
pobj = None
try:
except CalledProcessError as cpe:
output = ''
raise BootLoaderInstallError('installgrub failed for '
raise BootLoaderInstallError('Error while trying to '
'invoke installgrub for '
finally:
if pobj and verbose_file:
print >> verbose_file, 'Output from "%s" was:' % \
print >> verbose_file, '<END OF OUTPUT>'
#
# Legacy GRUB menu.lst
#
"""
Command := Keyword Arguments | VarName '=' Value
Arguments := Arguments [ \t]+ Argument | Argument
Keyword := 'blocklist' | 'boot' | 'bootfs' | 'bootp' |
'cat' | 'chainloader' | 'cmp' | 'color' |
'configfile' | 'debug' | 'default' |
'device' | 'dhcp' | 'displayapm' |
'displaymem' | 'embed' | 'fallback' |
'find' | 'findroot' | 'fstest' | 'geometry' |
'halt' | 'help' | 'hiddenmenu' | 'hide' |
'ifconfig' | 'impsprobe' | 'initrd' |
'install' | 'ioprobe' | 'kernel$' |
'kernel' | 'lock' | 'makeactive' | 'map' |
'md5crypt' | 'min_mem64' | 'module$' |
'module' | 'modulenounzip' | 'pager' |
'partnew' | 'parttype' | 'password' |
'pause' | 'quit' | 'rarp' | 'read' |
'reboot' | 'root' | 'rootnoverify' |
'savedefault' | 'serial' | 'setkey' |
'setup' | 'terminal' | 'terminfo' |
'testload' | 'testvbe' | 'tftpserver' |
'timeout' | 'title' | 'unhide' |
'uppermem' | 'vbeprobe'
Argument := [^ \t\n]+
VarName := [A-Za-z0-9]+
Value := [^\n]+
"""
# XXX - Currently a NOP
def bootloader_classes():
return [LegacyGRUBBootLoader]