#
# 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
#
#
"""
Abstractions for boot configuration management. A BootConfig object aggregates
the boot configuration for a system. Implementations are provided for active
systems (systems that boot from disk devices) as well as for installation image
creation (i.e. for network-based installation images, optical disc installation
images, and USB-based installation images).
BootConfig provides access, through abstractions, to the system boot loader,
general system boot configuration variables, and the set of bootable instances
that the boot loader provides a means to boot.
"""
import collections
import re
import unittest
"""Abstract base class for boot configuration classes"""
# Valid flags to pass to __init__:
# Tuple indices for each tuple in the list output by the install() method
"""
Initialize the basic set of attributes for all BootConfig classes
-----------------------------------------------------------------
Argument| Valid Value(s)
--------+--------------------------------------------------------
flags | <Tuple of flags that modify the behavior of this
| BootConfig object> [tuple]
|
| Allowed values in the tuple:
| ----------------------------
| BootConfig.BCF_CREATE: Create a new boot
| configuration. If one already
| exists, it will be discarded.
| If this flag is not present and
| no boot configuration exists,
| an exception
| (BootmgmtConfigReadError)
| will be raised.
| BootConfig.BCF_ONESHOT:If True, only the final boot
| configuration files will be
| created when the
| commit_boot_config() is called.
| This is useful for one-shot
| creation of boot configurations
| for installation media.
| BootConfig.BCF_AUTOGEN:The set of boot instances will be
| automatically generated by
| scanning the system. The list of
| boot instances found by the scan
| will be available after
| the BootConfig object is created.
| This set of boot instances can
| then be tailored before calling
| commit_boot_config(). Note that
| the set of BootInstance objects
| added to this object's boot_instances
| are only guaranteed to include
| Solaris boot instances.
| BootConfig.BCF_MIGRATE:If supported, the existing boot
| configuration will be migrated
| from an older form to a newer
| form (i.e. conversion from legacy
| GRUB's menu.lst to GRUB2's
| configuration format). The conversion
| is performed at commit_boot_config()
| time. Cannot be used with the
| BCF_CREATE flag.
| BootConfig.BCF_NO_LOAD_BOOT_INSTANCES: Omits boot instances
| from the set of configuration data
| loaded.
| BootConfig.BCF_LOAD_SILENT_FAILURE: Does not raise an
| exception when loading configuration
| data fails. Using this allows a
| partial set of configuration data to
| be read.
|
platform| [optional] The target system architecture / firmware tuple for
(keyword| which this boot configuration will be written. Useful
arg) | for the creation of boot configuration files for network
| boot instances, or when a boot configuration is created
| on a system of a different architecture / firmware than
| the target. If this is not supplied, the assumption is
| that the boot configuration will be for the current
| system. Supported tuple values are:
| { ('sparc', 'obp'),
| ('x86', 'bios'),
| ('x86', 'uefi64') }
| A firmware value of None means that the boot
| configuration will be written for all supported
| firmware types for the specified architecture. Note
| that not all BootConfig subclasses support this
| wildcard platform firmware specification.
| If a particular BootConfig subclass does not support
| manipulation of a different platform's boot
| configuration, a BootmgmtNotSupportedError will be
| raised.
loader\ |
class | The class corresponding to the BootLoader that the
| BootConfig's creator requests wants to be this
| BootConfig's BootLoader. If this BootLoader cannot be
| used, a BootmgmtInvalidBootloaderError is raised.
-----------------------------------------------------------------
"""
self.boot_instances = []
raise BootmgmtArgumentError('Invalid platform value specified. '
'Expected 2-tuple or 2-item list')
'Migration cannot be combined with creation')
elif (self.boot_loader and
'to load boot loader configuration. Creating new boot '
'configuration instead. artifacts were %s.',
else:
try:
# _load_boot_config raises BootmgmtConfigReadError
except BootmgmtConfigReadError:
raise
else:
"""[Implemented by child classes] Loads the boot configuration (with
guidance from kwargs)."""
pass
"""[Implemented by child classes] Initializes this instance with
a new boot configuration (with guidance from kwargs)."""
pass
"""Initializes this instance's boot_loader"""
return loader
"""Returns the requested platform string for this BootConfig
"""
"""Returns the requested firmware string for this BootConfig
"""
"Return the root directory where this BootConfig is be stored"
return None
"""
Adds one or more new boot instances to this boot configuration.
boot_instance is a list of BootInstance references or a single
BootInstance reference.
The where argument determines where in the ordered list of boot
instances this boot instance entry will be placed. The default
is -1, which means the end of the list of boot instances. If
where is a callable object, it will be used to iterate through
the boot instances list. If it returns a negative value, the
boot_instance will be inserted BEFORE that item; If it returns
a positive value, the boot_instance will be inserted AFTER that
item; If it returns 0, no insertion will occur at that point
in the iteration. The arguments to the function are
(<current_boot_instance_in_the_iteration>, boot_instance).
"""
for instance in boot_instances:
# If where is not -1, make sure we increment it so that
# subsequent boot instances are placed in the boot_instances
# array in the proper order
if where > -1:
else:
prevDefault = None
# Find previous default (if any)
# where is an index if it's > 0
if whereval < 0:
break
elif whereval > 0:
break
else:
raise BootmgmtArgumentError('The where parameter is malformed')
if not prevDefault is None:
"""
Deletes one or more boot instances from the boot configuration.
filter_func is a function that takes a single argument (a
BootInstance). If it returns True, that BootInstance is removed
from the boot configuration.
The 'all' parameter is True if all matching boot instances are
to be deleted. If False, only the first instance for which
filter_func returns True is deleted.
For example:
delete_boot_instance(lambda x: x.title == 'snv_158')
will delete all boot instances whose titles match 'snv_158'.
"""
else:
return 1
return 0
"""
Applies mod_func to all boot instances for which filter_func
returns True.
This is shorthand for:
for bi in filter(filter_func, bootconfig_obj.boot_instances):
mod_func(bi)
(The (single) argument to filter_func and mod_func is a
BootInstance ref).
"""
"""Writes the boot configuration (including boot instances and boot
loader settings) to stable storage.
If this object was created with the BootConfig.BC_ONESHOT flag,
then only the final boot configuration file(s) will be created
(i.e. the Legacy GRUB menu.lst, GRUB2 configuration file, or
SPARC menu.lst)-- any other state files that store boot
configuration information will not be written (customizations to
this boot configuration may not persist). This may prevent
incremental modifications to the boot configuration from
being possible (depending on the boot loader in use). BC_ONESHOT
should only be used when creating a boot configuration that is
not intended to change (i.e. when creating a boot configuration
for use with an install image, not a running system).
boot_devices is a list of strings, each of which is the path
to a character-special device where the boot loader should be
installed. This argument should be omitted when the consumer
desires to write the boot configuration files to a temporary
directory.
If temp_dir is not None, then the set of files that constitute
the boot configuration is written to a temporary directory (and
not to the official location(s)). (temp_dir must exist or an
IOError will be raised). When files are written to temp_dir,
commit_boot_config() returns a list of 7-tuples of the following
form:
(<type>, <srcpath>, <object>, <destpath>, <uid>, <gid>, <mode>)
Each of these tuples describes a boot configuration file that
was written, along with the required system-relative path where
it must be copied. This enables a consumer to install the
file(s) into the correct place(s) on a system or install image
without having to hard-code knowledge of the filenames)).
For example:
[('file', '/tmp/bc.Jzx1cZa/boot/solaris/bootenv.rc',
<bootmgmt.bootconfig.SolarisDiskBootInstance object at ...>,
'%(systemroot)s/boot/solaris/bootenv.rc', 'root', 'sys', 0644)]
The <type> value in the tuple identifies the type of file in
the tuple. This is useful for conveying platform-specific
attributes of a particular file. For example, <type> could be
'eltorito' to identify an eltorito boot image, which a consumer
would recognize and then use to supply the argument to
`mkisofs -b`. See child class definitions for additional <type>
values. Only <type>='file' is defined at the BootConfig level.
The <object> element, if not None, is a reference to a
BootInstance object. It provides clarification for tokens that
are embedded in the destination path. In the above example, the
`systemroot' token refers to the root path of the Solaris BE
identified by the SolarisDiskBootInstance object in the tuple.
In this example, including the object allows the consumer to
resolve the root path location.
Note the use of Python string-formatting tokens, for which a
consumer must supply definitions. The following list of tokens
are defined at the BootConfig level; child classes may define
additional tokens:
----------------------------------------------------------------
Token Name | Meaning
------------------+---------------------------------------------
systemroot | The path to a boot instance's mounted root
| filesystem. [string]
----------------------------------------------------------------
"""
if not self.boot_loader:
raise BootmgmtError('A boot loader could not be established '
'for this boot configuration (this is usually '
'due to missing boot loader files). '
'The system cannot be guaranteed to be '
'bootable.')
if temp_dir is None:
return None
else:
tuple_list = []
# If a temp dir is specified, ignore the dirty flag and just write
# all the config to the temporary directory
if bv_tuples:
if not self.boot_loader is None:
if bl_tuples:
return tuple_list
'\n')
s += '===[ End Boot Instances ]===\n'
if not self.boot_loader is None:
s += 'Boot loader:\n'
else:
s += 'No boot loader'
return s
"""A class for managing the boot configuration stored on hard disk-like
storage devices. Handles Solaris boot configurations stored in ZFS root
pools and on devices with a UFS root filesystem"""
# Key names for keyword arguments to the constructor
# Tokens returned from commit_boot_config()
# DiskBootConfig does not support platforms other than native
if not platform is None:
'not support cross-platform operations')
fstype = None
fstype = 'zfs'
fstype = 'ufs'
if fstype is None:
raise BootmgmtNotSupportedError('The filesystem type supplied '
'to the DiskBootConfig constructor was not recognized')
"""Loads the boot configuration"""
if self.boot_loader:
return
# If, after loading the boot loader's config, we do not have a
# default bootfs, check the root pool's bootfs property and use
# it to find a boot instance. In the event there are multiple
# boot instances with the same bootfs, choose the first one.
return
# Get the default bootfs for this root pool:
lzfsh = libzfs_init()
# If there is no default or we couldn't get it, bail
if pool_default_bootfs is None:
return
# If there is at least one boot instance with a bootfs that matches,
# set the first as default
"""Initializes this instance with a new boot configuration"""
if self.boot_loader:
return # Nothing to do
# Look for the default boot instance. If there is none, use the first
# one.
default_inst = None
break
if default_inst is None:
if default_bootfs is None:
'attribute; Not setting root pool bootfs property')
return # Nothing to do!
# Use libzfs to set the bootfs property
lzfsh = libzfs_init()
if setprop_rv == 0:
else:
setprop_rv = 0
if setprop_rv != 0:
return tuples
"""A class for managing the boot configuration stored on optical disk
storage devices. Handles Solaris boot configurations stored on DVD
media"""
""
# Weed out unsupported flags:
': Migration is not \
# Save the image's root directory:
if self.odd_image_root is None:
raise BootmgmtArgumentError('Missing oddimage_root argument')
**kwargs)
return self.odd_image_root
"""A class for managing the boot configuration stored on a network boot
server.
"""
# NetBootConfigs must not use ProxyBootLoader -- it should only be
# associated with a single boot loader class. There is no 'upgrade'
# issue that would require ProxyBootConfig
""
# Weed out unsupported flags:
': Migration is not \
# Save the image's root directory:
if self.net_osimage_root is None:
raise BootmgmtArgumentError('Missing net_osimage_root argument')
# Save the data directory:
if self.net_data_root is None:
raise BootmgmtArgumentError('Missing net_data_root argument')
# Save the (optional) tftproot-relative directory for config files
# / boot loader files
# Now look for the client id:
"^(([0-9A-Fa-f]){12}|([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2})$",
if client_id_valid is None:
raise BootmgmtArgumentError('Bad client_id value: %s' %
**kwargs)
"""Loads the boot configuration"""
if self.boot_loader:
"""Initializes this instance with a new boot configuration"""
if self.boot_loader:
return self.net_osimage_root
"""
"""
# NetBootConfig is a bit different from the other BootConfig classes
# in that we need the tuple list to be returned even though we're
# maintaining the configuration files so that they can be loaded
# and modified in the future. As a result, we need to pass a "temp"
# dir to commit_boot_config(), but in this case, it's not temporary--
# it's the location from which we can load the config in the future.
"""Returns a string that should be used as a client-specific suffix
for configuration filenames, or the empty string if this configuration
is not client-specific."""
else:
return ''
###############################################################################
"""BootInstance is the core abstraction for a bootable instance of an
operating system. BootConfig objects aggregate zero or more
BootInstance objects. A BootInstance should always be associated
with a BootConfig object (so that if a consumer modifies a
BootInstance, the BootConfig is notified that its state has
changed so it can write updated configuration information at
BootConfig.commit_boot_config()-time). This association is made
when a BootInstance is passed to BootConfig.add_boot_instance()."""
# TARGET should be set to None if this class can be used with any
# system target, otherwise should be set to the tuple
# (target, firmwarelist) (i.e. "('x86', ['bios'])"). If firmwarelist is
# None, all firmware types are allowed to use this BootInstance.
TARGET = None
# Valid attributes ('default' is a special case)
'transient': False,
'identfile': None,
'platform' : None}
if copysrc:
# The copy implementation is a bit tricky. First, we need
# to initialize the current instance's _attributes member,
# because we override __setattr__ and it needs access to
# _attributes to determine what attribute-setting causes
# this instance to mark the associated bootconfig as dirty.
# Next, initialize a dictionary with all attributes and their
# either current or default values, so the copy has all
# attributes of the original
# Finally, init the private state associated with this BootInstance
# The copied BootInstance does NOT get its _bootconfig initialized
self._bootconfig = None
self.linked_boot_instance = None
return
# If the child class added its own set of attributes, just append to
# it; overwise, set it to the default set from this class
self._bootconfig = None
self.linked_boot_instance = None
# Only add attributes to this instance if they were not overridden
# by the caller's keyword arguments
# If the key wasn't already set in this instance,
# init it to a default value here
# If the user passed in keyword args, add them as attributes
# If the key wasn't already set in this instance,
# init it to a default value here
continue
# init_from_rootpath must be called after all instance variables
# have been set
if rootpath is not None:
"""Returns a dictionary of default attributes
"""
return defattr
"""Returns a dictionary of current attributes and their values.
Child classes should extend this method if there are attributes
they need to preserve that are not in the class's _attributes dict.
"""
retdict = {}
if value:
# default is a special case, and is not covered by default_attributes:
return retdict
"""Returns a copy of the current BootInstance instance."""
"""Updates the attributes in this BootInstances with the values from
other. The 'default' property IS NOT updated."""
try:
except AttributeError as attrerr:
"""Returns True if self is the same as other (attributes and
class names are compared)"""
return False
return False
try:
return False
except AttributeError as attrerr:
return False
return False
return True
"""Initialize the boot instance with information from the root path
given. This can be done after the object has been constructed, so
that consumers do not have to complete the cumbersome task of
mounting each BootInstance that's created."""
try:
except BootmgmtError as bmerr:
"""By setting this BootInstance to be the default, the previous default
must be disabled. This property setter function does just that by
using the _bootconfig reference to find the list of boot instances,
locating the previous default, and setting it to False. Note that
only setting the default to True triggers this search (otherwise, we
could end up in an infinite recursive loop)."""
raise BootmgmtArgumentError('default must be either True or False')
return
else:
if not self._bootconfig is None:
# The following assignment's side-effect marks the
# BootConfig dirty
return
if not self._bootconfig is None:
# If we got here, then either the value is False or it was True
# and no previous default was found, so mark the BootConfig dirty
"""Initializes this instance's boot_vars"""
return platform
return platform[0]
return None
"""Returns the platform-specific default-attribute dict (if it exists)
otherwise returns the _attributes dict (if it exists), otherwise
returns {}
"""
attrname = '_attributes'
elif platform:
raise BootmgmtArgumentError('Invalid platform given: Must be '
'a string or a 2-tuple of (string, string|None)')
if not platform:
attrname = '_attributes'
"""Intercept the set attribute method to enforce setting of a
particular set of boot instance attributes (the union of those
defined in child classes and this class). 'default' is treated
specially (see _set_default())"""
if key == 'default':
"""Delete an attribute from this BootInstance. 'default' is treated
specially, as with __setattr__."""
if key == 'default':
return
if not self._bootconfig is None:
s = ''
s += 'default = True\n'
s += '\n===[ End Boot Variables ]==='
else:
s += '===[ No Boot Variables ]==='
return s
# XXX Ugh
return False
return False
return True
"""A boot instance of a chainloaded operating system"""
'chainstart' : '0',
'chaincount' : '1',
'forceactive': False}
"""rootpath is not supported, so should remain `None'"""
if not copysrc:
if copysrc:
# Treat the copy of chaininfo specially-- must be done after
# the super().__init__ to set up the bootconfig reference!
"""Returns a dictionary of default attributes
"""
return superdict
"""Returns a dictionary of attribute values suitable for rebuilding
this instance. chaininfo is converted into a comma-delimited string.
"""
tuple_strings = []
# Chaininfo is a nested tuple, but for serialization, we flatten
# it.
else:
return superdict
"""Checks the values passed in kwargs and fixes them to the proper
types. Returns a new dictionary with the modified values.
"""
# Split string into a list:
part = None
try: # Convert each element to an int, if possible
except ValueError:
part = []
try: # Convert each element to an int, if possible
except ValueError:
pass
if part is not None:
return kwargs
"""Abstraction for a Solaris Boot Instance. Supported attributes are:
- kernel [string] [optional] [x86-only]
- boot_archive [string] [optional] [x86-only]
- kargs [string] [optional] [x86-only]: Kernel argument string
- signature [string] [optional] [x86-only]: The "boot
signature" of this
boot instance.
- Public Methods:
- expanded_kargs(): [x86-only] Expands all macros in kargs
and returns the expanded kernel argument string.
"""
x86_attributes = {
'boot_archive': '/platform/i86pc/amd64/boot_archive',
'kargs': None,
'signature': None,
'splashimage': None,
'foreground': None,
'background': None
}
if not copysrc:
# If the child class added its own set of attributes, just append to
# it; overwise, set it to the default set from this class
# If kargs are specified, scrub them for any args that may be
# boot loader implementation-dependent strings so that boot
# instances loaded from different boot loaders' config files can
# be directly compared
# If the args are empty after the scrub, just set them to None
# to make comparisons sane.
kwargs['kargs'] = None
"""Returns a dictionary of default attributes
"""
return superdict
"""Expands all macros found in self.kargs and returns the string
with all macros expanded. Currently returns the kargs unmodified."""
if get_current_arch_string() != 'x86':
raise BootmgmtUnsupportedPlatformError('expanded_kargs() not '
'supported on the %s platform' % get_current_arch_string())
"""Abstraction for a Disk-based Solaris Boot Instance. Additional
attributes supported are:
- fstype [string] [required]: One of: [ 'ufs', 'zfs' ]
- If fstype == 'zfs':
* bootfs [string] [overrides the value of 'rpool']
* rpool [string]
Public methods implementations:
- expanded_kargs(): [x86-only] Expands all macros in kargs
and returns the expanded kernel argument
string (currently only $ZFS-BOOTFS
expansion).
"""
'bootfs': None,
'rpool' : None}
if not copysrc:
# If the child class added its own set of attributes, just append to
# it; overwise, set it to the default set from this class
if copysrc:
return
raise BootmgmtMissingInfoError('missing bootfs or rpool arg')
if 'bootfs' in kwargs:
# Make sure bootfs appears to be well-formed
raise BootmgmtArgumentError('Invalid bootfs: %s' %
kwargs['bootfs'])
# If title is STILL None, try an alternate (the last component of the
# bootfs):
"""Returns the default title string from the bootfs, if any, or None
"""
try:
except:
return None
"""Returns a dictionary of default attributes
"""
return superdict
# Invoke the parent's init_from_rootpath first
# self.title is guaranteed to have been initialized to something
try:
except:
alt_title = None
"""Expands all macros found in self.kargs and returns the string
with all macros expanded. Currently, only $ZFS-BOOTFS is a
supported macro."""
if get_current_arch_string() != 'x86':
raise BootmgmtUnsupportedPlatformError('expanded_kargs() not '
'supported on the %s platform' % get_current_arch_string())
# If kargs is the empty string (or is None) or if the well-known
# legacy GRUB $ZFS-BOOTFS token does NOT exist in the kargs and
# this is a ZFS boot instance, we need to create the entire
# zfs bootfs argument from scratch.
if not kargs:
kargs = ''
elif kargs is None:
return None
# ZFS-BOOTFS expands to a string that contains two elements:
# The zfs-bootfs element and the bootpath element. Retrieve
# Them both from libzfs here, then perform the substitution.
"""Use libzfs (via ctypes) to get the zpool properties and return a
string that consists of the kernel arguments needed to specify the
zfs root pool and bootfs associated with this BootInstance
"""
lzfsh = libzfs_init()
else:
return ''
else:
# XXX: We're just using the first physpath for now
"""Abstraction for a Network-based Solaris Boot Instance.
"""
if copysrc:
return
# If title is STILL None, make one up.
"""Abstraction for an optical-disc-based Solaris Boot Instance
"""
# No further initialization is needed if there is a copysrc
# Invoke the parent's init_from_rootpath first
# self.title is guaranteed to have been initialized to something
return
# On a disk-based instance, the title can be derived from the
try:
except IOError:
pass
return title
###############################################################################
#################################### TESTS ##################################
###############################################################################
pass
pass
def testSuite():
if __name__ == '__main__':