7038421 Provide a boot checkpoint implementation based on pybootmgmt
authorNiall Power <niall@brawndo.local>
Fri, 06 May 2011 11:44:10 +1000
changeset 1111 0c6c862af34f
parent 1110 17d2e44bfc3f
child 1112 6a71a28b230b
7038421 Provide a boot checkpoint implementation based on pybootmgmt
usr/src/Makefile.master
usr/src/Targetdirs
usr/src/cmd/distro_const/__init__.py
usr/src/cmd/distro_const/checkpoints/Makefile
usr/src/cmd/distro_const/checkpoints/create_iso.py
usr/src/cmd/distro_const/checkpoints/grub_setup.py
usr/src/cmd/distro_const/checkpoints/test/test_grub_setup.py
usr/src/cmd/distro_const/distro_spec.py
usr/src/cmd/distro_const/manifest/dc_ai_x86.xml
usr/src/cmd/distro_const/manifest/dc_livecd.xml
usr/src/cmd/distro_const/manifest/dc_text_x86.xml
usr/src/lib/Makefile
usr/src/lib/Makefile.targ
usr/src/lib/install_boot/Makefile
usr/src/lib/install_boot/__init__.py
usr/src/lib/install_boot/boot.py
usr/src/lib/install_boot/boot_spec.py
usr/src/lib/install_boot/test/test_boot.py
usr/src/lib/install_boot/test/test_boot_spec.py
usr/src/lib/install_manifest/dtd/Makefile
usr/src/lib/install_manifest/dtd/boot_mods.dtd
usr/src/lib/install_manifest/dtd/dc.dtd
usr/src/pkg/manifests/install-distribution-constructor.mf
usr/src/pkg/manifests/system-library-install.mf
--- a/usr/src/Makefile.master	Thu May 05 13:47:10 2011 -0600
+++ b/usr/src/Makefile.master	Fri May 06 11:44:10 2011 +1000
@@ -100,6 +100,7 @@
 ROOTPYTHONVENDORINSTALLPROF=    $(ROOTPYTHONVENDORINSTALL)/profile
 ROOTPYTHONVENDORINSTALLTI=	$(ROOTPYTHONVENDORINSTALL)/text_install
 ROOTPYTHONVENDORSOLINSTALL=	$(ROOTPYTHONVENDOR)/solaris_install
+ROOTPYTHONVENDORSOLINSTALLBOOT =	$(ROOTPYTHONVENDORSOLINSTALL)/boot
 ROOTPYTHONVENDORSOLINSTALLDATACACHE= $(ROOTPYTHONVENDORSOLINSTALL)/data_object
 ROOTPYTHONVENDORINSTALLDC=	$(ROOTPYTHONVENDORSOLINSTALL)/distro_const
 ROOTPYTHONVENDORINSTALLDCCHKPT= $(ROOTPYTHONVENDORINSTALLDC)/checkpoints
--- a/usr/src/Targetdirs	Thu May 05 13:47:10 2011 -0600
+++ b/usr/src/Targetdirs	Fri May 06 11:44:10 2011 +1000
@@ -72,6 +72,7 @@
 	/usr/lib/python2.6/vendor-packages/solaris_install/distro_const/checkpoints \
 	/usr/lib/python2.6/vendor-packages/solaris_install/distro_const/checkpoints/defaultfiles \
 	/usr/lib/python2.6/vendor-packages/solaris_install \
+	/usr/lib/python2.6/vendor-packages/solaris_install/boot \
 	/usr/lib/python2.6/vendor-packages/solaris_install/data_object \
 	/usr/lib/python2.6/vendor-packages/solaris_install/engine \
 	/usr/lib/python2.6/vendor-packages/solaris_install/engine/test \
--- a/usr/src/cmd/distro_const/__init__.py	Thu May 05 13:47:10 2011 -0600
+++ b/usr/src/cmd/distro_const/__init__.py	Fri May 06 11:44:10 2011 +1000
@@ -48,6 +48,7 @@
 
 from osol_install.install_utils import set_http_proxy
 from osol_install.liberrsvc import ES_DATA_EXCEPTION
+from solaris_install.boot.boot_spec import BootMods
 from solaris_install.data_object import DataObject, ObjectNotFoundError
 from solaris_install.data_object.cache import DataObjectCache
 from solaris_install.data_object.data_dict import DataObjectDict
--- a/usr/src/cmd/distro_const/checkpoints/Makefile	Thu May 05 13:47:10 2011 -0600
+++ b/usr/src/cmd/distro_const/checkpoints/Makefile	Fri May 06 11:44:10 2011 +1000
@@ -20,7 +20,7 @@
 #
 
 #
-# Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
 #
 
 include ../../Makefile.cmd
@@ -39,7 +39,6 @@
 		create_iso.py \
 		create_usb.py \
 		custom_script.py \
-		grub_setup.py \
 		pkg_img_mod.py \
 		pre_pkg_img_mod.py
 
--- a/usr/src/cmd/distro_const/checkpoints/create_iso.py	Thu May 05 13:47:10 2011 -0600
+++ b/usr/src/cmd/distro_const/checkpoints/create_iso.py	Fri May 06 11:44:10 2011 +1000
@@ -106,10 +106,16 @@
             else:
                 self.distro_name = distro[0].name
 
-            dc_pers_dict = self.doc.persistent.get_children(name=DC_PERS_LABEL,
-                class_type=DataObjectDict)
+            # Look for DC persistent dictionary. Force an error if not found
+            # on X86 since the 'bios-eltorito-img' key is required.
+            dc_pers_dict = self.doc.persistent.get_children(
+                name = DC_PERS_LABEL,
+                class_type=DataObjectDict,
+                not_found_is_err=(self.arch=='i386'))
             if dc_pers_dict:
                 self.dc_pers_dict = dc_pers_dict[0].data_dict
+            if self.arch == 'i386':
+                self.bios_eltorito = self.dc_pers_dict["bios-eltorito-img"]
         except KeyError, msg:
             raise RuntimeError("Error retrieving a value from the DOC: " + \
                 str(msg))
@@ -141,7 +147,7 @@
         # set the mkisofs_cmd
         if self.arch == "i386":
             self.mkisofs_cmd = [cli.MKISOFS, "-quiet", "-o", self.dist_iso,
-                                "-b", "boot/grub/stage2_eltorito", "-c",
+                                "-b", self.bios_eltorito, "-c",
                                 ".catalog", "-no-emul-boot",
                                 "-boot-load-size", "4", "-boot-info-table",
                                 "-N", "-l", "-R", "-U", "-allow-multidot",
--- a/usr/src/cmd/distro_const/checkpoints/grub_setup.py	Thu May 05 13:47:10 2011 -0600
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,396 +0,0 @@
-#!/usr/bin/python
-#
-# 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
-# or http://www.opensolaris.org/os/licensing.
-# 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
-#
-
-#
-# Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
-#
-
-""" grub_setup.py - Creates a custom grub menu.lst file
-"""
-import abc
-import os
-import os.path
-
-from solaris_install.data_object import ObjectNotFoundError
-from solaris_install.data_object.data_dict import DataObjectDict
-from solaris_install.distro_const import DC_LABEL
-from solaris_install.distro_const.distro_spec import GrubMods, GrubEntry
-from solaris_install.engine import InstallEngine
-from solaris_install.engine.checkpoint import AbstractCheckpoint as Checkpoint
-
-
-class GrubSetup(Checkpoint):
-    """ GrubSetup - class to customize the grub menu
-    """
-    DEFAULT_ENTRY = 0
-    DEFAULT_TIMEOUT = 30
-    # set the DEFAULT_MIN_MEM to 0
-    DEFAULT_MIN_MEM = 0
-
-    def __init__(self, name):
-        """ constructor for class.
-        """
-        super(GrubSetup, self).__init__(name)
-
-        # instance attributes
-        self.doc = None
-        self.dc_dict = {}
-        self.pkg_img_path = None
-        self.grub_mods = None
-        self.grub_entry_list = None
-        self.img_info_path = None
-        self.menu_list = None
-
-    def get_progress_estimate(self):
-        """Returns an estimate of the time this checkpoint will take
-        """
-        return 1
-
-    def parse_doc(self):
-        """ class method for parsing data object cache (DOC) objects for use by
-        the checkpoint.
-        """
-        self.doc = InstallEngine.get_instance().data_object_cache
-
-        self.dc_dict = self.doc.volatile.get_children(name=DC_LABEL,
-            class_type=DataObjectDict)[0].data_dict
-
-        try:
-            self.pkg_img_path = self.dc_dict["pkg_img_path"]
-        except KeyError, msg:
-            raise RuntimeError("Error retrieving a value from the DOC: " + \
-                str(msg))
-
-        self.img_info_path = os.path.join(self.pkg_img_path, ".image_info")
-        self.menu_list = os.path.join(self.pkg_img_path, "boot/grub/menu.lst")
-
-        # retrieve manifest specific grub configurations
-        grub_mods = self.doc.volatile.get_descendants(class_type=GrubMods)
-        if len(grub_mods) == 0:
-            # if there are no GrubMods in the doc, create a new GrubMods
-            # instance and allow it to be populated with default values
-            # the DEFAULT_MIN_MEM is only used for setting up GRUB_MIN_MEM64
-            # in AI .image_info for installadm backward compatibility
-            self.grub_mods = GrubMods("grub mods")
-        else:
-            self.grub_mods = grub_mods[0]
-
-        if self.grub_mods.default_entry is None:
-            self.grub_mods.default_entry = self.DEFAULT_ENTRY
-        if self.grub_mods.timeout is None:
-            self.grub_mods.timeout = self.DEFAULT_TIMEOUT
-        if self.grub_mods.min_mem is None:
-            self.grub_mods.min_mem = self.DEFAULT_MIN_MEM
-        if self.grub_mods.title is None:
-            # set the default title
-            rel_file = os.path.join(self.pkg_img_path, "etc/release")
-            with open(rel_file, "r") as rel:
-                # read the first line of /etc/release
-                self.grub_mods.title = rel.readline().strip()
-        else:
-            # the manifest specified a special grub title.  Record it in
-            # .image_info
-            with open(self.img_info_path, "a+") as fh:
-                fh.write("GRUB_TITLE=" + self.grub_mods.title + "\n")
-
-        # verify something is present
-        if len(self.grub_mods.title) == 0:
-            raise RuntimeError("Error finding or extracting non-empty " + \
-                               "release string from /etc/release")
-
-        self.grub_entry_list = self.doc.volatile.get_descendants(
-            class_type=GrubEntry)
-
-    @abc.abstractmethod
-    def build_entries(self):
-        """ abstract method which is required by subclasses to implement
-        """
-        raise NotImplementedError
-
-    def build_position_specific(self, entries):
-        """ class method to insert position specific entries to the menu.lst
-        file.
-        """
-        # walk each entry in the grub_entry_list
-        for grub_entry in self.grub_entry_list:
-            entry = [" ".join(["title", self.grub_mods.title,
-                              grub_entry.title_suffix])]
-            for line in grub_entry.lines:
-                entry.append("\t" + line)
-
-            # place the entire new entry into the entries list
-            if grub_entry.position is None:
-                entries.append(entry)
-            else:
-                entries.insert(int(grub_entry.position), entry)
-
-        return entries
-
-    def write_entries(self, entries):
-        """ class method to write out everything in the entries list to the
-        menu.lst file.
-        """
-        with open(self.menu_list, "w") as menu_lst_fh:
-            menu_lst_fh.write("default %s\n" % self.grub_mods.default_entry)
-            menu_lst_fh.write("timeout %s\n" % self.grub_mods.timeout)
-
-            # write out the entries
-            for entry in entries:
-                for line in entry:
-                    menu_lst_fh.write(line + "\n")
-                menu_lst_fh.write("\n")
-
-    def execute(self, dry_run=False):
-        """ Primary execution method used by the Checkpoint parent class.
-        dry_run is not used in DC
-        """
-        self.logger.info("=== Executing Grub Setup Checkpoint ===")
-
-        # parse the DOC object
-        self.parse_doc()
-
-        # construct the entries list
-        entries = self.build_entries()
-
-        # add all position specific grub entries
-        entries = self.build_position_specific(entries)
-
-        # write out the default entry number and default timeout
-        # values, along with everything in entries
-        self.logger.info("Writing menu.lst")
-        self.write_entries(entries)
-
-
-class AIGrubSetup(GrubSetup):
-    """ AIGrubSetup - class to customize the grub menu for AI distributions
-    """
-
-    def __init__(self, name, arg=None):
-        GrubSetup.__init__(self, name)
-        if arg:
-            self.__setup(**arg)
-        else:
-            self.__setup()
-
-    def __setup(self, installadm_entry="boot image"):
-        self.installadm_entry = installadm_entry
-
-    def build_entries(self):
-        """ class method for constructing the entries list.
-        """
-        entries = []
-
-        title = "title " + self.grub_mods.title
-        kernel = "\tkernel$ /platform/i86pc/kernel/$ISADIR/unix"
-        module = "\tmodule$ /platform/i86pc/$ISADIR/boot_archive"
-
-        # create lists of grub titles, kernels, and module lines to use
-        ai_titles = [title + " Automated Install custom",
-                     title + " Automated Install",
-                     title + " Automated Install custom ttya",
-                     title + " Automated Install custom ttyb",
-                     title + " Automated Install ttya",
-                     title + " Automated Install ttyb"]
-        ai_kernel = [kernel + " -B install=true,aimanifest=prompt",
-                     kernel + " -B install=true",
-                     kernel + " -B install=true,aimanifest=prompt," + \
-                         "console=ttya",
-                     kernel + " -B install=true,aimanifest=prompt," + \
-                         "console=ttyb",
-                     kernel + " -B install=true,console=ttya",
-                     kernel + " -B install=true,console=ttyb"]
-        ai_module = len(ai_titles) * [module]
-
-        # create a new list from zipper'ing each of the lists
-        entries = zip(ai_titles, ai_kernel, ai_module)
-
-        # boot from hd entry.
-        entries.append(["title Boot from Hard Disk",
-                        "\trootnoverify (hd0)",
-                        "\tchainloader +1"])
-        return entries
-
-    def update_img_info_path(self):
-        """ class method to write out the .img_info_path file.
-        """
-        self.logger.debug("updating %s" % self.img_info_path)
-
-        # write out the GRUB_MIN_MEM64 and GRUB_DO_SAFE_DEFAULT lines
-        with open(self.img_info_path, "a+") as iip:
-            iip.write("GRUB_MIN_MEM64=%s\n" % self.grub_mods.min_mem)
-            iip.write("GRUB_DO_SAFE_DEFAULT=true\n")
-            iip.write("NO_INSTALL_GRUB_TITLE=%s\n" % self.installadm_entry)
-
-    def execute(self, dry_run=False):
-        """ Primary execution method used by the Checkpoint parent class.
-        """
-        self.logger.info("=== Executing Grub Setup Checkpoint ===")
-
-        # parse the DOC object
-        self.parse_doc()
-
-        # update the .img_info_path file
-        self.update_img_info_path()
-
-        # construct the entries list
-        entries = self.build_entries()
-
-        # add all position specific grub entries
-        entries = self.build_position_specific(entries)
-
-        # write out the default entry number and default timeout
-        # values, along with everything in entries
-        self.logger.info("Writing menu.lst")
-        self.write_entries(entries)
-
-
-class LiveCDGrubSetup(GrubSetup, Checkpoint):
-    """ LiveCDGrubSetup - class to customize the grub menu for livecd
-    distributions
-    """
-
-    def __init__(self, name):
-        """ constructor for class.
-        """
-        super(LiveCDGrubSetup, self).__init__(name)
-
-    def get_progress_estimate(self):
-        """Returns an estimate of the time this checkpoint will take
-        """
-        return 1
-
-    def build_position_specific(self, entries):
-        """ class method to insert position specific entries to the menu.lst
-        file.
-        """
-        # walk each entry in the grub_entry_list
-        for grub_entry in self.grub_entry_list:
-            entry = [" ".join(["title", self.grub_mods.title,
-                              grub_entry.title_suffix])]
-            for line in grub_entry.lines:
-                entry.append("\t" + line)
-
-            # place the entire new entry into the entries list
-            if grub_entry.position is None:
-                entries.append(entry)
-            else:
-                entries.insert(int(grub_entry.position), entry)
-
-        return entries
-
-    def build_entries(self):
-        """ class method for constructing the entries list.
-        """
-        entries = []
-
-        title = "title " + self.grub_mods.title
-        kernel = "\tkernel$ /platform/i86pc/kernel/$ISADIR/unix"
-        module = "\tmodule$ /platform/i86pc/$ISADIR/boot_archive"
-
-        # create lists of grub titles, kernels, and module lines to use
-        lcd_titles = [title,
-                      title + " VESA driver",
-                      title + " text console",
-                      title + " Enable SSH"]
-
-        lcd_kernel = [kernel,
-                      kernel + " -B livemode=vesa",
-                      kernel + " -B livemode=text",
-                      kernel + " -B livessh=enable"]
-
-        lcd_module = len(lcd_titles) * [module]
-
-        # create a new list from zipper'ing each of the lists
-        entries = zip(lcd_titles, lcd_kernel, lcd_module)
-
-        # boot from hd entry.
-        entries.append(("title Boot from Hard Disk", "\trootnoverify (hd0)",
-                        "\tchainloader +1"))
-
-        return entries
-
-    def write_entries(self, entries):
-        """ class method to write out everything in the entries list to the
-        menu.lst file.
-        """
-        with open(self.menu_list, "w") as menu_lst_fh:
-            menu_lst_fh.write("default %s\n" % self.grub_mods.default_entry)
-            menu_lst_fh.write("timeout %s\n" % self.grub_mods.timeout)
-
-            # livecd needs graphics entries
-            menu_lst_fh.write("splashimage=/boot/grub/splash.xpm.gz\n")
-            menu_lst_fh.write("foreground=343434\n")
-            menu_lst_fh.write("background=F7FBFF\n")
-
-            # write out the entries
-            for entry in entries:
-                for line in entry:
-                    menu_lst_fh.write(line + "\n")
-                menu_lst_fh.write("\n")
-
-
-class TextGrubSetup(GrubSetup, Checkpoint):
-    """ TextGrubSetup - class to customize the grub menu for text installer
-    distributions
-    """
-
-    def __init__(self, name):
-        """ constructor for class.
-        """
-        super(TextGrubSetup, self).__init__(name)
-
-    def get_progress_estimate(self):
-        """Returns an estimate of the time this checkpoint will take
-        """
-        return 1
-
-    def build_entries(self):
-        """ class method for constructing the entries list.
-        """
-        title = "title " + self.grub_mods.title
-        kernel = "\tkernel$ /platform/i86pc/kernel/$ISADIR/unix"
-        module = "\tmodule$ /platform/i86pc/$ISADIR/boot_archive"
-        entries = [[title, kernel, module]]
-
-        # boot from hd entry.
-        entries.append(["title Boot from Hard Disk",
-                        "\trootnoverify (hd0)",
-                        "\tchainloader +1"])
-        return entries
-
-    def write_entries(self, entries):
-        """ class method to write out everything in the entries list to the
-        text install menu.lst file.
-        """
-        with open(self.menu_list, "w") as menu_lst_fh:
-            menu_lst_fh.write("default %s\n" % self.grub_mods.default_entry)
-            # set the timeout for text installs to 5 seconds
-            menu_lst_fh.write("timeout 5\n")
-
-            # text installer needs to hide the menu
-            menu_lst_fh.write("hiddenmenu\n")
-
-            # write out the entries
-            for entry in entries:
-                for line in entry:
-                    menu_lst_fh.write(line + "\n")
-                menu_lst_fh.write("\n")
--- a/usr/src/cmd/distro_const/checkpoints/test/test_grub_setup.py	Thu May 05 13:47:10 2011 -0600
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,349 +0,0 @@
-#!/usr/bin/python
-#
-# 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
-# or http://www.opensolaris.org/os/licensing.
-# 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
-#
-# Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
-#
-
-""" test_grub_setup
-
- Test program for grub_setup
-"""
-
-import os
-import os.path
-import shutil
-import unittest
-
-import testlib
-
-from solaris_install.distro_const.checkpoints.grub_setup import AIGrubSetup, \
-    LiveCDGrubSetup, TextGrubSetup
-from solaris_install.distro_const.distro_spec import GrubEntry, GrubMods
-from solaris_install.engine import InstallEngine
-
-
-class TestBuildPositionSpecific(unittest.TestCase):
-    """ test case to test the build_position_specific() method of
-        GrubSetup
-    """
-
-    def setUp(self):
-        # create a dummy filesystem with some files created in the proper
-        # location
-        InstallEngine()
-        self.filelist = ["/menu.lst", "/.image_info"]
-        self.grubsetup = LiveCDGrubSetup("Test GrubSetup")
-        self.grubsetup.pkg_img_path = testlib.create_filesystem(*self.filelist)
-        self.grubsetup.img_info_path = \
-            os.path.join(self.grubsetup.pkg_img_path, ".image_info")
-        self.grubsetup.menu_list = \
-            os.path.join(self.grubsetup.pkg_img_path, "menu.lst")
-
-        grub_mods = GrubMods("GrubMods")
-        grub_mods.min_mem = "1000"
-        grub_mods.timeout = "30"
-        grub_mods.default_entry = "0"
-        grub_mods.title = "myentry"
-        self.grubsetup.grub_mods = grub_mods
-
-    def tearDown(self):
-        shutil.rmtree(self.grubsetup.pkg_img_path, ignore_errors=True)
-        InstallEngine._instance = None
-
-    def test_position_begin(self):
-        begin_entry = GrubEntry("Begin Entry")
-        begin_entry.position = "0"
-        begin_entry.title_suffix = "Sonia"
-        begin_entry.lines = ["kernel$ SonKern", "module$ SonMod"]
-
-        entries = self.grubsetup.build_entries()
-        self.grubsetup.grub_entry_list = [begin_entry]
-        self.grubsetup.build_position_specific(entries)
-        self.grubsetup.write_entries(entries)
-
-        try:
-            with open(self.grubsetup.menu_list, "r") as fh:
-                menu_lst_data = [line.strip() for line in fh.readlines()]
-                fh.close()
-        except IOError, msg:
-            self.fail("unable to open the menu.list file: " + str(msg))
-
-        # walk the menu.lst file and look for the first title
-        for line in menu_lst_data:
-            if line.startswith("title"):
-                self.assert_("Sonia" in line, line)
-                break
-
-    def test_position_second(self):
-        second_entry = GrubEntry("Second Entry")
-        second_entry.position = "1"
-        second_entry.title_suffix = "derp"
-        second_entry.lines = ["kernel$ derpKern", "module$ derpKern"]
-
-        entries = self.grubsetup.build_entries()
-        self.grubsetup.grub_entry_list = [second_entry]
-        self.grubsetup.build_position_specific(entries)
-        self.grubsetup.write_entries(entries)
-
-        try:
-            with open(self.grubsetup.menu_list, "r") as fh:
-                menu_lst_data = [line.strip() for line in fh.readlines()]
-                fh.close()
-        except IOError, msg:
-            self.fail("unable to open the menu.list file: " + str(msg))
-
-        # walk the menu.lst file and look for the first title
-        title = 0
-        for line in menu_lst_data:
-            if line.startswith("title"):
-                if title == 1:
-                    self.assert_("derp" in line, line)
-                    break
-                else:
-                    title += 1
-
-        # verify the default, timeout and min_mem64 values
-        for line in menu_lst_data:
-            if line.startswith("default"):
-                self.assertEqual(line.split()[1], "0")
-            if line.startswith("timeout"):
-                self.assertEqual(line.split()[1], "30")
-            if line.startswith("min_mem"):
-                self.assertEqual(line.split()[1], "1000")
-
-
-class TestUpdateImgInfo(unittest.TestCase):
-    """ test case to test the update_img_info_path() method of
-        GrubSetup
-    """
-
-    def setUp(self):
-        # create a dummy filesystem with some files created in the proper
-        # location
-        InstallEngine()
-        self.filelist = ["/menu.lst", "/.image_info", "/etc/release"]
-        self.grubsetup = AIGrubSetup("Test GrubSetup")
-        self.grubsetup.pkg_img_path = testlib.create_filesystem(*self.filelist)
-        self.grubsetup.img_info_path = \
-            os.path.join(self.grubsetup.pkg_img_path, ".image_info")
-        self.grubsetup.menu_list = \
-            os.path.join(self.grubsetup.pkg_img_path, "menu.lst")
-
-        grub_mods = GrubMods("GrubMods")
-        grub_mods.min_mem = "1000"
-        grub_mods.timeout = "30"
-        grub_mods.default_entry = "0"
-        grub_mods.title = "Sonia's entry"
-        self.grubsetup.grub_mods = grub_mods
-
-    def tearDown(self):
-        shutil.rmtree(self.grubsetup.pkg_img_path, ignore_errors=True)
-        InstallEngine._instance = None
-
-    def test_ai_entries(self):
-        self.grubsetup.update_img_info_path()
-        iip = os.path.join(self.grubsetup.pkg_img_path, ".image_info")
-        with open(iip, "r") as fh:
-            image_info = fh.read().splitlines()
-
-        # verify the AI specific entries
-        line = "GRUB_MIN_MEM64=%s" % self.grubsetup.grub_mods.min_mem
-        self.assert_(line in image_info)
-        self.assert_("GRUB_DO_SAFE_DEFAULT=true" in image_info)
-
-
-class TestAIBuildEntries(unittest.TestCase):
-    """ test case to test the build_entries() method of
-        AIGrubSetup
-    """
-
-    def setUp(self):
-        # create a dummy filesystem with some files created in the proper
-        # location
-        InstallEngine()
-        self.filelist = ["/menu.lst"]
-        self.grubsetup = AIGrubSetup("Test AIGrubSetup")
-        self.grubsetup.pkg_img_path = testlib.create_filesystem(*self.filelist)
-        self.grubsetup.menu_list = \
-            os.path.join(self.grubsetup.pkg_img_path, "menu.lst")
-
-        grub_mods = GrubMods("GrubMods")
-        grub_mods.min_mem = "1000"
-        grub_mods.timeout = "30"
-        grub_mods.default_entry = "0"
-        grub_mods.title = "Sonia's entry"
-        self.grubsetup.grub_mods = grub_mods
-
-    def tearDown(self):
-        shutil.rmtree(self.grubsetup.pkg_img_path, ignore_errors=True)
-        InstallEngine._instance = None
-
-    def test_write_entries(self):
-        second_entry = GrubEntry("Second Entry")
-        second_entry.position = "1"
-        second_entry.title_suffix = "derp"
-        second_entry.lines = ["kernel$ derpKern", "module$ derpKern"]
-
-        entries = self.grubsetup.build_entries()
-        self.grubsetup.grub_entry_list = [second_entry]
-        self.grubsetup.build_position_specific(entries)
-        self.grubsetup.write_entries(entries)
-
-        try:
-            with open(self.grubsetup.menu_list, "r") as fh:
-                menu_lst_data = [line.strip() for line in fh.readlines()]
-                fh.close()
-        except IOError, msg:
-            self.fail("unable to open the menu.list file: " + str(msg))
-
-        # verify the 'custom' and regular entry
-        for line in menu_lst_data:
-            if line.startswith("title") and \
-                line.strip().endswith("Automated Install custom"):
-                for innerline in menu_lst_data:
-                    if line.startswith("kernel"):
-                        self.assert_(
-                            innerline.strip().endswith("aimanifest=prompt"))
-                        break
-
-            if line.startswith("title") and \
-                line.strip().endswith("Automated Install"):
-                for innerline in menu_lst_data:
-                    if line.startswith("kernel"):
-                        self.assert_("=" in innerline)
-                        break
-
-
-class TestLiveCDBuildEntries(unittest.TestCase):
-    """ test case to test the build_entries() method of
-        LiveCDGrubSetup
-    """
-
-    def setUp(self):
-        # create a dummy filesystem with some files created in the proper
-        # location
-        InstallEngine()
-        self.filelist = ["/menu.lst"]
-        self.grubsetup = LiveCDGrubSetup("Test LiveCDGrubSetup")
-        self.grubsetup.pkg_img_path = testlib.create_filesystem(*self.filelist)
-        self.grubsetup.menu_list = \
-            os.path.join(self.grubsetup.pkg_img_path, "menu.lst")
-
-        grub_mods = GrubMods("GrubMods")
-        grub_mods.min_mem = "1000"
-        grub_mods.timeout = "30"
-        grub_mods.default_entry = "0"
-        grub_mods.title = "Sonia's entry"
-        self.grubsetup.grub_mods = grub_mods
-
-    def tearDown(self):
-        shutil.rmtree(self.grubsetup.pkg_img_path, ignore_errors=True)
-        InstallEngine._instance = None
-
-    def test_write_entries(self):
-        second_entry = GrubEntry("Second Entry")
-        second_entry.position = "1"
-        second_entry.title_suffix = "derp"
-        second_entry.lines = ["kernel$ derpKern", "module$ derpKern"]
-
-        entries = self.grubsetup.build_entries()
-        self.grubsetup.grub_entry_list = [second_entry]
-        self.grubsetup.build_position_specific(entries)
-        self.grubsetup.write_entries(entries)
-
-        try:
-            with open(self.grubsetup.menu_list, "r") as fh:
-                menu_lst_data = [line.strip() for line in fh.readlines()]
-                fh.close()
-        except IOError, msg:
-            self.fail("unable to open the menu.list file: " + str(msg))
-
-        # verify the 'vesa' and 'text console' entry
-        for line in menu_lst_data:
-            if line.startswith("title") and \
-                line.strip().endswith("VESA driver"):
-                for innerline in menu_lst_data:
-                    if line.startswith("kernel"):
-                        self.assert_(
-                            innerline.strip().endswith("-B livemode=vesa"))
-                        break
-
-            if line.startswith("title") and \
-                line.strip().endswith("text console"):
-                for innerline in menu_lst_data:
-                    if line.startswith("kernel"):
-                        self.assert_(
-                            innerline.strip().endswith("-B livemode=text"))
-                        break
-
-
-class TestTextBuildEntries(unittest.TestCase):
-    """ test case to test the build_entries() method of
-        TextGrubSetup
-    """
-
-    def setUp(self):
-        # create a dummy filesystem with some files created in the proper
-        # location
-        InstallEngine()
-        self.filelist = ["/menu.lst"]
-        self.grubsetup = TextGrubSetup("Test TextGrubSetup")
-        self.grubsetup.pkg_img_path = testlib.create_filesystem(*self.filelist)
-        self.grubsetup.menu_list = \
-            os.path.join(self.grubsetup.pkg_img_path, "menu.lst")
-
-        grub_mods = GrubMods("GrubMods")
-        grub_mods.min_mem = "1000"
-        grub_mods.timeout = "30"
-        grub_mods.default_entry = "0"
-        grub_mods.title = "Sonia's entry"
-        self.grubsetup.grub_mods = grub_mods
-
-    def tearDown(self):
-        shutil.rmtree(self.grubsetup.pkg_img_path, ignore_errors=True)
-        InstallEngine._instance = None
-
-    def test_write_entries(self):
-        second_entry = GrubEntry("Second Entry")
-        second_entry.position = "1"
-        second_entry.title_suffix = "derp"
-        second_entry.lines = ["kernel$ derpKern", "module$ derpKern"]
-
-        entries = self.grubsetup.build_entries()
-        self.grubsetup.grub_entry_list = [second_entry]
-        self.grubsetup.build_position_specific(entries)
-        self.grubsetup.write_entries(entries)
-
-        try:
-            with open(self.grubsetup.menu_list, "r") as fh:
-                menu_lst_data = [line.strip() for line in fh.readlines()]
-                fh.close()
-        except IOError, msg:
-            self.fail("unable to open the menu.list file: " + str(msg))
-
-        # verify that there's a 'hiddenmenu' line
-        hiddenmenu = 0
-        for line in menu_lst_data:
-            if line.startswith("hiddenmenu"):
-                hiddenmenu = 1
-                break
-
-        self.assert_(hiddenmenu == 1)
--- a/usr/src/cmd/distro_const/distro_spec.py	Thu May 05 13:47:10 2011 -0600
+++ b/usr/src/cmd/distro_const/distro_spec.py	Fri May 06 11:44:10 2011 +1000
@@ -118,137 +118,6 @@
     TAG_NAME = "vm_im"
 
 
-class GrubMods(SimpleXmlHandlerBase):
-    TAG_NAME = "grub_mods"
-    MIN_MEM_LABEL = "min_mem"
-    TITLE_LABEL = "title"
-    DEFAULT_ENTRY_LABEL = "default_entry"
-    TIMEOUT_LABEL = "timeout"
-
-    def __init__(self, name):
-        super(GrubMods, self).__init__(name)
-        self.min_mem = None
-        self.title = None
-        self.default_entry = None
-        self.timeout = None
-
-    def to_xml(self):
-        element = etree.Element(GrubMods.TAG_NAME)
-
-        if self.min_mem is not None:
-            element.set(GrubMods.MIN_MEM_LABEL, self.min_mem)
-        if self.title is not None:
-            element.set(GrubMods.TITLE_LABEL, self.title)
-        if self.default_entry is not None:
-            element.set(GrubMods.DEFAULT_ENTRY_LABEL, self.default_entry)
-        if self.timeout is not None:
-            element.set(GrubMods.TIMEOUT_LABEL, self.timeout)
-
-        return element
-
-    @classmethod
-    def can_handle(cls, element):
-        if element.tag == cls.TAG_NAME:
-            return True
-        return False
-
-    @classmethod
-    def from_xml(cls, element):
-        min_mem = element.get(cls.MIN_MEM_LABEL)
-        title = element.get(cls.TITLE_LABEL)
-        default_entry = element.get(cls.DEFAULT_ENTRY_LABEL)
-        timeout = element.get(cls.TIMEOUT_LABEL)
-
-        grub_mods = GrubMods(cls.TAG_NAME)
-        if min_mem is not None:
-            grub_mods.min_mem = min_mem
-        if title is not None:
-            grub_mods.title = title
-        if default_entry is not None:
-            grub_mods.default_entry = default_entry
-        if timeout is not None:
-            grub_mods.timeout = timeout
-
-        return grub_mods
-
-
-class GrubEntry(SimpleXmlHandlerBase):
-    TAG_NAME = "grub_entry"
-    POSITION_LABEL = "position"
-    TITLE_SUFFIX_LABEL = "title_suffix"
-    LINE_LABEL = "line"
-
-    def __init__(self, name):
-        super(GrubEntry, self).__init__(name)
-        self.position = None
-        self.title_suffix = None
-        self.lines = None
-
-    def to_xml(self):
-        element = etree.Element(GrubEntry.TAG_NAME)
-        if self.position is not None:
-            element.set(GrubEntry.POSITION_LABEL, self.position)
-        if self.title_suffix is not None:
-            title_element = etree.SubElement(element,
-                GrubEntry.TITLE_SUFFIX_LABEL)
-            title_element.text = self.title_suffix
-        if self.lines is not None:
-            for entry in self.lines:
-                line_element = etree.SubElement(element, GrubEntry.LINE_LABEL)
-                line_element.text = entry
-
-        return element
-
-    @classmethod
-    def can_handle(cls, element):
-        '''
-        Returns True if element has:
-        - the tag 'grub_entry'
-        - a title_suffix element
-        - a line element
-
-        Otherwise returns False
-        '''
-        if element.tag != cls.TAG_NAME:
-            return False
-
-        title_suffix = None
-        line = None
-
-        for subelement in element.iterchildren():
-            if subelement.tag == cls.TITLE_SUFFIX_LABEL:
-                title_suffix = subelement.text
-
-            if subelement.tag == cls.LINE_LABEL:
-                line = subelement.text
-
-        return True
-
-    @classmethod
-    def from_xml(cls, element):
-        title_suffix = None
-        lines = []
-
-        position = element.get(cls.POSITION_LABEL)
-
-        for subelement in element.iterchildren():
-            if subelement.tag == cls.TITLE_SUFFIX_LABEL:
-                title_suffix = subelement.text
-
-            if subelement.tag == cls.LINE_LABEL:
-                lines.append(subelement.text)
-
-        grub_entry = GrubEntry(cls.TAG_NAME)
-        if position is not None:
-            grub_entry.position = position
-        if title_suffix is not None:
-            grub_entry.title_suffix = title_suffix
-        if lines:
-            grub_entry.lines = lines
-
-        return grub_entry
-
-
 class MaxSize(SimpleXmlHandlerBase):
     TAG_NAME = "max_size"
 
--- a/usr/src/cmd/distro_const/manifest/dc_ai_x86.xml	Thu May 05 13:47:10 2011 -0600
+++ b/usr/src/cmd/distro_const/manifest/dc_ai_x86.xml	Fri May 06 11:44:10 2011 +1000
@@ -39,38 +39,55 @@
       <img_params>
         <media_im>
           <!--
-            Grub menu modifications to be applied to the image.
+            Boot menu modifications to be applied to the image.
 
             Optional attributes that can be specified here are:
-            title   - title for the specialized GRUB entry
+            title   - title for the specialized boot entry
                       Default is to use the first line of /etc/release
-            default_entry - which entry should be the default entry
-            timeout - GRUB menu timeout value in seconds
+            timeout - boot loader timeout value before the default boot entry
+                      is automatically activated.
           -->
           <!-- Uncomment before using
-          <grub_mods title="myentry" default_entry="0" timeout="5">
+          <boot_mods title="myentry" timeout="5">
           -->
-            <!-- 
-              Grub entries to add to the default grub menu in the image.  Each
-              entry is added sequentially to the menu.lst file in order of
-              listing.
+            <!--
+              Boot entries to add to the default boot menu in the image. Each
+              entry is either prepended or appended sequentially to the boot
+              menu in order of listing based on each boot entry element's
+              "insert_at" attribute value ("start" or "end").
 
-              An optional "position" attribute is the location in the menu.lst
-              file to insert the entry.  If omitted, simply append the entry
-              to the end of the menu.lst file.
+              Optional attributes:
+              default_entry - If the boot_entry has this attribute set to
+                              "true" then it will be the default boot entry
+                              activated by the boot loader.
+                              Note that if more than one boot entry has this
+                              attribute set to "true", the last entry defined
+                              as such will override preceeding default
+                              boot_entry elements set to "true".
 
-              title suffix - text string added to this specific entry title
-              line - each line element will be added in order specified
+              insert_at     - Optional attribute indicating the desired
+                              insertion point relative to the existing list of
+                              boot entries.
+                              Valid values are "start" or "end" only. If
+                              omitted the default action is to append the entry
+                              to the end of the list.
+
+              Required sub-elements:
+              title_suffix  - Text string appended to this specific entry's
+                              title.
+
+              Optional sub-elements:
+              kernel_args   - Optional kernel arguments passed to the kernel by
+                              the boot loader.
             -->
             <!-- Uncomment before using
-            <grub_entry position="1">
-              <title_suffix>MyTitle</title_suffix>
-              <line>kernel$ /platform/i86pc/kernel/$ISADIR/unix</line>
-              <line>module$ /platform/i86pc/$ISADIR/boot_archive</line>
-            </grub_entry>
+            <boot_entry default_entry="false" insert_at="end">
+              <title_suffix>My Title</title_suffix>
+              <kernel_args></kernel_args>
+            </boot_entry>
             -->
           <!-- Uncomment before using
-          </grub_mods>
+          </boot_mods>
           -->
         </media_im>
       </img_params>
@@ -260,13 +277,13 @@
             <arg name="bytes_per_inode">0</arg>
           </kwargs>
       </checkpoint>
-      <checkpoint name="grub-setup"
-          desc="Setup GRUB menu"
-          mod_path="solaris_install/distro_const/checkpoints/grub_setup"
-          checkpoint_class="AIGrubSetup">
+      <checkpoint name="boot-setup"
+          desc="Setup boot menu"
+          mod_path="solaris_install/boot/boot"
+          checkpoint_class="AIISOImageBootMenu">
           <kwargs>
             <!-- The installadm_entry defines the title for the default
-            Grub entry used by installadm when unpacking the image for use -->
+            boot entry used by installadm when unpacking the image for use -->
             <arg name="installadm_entry">Text Installer and command line</arg>
           </kwargs>
       </checkpoint>
--- a/usr/src/cmd/distro_const/manifest/dc_livecd.xml	Thu May 05 13:47:10 2011 -0600
+++ b/usr/src/cmd/distro_const/manifest/dc_livecd.xml	Fri May 06 11:44:10 2011 +1000
@@ -39,45 +39,64 @@
       <img_params>
         <media_im>
           <!--
-            Grub menu modifications to be applied to the image.
+            Boot menu modifications to be applied to the image.
 
             Optional attributes that can be specified here are:
-            title   - title for the specialized GRUB entry
+            title   - title for the specialized boot entry
                       Default is to use the first line of /etc/release
-            default_entry - which entry should be the default entry
-            timeout - GRUB menu timeout value in seconds
+
+            timeout - boot loader timeout value before the default boot entry
+                      is automatically activated.
           -->
-          <!-- Uncomment before using
-          <grub_mods title="myentry" default_entry="0" timeout="5">
+          <!-- Sample boot_mods with title and timeout attributes.
+               Uncomment and replace un-attributed boot_mods entry below
+               before using.
+          <boot_mods title="myentry" timeout="5">
           -->
-          <grub_mods>
-            <!-- 
-              Grub entries to add to the default grub menu in the image.  Each
-              entry is added sequentially to the menu.lst file in order of
-              listing.
+          <boot_mods>
+            <!--
+              Boot entries to add to the default boot menu in the image. Each
+              entry is either prepended or appended sequentially to the boot
+              menu in order of listing based on each boot entry element's
+              "insert_at" attribute value ("start" or "end").
+
+              Optional attributes:
+              default_entry - If the boot_entry has this attribute set to
+                              "true" then it will be the default boot entry
+                              activated by the boot loader.
+                              Note that if more than one boot entry has this
+                              attribute set to "true", the last entry defined
+                              as such will override preceeding default
+                              boot_entry elements set to "true".
 
-              An optional "position" attribute is the location in the menu.lst
-              file to insert the entry.  If omitted, simply append the entry
-              to the end of the menu.lst file.
+              insert_at     - Optional attribute indicating the desired
+                              insertion point relative to the existing list of
+                              boot entries.
+                              Valid values are "start" or "end" only. If
+                              omitted the default action is to append the entry
+                              to the end of the list.
 
-              title suffix - text string added to this specific entry title
-              line - each line element will be added in order specified
-    
+              Required sub-elements:
+              title_suffix  - Text string appended to this specific entry's
+                              title.
+
+              Optional sub-elements:
+              kernel_args   - Optional kernel arguments passed to the kernel by
+                              the boot loader.
+
               NOTE: It is strongly suggested that any entries to be added
                     must be specified *before* the "with magnifier" entry
                     below.
             -->
-            <grub_entry>
+            <boot_entry>
               <title_suffix>with magnifier</title_suffix>
-              <line>kernel$ /platform/i86pc/kernel/$ISADIR/unix -B assistive_tech=magnifier</line>
-              <line>module$ /platform/i86pc/$ISADIR/boot_archive</line>
-            </grub_entry>
-            <grub_entry>
+              <kernel_args>-B assistive_tech=magnifier</kernel_args>
+            </boot_entry>
+            <boot_entry>
               <title_suffix>with screen reader</title_suffix>
-              <line>kernel$ /platform/i86pc/kernel/$ISADIR/unix -B assistive_tech=reader</line>
-              <line>module$ /platform/i86pc/$ISADIR/boot_archive</line>
-            </grub_entry>
-          </grub_mods>
+              <kernel_args>-B assistive_tech=reader</kernel_args>
+            </boot_entry>
+          </boot_mods>
         </media_im>
       </img_params>
     </distro_spec>
@@ -273,10 +292,10 @@
             <arg name="bytes_per_inode">0</arg>
           </kwargs>
       </checkpoint>
-      <checkpoint name="grub-setup"
-          desc="Setup GRUB menu"
-          mod_path="solaris_install/distro_const/checkpoints/grub_setup"
-          checkpoint_class="LiveCDGrubSetup"/>
+      <checkpoint name="boot-setup"
+          desc="Setup LiveCD boot menu"
+          mod_path="solaris_install/boot/boot"
+          checkpoint_class="LiveCDISOImageBootMenu"/>
       <checkpoint name="pkg-img-mod"
           desc="Pkg image area modification"
           mod_path="solaris_install/distro_const/checkpoints/pkg_img_mod"
--- a/usr/src/cmd/distro_const/manifest/dc_text_x86.xml	Thu May 05 13:47:10 2011 -0600
+++ b/usr/src/cmd/distro_const/manifest/dc_text_x86.xml	Fri May 06 11:44:10 2011 +1000
@@ -40,39 +40,56 @@
       <img_params>
         <media_im>
           <!--
-            Grub menu modifications to be applied to the image.
+            Boot menu modifications to be applied to the image.
 
             Optional attributes that can be specified here are:
-            title   - title for the specialized GRUB entry
+            title   - title for the specialized boot entry
                       Default is to use the first line of /etc/release
-            default_entry - which entry should be the default entry
-            timeout - GRUB menu timeout value in seconds
+
+            timeout - boot loader timeout value before the default boot entry
+                      is automatically activated.
           -->
           <!-- Uncomment before using
-          <grub_mods title="myentry" default_entry="0" timeout="5">
+          <boot_mods title="myentry" timeout="5">
           -->
-            <!-- 
-              Grub entries to add to the default grub menu in the image.  Each
-              entry is added sequentially to the menu.lst file in order of
-              listing.
+            <!--
+              Boot entries to add to the default boot menu in the image. Each
+              entry is either prepended or appended sequentially to the boot
+              menu in order of listing based on each boot entry element's
+              "insert_at" attribute value ("start" or "end").
 
-              An optional "position" attribute is the location in the menu.lst
-              file to insert the entry.  If omitted, simply append the entry
-              to the end of the menu.lst file.
+              Optional attributes:
+              default_entry - If the boot_entry has this attribute set to
+                              "true" then it will be the default boot entry
+                              activated by the boot loader.
+                              Note that if more than one boot entry has this
+                              attribute set to "true", the last entry defined
+                              as such will override preceeding default
+                              boot_entry elements set to "true".
 
-              title suffix - text string added to this specific entry title
-              line - each line element will be added in order specified
+              insert_at     - Optional attribute indicating the desired
+                              insertion point relative to the existing list of
+                              boot entries.
+                              Valid values are "start" or "end" only. If
+                              omitted the default action is to append the entry
+                              to the end of the list.
 
+              Required sub-elements:
+              title_suffix  - Text string appended to this specific entry's
+                              title.
+
+              Optional sub-elements:
+              kernel_args   - Optional kernel arguments passed to the kernel by
+                              the boot loader.
             -->
             <!-- Uncomment before using
-            <grub_entry position="1">
-              <title_suffix>MyTitle</title_suffix>
-              <line>kernel$ /platform/i86pc/kernel/$ISADIR/unix</line>
-              <line>module$ /platform/i86pc/$ISADIR/boot_archive</line>
-            </grub_entry>
+            <boot_entry default_entry="false" insert_at="end">
+              <title_suffix>My Title</title_suffix>
+              <kernel_args></kernel_args>
+            </boot_entry>
             -->
           <!-- Uncomment before using
-          </grub_mods>
+          </boot_mods>
           -->
         </media_im>
       </img_params>
@@ -266,11 +283,10 @@
             <arg name="bytes_per_inode">0</arg>
           </kwargs>
       </checkpoint>
-      <checkpoint name="grub-setup"
-          desc="Setup GRUB menu"
-          mod_path="solaris_install/distro_const/checkpoints/grub_setup"
-          checkpoint_class="TextGrubSetup">
-      </checkpoint>
+      <checkpoint name="boot-setup"
+          desc="Setup boot menu"
+          mod_path="solaris_install/boot/boot"
+          checkpoint_class="TextISOImageBootMenu"/>
       <checkpoint name="pkg-img-mod"
           desc="Pkg image area modification"
           mod_path="solaris_install/distro_const/checkpoints/pkg_img_mod"
--- a/usr/src/lib/Makefile	Thu May 05 13:47:10 2011 -0600
+++ b/usr/src/lib/Makefile	Fri May 06 11:44:10 2011 +1000
@@ -51,6 +51,7 @@
 
 COMSUBDIRS=	liberrsvc_pymod \
 		liberrsvc \
+		install_boot \
 		install_doc \
 		install_engine \
 		install_ict \
--- a/usr/src/lib/Makefile.targ	Thu May 05 13:47:10 2011 -0600
+++ b/usr/src/lib/Makefile.targ	Fri May 06 11:44:10 2011 +1000
@@ -112,6 +112,9 @@
 $(ROOTPYTHONVENDORSOLINSTALL)/%: $$(PNAME)/$(ARCH)/%
 	$(INS.file)
 
+$(ROOTPYTHONVENDORSOLINSTALLBOOT):
+	$(INS.dir)
+
 $(ROOTPYTHONVENDORSOLINSTALLDATACACHE):
 	$(INS.dir)
 
@@ -194,6 +197,9 @@
 $(ROOTPYTHONVENDORSOLINSTALL)/%: %
 	$(CP_P.file)
 
+$(ROOTPYTHONVENDORSOLINSTALLBOOT)/%: %
+	$(CP_P.file)
+
 $(ROOTPYTHONVENDORSOLINSTALLDATACACHE)/%: %
 	$(CP_P.file)
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/lib/install_boot/Makefile	Fri May 06 11:44:10 2011 +1000
@@ -0,0 +1,61 @@
+#
+# 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
+# or http://www.opensolaris.org/os/licensing.
+# 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
+#
+
+#
+# Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+#
+
+PYMODS		= __init__.py \
+		boot.py \
+		boot_spec.py
+
+PYCMODS		= $(PYMODS:%.py=%.pyc)
+
+ROOTPYMODS	= $(PYMODS:%=$(ROOTPYTHONVENDORSOLINSTALLBOOT)/%)
+
+ROOTPYCMODS	= $(PYCMODS:%=$(ROOTPYTHONVENDORSOLINSTALLBOOT)/%)
+
+
+CLOBBERFILES	= $(PYCMODS) 
+CLEANFILES	= $(CLOBBERFILES)
+
+include ../Makefile.lib
+
+HDRS		= $(EXPHDRS) $(PRIVHDRS)
+
+python:
+	$(PYTHON) -m compileall -l $(@D)
+
+all:		$(HDRS) python
+
+install_h:
+
+install:	all .WAIT \
+		$(ROOTPYTHONVENDOR) \
+		$(ROOTPYTHONVENDORSOLINSTALL) \
+		$(ROOTPYTHONVENDORSOLINSTALLBOOT) \
+		$(ROOTPYMODS) $(ROOTPYCMODS)
+
+lint:		lint_SRCS
+
+FRC:
+
+include ../Makefile.targ
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/lib/install_boot/__init__.py	Fri May 06 11:44:10 2011 +1000
@@ -0,0 +1,36 @@
+#!/usr/bin/python
+#
+# 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
+# or http://www.opensolaris.org/os/licensing.
+# 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
+#
+
+#
+# Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+#
+
+"""init module for the boot checkpoints"""
+
+from solaris_install.data_object.cache import DataObjectCache
+from solaris_install.boot.boot import BootMods, BootEntry
+
+__all__ = ["boot", "boot_spec"]
+
+# Register DataObject sub-classes with the DOC
+DataObjectCache.register_class(BootMods)
+DataObjectCache.register_class(BootEntry)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/lib/install_boot/boot.py	Fri May 06 11:44:10 2011 +1000
@@ -0,0 +1,1046 @@
+#!/usr/bin/python
+#
+# 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
+# or http://www.opensolaris.org/os/licensing.
+# 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
+#
+
+#
+# Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+#
+""" boot.py -- Installs and configures boot loader and boot menu
+    onto physical systems and installation images.
+"""
+
+import abc
+import array
+import fcntl
+import linecache
+import os
+import platform
+import stat
+import struct
+import tempfile
+
+from grp import getgrnam
+from pwd import getpwnam
+from shutil import move, copyfile, rmtree
+
+from bootmgmt import bootconfig, BootmgmtUnsupportedPropertyError
+from bootmgmt.bootconfig import BootConfig, DiskBootConfig, ODDBootConfig, \
+    SolarisDiskBootInstance, SolarisODDBootInstance, ChainDiskBootInstance
+from solaris_install import CalledProcessError, Popen
+from solaris_install.boot.boot_spec import BootMods, BootEntry
+from solaris_install.data_object import ObjectNotFoundError
+from solaris_install.data_object.data_dict import DataObjectDict
+from solaris_install.engine import InstallEngine
+from solaris_install.engine.checkpoint import AbstractCheckpoint as Checkpoint
+from solaris_install.logger import INSTALL_LOGGER_NAME as ILN
+from solaris_install.target import Target
+from solaris_install.target.logical import be_list, BE, Zpool
+from solaris_install.target.physical import Disk, Slice
+
+BOOT_ENV = "boot-env"
+# Character boot device paths eg. c0t0d0s0
+DEVS = "devs"
+
+
+class BootMenu(Checkpoint):
+    """ Abstract class for BootMenu checkpoint
+    """
+    __metaclass__ = abc.ABCMeta
+
+    def __init__(self, name):
+        """ Constructor for class
+        """
+        super(BootMenu, self).__init__(name)
+        self.arch = platform.processor()
+        self.boot_entry_list = list()
+        self.boot_mods = None
+        self.boot_title = None
+        # Used for string token substitution of pybootgmmt return data
+        self.boot_tokens = dict()
+        self.config = None
+        self.doc = InstallEngine.get_instance().data_object_cache
+        self.rel_file_title = None
+
+    def get_progress_estimate(self):
+        """ Returns an estimate of the time this checkpoint will
+            take to complete.
+        """
+        return 5
+
+    @abc.abstractmethod
+    def build_default_entries(self):
+        """ This method is required to be implemented by all subclasses.
+            Standard, pre-defined boot menu entries get created by
+            subclass implementors of this method.
+        """
+        raise NotImplementedError
+
+    @abc.abstractmethod
+    def build_custom_entries(self):
+        """ This method is required to be implemented by all subclasses.
+            Custom entries specified in the boot_mods section of the
+            XML manifest get created by subclass implementors of this method.
+        """
+        raise NotImplementedError
+
+    @abc.abstractmethod
+    def _get_rel_file_path(self):
+        """ This method is required to be implemented by all subclasses.
+            Returns file system path to the release file.
+        """
+        raise NotImplementedError
+
+    def parse_doc(self, dry_run=False):
+        """ Parse data object cache for values required by the checkpoint
+            execution.
+        """
+        # Break doc parsing down into 2 specific parts as the target
+        # configuration in the doc differs between DC (for ISO images)
+        # and and live system installation.
+        self._parse_doc_target(dry_run)
+        self._parse_doc_boot_mods()
+
+    def _parse_doc_boot_mods(self):
+        """ Parses data object cache (DOC) BootMods tree for configuration of
+            the checkpoint.
+        """
+        try:
+            # Check and retrieve the optional manifest
+            # specific boot configurations.
+            boot_mods = self.doc.get_descendants(class_type=BootMods)
+            if len(boot_mods) < 1:
+                return
+            self.boot_mods = boot_mods[0]
+        except ObjectNotFoundError as err:
+            raise RuntimeError("Error retrieving BootMods from the DOC: " + \
+                str(err))
+
+        if self.boot_mods.title and len(self.boot_mods.title) > 0:
+            self.logger.info("Setting boot title prefix from manifest value: \
+                             '%s'" % self.boot_mods.title)
+            self.boot_title = self.boot_mods.title
+
+        try:
+            self.boot_entry_list = self.boot_mods.get_children(
+                class_type=BootEntry)
+        except ObjectNotFoundError:
+            # the manifest does not contain any custom boot entries
+            self.logger.debug("No custom boot entries found in the DOC")
+
+    @abc.abstractmethod
+    def _parse_doc_target(self, dry_run=False):
+        """ This method is required to be implemented by all subclasses.
+            Additional class specific parsing to determine the boot
+            configuration's target paths or devices get performed by
+            subclass implementors this method.
+
+            Input:
+                None
+            Output:
+                None
+        """
+        raise NotImplementedError
+
+    @abc.abstractmethod
+    def init_boot_config(self, autogen=True):
+        """ This method is required to be implemented by all subclasses.
+
+            Input:
+                None
+            Output:
+                None
+        """
+        raise NotImplementedError
+
+    @abc.abstractmethod
+    def install_boot_loader(self, dry_run=False):
+        """ This method is required to be implemented by all subclasses.
+
+            Input:
+                dry_run
+                - If True, the set of files that constitute the boot
+                  configuration is written to a temporary directory (and not to
+                  the official location(s)) When files are written to a
+                  temporary directory, the list and format of the files written
+                  is returned (see Output)
+            Output:
+                None
+        """
+        raise NotImplementedError
+
+    def _handle_boot_config_list(self, boot_config_list, dry_run):
+        """ Method that checks returned tuple list from commit_boot_config()
+            and copies over content as appropriate to their targets.
+        """
+        for boot_config in boot_config_list:
+            for config_set in boot_config:
+                ftype = config_set[0]
+                if ftype == BootConfig.OUTPUT_TYPE_FILE:
+                    self._handle_file_type(config_set, dry_run)
+                elif ftype in [BootConfig.OUTPUT_TYPE_BIOS_ELTORITO]:
+                # UEFI - add handlers for the following when supported:
+                #              BootConfig.OUTPUT_TYPE_UEFI_ELTORITO
+                #              BootConfig.OUTPUT_TYPE_HSFS_BOOTBLK
+                    self._handle_iso_boot_image_type(config_set, dry_run)
+                else:
+                    raise RuntimeError("Unrecognized boot loader " \
+                                       "file type: %s" % ftype)
+
+    def _handle_file_type(self, config, dry_run):
+        """ Method that copies generic file types to their appropriate targets.
+        """
+        ftype, src, objref, dest, uid, gid, mode = config
+        # Copy from 'src' to 'dest' and set uid:gid + mode
+        if src is None:
+            raise RuntimeError("No source path defined for boot" \
+                               "file")
+        if dest is None:
+            raise RuntimeError("No destination path defined for boot" \
+                               "file")
+        # XXX The 'dest' field, I believe, should be prepended with the
+        # string token '%(systemroot)s' but it currently appears not to
+        # be the case sometimes, and instead specifies an absolute path
+        # such as '/boot/grub/menu.lst'
+        # Check for a leading slash and prepend if necessary
+        if os.path.isabs(dest):
+            dest = '%(' + BootConfig.TOKEN_SYSTEMROOT + ')s' + dest
+
+        real_dest = dest % (self.boot_tokens)
+        par_dir = os.path.abspath(os.path.join(real_dest, os.path.pardir))
+        if dry_run is True:
+            os.unlink(src)
+            return
+        try:
+            if not os.path.exists(par_dir):
+                self.logger.debug("Creating boot configuration folder: %s" \
+                                  % par_dir)
+                os.makedirs(par_dir)
+            self.logger.debug("Moving boot configuration file: \n" \
+                              "%s -> %s" % (src, real_dest))
+            move(src, real_dest)
+            if uid is None:
+                uid = -1
+            if gid is None:
+                gid = -1
+            self.logger.debug("Setting ownership of %s to: %s:%s" \
+                              % (real_dest, uid, gid))
+            os.chown(real_dest,
+                     getpwnam(uid).pw_uid,
+                     getgrnam(gid).gr_gid)
+            if mode:
+                self.logger.debug("Setting permissions of %s to: %d" \
+                                  % (real_dest, mode))
+                os.chmod(real_dest, mode)
+        except OSError as err:
+            raise RuntimeError("Error copying boot configuration files to " \
+                                 "target: %s" % str(err))
+
+    def execute(self, dry_run=False):
+        """ Primary execution method used by the Checkpoint parent class
+        """
+        self.parse_doc(dry_run)
+        self.init_boot_config()
+        self.build_default_entries()
+        self.build_custom_entries()
+        self.install_boot_loader(dry_run)
+
+
+class SystemBootMenu(BootMenu):
+    """ Class for SystemBootMenu checkpoint. Suitable for automatic boot loader
+        and boot menu configuration for install applications that install onto
+        a physical system device such as gui-install, text install and AI
+    """
+
+    def __init__(self, name):
+        """ Constructor for class
+        """
+        super(SystemBootMenu, self).__init__(name)
+
+        # Dictionary for quick lookup of paramaters needed to configure and
+        # install the boot loader.
+        self.boot_target = dict()
+        self.boot_target[BOOT_ENV] = None
+        self.boot_target[DEVS] = list()
+
+        # Filesystem name of the target boot environment eg.
+        # rpool/ROOT/solaris-11-XYZ
+        self.target_bootfs = None
+        # Mountpoint of pool toplevel dataset
+        self.pool_tld_mount = None
+
+    def init_boot_config(self, autogen=True):
+        """ Instantiates the appropriate bootmgmt.bootConfig subclass object
+            for this class
+        """
+        bc_flags = [bootconfig.BootConfig.BCF_CREATE]
+        if autogen:
+            bc_flags.append(bootconfig.BootConfig.BCF_AUTOGEN)
+        bc_flags = tuple(bc_flags)
+        target_be = self.boot_target[BOOT_ENV]
+        # If the BE is unmounted for some reason we need to go and mount it.
+        if target_be.mountpoint == None:
+            tmp_be_mp = tempfile.mkdtemp(dir="/system/volatile",
+                                         prefix="be_mount_%s_" \
+                                         % (target_be.name))
+            self.logger.debug("Mounting BE %s at: %s" % \
+                               (target_be.name, tmp_be_mp))
+            target_be.mount(tmp_be_mp, dry_run=False)
+
+        # Note that 'platform' tuple is not supplied here since we want to
+        # go with the running system arch/firmware, which bootmgmt defaults to.
+        self.config = DiskBootConfig(bc_flags,
+                                     rpname=target_be.parent.name,
+                                     tldpath=self.pool_tld_mount,
+                                     zfspath=target_be.mountpoint)
+
+        # Apply boot_mods manifest XML attributes to boot loader:
+        # - Override the default timeout if specifed in boot_mods
+        if self.boot_mods:
+            try:
+                if self.boot_mods.timeout:
+                    self.config.boot_loader.setprop('timeout',
+                                                    self.boot_mods.timeout)
+            except BootmgmtUnsupportedPropertyError:
+                self.logger.warning("Boot loader type %s does not support the \
+                                    'timeout' property. Ignoring." \
+                                    % self.config.bootloader.name)
+
+    def execute(self, dry_run=False):
+        """ Primary execution method used by the Checkpoint parent class
+        """
+        # UEFI The divergent code paths for sparc and X86 is a temporary
+        # workaround for the lack of sparc support in the bootmgmt module.
+        # When this is addressed, I expect to revert to just calling the
+        # parent class method.
+        if self.arch == 'sparc':
+            # For sparc, generate a manual menu.lst and copy the bootlst
+            # binary into the boot dataset.
+            self.parse_doc(dry_run)
+            # Check to make sure that a non-dry run attempt is not
+            # executed on a live system
+            if self.boot_target[BOOT_ENV].mountpoint == '/' and \
+                not dry_run:
+                raise RuntimeError("Boot checkpoint can not be executed on" \
+                                     "live systems except with dry run mode")
+            self._set_bootfs_pool_prop(dry_run)
+            self._install_sparc_bootblk(dry_run)
+            self._set_sparc_prom_boot_device(dry_run)
+            self._create_sparc_boot_menu(dry_run)
+            self._copy_sparc_bootlst(dry_run)
+        else:
+            super(SystemBootMenu, self).execute(dry_run)
+
+    def _set_bootfs_pool_prop(self, dry_run):
+        """ Set the bootfs property on the boot pool, activating the BE.
+            XXX: This should really be ultimately handled by pybootmgmt
+        """
+        pool = self.boot_target[BOOT_ENV].parent
+        self.logger.debug("Setting bootfs zpool property on %s to %s" \
+                          % (pool.name, self.target_bootfs))
+        pool.set("bootfs", self.target_bootfs, dry_run)
+
+    def _create_sparc_boot_menu(self, dry_run):
+        """ Create a boot menu.lst file on a SPARC system.
+        """
+        # Attempt to create the path to where the menu.lst file will reside
+        boot_menu_path = os.path.join(self.pool_tld_mount, 'boot')
+        if dry_run:
+            # Override boot_menu_path to temporary storage
+            temp_dir = tempfile.mkdtemp(dir="/tmp", prefix="sparc_boot_menu_")
+            boot_menu_path = os.path.join(temp_dir,
+                boot_menu_path.strip(os.path.sep))
+        boot_menu = os.path.join(boot_menu_path, 'menu.lst')
+        sparc_title_line = 'title %s\n' % self.boot_title
+        bootfs_line = 'bootfs ' + self.target_bootfs + '\n'
+        self.logger.debug("Creating SPARC boot menu file: %s" \
+                          % boot_menu)
+        if not os.path.isdir(boot_menu_path):
+            os.makedirs(boot_menu_path, 0755)
+
+        try:
+            with open(boot_menu, 'w') as menu_lst:
+                menu_lst.write(sparc_title_line)
+                menu_lst.write(bootfs_line)
+            os.chmod(boot_menu,
+                     stat.S_IREAD | stat.S_IWRITE | \
+                     stat.S_IRGRP | stat.S_IROTH)
+            # Requires root privilige so bypass on dry_run
+            if not dry_run:
+                os.chown(boot_menu, 0, 3)  # chown root:sys
+        except IOError, msg:
+            raise RuntimeError(msg)
+        if dry_run:
+            rmtree(temp_dir)
+
+    def _copy_sparc_bootlst(self, dry_run):
+        """ Copy the bootlst file on a SPARC system.
+            On SPARC systems a bootlst file is maintained at:
+            /platform/`uname -m`/bootlst
+
+            It needs to be copied to:
+            <rootpool>/platform/`uname -m`/bootlst
+        """
+        bootlst_src = os.path.join(self.boot_target[BOOT_ENV].mountpoint,
+            'platform', platform.machine(),
+            'bootlst')
+
+        # Copy file bootlst from basedir to the rootpool
+        bootlst_dir = os.path.join(self.pool_tld_mount,
+                                   'platform', platform.machine())
+        if dry_run:
+            # Override bootlst_dir to temporary storage
+            temp_dir = tempfile.mkdtemp(dir="/tmp", prefix="sparc_bootlst_")
+            bootlst_dir = os.path.join(temp_dir,
+                                       bootlst_dir.strip(os.path.sep))
+        bootlst_dst = os.path.join(bootlst_dir, 'bootlst')
+        self.logger.debug("Copying SPARC bootlst file: %s" \
+                          % bootlst_src)
+
+        # Create the destination directory if it does not already exist.
+        if not os.path.isdir(bootlst_dir):
+            os.makedirs(bootlst_dir, 0755)
+
+        # Copy the bootlst
+        copyfile(bootlst_src, bootlst_dst)
+        os.chmod(bootlst_dst,
+                 stat.S_IREAD | stat.S_IWRITE | \
+                 stat.S_IRGRP | stat.S_IROTH)
+        # Requires root privilige so bypass on dry_run
+        if dry_run:
+            rmtree(bootlst_dir)
+        else:
+            os.chown(bootlst_dst, 0, 3)  # chown root:sys
+
+    def _install_sparc_bootblk(self, dry_run):
+        """ Runs installboot(1M) command on SPARC architectures to
+            install the zfs bootblock program.
+        """
+        # This method is only supported on SPARC platforms.
+        if self.arch != 'sparc':
+            raise RuntimeError("Can not install SPARC bootblk on a non-" \
+                                 "SPARC architecture system")
+        boot_devs = list()
+        boot_devs = [os.path.join('/dev/rdsk', dev) \
+            for dev in self.boot_target[DEVS]]
+
+        if len(boot_devs) < 1:
+            raise RuntimeError("No devices to install SPARC bootblk onto!")
+
+        self.logger.info("Installing SPARC bootblk to root pool devices: %s" \
+                          % str(boot_devs))
+
+        bootblk_src = os.path.join(self.boot_target[BOOT_ENV].mountpoint,
+                                   'platform', platform.machine(),
+                                   'lib', 'fs', 'zfs', 'bootblk')
+        sub_cmd = ["/usr/sbin/installboot", "-F", "zfs", bootblk_src]
+
+        for dev in boot_devs:
+            cmd = sub_cmd[:]
+            cmd.append(dev)
+            self.logger.debug("Executing: %s" % cmd)
+            if not dry_run:
+                Popen.check_call(cmd, stdout=Popen.STORE, stderr=Popen.STORE,
+                                 logger=ILN)
+
+    def _set_sparc_prom_boot_device(self, dry_run):
+        """ Set the SPARC boot-device parameter using eeprom.
+            If the root pool is mirrored, sets the boot-device
+            parameter as a sequence of the devices forming the
+            root pool.
+        """
+        self.logger.info("Setting openprom boot-device")
+        prom_device = "/dev/openprom"
+        #ioctl codes and OPROMMAXPARAM taken from /usr/include/sys/openpromio.h
+        oioc = ord('O') << 8
+        opromdev2promname = oioc | 15  # Convert devfs path to prom path
+        oprommaxparam = 32768
+
+        boot_devs = list()
+        boot_devs = [os.path.join('/dev/dsk', dev) \
+            for dev in self.boot_target[DEVS]]
+        if len(boot_devs) < 1:
+            raise RuntimeError("No boot devices identified!")
+
+        self.logger.debug("Opening prom device: %s" % prom_device)
+        try:
+            with open(prom_device, "r") as prom:
+                prom_arg_str = None
+                # Set boot-device as a sequence for mirrored root pools since
+                # the OBP will try to boot each successive device specifier
+                # in the list until something opens successfully.
+                for i, boot_dev in enumerate(boot_devs):
+                    self.logger.debug("Boot device %d: %s" % (i, boot_dev))
+
+                    # Set up a mutable array for ioctl to read from and write
+                    # to. Standard Python objects are not usable here.
+                    # fcntl.ioctl requires a mutable buffer pre-packed with the
+                    # correct values (as determined by the device-driver).
+                    # In this case,openprom(7D) describes the following C
+                    # stucture as defined in <sys.openpromio.h>
+                    # struct openpromio {
+                    #     uint_t  oprom_size; /* real size of following data */
+                    #     union {
+                    #         char  b[1];  /* NB: Adjacent, Null terminated */
+                    #         int   i;
+                    #     } opio_u;
+                    # };
+                    dev = (boot_dev + "\0").ljust(oprommaxparam)
+                    buf = array.array('c', struct.pack('I%ds' % oprommaxparam,
+                                      oprommaxparam, dev))
+
+                    # use ioctl to query the prom device.
+                    fcntl.ioctl(prom, opromdev2promname, buf, True)
+
+                    # Unpack the mutable array, buf, which
+                    # ioctl just wrote into.
+                    new_oprom_size, new_dev = struct.unpack(
+                        'I%ds' % oprommaxparam,
+                        buf)
+
+                    # Device names are a list of null-terminated tokens, with a
+                    # double null on the final token.
+                    # We use only the first token.
+                    prom_name = new_dev.split('\0')[0]
+                    self.logger.debug("%s prom device name: %s" \
+                                      % (boot_dev, prom_name))
+                    if prom_arg_str is None:
+                        prom_arg_str = prom_name
+                    else:
+                        prom_arg_str += " " + prom_name
+        except StandardError as std:
+            # Treat this as non-fatal. It can be manually fixed later
+            # on reboot.
+            self.logger.warning("Failed to set openprom boot-device " \
+                                "parameter:\n%s" % str(std))
+            return
+
+        # Set the boot device using eeprom
+        cmd = ["/usr/sbin/eeprom", "boot-device=%s" % prom_arg_str]
+        self.logger.debug("Executing: %s" % cmd)
+        if not dry_run:
+            try:
+                Popen.check_call(cmd,
+                                 stdout=Popen.STORE,
+                                 stderr=Popen.STORE,
+                                 logger=ILN)
+            except CalledProcessError as cpe:
+                # Treat this as non-fatal. It can be manually fixed later
+                # on reboot.
+                self.logger.warning("Failed to set openprom boot-device:")
+                self.logger.warning(str(cpe))
+
+    def _is_target_instance(self, instance):
+        """ Returns True if instance is the boot instance we just installed.
+            Otherwise returns False
+            Inputs:
+            - instance: A BootInstance object
+        """
+        # If not a SolarisDiskBootInstance it's not what we just installed
+        if not isinstance(instance, bootconfig.SolarisDiskBootInstance):
+            return False
+        # If the fstype of the instance is not zfs
+        if instance.fstype is not "zfs":
+            return False
+        # Check the bootfs property to see if it's a match
+        if instance.bootfs == self.target_bootfs:
+            return True
+
+        return False
+
+    def _set_as_default_instance(self, instance):
+        """ Sets instance as the default boot instance in a boot configuration
+            Inputs:
+            - instance: A BootInstance object
+        """
+        self.logger.debug("Marking '%s' as the default boot instance" \
+                         % instance.title)
+        instance.default = True
+
+    def _set_instance_title(self, instance):
+        """ Sets the title of instance to match self.boot_title
+            Inputs:
+            - instance: A BootInstance object
+        """
+        self.logger.debug("Setting title of boot instance '%s' to '%s'" \
+                         % (instance.title, self.boot_title))
+        instance.title = self.boot_title
+
+    def _get_rel_file_path(self):
+        """
+            Returns file system path to the release file.
+        """
+        return os.path.join(self.boot_target[BOOT_ENV].mountpoint,
+                             "etc/release")
+
+    def _parse_doc_target(self, dry_run=False):
+        """ Parses the target objects in the DOC to determine the
+            installation target device for boot loader installation
+        """
+        try:
+            target = self.doc.get_descendants(Target.DESIRED, Target,
+                max_count=1, max_depth=2, not_found_is_err=True)[0]
+        except ObjectNotFoundError:
+            raise RuntimeError("No desired target element specified")
+
+        # Find the root pool(s).
+        root_pools = list()
+        for pool in target.get_descendants(class_type=Zpool):
+            if pool.is_root:
+                root_pools.append(pool)
+        if len(root_pools) < 1:
+            raise RuntimeError("No desired target Zpool specified")
+        root_pool = root_pools[0]
+        boot_env = root_pool.get_first_child(class_type=BE)
+        if boot_env is None:
+            raise RuntimeError("No BE specified in Target.Desired tree")
+        self.boot_target[BOOT_ENV] = boot_env
+
+        # If dry_run is True, the target BE might not exist so only bail out
+        # if not a dry run.
+        result = be_list(boot_env.name)
+        if len(result) != 1:
+            if dry_run == False:
+                raise RuntimeError("Target BE \'%s\' does not exist" \
+                                      % (boot_env.name))
+        else:
+            self.target_bootfs = result[0][2]
+
+        # Get root pool's top level filesystem mountpoint
+        pool = boot_env.parent
+        if pool.mountpoint:
+            self.pool_tld_mount = pool.mountpoint
+        # If mountpoint not defined by the zpool, look at its datasets
+        # for one with the same name as the zpool.
+        if self.pool_tld_mount is None:
+            for filesystem in pool.filesystems:
+                if filesystem.name == pool.name:
+                    self.pool_tld_mount = filesystem.get("mountpoint")
+                    break
+        # If no top level dataset definition exists then assume default zfs
+        # behaviour for pool top level dataset mount point as '/<pool.name>'
+        if self.pool_tld_mount is None:
+            self.pool_tld_mount = os.path.join('/', pool.name)
+
+        if not os.path.exists(self.pool_tld_mount):
+            raise RuntimeError("Expected mountpoint \'%\' of top level" \
+                               "fileystem \'%s\' does not exist!" \
+                               % (pool.name, self.pool_tld_mount))
+
+        # Figure out the boot device names in ctd notation
+        # XXX Will need to check for UEFI/GPT partitions here in future
+        for disk in target.get_children(class_type=Disk):
+            # Look for slices that are in the boot/root pool and store
+            # their ctd device names. "slice" is a python reserved word
+            # so use "slc" to represent slice instead.
+            slices = disk.get_descendants(class_type=Slice, max_depth=2)
+            boot_slices = [slc for slc in slices if \
+                           slc.in_zpool == root_pool.name]
+            bdevs = ['%ss%s' % (disk.ctd, slc.name) for slc in boot_slices]
+            self.boot_target[DEVS].extend(bdevs)
+
+        title_line = linecache.getline(self._get_rel_file_path(), 1)
+        self.rel_file_title = title_line.strip()
+        # Set an initial boot_title value. It can be overwritten later when
+        # parsing the BootMods tree of the DOC
+        self.boot_title = self.rel_file_title
+
+    def build_default_entries(self):
+        """ Method for constructing the default entries list.
+            When installing onto physical target systems, the list of entries
+            is autogenerated based on discovery routines within bootmgmt.
+            The discovered entries should already be present from the
+            invocation of init_boot_config() so the only remaining tasks here
+            are to set the new boot environment as the default boot instance.
+        """
+        # Find the boot environment (BE) we just installed and
+        # make it the default boot instance
+        self.config.modify_boot_instance(self._is_target_instance,
+                                         self._set_as_default_instance)
+        # And set its boot title
+        self.config.modify_boot_instance(self._is_target_instance,
+                                         self._set_instance_title)
+
+    def build_custom_entries(self):
+        """ Currently only consumed by AI installer app. GUI & Text do not
+            consume a manifest XML file.
+        """
+        for entry in self.boot_entry_list:
+            instance = \
+                SolarisDiskBootInstance(self.boot_target[BOOT_ENV].mountpoint,
+                                        fstype='zfs',
+                                        bootfs=self.target_bootfs)
+            instance.title = self.boot_title + \
+                             " " + entry.title_suffix
+            if entry.default_entry:
+                instance.default = True
+            else:
+                instance.default = False
+            instance.kargs = entry.kernel_args
+
+            if entry.insert_at == "start":
+                where = 0
+            else:
+                where = -1
+            self.logger.info("Adding custom boot entry: \'%s\'" \
+                             % instance.title)
+            self.config.add_boot_instance(instance, where)
+
+    def install_boot_loader(self, dry_run=False):
+        """ Install the boot loader and associated boot
+            configuration files
+        """
+        boot_rdevs = [os.path.join('/dev/rdsk', dev) \
+            for dev in self.boot_target[DEVS]]
+
+        if dry_run == True:
+            # Write boot config to a temporary directory instead of to
+            # a physical target
+            temp_dir = \
+                tempfile.mkdtemp(dir="/tmp", prefix="boot_config_")
+            # Add dictionary mappings for tokens that commit_boot_config()
+            # might return.
+            self.boot_tokens[DiskBootConfig.TOKEN_ZFS_RPOOL_TOP_DATASET] = \
+                self.pool_tld_mount
+            self.boot_tokens[BootConfig.TOKEN_SYSTEMROOT] = \
+                self.boot_target[BOOT_ENV].mountpoint
+            self.logger.info("Installing boot loader configuration files to: \
+                             %s" % (temp_dir))
+            boot_config_list = \
+                self.config.commit_boot_config(temp_dir, None)
+            self._handle_boot_config_list(boot_config_list, dry_run)
+        else:
+            # Check to make sure that an installation attempt is not
+            # executed on a live system
+            if self.boot_target[BOOT_ENV].mountpoint == '/':
+                raise RuntimeError("Boot checkpoint can not be executed on " \
+                                     "a live system except with dry run mode")
+            # XXX Pybootmgmt ought to be creating this directory tree when
+            # doing a physical boot loader installation onto the disk.
+            # While it's being fixed, create the directories ourselves to
+            # make legacy Grub installation work.
+            if not dry_run and self.config.boot_loader.name == "Legacy GRUB":
+                grub_dir = os.path.join(self.pool_tld_mount, "boot", "grub")
+                if not os.path.exists(grub_dir):
+                    self.logger.info("Creating Legacy GRUB config directory:" \
+                                     "\n\t%s" % (grub_dir))
+                    os.makedirs(grub_dir, 755)
+
+            # Danger Will Robinson!!
+            self.logger.info("Installing boot loader to devices: %s" \
+                             % str(boot_rdevs))
+            self.config.commit_boot_config(
+                boot_devices=boot_rdevs)
+        # XXX Pybootmgmt ought to do this but doesn't currently so
+        # set up the bootfs property on the boot pool.
+        self._set_bootfs_pool_prop(dry_run)
+        if dry_run:
+            rmtree(temp_dir)
+
+
+class ISOImageBootMenu(BootMenu):
+    """ Abstract base class for ISOImageBootMenu checkpoint
+    """
+    __metaclass__ = abc.ABCMeta
+
+    def __init__(self, name):
+        """ Constructor for class
+        """
+        super(ISOImageBootMenu, self).__init__(name)
+        self.dc_dict = dict()
+        self.dc_pers_dict = dict()
+        self.pkg_img_path = None
+        from solaris_install.distro_const import DC_LABEL, DC_PERS_LABEL
+        self.DC_LABEL = DC_LABEL
+        self.DC_PERS_LABEL = DC_PERS_LABEL
+
+    def init_boot_config(self, autogen=True):
+        """ Instantiates the appropriate bootmgmt.bootConfig subclass object
+            for this class
+        """
+        bc_flags = (bootconfig.BootConfig.BCF_CREATE,
+                    bootconfig.BootConfig.BCF_ONESHOT)
+        self.config = ODDBootConfig(bc_flags,
+                                    oddimage_root=self.pkg_img_path)
+        # UEFI Note that we will have to specify additional firmware
+        # targets (uefi64 & SPARC OBP) when pybootmgmt supports them.
+        self.config.boot_loader.setprop('boot-targets', 'bios')
+
+    def _get_rel_file_path(self):
+        """
+            Returns file system path to the release file.
+        """
+        return os.path.join(self.pkg_img_path, "etc/release")
+
+    def _add_chainloader_entry(self):
+        """ Adds a chainloader entry to the boot configuration.
+        """
+        instance = ChainDiskBootInstance(chaininfo=(0, 0))
+        instance.title = "Boot from Hard Disk"
+        self.config.add_boot_instance(instance)
+
+    def build_custom_entries(self):
+        """ Add custom defined boot entries from the manifest XML
+        """
+        for entry in self.boot_entry_list:
+            instance = SolarisODDBootInstance(self.pkg_img_path)
+            instance.title = self.boot_title + " " + entry.title_suffix
+            # The last boot entry in the list tagged as the default boot entry
+            # overrides the previous default if more than one is tagged as
+            # the default.
+            if entry.default_entry:
+                instance.default = True
+            else:
+                instance.default = False
+            instance.kargs = entry.kernel_args
+            if entry.insert_at == "start":
+                where = 0
+            else:
+                where = -1
+            self.config.add_boot_instance(instance, where)
+
+    def _parse_doc_target(self, dry_run=False):
+        """ Class method for parsing data object cache (DOC) objects for use by
+            the checkpoint.
+        """
+        try:
+            self.dc_pers_dict = self.doc.persistent.get_children(
+                name=self.DC_PERS_LABEL,
+                class_type=DataObjectDict,
+                not_found_is_err=True)[0].data_dict
+        except ObjectNotFoundError:
+            pass
+
+        self.dc_dict = self.doc.volatile.get_children(name=self.DC_LABEL,
+            class_type=DataObjectDict,
+            not_found_is_err=True)[0].data_dict
+
+        try:
+            self.pkg_img_path = self.dc_dict["pkg_img_path"]
+        except KeyError, msg:
+            raise RuntimeError("Error retrieving a value from the DOC: " + \
+                str(msg))
+
+        title_line = linecache.getline(self._get_rel_file_path(), 1)
+        self.rel_file_title = title_line.strip()
+        # Set an initial boot_title value. It can be overwritten later when
+        # parsing the BootMods tree of the DOC
+        self.boot_title = self.rel_file_title
+
+    def execute(self, dry_run=False):
+        """ Primary execution method used by the Checkpoint parent class
+        """
+        self.logger.info("=== Executing Boot Loader Setup Checkpoint ===")
+        super(ISOImageBootMenu, self).execute(dry_run)
+
+    def install_boot_loader(self, dry_run=False):
+        """ Install the boot loader and associated boot configuration files.
+        """
+        if dry_run == True:
+            temp_dir = \
+                tempfile.mkdtemp(dir="/tmp", prefix="iso_boot_config_")
+        else:
+            temp_dir = self.pkg_img_path
+        # Add dictionary mappings for tokens that commit_boot_config() might
+        # return.
+        self.boot_tokens[BootConfig.TOKEN_SYSTEMROOT] = self.pkg_img_path
+        self.logger.debug("Writing boot configuration to %s" % (temp_dir))
+        boot_config_list = self.config.commit_boot_config(temp_dir, None)
+        self._handle_boot_config_list(boot_config_list, dry_run)
+        if dry_run:
+            rmtree(temp_dir)
+
+    def _handle_iso_boot_image_type(self, config, dry_run):
+        """ Method that copies ISO El Torito and HSFS bootblockimage file types
+            to their appropriate targets.
+        """
+        # Store the ISO boot image path in DC's dictionary for later use
+        # when constructing the ISO image
+        img_type = config[0]
+        src = config[1]
+        if not os.path.exists(src):
+            raise RuntimeError("Expected  boot image type \'%s\' does not " \
+                               "exist at path: %s" % (img_type, src))
+        if dry_run is True:
+            self.logger.debug("Deleting El Torito image: %s" % src)
+            os.unlink(src)
+            return
+
+        if not os.path.abspath(src).startswith(self.pkg_img_path):
+            raise RuntimeError("El Torito boot image \'%s\' mislocated "
+                                 "outside of image root: %s" \
+                                 % (src, self.pkg_img_path))
+        if img_type == BootConfig.OUTPUT_TYPE_BIOS_ELTORITO:
+            # bios-eltorito-img needs to live in the persistent section
+            # of the DOC to ensure pause/resume works correctly.
+            #
+            # Update the DC_PERS_LABEL DOC object with an entry for
+            # bios-eltorito-img
+            if self.dc_pers_dict:
+                self.doc.persistent.delete_children(name=self.DC_PERS_LABEL)
+
+            # Strip out the pkg_img_path prefix from src. Otherwise
+            # mkisofs will choke because it requires a relative rather
+            # than an absolute path for the eltorito image argument
+            self.dc_pers_dict["bios-eltorito-img"] = \
+                src.split(self.pkg_img_path + os.sep)[1]
+            self.doc.persistent.insert_children(
+                DataObjectDict(self.DC_PERS_LABEL,
+                self.dc_pers_dict, generate_xml=True))
+            self.logger.debug("BIOS El Torito boot image: %s" \
+                              % self.dc_pers_dict["bios-eltorito-img"])
+        # UEFI / Pybootmgmt.
+        # Add in blocks to handle UEFI ELTORIO and SPARC HSFS BOOTBLK
+        # types here when pybootmgmt supports these firmware types.
+        # For now just it just does BIOS ELTORITO above.
+        else:
+            raise RuntimeError("Unrecognised ISO boot loader image type: %s" \
+                               % img_type)
+
+
+class AIISOImageBootMenu(ISOImageBootMenu):
+    """ Class for AIISOImageBootMenu checkpoint.
+        Used by Distro Constructor for creation of Automated Install ISO image.
+    """
+
+    def __init__(self, name, arg=None):
+        """ Constructor for class
+        """
+        super(AIISOImageBootMenu, self).__init__(name)
+        self.img_info_path = None
+        self.installadm_entry = None
+        if arg:
+            self.__setup(**arg)
+        else:
+            self.__setup()
+
+    def __setup(self, installadm_entry="boot image"):
+        """ Setup checkpoint with any kwargs in manifest XML
+        """
+        self.installadm_entry = installadm_entry
+
+    def build_default_entries(self):
+        """ Constructs the default boot entries and inserts them into the
+            bootConfig object's boot_instances list
+        """
+        ai_titles = [self.boot_title + " Automated Install custom",
+                     self.boot_title + " Automated Install",
+                     self.boot_title + " Automated Install custom ttya",
+                     self.boot_title + " Automated Install custom ttyb",
+                     self.boot_title + " Automated Install ttya",
+                     self.boot_title + " Automated Install ttyb"]
+        ai_kargs = ["-B aimanifest=prompt",
+                    None,
+                    "-B install=true,aimanifest=prompt,console=ttya",
+                    "-B install=true,aimanifest=prompt,console=ttyb",
+                    "-B install=true,console=ttya",
+                    "-B install=true,console=ttyb"]
+        for i, title in enumerate(ai_titles):
+            instance = SolarisODDBootInstance(self.pkg_img_path)
+            instance.title = title
+            # Make the first entry the default
+            if i == 0:
+                instance.default = True
+            instance.kargs = ai_kargs[i]
+            self.config.add_boot_instance(instance)
+
+        # Create a chainloader boot from HD entry
+        self._add_chainloader_entry()
+
+    def execute(self, dry_run=False):
+        """ Primary execution method used by the Checkpoint parent class
+        """
+        super(AIISOImageBootMenu, self).execute(dry_run)
+        self.update_img_info_path()
+
+    def update_img_info_path(self):
+        """ Method to write out the .img_info_path file.
+        """
+        self.img_info_path = os.path.join(self.pkg_img_path, ".image_info")
+        self.logger.info("Updating %s" % self.img_info_path)
+
+        # write out the GRUB_TITLE line
+        with open(self.img_info_path, "a+") as iip:
+            try:
+                iip.write("GRUB_TITLE=" + self.boot_title + "\n")
+                iip.write("GRUB_MIN_MEM64=%d\n" % 1000)
+                iip.write("GRUB_DO_SAFE_DEFAULT=true\n")
+                iip.write("NO_INSTALL_GRUB_TITLE=%s\n" % self.installadm_entry)
+            except IOError, msg:
+                raise RuntimeError(msg)
+
+
+class LiveCDISOImageBootMenu(ISOImageBootMenu):
+    """ Class for LiveCDISOImageBootMenu checkpoint.
+        Used by Distro Constructor for creation of LiveCD
+        ISO image.
+    """
+
+    def __init__(self, name):
+        """ Constructor for class
+        """
+        super(LiveCDISOImageBootMenu, self).__init__(name)
+
+    def build_default_entries(self):
+        """ Constructs the default boot entries and inserts them into the
+            bootConfig object's boot_instances list
+        """
+        # create lists of boot titles and kernel args to use
+        lcd_titles = [self.boot_title,
+                      self.boot_title + " VESA driver",
+                      self.boot_title + " text console"]
+        lcd_kargs = [None,
+                     "-B livemode=vesa",
+                     "-B livemode=text"]
+        for i, title in enumerate(lcd_titles):
+            instance = SolarisODDBootInstance(self.pkg_img_path)
+            instance.title = title
+            # Make the first entry the default
+            if i == 0:
+                instance.default = True
+            instance.kargs = lcd_kargs[i]
+            self.config.add_boot_instance(instance)
+
+        # Create a chainloader boot from HD entry
+        self._add_chainloader_entry()
+
+
+class TextISOImageBootMenu(ISOImageBootMenu):
+    """ Class for TextISOImageBootMenu checkpoint.
+        Used by Distro Constructor for creation of Text installer ISO image.
+    """
+
+    def __init__(self, name):
+        """ Constructor for class
+        """
+        super(TextISOImageBootMenu, self).__init__(name)
+
+    def build_default_entries(self):
+        """ Constructs the default boot entries and inserts them into the
+            bootConfig object's boot_instances list
+        """
+        instance = SolarisODDBootInstance(self.pkg_img_path)
+        instance.title = self.boot_title
+        # Make this entry the default
+        instance.default = True
+        self.config.add_boot_instance(instance)
+
+        # Create a chainloader boot from HD entry
+        self._add_chainloader_entry()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/lib/install_boot/boot_spec.py	Fri May 06 11:44:10 2011 +1000
@@ -0,0 +1,202 @@
+#!/usr/bin/python
+#
+# 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
+# or http://www.opensolaris.org/os/licensing.
+# 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
+#
+
+#
+# Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+#
+""" boot_spec.py -- library containing class definitions for boot DOC objects,
+    including BootMods and BootEntry.
+"""
+
+from lxml import etree
+
+from solaris_install.data_object import DataObject
+
+
+class BootMods(DataObject):
+    """ Subclass of DataObject to contain the boot_mods
+        information in the Data Object Cache.
+    """
+    BOOT_MODS_LABEL = "boot_mods"
+    TITLE_LABEL = "title"
+    TIMEOUT_LABEL = "timeout"
+
+    def __init__(self, name):
+        """ Initialize the DataObject object with name.
+        """
+        super(BootMods, self).__init__(name)
+        self.title = None
+        self.timeout = None
+
+    def __repr__(self):
+        """ String representation of a BootMods object.
+        """
+        rep = "BootMods: "
+        if self.title is not None:
+            rep += 'title="%s"; ' % self.title
+        if self.timeout is not None:
+            rep += 'timeout=%d' % self.timeout
+        return rep
+
+    def to_xml(self):
+        """ Method to create the xml boot_mods element.
+        """
+        element = etree.Element(BootMods.BOOT_MODS_LABEL)
+
+        if self.title is not None:
+            element.set(BootMods.TITLE_LABEL, self.title)
+
+        if self.timeout is not None:
+            element.set(BootMods.TIMEOUT_LABEL, str(self.timeout))
+
+        return element
+
+    @classmethod
+    def can_handle(cls, element):
+        """ Returns True if element has tag: "boot_mods".
+            Returns False otherwise.
+        """
+        if element.tag == BootMods.BOOT_MODS_LABEL:
+            return True
+        return False
+
+    @classmethod
+    def from_xml(cls, element):
+        """ Method to create the DOC boot_mods element.
+        """
+        title = element.get(BootMods.TITLE_LABEL)
+        timeout = element.get(BootMods.TIMEOUT_LABEL)
+        boot_mods = BootMods(BootMods.BOOT_MODS_LABEL)
+
+        if title is not None:
+            boot_mods.title = title
+        if timeout is not None:
+            boot_mods.timeout = int(timeout)
+
+        return boot_mods
+
+
+class BootEntry(DataObject):
+    """ Subclass of DataObject to contain a boot_entry objects
+        information in the Data Object Cache.
+    """
+    BOOT_ENTRY_LABEL = "boot_entry"
+    DEFAULT_ENTRY_LABEL = "default_entry"
+    INSERT_AT_LABEL = "insert_at"
+    TITLE_SUFFIX_LABEL = "title_suffix"
+    KERNEL_ARGS_LABEL = "kernel_args"
+
+    def __init__(self, name):
+        """ Initialize the DataObject object with name.
+        """
+        super(BootEntry, self).__init__(name)
+        self.default_entry = None
+        self.insert_at = None
+        self.title_suffix = None
+        self.kernel_args = None
+
+    def __repr__(self):
+        """ String representation of a BootEntry object.
+        """
+        rep = 'BootEntry: '
+        if self.title_suffix is not None:
+            rep += 'title_suffix="%s"; ' % self.title_suffix
+        if self.default_entry is not None:
+            rep += 'default=%s; ' % self.default_entry
+        if self.insert_at is not None:
+            rep += 'insert_at=%s; ' % self.insert_at
+        if self.kernel_args is not None:
+            rep += 'kernel_args="%s"' % self.kernel_args
+        return rep
+
+    def to_xml(self):
+        """ Method to create the xml boot_entry element.
+        """
+        element = etree.Element(BootEntry.BOOT_ENTRY_LABEL)
+
+        if self.default_entry is not None:
+            element.set(BootEntry.DEFAULT_ENTRY_LABEL,
+                        (str(self.default_entry).lower()))
+        if self.insert_at is not None:
+            element.set(BootEntry.INSERT_AT_LABEL, self.insert_at)
+
+        if self.title_suffix is not None:
+            title_element = etree.SubElement(element,
+                BootEntry.TITLE_SUFFIX_LABEL)
+            title_element.text = self.title_suffix
+        if self.kernel_args is not None:
+            kargs_element = etree.SubElement(element,
+                                             BootEntry.KERNEL_ARGS_LABEL)
+            kargs_element.text = self.kernel_args
+
+        return element
+
+    @classmethod
+    def can_handle(cls, element):
+        """ Returns True if element has the tag "boot_entry",
+            and a sub-elemenent that has the tag: "title_suffix".
+            Returns False otherwise.
+        """
+        if element.tag != BootEntry.BOOT_ENTRY_LABEL:
+            return False
+
+        title_suffix = None
+
+        for subelement in element.iterchildren():
+            if subelement.tag == BootEntry.TITLE_SUFFIX_LABEL:
+                title_suffix = subelement.text
+
+        if title_suffix is None:
+            return False
+
+        return True
+
+    @classmethod
+    def from_xml(cls, element):
+        """ Method to create a DOC boot_entry element.
+        """
+        title_suffix = None
+        kernel_args = None
+        default_entry = element.get(BootEntry.DEFAULT_ENTRY_LABEL)
+        insert_at = element.get(BootEntry.INSERT_AT_LABEL)
+
+        for subelement in element.iterchildren():
+            if subelement.tag == BootEntry.TITLE_SUFFIX_LABEL:
+                title_suffix = subelement.text
+
+            if subelement.tag == BootEntry.KERNEL_ARGS_LABEL:
+                kernel_args = subelement.text
+
+        boot_entry = BootEntry(BootEntry.BOOT_ENTRY_LABEL)
+        if insert_at is not None:
+            boot_entry.insert_at = insert_at
+        if title_suffix is not None:
+            boot_entry.title_suffix = title_suffix
+        if kernel_args is not None:
+            boot_entry.kernel_args = kernel_args
+        if default_entry is not None:
+            if default_entry.lower() == "true":
+                boot_entry.default_entry = True
+            elif default_entry.lower() == "false":
+                boot_entry.default_entry = False
+
+        return boot_entry
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/lib/install_boot/test/test_boot.py	Fri May 06 11:44:10 2011 +1000
@@ -0,0 +1,646 @@
+#!/usr/bin/python
+#
+# 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
+# or http://www.opensolaris.org/os/licensing.
+# 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
+#
+
+#
+# Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+#
+
+""" Boot checkpoint Unit Tests
+"""
+
+import os
+import abc
+import tempfile
+import unittest
+import platform
+import pwd
+import grp
+
+from shutil import rmtree, copyfile
+from lxml import etree
+
+from bootmgmt.bootconfig import BootConfig
+from solaris_install.boot.boot import SystemBootMenu, AIISOImageBootMenu, \
+    TextISOImageBootMenu, LiveCDISOImageBootMenu, BOOT_ENV, DEVS
+from solaris_install.data_object.data_dict import DataObjectDict
+from solaris_install.engine import InstallEngine
+from solaris_install.engine.test.engine_test_utils import \
+    get_new_engine_instance, reset_engine
+from solaris_install.target.logical import be_list, BE, Filesystem
+
+TMP_TEST_DIR = "/tmp/boot-test"
+
+BOOT_MODS_XML = '''
+<root>
+  <boot_mods timeout="20" title="Boot Testing:">
+    <boot_entry default_entry="false" insert_at="end">
+      <title_suffix>2nd last Boot Entry</title_suffix>
+      <kernel_args>test_kernel_args=true</kernel_args>
+    </boot_entry>
+    <boot_entry default_entry="true" insert_at="start">
+      <title_suffix>1st (default) Boot Entry</title_suffix>
+      <kernel_args>test_kernel_args=true</kernel_args>
+    </boot_entry>
+    <boot_entry>
+      <title_suffix>Last Boot Entry</title_suffix>
+    </boot_entry>
+    <boot_entry>
+    </boot_entry>
+  </boot_mods>
+</root>'''
+
+# Physical target representation for X86 tests
+PHYS_X86_XML = \
+'''<disk whole_disk="false">
+    <disk_name name="c600d600" name_type="ctd"/>
+    <disk_prop dev_type="FIXED" dev_size="666666666"/>
+    <disk_keyword key="boot_disk"/>
+    <partition action="preserve" name="1" part_type="191">
+      <size val="66666666secs" start_sector="16065"/>
+      <slice name="0" action="preserve" force="false" in_zpool="rpool">
+        <size val="33479459secs" start_sector="16065"/>
+      </slice>
+    </partition>
+  </disk>
+  <disk whole_disk="false">
+    <disk_name name="c700d700" name_type="ctd"/>
+    <disk_prop dev_type="FIXED" dev_size="777777777"/>
+    <partition action="preserve" name="1" part_type="191">
+      <size val="33527655secs" start_sector="16065"/>
+      <slice name="0" action="preserve" force="false" in_zpool="rpool">
+        <size val="33479459secs" start_sector="16065"/>
+      </slice>
+    </partition>
+  </disk>'''
+
+# Physical target representation for SPARC tests
+PHYS_SPARC_XML = '''
+<root>
+  <disk whole_disk="false">
+    <disk_name name="c600d600" name_type="ctd"/>
+    <disk_prop dev_type="FIXED" dev_size="666666666"/>
+    <disk_keyword key="boot_disk"/>
+    <slice name="0" action="preserve" force="false" in_zpool="rpool">
+      <size val="33479459secs" start_sector="16065"/>
+    </slice>
+  </disk>
+  <disk whole_disk="false">
+    <disk_name name="c700d700" name_type="ctd"/>
+    <disk_prop dev_type="FIXED" dev_size="777777777"/>
+      <slice name="0" action="preserve" force="false" in_zpool="rpool">
+        <size val="33479459secs" start_sector="16065"/>
+      </slice>
+  </disk>
+</root>'''
+
+# This is a template logical target specification. The physical part is
+# populated with the appropriate architecture specific physical specification
+# above. The logical part is populated based on a run time inspection of the
+# live system's active BE and pool(s).
+DYNAMIC_DESIRED_XML = '''
+<root>
+<target name="desired">
+  %(phys_xml)s
+  <logical noswap="true" nodump="true">
+    <zpool name="%(rpoolname)s" action="preserve" is_root="true">
+      <be name="%(bename)s" mountpoint="%(bemount)s"/>
+    </zpool>
+  </logical>
+</target>
+</root>'''
+
+
+class BootMenuTestCaseBase(unittest.TestCase):
+    """ Abstracy base class for unit tests that doesn't run any tests of
+        its own, but sets up an environment for boot unit tests
+    """
+    __metaclass__ = abc.ABCMeta
+
+    def __init__(self, test_case_name):
+        """ Constructor for class so that architecture can be overriden if
+            necessary
+        """
+        super(BootMenuTestCaseBase, self).__init__(test_case_name)
+        self.arch = platform.processor()
+
+    def setUp(self):
+        """ Common unit test setup method for all subclasses
+        """
+        self.engine = get_new_engine_instance()
+        self.doc = InstallEngine.get_instance().data_object_cache
+        if not os.path.exists(TMP_TEST_DIR):
+            os.makedirs(TMP_TEST_DIR)
+
+    def tearDown(self):
+        """ Common unit test tear down method for all subclasses
+        """
+        if self.engine:
+            reset_engine(self.engine)
+        self.doc = None
+        self.boot_mods = None
+        self.boot_menu = None
+        self.arch = None
+
+
+class SystemBootMenuTestCase(BootMenuTestCaseBase):
+    """ Class for unit testing of SystemBootMenu class
+    """
+
+    def setUp(self):
+        """ Initialises a runtime environment for execution of
+            SystemBootMenu unit tests.
+        """
+        super(SystemBootMenuTestCase, self).setUp()
+
+        desired_dict = {}
+        # For the purposes of having a sane environment for the
+        # test case, use the active BE. This allows pybootmgt
+        # initialisation to locate all the bits it expects.
+        for self.be_name, be_pool, be_root_ds in be_list():
+            be_fs = Filesystem(be_root_ds)
+            if be_fs.get('mounted') == 'yes' and \
+               be_fs.get('mountpoint') == '/':
+                desired_dict["rpoolname"] = be_pool
+                desired_dict["rpoolmount"] = \
+                    Filesystem(be_pool).get('mountpoint')
+                desired_dict["bename"] = self.be_name
+                desired_dict["bemount"] = '/'
+                break
+        if self.arch == "sparc":
+            desired_dict["phys_xml"] = PHYS_SPARC_XML
+        else:
+            desired_dict["phys_xml"] = PHYS_X86_XML
+        # Make sure the active BE was found before proceeding with the test
+        try:
+            desired_dict["bemount"]
+        except KeyError:
+            raise RuntimeError("Couldn't find active BE to use in unit test")
+        boot_mods_dom = etree.fromstring(BOOT_MODS_XML)
+        self.doc.import_from_manifest_xml(boot_mods_dom, volatile=True)
+        target_desired_dom = etree.fromstring(DYNAMIC_DESIRED_XML \
+                                              % desired_dict)
+        self.doc.import_from_manifest_xml(target_desired_dom, volatile=True)
+
+        # logical BE:from_xml() method ignores the mountpoint property in the
+        # XML so manually override its None value.
+        boot_env = self.doc.volatile.get_descendants(class_type=BE)[0]
+        boot_env.mountpoint = '/'
+        self.boot_menu = SystemBootMenu("Test SystemBootMenu Checkpoint")
+        # Force the arch in SystemBootMenu to match self.arch in case we
+        # are trying to override it.
+        self.boot_menu.arch = self.arch
+
+    def test_parse_doc_target(self):
+        """ Tests parsing of Target.Desired in the Data Object Cache
+        """
+        for arch in ['sparc', 'i386']:
+            self.tearDown()
+            self.arch = arch
+            self.setUp()
+
+            # Invoke target parsing method and verify it matches
+            # expected values
+            self.boot_menu._parse_doc_target(dry_run=True)
+            boot_targets = self.boot_menu.boot_target
+
+            # Check that the vtoc slice object was identified as a boot device
+            # Slice should be slice 0 belonging to disk c600d600
+            # ie. c600d700s0
+
+            boot_slice = boot_targets[DEVS][0]
+            self.failUnlessEqual(boot_slice, 'c600d600s0',
+                "%s target boot device slice object doesn't match "\
+                "manifest entry:\n" \
+                "(Manifest slice device: c600d600s0, SystemBootMenu slice " \
+                "slice device: %s)\n" % (arch, boot_slice))
+
+            # UEFI Further checks for gpt_partitions should be included here.
+
+            # Check the BE sub element of the logical node of target too
+            boot_env = boot_targets[BOOT_ENV]
+            self.failUnlessEqual(boot_env.name, self.be_name,
+                "Target boot environment object doesn't match " \
+                "manifest entry:\n" \
+                "(Manifest boot environment: \'foobar\', "
+                "SystemBootMenu boot " \
+                "environment: %s)\n" % (boot_env.name))
+
+    def test_get_progress_estimate(self):
+        """ Tests return value of the boot checkpoint's
+            get_progress_estimate() method
+        """
+        estimate = self.boot_menu.get_progress_estimate()
+        self.failUnlessEqual(estimate, 5,
+            "BootMenu:get_progress_estimate() returned unexpected " \
+            "estimate:\n(Expected estimate: \'5\', Actual estimate '%d'\n" \
+            % (estimate))
+
+    def test_build_custom_entries(self):
+        """ Unit tests custom boot menu entry creation from manifest XML
+        """
+        self.boot_menu.parse_doc()
+        self.boot_menu.init_boot_config()
+        self.boot_menu.build_custom_entries()
+
+    def test_build_default_entries(self):
+        """ Unit tests default boot menu entry creation (autogenerated)
+        """
+        self.boot_menu.parse_doc()
+        self.boot_menu.init_boot_config()
+        # Force method to construct a boot title
+        self.boot_menu.boot_title = None
+        self.boot_menu.build_default_entries()
+
+    def test_install_boot_loader(self):
+        """ Unit tests boot loader installation method
+        """
+        self.boot_menu.parse_doc()
+        self.boot_menu.init_boot_config()
+        self.boot_menu.build_default_entries()
+        self.boot_menu.install_boot_loader(dry_run=True)
+
+    def test_copy_sparc_bootlst(self):
+        """ Unit tests copying of the SPARC bootlst binary.
+            Can only be partially tested on X86.
+        """
+        if self.arch == 'sparc':
+            self.boot_menu._parse_doc()
+            self.boot_menu._copy_sparc_bootlst(dry_run=True)
+
+    def test_create_sparc_boot_menu(self):
+        """ Test creation of the SPARC menu.lst file
+        """
+        self.tearDown()
+        self.arch = 'sparc'
+        self.setUp()
+        self.boot_menu._parse_doc_target()
+        self.boot_menu._create_sparc_boot_menu(dry_run=True)
+        self.tearDown()
+
+    def test_install_sparc_bootblk(self):
+        """ Test installation of the SPARC bootblk binary via installboot(1M)
+        """
+        # Make sure the method thinks it's runnning on SPARC
+        self.tearDown()
+        self.arch = 'sparc'
+        self.setUp()
+        self.boot_menu._parse_doc_target()
+        self.boot_menu._install_sparc_bootblk(dry_run=True)
+        # Make sure it raises a RuntimeError on X86
+        self.boot_menu.arch = 'i386'
+        self.assertRaises(RuntimeError,
+                          self.boot_menu._install_sparc_bootblk,
+                          dry_run=True)
+
+    def test_set_sparc_prom_boot_device(self):
+        """ Tests setting of the prom boot-device variable on SPARC
+            This test is limited because reading from /dev/openprom
+            requires root priviliges which causes a non-fatal exception
+            in the called method.
+        """
+        # Make sure the method thinks it's runnning on SPARC
+        self.tearDown()
+        self.arch = 'sparc'
+        self.setUp()
+        self.boot_menu._parse_doc_target()
+        self.boot_menu._set_sparc_prom_boot_device(dry_run=True)
+
+    def test_execute(self):
+        """ Test that runs the entire checkpoint as an install application
+            would. Essentially an aggregation of the above tests.
+        """
+        self.boot_menu.execute(dry_run=True)
+
+
+class ISOBootMenuTestCase(BootMenuTestCaseBase):
+    """ Abstract unit test base class for ISO image derived classes such as
+        AI, Text and LiveCD ISO BootMenu classes.
+        Doesn't directly perform any unit tests
+    """
+    __metaclass__ = abc.ABCMeta
+
+    def setUp(self):
+        """ Initialises a runtime environment for execution of
+            ISOImageBootMenu subclassed unit tests. Currently needs
+            an X86 legacy GRUB environment.
+        """
+        super(ISOBootMenuTestCase, self).setUp()
+        temp_dir = tempfile.mkdtemp(dir=TMP_TEST_DIR,
+                                    prefix="install_boot_test_")
+        rel_file_dir = os.path.join(temp_dir, "etc")
+        os.makedirs(rel_file_dir)
+        rel_file = os.path.join(rel_file_dir, 'release')
+        with open(rel_file, 'w') as file_handle:
+            file_handle.write('Solaris Boot Test')
+        doc_dict = {"pkg_img_path": temp_dir}
+        self.temp_dir = temp_dir
+        self.doc.volatile.insert_children(DataObjectDict("DC specific",
+                                          doc_dict,
+                                          generate_xml=True))
+        # Create a dummy boot and grub menu.ls environment
+        # so that pybootmgmt can instantiate a legacyGrub
+        # boot loader under the bootconfig object
+        # UEFI - this needs updating for SPARC, GRUB2 & UEFI
+        # when support gets added to pybootmgmt
+        if platform.processor() == 'i386':
+            os.makedirs(os.path.join(temp_dir, 'boot/grub'))
+            copyfile('/boot/grub/menu.lst',
+                     os.path.join(temp_dir, 'boot/grub/menu.lst'))
+            copyfile('/boot/grub/stage2_eltorito',
+                     os.path.join(temp_dir, 'boot/grub/stage2_eltorito'))
+            os.makedirs(os.path.join(temp_dir, 'boot/solaris'))
+            copyfile('/boot/solaris/bootenv.rc',
+                     os.path.join(temp_dir, 'boot/solaris/bootenv.rc'))
+
+    def tearDown(self):
+        """ Cleans up after each unit test is executed
+        """
+        super(ISOBootMenuTestCase, self).tearDown()
+        rmtree(self.temp_dir)
+
+    def _test_build_custom_entries(self):
+        """ Implements a custom boot menu entry listtest for derived classes
+        """
+        boot_mods_dom = etree.fromstring(BOOT_MODS_XML)
+        self.doc.import_from_manifest_xml(boot_mods_dom, volatile=True)
+        self.boot_menu.parse_doc()
+        self.boot_menu.init_boot_config()
+        self.boot_menu.build_default_entries()
+        self.boot_menu.build_custom_entries()
+        config = self.boot_menu.config
+
+        sorted_entry_list = []
+        for entry in self.boot_menu.boot_entry_list:
+            if entry.insert_at == "start":
+                sorted_entry_list.insert(0, entry)
+            else:
+                sorted_entry_list.append(entry)
+
+        # Map manifest defined custom entries to their expected positions in
+        # the bootConfig objects boot_instances list and compare.
+        num_entries = len(config.boot_instances)
+        j = 0
+        for i in [0, num_entries - 2, num_entries - 1]:
+            full_title = self.boot_menu.boot_title + \
+                        " " + \
+                        sorted_entry_list[j].title_suffix
+            self.failUnlessEqual(full_title, config.boot_instances[i].title,
+                "Resulting BootConfig doesn't match manifest.\n\
+                Mismatched boot entry titles at entry: %d\n\
+                BootConfig Title: %s\n\
+                Manifest Title: %s\n"\
+                % (i, config.boot_instances[i].title, full_title))
+            j += 1
+
+    def _test_build_default_entries(self, titles, kargs, chainloader=True):
+        """ Implements a default menu entry unit test for sub-classes
+        """
+        # How many boot entries the TextCD ISO defines by default. Needs to
+        # be adjusted along with expected entry contents if the defaults
+        # change.
+        exp_len = len(titles)
+        if chainloader:
+            exp_len += 1
+        self.boot_menu.parse_doc()
+        self.boot_menu.init_boot_config()
+        self.boot_menu.build_default_entries()
+
+        instances = self.boot_menu.config.boot_instances
+        self.assertEqual(len(titles), len(kargs),
+            "Arguments mismatch. titles and kargs arguments must be the " \
+            "same length")
+        self.assertEqual(len(instances), exp_len,
+            "Unexpected number of boot entries in default menu:"
+            "\n\tACTUAL: %d\tExpected: %d" % (len(instances), exp_len))
+
+        # Only one element for now, but making it a list makes it easier to
+        # expand later, even if it looks silly.
+        self.boot_title = self.boot_menu.boot_title
+
+        for i, exp_title in enumerate(titles):
+            actual_title = self.boot_menu.config.boot_instances[i].title
+            self.assertEqual(actual_title,
+                             exp_title,
+                             "Unexpected boot entry title at index %d:" \
+                             "\nACTUAL:\t%s\nEXPECTED:\t%s" \
+                             % (i, actual_title, exp_title))
+            actual_kargs = self.boot_menu.config.boot_instances[i].kargs
+            exp_kargs = kargs[i]
+            self.assertEqual(actual_kargs,
+                             exp_kargs,
+                             "Unexpected boot entry kernel args at index %d:" \
+                             "\nACTUAL:\t%s\nEXPECTED:\t%s" \
+                             % (i, actual_kargs, exp_kargs))
+        # Finally, check the chainloader entry
+        if chainloader:
+            i += 1
+            actual_title = self.boot_menu.config.boot_instances[i].title
+            exp_title = "Boot from Hard Disk"
+            self.assertEqual(actual_title,
+                             exp_title,
+                             "Unexpected chainloader title at index %d:" \
+                             "\nACTUAL:\t%s\nEXPECTED:\t%s" \
+                             % (i, actual_title, exp_title))
+            actual_cinfo = self.boot_menu.config.boot_instances[i].chaininfo
+            exp_cinfo = (0, 0)
+            self.assertEqual(actual_cinfo,
+                             exp_cinfo,
+                             "Unexpected chainloader chaininfo at index %d:" \
+                             "\nACTUAL:\t%s\nEXPECTED:\t%s" \
+                             % (i, str(actual_cinfo), str(exp_cinfo)))
+
+    def _test_install_boot_loader(self):
+        """ Implements a boot loader installation unit test for sub-classes
+        """
+        boot_mods_dom = etree.fromstring(BOOT_MODS_XML)
+        self.doc.import_from_manifest_xml(boot_mods_dom, volatile=True)
+        self.boot_menu.parse_doc()
+        self.boot_menu.init_boot_config()
+        self.boot_menu.build_default_entries()
+        self.boot_menu.build_custom_entries()
+        self.boot_menu.install_boot_loader(dry_run=True)
+
+    def _test_handle_boot_config_list(self):
+        """ Implemnts a unit test to validate that returned boot config data
+            from bootmgmt is handled correctly. Used by sub-classes
+            Checks file and bios eltorito boot images for now.
+        """
+        self.boot_menu.parse_doc()
+        self.boot_menu.boot_tokens[BootConfig.TOKEN_SYSTEMROOT] = self.temp_dir
+        dst_file = "%(" + BootConfig.TOKEN_SYSTEMROOT + ")s" + "/dst-file"
+        dst_blt = "%(" + BootConfig.TOKEN_SYSTEMROOT + ")s" + \
+                  "/dst-bios-eltorito-img"
+
+        # Assign matching user and group names so file ownership can be
+        # processed.
+        user = pwd.getpwuid(os.getuid())[0]
+        group = grp.getgrgid(os.getgid())[0]
+
+        # Test standard file handle type first
+        src_dir = tempfile.mkdtemp(dir=TMP_TEST_DIR, prefix="boot_config_src_")
+        src_file = os.path.join(src_dir, "src-file")
+        with open(src_file, 'w') as file_handle:
+            file_handle.write("Rhubarb")
+        boot_files = [[(BootConfig.OUTPUT_TYPE_FILE,
+                       src_file, None,
+                       dst_file,
+                       user,
+                       group,
+                       420)]]
+        self.boot_menu._handle_boot_config_list(boot_files, dry_run=False)
+        os.unlink(os.path.join(self.temp_dir, "dst-file"))
+
+        # Test BIOS eltorito image type. mkisofs(1M) requires that the
+        # eltorito image file be underneath the top level folder of the
+        # ISO filesystem tree.
+        src_blt = os.path.join(self.temp_dir, "src-bios-eltorito-img")
+        with open(src_blt, 'w') as file_handle:
+            file_handle.write("BLT")
+        boot_files = [[(BootConfig.OUTPUT_TYPE_BIOS_ELTORITO,
+                       src_blt, None,
+                       dst_blt,
+                       user,
+                       group,
+                       420)]]
+        self.boot_menu._handle_boot_config_list(boot_files, dry_run=False)
+        os.unlink(src_blt)
+
+        # Test BIOS eltorito image type with a bad src directory
+        bad_blt_dir = tempfile.mkdtemp(dir=TMP_TEST_DIR, prefix="bad_blt_")
+        src_blt = os.path.join(bad_blt_dir, "src-bios-eltorito-img")
+        with open(src_blt, 'w') as file_handle:
+            file_handle.write("BLT")
+        boot_blt = [[(BootConfig.OUTPUT_TYPE_BIOS_ELTORITO,
+                     src_blt, None,
+                     dst_blt,
+                     user,
+                     group,
+                     420)]]
+        self.assertRaises(RuntimeError,
+                          self.boot_menu._handle_boot_config_list,
+                          boot_config_list=boot_blt, dry_run=False)
+        # Test with an invalid file type.
+        boot_unsup = [[("DEADBEEF",
+                     src_blt, None,
+                     dst_blt,
+                     user,
+                     group,
+                     420)]]
+        self.assertRaises(RuntimeError,
+                          self.boot_menu._handle_boot_config_list,
+                          boot_config_list=boot_unsup, dry_run=False)
+        os.unlink(src_blt)
+        rmtree(bad_blt_dir)
+        rmtree(src_dir)
+
+
+class AIISOBootMenuTestCase(ISOBootMenuTestCase):
+    """ Unit test class for AIIsoBootMenu class
+    """
+
+    def test_build_custom_entries(self):
+        self.boot_menu = AIISOImageBootMenu("Test AI Boot Checkpoint")
+        self._test_build_custom_entries()
+
+    def test_update_img_info_path(self):
+        self.boot_menu = AIISOImageBootMenu("Test AI Boot Checkpoint")
+        boot_mods_dom = etree.fromstring(BOOT_MODS_XML)
+        self.doc.import_from_manifest_xml(boot_mods_dom, volatile=True)
+        self.boot_menu.parse_doc()
+        self.boot_menu.img_info_path = tempfile.mktemp(dir=TMP_TEST_DIR,
+                                                        prefix="img_info_")
+        self.boot_menu.update_img_info_path()
+        os.unlink(self.boot_menu.img_info_path)
+
+    def test_install_boot_loader(self):
+        self.boot_menu = AIISOImageBootMenu("Test AIISOBootMenu Checkpoint")
+        self._test_install_boot_loader()
+
+    def test_handle_boot_config_list(self):
+        self.boot_menu = AIISOImageBootMenu("Test AIISOBootMenu Checkpoint")
+        self._test_handle_boot_config_list()
+
+    def test_build_default_entries(self):
+        """ Default menu entry unit test for AI ISO
+        """
+        self.boot_menu = AIISOImageBootMenu("Test AIISOBootMenu Checkpoint")
+        self.boot_menu.parse_doc()
+
+        # This assignment permits convenient copy and paste from the
+        # AIISOImageBootMenu checkpoint codes ai_titles and ai_kargs values
+        self.boot_title = self.boot_menu.boot_title
+        ai_titles = [self.boot_title + " Automated Install custom",
+                     self.boot_title + " Automated Install",
+                     self.boot_title + " Automated Install custom ttya",
+                     self.boot_title + " Automated Install custom ttyb",
+                     self.boot_title + " Automated Install ttya",
+                     self.boot_title + " Automated Install ttyb"]
+        ai_kargs = ["-B aimanifest=prompt",
+                    None,
+                    "-B install=true,aimanifest=prompt,console=ttya",
+                    "-B install=true,aimanifest=prompt,console=ttyb",
+                    "-B install=true,console=ttya",
+                    "-B install=true,console=ttyb"]
+
+        self._test_build_default_entries(ai_titles, ai_kargs)
+
+
+class LiveCDISOBootMenuTestCase(ISOBootMenuTestCase):
+
+    def test_install_boot_loader(self):
+        self.boot_menu = LiveCDISOImageBootMenu("Test LiveCD Boot Checkpoint")
+        self.boot_menu.execute(dry_run=True)
+
+    def test_build_default_entries(self):
+        """ Default menu entry unit test for LiveCD ISO
+        """
+        self.boot_menu = LiveCDISOImageBootMenu("Test LiveCD Boot Checkpoint")
+        self.boot_menu.parse_doc()
+
+        # This assignment permits convenient copy and paste from the
+        # LiveCDISOImageBootMenu checkpoint codes ai_titles and ai_kargs values
+        self.boot_title = self.boot_menu.boot_title
+        lcd_titles = [self.boot_title,
+                      self.boot_title + " VESA driver",
+                      self.boot_title + " text console"]
+        lcd_kargs = [None,
+                     "-B livemode=vesa",
+                     "-B livemode=text"]
+        self._test_build_default_entries(lcd_titles, lcd_kargs)
+
+
+class TextISOBootMenuTestCase(ISOBootMenuTestCase):
+    def test_build_default_entries(self):
+        """ Default menu entry unit test for Text ISO
+        """
+        # How many boot entries the TextCD ISO defines by default. Needs to
+        # be adjusted along with expected entry contents if the defaults
+        # change.
+        self.boot_menu = TextISOImageBootMenu("Test Text Boot Checkpoint")
+        self.boot_menu.parse_doc()
+
+        titles = [self.boot_menu.boot_title]
+        kargs = [None]
+        self._test_build_default_entries(titles, kargs)
+
+if __name__ == '__main__':
+    unittest.main()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/lib/install_boot/test/test_boot_spec.py	Fri May 06 11:44:10 2011 +1000
@@ -0,0 +1,277 @@
+#!/usr/bin/python
+#
+# 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
+# or http://www.opensolaris.org/os/licensing.
+# 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
+#
+
+#
+# Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+#
+
+"""boot_spec Unit Tests"""
+
+import unittest
+
+from lxml import etree
+
+from solaris_install.boot.boot_spec import BootMods, BootEntry
+from solaris_install.engine import InstallEngine
+from solaris_install.engine.test.engine_test_utils import \
+    get_new_engine_instance, reset_engine
+
+
+indentation = '''\
+    '''
+
+
+class TestBootMods(unittest.TestCase):
+    """ Test case to test the from_xml() method of
+        BootMods
+    """
+
+    BOOT_MODS_XML = '''
+    <root>
+    <boot_mods timeout="20" title="Boot Testing"/>
+    </root>'''
+
+    BOOT_MODS_NOTITLE_XML = '''
+    <root>
+    <boot_mods timeout="20"/>
+    </root>'''
+
+    BOOT_MODS_NOTIMEOUT_XML = '''
+    <root>
+    <boot_mods title="Boot Testing"/>
+    </root>'''
+
+    def setUp(self):
+        """ Initialises a runtime environment for execution of
+            BootMods and BootMods unit tests.
+        """
+        self.engine = get_new_engine_instance()
+
+    def tearDown(self):
+        """ Cleans up after the previous unit test execution.
+        """
+        if self.engine is not None:
+            reset_engine(self.engine)
+        self.doc = None
+        self.boot_mods = None
+
+    def _run_manifest_parser(self, test_xml, expected_xml):
+        """ Support method to import manifest XML into the DOC and extract it
+            back out. Compares the two and raises and assertEqual exception if
+            they do not match.
+            Inputs:
+            - text_xml: The xml string representation imported into the DOC
+            - expected_xml: The xml representation out from the DOC
+        """
+        self.doc = InstallEngine.get_instance().data_object_cache
+        boot_mods_dom = etree.fromstring(test_xml)
+        self.doc.import_from_manifest_xml(boot_mods_dom, volatile=True)
+
+        boot_mods = self.doc.volatile.get_first_child(class_type=BootMods)
+        xml_str = boot_mods.get_xml_tree_str()
+        self.assertEqual(xml_str, expected_xml,
+            "Resulting XML doesn't match expected (len=%d != %d):\
+             \nGOT:\n'%s'\nEXPECTED:\n'%s'\n" %
+            (len(xml_str), len(expected_xml), xml_str, expected_xml))
+
+    def test_manifest_xml(self):
+        """ Tests the from_xml() and to_xml() methods of BootMods with
+            both timeout and title specfied.
+        """
+        expected_xml = '''\
+        <boot_mods title="Boot Testing" timeout="20"/>
+        '''.replace(indentation, "")
+
+        self._run_manifest_parser(self.BOOT_MODS_XML, expected_xml)
+
+    def test_manifest_notitle_xml(self):
+        """ Tests the from_xml() and to_xml() methods of BootMods with
+            no title specfied.
+        """
+        expected_xml = '''\
+        <boot_mods timeout="20"/>
+        '''.replace(indentation, "")
+        self._run_manifest_parser(self.BOOT_MODS_NOTITLE_XML, expected_xml)
+
+    def test_manifest_notimeout_xml(self):
+        """ Tests the from_xml() and to_xml() methods of BootMods with
+            no timeout specfied.
+        """
+        expected_xml = '''\
+        <boot_mods title="Boot Testing"/>
+        '''.replace(indentation, "")
+        self._run_manifest_parser(self.BOOT_MODS_NOTIMEOUT_XML, expected_xml)
+
+
+class TestBootEntry(unittest.TestCase):
+    """ Test case to test the from_xml() method of
+        BootMods
+    """
+    BOOT_ENTRY_XML = '''
+    <root>
+        <boot_entry>
+            <title_suffix>Basic Boot Entry</title_suffix>
+        </boot_entry>
+    </root>'''
+
+    BOOT_ENTRY_DEFAULT_XML = '''
+    <root>
+        <boot_entry default_entry="true">
+            <title_suffix>Default Boot Entry</title_suffix>
+        </boot_entry>
+    </root>'''
+
+    BOOT_ENTRY_NOTDEFAULT_XML = '''
+    <root>
+        <boot_entry default_entry="false">
+            <title_suffix>Non-Default Boot Entry</title_suffix>
+        </boot_entry>
+    </root>'''
+
+    BOOT_ENTRY_START_XML = '''
+    <root>
+        <boot_entry insert_at="start">
+            <title_suffix>First Boot Entry</title_suffix>
+        </boot_entry>
+    </root>'''
+
+    BOOT_ENTRY_END_XML = '''
+    <root>
+        <boot_entry insert_at="end">
+            <title_suffix>Last Boot Entry</title_suffix>
+        </boot_entry>
+    </root>'''
+
+    BOOT_ENTRY_KARGS_XML = '''
+    <root>
+        <boot_entry>
+            <title_suffix>Kernel Args Boot Entry</title_suffix>
+            <kernel_args>test_kernel_args=rhubarb</kernel_args>
+        </boot_entry>
+    </root>'''
+
+    def setUp(self):
+        """ Initialises a runtime environment for execution of
+            BootMods and BootMods unit tests.
+        """
+        self.engine = get_new_engine_instance()
+
+    def tearDown(self):
+        """ Cleans up after the previous unit test execution.
+        """
+        if self.engine is not None:
+            reset_engine(self.engine)
+        self.doc = None
+
+    def _run_manifest_parser(self, test_xml, expected_xml):
+        """ Support method to import manifest XML into the DOC and extract it
+            back out. Compares the two and raises and assertEqual exception if
+            they do not match.
+            Inputs:
+            - text_xml: The xml string representation imported into the DOC
+            - expected_xml: The xml representation out from the DOC
+        """
+        self.doc = InstallEngine.get_instance().data_object_cache
+        boot_entry_dom = etree.fromstring(test_xml)
+        self.doc.import_from_manifest_xml(boot_entry_dom, volatile=True)
+
+        boot_entry = self.doc.volatile.get_first_child(class_type=BootEntry)
+        xml_str = boot_entry.get_xml_tree_str()
+        self.assertEqual(xml_str, expected_xml,
+            "Resulting XML doesn't match expected (len=%d != %d):\
+             \nGOT:\n'%s'\nEXPECTED:\n'%s'\n" %
+            (len(xml_str), len(expected_xml), xml_str, expected_xml))
+
+    def test_manifest_basic_entry_xml(self):
+        """Tests the from_xml() and to_xml() with a basic barebones entry.
+        """
+        expected_xml = '''\
+        <boot_entry>
+        ..<title_suffix>Basic Boot Entry</title_suffix>
+        </boot_entry>
+        '''.replace(indentation, "").replace(".", " ")
+        self._run_manifest_parser(self.BOOT_ENTRY_XML, expected_xml)
+
+    def test_manifest_default_entry_xml(self):
+        """Tests the from_xml() and to_xml() methods of BootEntry with
+           default_entry = "true"
+        """
+
+        expected_xml = '''\
+        <boot_entry default_entry="true">
+        ..<title_suffix>Default Boot Entry</title_suffix>
+        </boot_entry>
+        '''.replace(indentation, "").replace(".", " ")
+        self._run_manifest_parser(self.BOOT_ENTRY_DEFAULT_XML, expected_xml)
+
+    def test_manifest_notdefault_entry_xml(self):
+        """Tests the from_xml() and to_xml() methods of BootEntry with
+           default_entry = "false"
+        """
+
+        expected_xml = '''\
+        <boot_entry default_entry="false">
+        ..<title_suffix>Non-Default Boot Entry</title_suffix>
+        </boot_entry>
+        '''.replace(indentation, "").replace(".", " ")
+        self._run_manifest_parser(self.BOOT_ENTRY_NOTDEFAULT_XML, expected_xml)
+
+    def test_manifest_start_entry_xml(self):
+        """Tests the from_xml() and to_xml() methods of BootEntry with
+           insert_at = "start"
+        """
+
+        expected_xml = '''\
+        <boot_entry insert_at="start">
+        ..<title_suffix>First Boot Entry</title_suffix>
+        </boot_entry>
+        '''.replace(indentation, "").replace(".", " ")
+        self._run_manifest_parser(self.BOOT_ENTRY_START_XML, expected_xml)
+
+    def test_manifest_end_entry_xml(self):
+        """Tests the from_xml() and to_xml() methods of BootEntry with
+           insert_at = "end"
+        """
+
+        expected_xml = '''\
+        <boot_entry insert_at="end">
+        ..<title_suffix>Last Boot Entry</title_suffix>
+        </boot_entry>
+        '''.replace(indentation, "").replace(".", " ")
+        self._run_manifest_parser(self.BOOT_ENTRY_END_XML, expected_xml)
+
+    def test_manifest_kargs_entry_xml(self):
+        """Tests the from_xml() and to_xml() methods of BootEntry with
+           kernel_args specified
+        """
+
+        expected_xml = '''\
+        <boot_entry>
+        ..<title_suffix>Kernel Args Boot Entry</title_suffix>
+        ..<kernel_args>test_kernel_args=rhubarb</kernel_args>
+        </boot_entry>
+        '''.replace(indentation, "").replace(".", " ")
+        self._run_manifest_parser(self.BOOT_ENTRY_KARGS_XML, expected_xml)
+
+
+if __name__ == '__main__':
+    unittest.main()
--- a/usr/src/lib/install_manifest/dtd/Makefile	Thu May 05 13:47:10 2011 -0600
+++ b/usr/src/lib/install_manifest/dtd/Makefile	Fri May 06 11:44:10 2011 +1000
@@ -21,7 +21,7 @@
 #
 
 #
-# Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
 #
 
 include ../../Makefile.lib
@@ -32,6 +32,7 @@
 install:=      TARGET= install
 
 DTD_FILES=	ai.dtd \
+	boot_mods.dtd \
 	dc.dtd \
 	configuration.dtd \
 	execution.dtd \
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/usr/src/lib/install_manifest/dtd/boot_mods.dtd	Fri May 06 11:44:10 2011 +1000
@@ -0,0 +1,79 @@
+<!--
+ 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
+ or http://www.opensolaris.org/os/licensing.
+ 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
+
+ Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved.
+
+-->
+
+<!--
+Boot menu modifications to be applied to the image (DC) or on the installed
+system (AI) on X86.
+
+NOTE: On SPARC, only the "title" element is supported and only then when using
+      the Automated Installer (AI), "title" will be applied to boot menu file,
+      '/boot/menu.lst'
+
+Optional attributes that can be specified here are:
+
+title   - title for the specialized boot entry
+          Default is to use the first line of /etc/release
+
+timeout
+        - boot loader timeout value before the default boot entry
+          is automatically activated. Ignored on OBP boot based
+          SPARC systems
+-->
+<!ELEMENT boot_mods (boot_entry*)>
+<!ATTLIST boot_mods title CDATA #IMPLIED>
+<!ATTLIST boot_mods timeout CDATA #IMPLIED>
+
+<!--
+Boot entries to add to the default boot menu in the image (DC) or
+on the installed system (AI) on X86 systems. 
+
+NOTE: This XML section is *not* supported on OBP boot based SPARC
+      systems and will be ignored.
+
+default_entry - If the boot_entry has this attribute set to "true" then it
+                will be the default boot entry activated by the boot loader.
+                Note that if more than one boot entry has this attribute set
+                to "true", the last entry defined as such will override
+                preceeding default boot_entry elements set to "true".
+
+insert_at     - Optional attribute indicating the desired insertion point
+                relative to the existing list of boot entries. Valid values
+                are "start" or "end" only. If omitted the default action
+                is to append the entry to the end of the list.
+-->
+<!ELEMENT boot_entry (title_suffix, kernel_args?)>
+<!ATTLIST boot_entry default_entry (true|false) "false">
+<!ATTLIST boot_entry insert_at (start|end) "end">
+
+<!--
+title_suffix - Text string appended to this specific entry's title.
+-->
+<!ELEMENT title_suffix (#PCDATA)>
+
+<!--
+kernel_args  - Optional kernel arguments passed to the kernel by the
+               boot loader.
+-->
+<!ELEMENT kernel_args (#PCDATA)>
+
--- a/usr/src/lib/install_manifest/dtd/dc.dtd	Thu May 05 13:47:10 2011 -0600
+++ b/usr/src/lib/install_manifest/dtd/dc.dtd	Fri May 06 11:44:10 2011 +1000
@@ -1,5 +1,32 @@
+<!--
+ 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
+ or http://www.opensolaris.org/os/licensing.
+ 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
+
+ Copyright (c) 2010, 2011, Oracle and/or its affiliates. All rights reserved.
+
+-->
+
 <!ELEMENT dc (distro)>
 
+<!ENTITY % boot_mods SYSTEM "boot_mods.dtd">
+%boot_mods;
+
 <!ENTITY % target SYSTEM "target.dtd">
 %target;
 
@@ -31,17 +58,8 @@
 
 <!ELEMENT img_params (media_im|vm_im)>
 
-<!ELEMENT media_im (grub_mods*, max_size?)>
-<!ELEMENT grub_mods (grub_entry*)>
-<!ATTLIST grub_mods min_mem CDATA #IMPLIED>
-<!ATTLIST grub_mods title CDATA #IMPLIED>
-<!ATTLIST grub_mods default_entry CDATA #IMPLIED>
-<!ATTLIST grub_mods timeout CDATA #IMPLIED>
+<!ELEMENT media_im (boot_mods?, max_size?)>
 
-<!ELEMENT grub_entry (title_suffix, line+)>
-<!ATTLIST grub_entry position CDATA #IMPLIED>
-<!ELEMENT title_suffix (#PCDATA)>
-<!ELEMENT line (#PCDATA)>
 
 <!--
 	max_size refers to the maximum size an image area can get,
--- a/usr/src/pkg/manifests/install-distribution-constructor.mf	Thu May 05 13:47:10 2011 -0600
+++ b/usr/src/pkg/manifests/install-distribution-constructor.mf	Fri May 06 11:44:10 2011 +1000
@@ -109,8 +109,6 @@
 file path=usr/lib/python2.6/vendor-packages/solaris_install/distro_const/checkpoints/create_iso.pyc mode=0444
 file path=usr/lib/python2.6/vendor-packages/solaris_install/distro_const/checkpoints/create_usb.py mode=0444
 file path=usr/lib/python2.6/vendor-packages/solaris_install/distro_const/checkpoints/create_usb.pyc mode=0444
-file path=usr/lib/python2.6/vendor-packages/solaris_install/distro_const/checkpoints/grub_setup.py mode=0444
-file path=usr/lib/python2.6/vendor-packages/solaris_install/distro_const/checkpoints/grub_setup.pyc mode=0444
 file path=usr/lib/python2.6/vendor-packages/solaris_install/distro_const/checkpoints/pre_pkg_img_mod.py mode=0444
 file path=usr/lib/python2.6/vendor-packages/solaris_install/distro_const/checkpoints/pre_pkg_img_mod.pyc mode=0444
 file path=usr/lib/python2.6/vendor-packages/solaris_install/distro_const/checkpoints/pkg_img_mod.py mode=0444
--- a/usr/src/pkg/manifests/system-library-install.mf	Thu May 05 13:47:10 2011 -0600
+++ b/usr/src/pkg/manifests/system-library-install.mf	Fri May 06 11:44:10 2011 +1000
@@ -35,6 +35,7 @@
 dir path=usr/lib/python2.6/vendor-packages
 dir path=usr/lib/python2.6/vendor-packages/osol_install
 dir path=usr/lib/python2.6/vendor-packages/solaris_install
+dir path=usr/lib/python2.6/vendor-packages/solaris_install/boot
 dir path=usr/lib/python2.6/vendor-packages/solaris_install/data_object
 dir path=usr/lib/python2.6/vendor-packages/solaris_install/engine
 dir path=usr/lib/python2.6/vendor-packages/solaris_install/ict
@@ -64,6 +65,12 @@
 file path=usr/lib/python2.6/vendor-packages/solaris_install/__init__.pyc
 file path=usr/lib/python2.6/vendor-packages/solaris_install/logger.py
 file path=usr/lib/python2.6/vendor-packages/solaris_install/logger.pyc
+file path=usr/lib/python2.6/vendor-packages/solaris_install/boot/__init__.py
+file path=usr/lib/python2.6/vendor-packages/solaris_install/boot/__init__.pyc
+file path=usr/lib/python2.6/vendor-packages/solaris_install/boot/boot.py
+file path=usr/lib/python2.6/vendor-packages/solaris_install/boot/boot.pyc
+file path=usr/lib/python2.6/vendor-packages/solaris_install/boot/boot_spec.py
+file path=usr/lib/python2.6/vendor-packages/solaris_install/boot/boot_spec.pyc
 file path=usr/lib/python2.6/vendor-packages/solaris_install/data_object/__init__.py
 file path=usr/lib/python2.6/vendor-packages/solaris_install/data_object/__init__.pyc
 file path=usr/lib/python2.6/vendor-packages/solaris_install/data_object/cache.py
@@ -245,6 +252,7 @@
 file path=usr/lib/python2.6/vendor-packages/bootmgmt/backend/loader/sbb.py
 file path=usr/lib/python2.6/vendor-packages/bootmgmt/backend/loader/sbb.pyc
 file path=usr/share/install/ai.dtd mode=0444 group=sys
+file path=usr/share/install/boot_mods.dtd mode=0444 group=sys
 file path=usr/share/install/configuration.dtd mode=0444 group=sys
 file path=usr/share/install/dc.dtd mode=0444 group=sys
 file path=usr/share/install/execution.dtd mode=0444 group=sys