usr/src/lib/libict_pymod/ict.py
author Jon Tibble <meths@btinternet.com>
Tue, 26 Jun 2012 14:11:05 +0100
branchoi_151a
changeset 1447 f7571d01f887
parent 1433 8c642698bcb5
permissions -rw-r--r--
Fix preferred publisher install errors

#!/usr/bin/python2.6
#
# 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) 2008, 2010, Oracle and/or its affiliates. All rights reserved.
#
'''Install Completion Tasks (ICT)

Each ICT is implemented as a method of class ict.

Guide to calling ICTs

The most efficient way to invoke ICTs is to create an ict class instance,
then to invoke the ICT methods of the ict class:
    - create an ict class instance, providing at least root target directory
    - call ICT as a method of the class instance just created
    - returned status is a tuple of exit status code and any other return
      information
    - logging is done by ICT, but you can return exit status to caller
    - logging service options are inherited from the environment
    - logging level can be set (second parameter) and overridden in environment
        LS_DBG_LVL=(1-4)
    - do not abort if an ICT fails (unless it is an untenable situation)

ICTs can also be invoked singly through a command line using function
exec_ict():

    python ict.py <ICT method> <target directory>  [<ICT-specific parameter>]
    Example command line:
        python ict.py ict_test /a  #runs test ICT

Guide to writing ICTs

- Locate ict class (line starts with "def ict")
- Within the ict class, find "end Install Completion Tasks"
- Add new ICT as method just before that
- ICTs are methods of the ict class
- ICTs have 'self' as 1st parameter - add other parameters as desired
- Create error return code(s)
- ICTs should return either status code, or a tuple with status code first
- See __init__ method for initialization of class members
- self.basedir is the root directory of the target
- for first line of ICT, use _register_task(inspect.currentframe())
    as a trace aid
- do not allow unhandled exceptions
- if a critical condition is encountered, log error and sys.exit()
    with error status
- as a last exception handler, instead of raising the exception,
    log the traceback,

    e.g.:
        except StandardError:
                prerror('Unexpected error doing something.')
                prerror(traceback.format_exc())

- use the module utility routines:
    prerror(string) - log error message
    _dbg_msg(string) - log output debugging message
    info_msg(string) - log informational message
    _cmd_out(cmd) - execute shell command, returning only exit status
    _cmd_status(cmd) - execute shell command, returning exit status and stdout

- place ICT comments just before or after def statement for module
   pydoc to generate documentation

Skeleton ICT:

from osol_install.ict import *
icto = ict('/a')
status = icto.<some ICT (class method)>(<parameters depend on ICT>)

ICT initial project tasks from Transfer Module (TM):
        Setting default keyboard layout TM
        Creating initial SMF repository TM
        Creating /etc/mnttab TM
        Cleanup unnecessary symbolic links and files from the alternate root.
        (clobber files) TM
'''
import errno
import os
import os.path
import sys

from stat import S_IREAD, \
     S_IWRITE, \
     S_IEXEC, \
     S_IRUSR, \
     S_IWUSR, \
     S_IRGRP, \
     S_IXGRP, \
     S_IROTH, \
     S_IXOTH, \
     S_ISLNK

import fcntl
import array
import struct
import shutil
import tempfile

import inspect
import filecmp
import traceback
import re
import platform
import signal
import commands

from pkg.cfgfiles import PasswordFile, UserattrFile

from osol_install.liblogsvc import LS_DBGLVL_ERR, \
LS_DBGLVL_INFO, \
init_log, \
write_dbg, \
write_log

ICTID = 'ICT'
(
ICT_INVALID_PARAMETER,
ICT_INVALID_PLATFORM,
ICT_NOT_MULTIBOOT,
ICT_ADD_FAILSAFE_MENU_FAILED,
ICT_KIOCLAYOUT_FAILED,
ICT_OPEN_KEYBOARD_DEVICE_FAILED,
ICT_KBD_LAYOUT_NAME_NOT_FOUND,
ICT_UPDATE_BOOTPROP_FAILED,
ICT_MKMENU_FAILED,
ICT_SPLASH_IMAGE_FAILURE,
ICT_REMOVE_LIVECD_COREADM_CONF_FAILURE,
ICT_SET_BOOT_ACTIVE_TEMP_FILE_FAILURE,
ICT_FDISK_FAILED,
ICT_UPDATE_DUMPADM_NODENAME_FAILED,
ICT_CONFIGURE_NWAM_FAILED,
ICT_ENABLE_NWAM_FAILED,
ICT_FIX_FAILSAFE_MENU_FAILED,
ICT_CREATE_SMF_REPO_FAILED,
ICT_CREATE_MNTTAB_FAILED,
ICT_PACKAGE_REMOVAL_FAILED,
ICT_DELETE_BOOT_PROPERTY_FAILURE,
ICT_GET_ROOTDEV_LIST_FAILED,
ICT_SETUP_DEV_NAMESPACE_FAILED,
ICT_UPDATE_ARCHIVE_FAILED,
ICT_COPY_SPLASH_XPM_FAILED,
ICT_SMF_CORRECT_SYS_PROFILE_FAILED,
ICT_REMOVE_BOOTPATH_FAILED,
ICT_ADD_SPLASH_IMAGE_FAILED,
ICT_SYSIDTOOL_ENTRIES_FAILED,
ICT_SYSIDTOOL_CP_STATE_FAILED,
ICT_SET_FLUSH_CONTENT_CACHE_ON_SUCCESS_FAILED,
ICT_FIX_GRUB_ENTRY_FAILED,
ICT_CREATE_SPARC_BOOT_MENU_FAILED,
ICT_COPY_SPARC_BOOTLST_FAILED,
ICT_CLOBBER_FILE_FAILED,
ICT_CLEANUP_FAILED,
ICT_REBUILD_PKG_INDEX_FAILED,
ICT_PKG_RESET_UUID_FAILED,
ICT_PKG_SEND_UUID_FAILED,
ICT_SET_SWAP_AS_DUMP_FAILED,
ICT_EXPLICIT_BOOTFS_FAILED,
ICT_ENABLE_HAPPY_FACE_BOOT_FAILED,
ICT_POPEN_FAILED,
ICT_REMOVE_LIVECD_ENVIRONMENT_FAILED,
ICT_SET_ROOT_PW_FAILED,
ICT_CREATE_NU_FAILED,
ICT_OPEN_PROM_DEVICE_FAILED,
ICT_IOCTL_PROM_FAILED,
ICT_SET_PART_ACTIVE_FAILED,
ICT_SVCCFG_FAILURE,
ICT_SET_AUTOHOME_FAILED,
ICT_COPY_CAPABILITY_FAILED,
ICT_APPLY_SYSCONFIG_FAILED,
ICT_GENERATE_SC_PROFILE_FAILED,
ICT_SETUP_RBAC_FAILED,
ICT_SETUP_SUDO_FAILED
) = range(200,256)

# Global variables
DEBUGLVL = LS_DBGLVL_ERR
CUR_ICT_FRAME = None  # frame info for debugging and tracing
MENU_LST_DEFAULT_TITLE = "OpenIndiana"


#Module functions - intended for local use, but usable by importers
def _register_task(fm):
    '''register current ICT for logging, debugging and tracing
    By convention, use as 1st executable line in ICT
    '''
    global CUR_ICT_FRAME

    CUR_ICT_FRAME = fm
    if CUR_ICT_FRAME != None:
        cf = inspect.getframeinfo(CUR_ICT_FRAME)
        write_log(ICTID, 'current task:' + cf[2] + '\n')


def prerror(msg):
    '''Log an error message to logging service and stderr
    '''
    msg1 = msg + "\n"
    write_dbg(ICTID, LS_DBGLVL_ERR, msg1)


def _move_in_updated_config_file(new, orig):
    '''move in new version of file to original file location,
    overwriting original
    side effect: deletes temporary file upon failure
    '''
    # if files are identical
    if os.path.exists(new) and os.path.exists(orig) and filecmp.cmp(new, orig):
        _delete_temporary_file(new)
        return True
    try:
        shutil.copyfile(new, orig)
        os.remove(new)
    except IOError:
        prerror('IO error - cannot move file ' + new + ' to ' + orig)
        prerror(traceback.format_exc())
        _delete_temporary_file(new)
        return False
    except StandardError:
        prerror('Unrecognized error - failure to move file ' + new +
                ' to ' + orig)
        prerror(traceback.format_exc())
        _delete_temporary_file(new)
        return False
    return True


def _cmd_out(cmd):
    '''execute a shell command and return output
    cmd - command to execute
    returns tuple:
            status = command exit status
            dfout = array of lines output to stdout, stderr by command
    '''
    _dbg_msg('_cmd_out: executing cmd=' + cmd)
    status = 0
    dfout = []
    '''Since Python ignores SIGPIPE, according to Python issue 1652,
    UNIX scripts in subprocesses will also ignore SIGPIPE.
    Workaround is to save original signal handler, restore default handler,
    launch script, restore original signal handler
    '''
    #save SIGPIPE signal handler
    orig_sigpipe = signal.getsignal(signal.SIGPIPE)
    #restore default signal handler for SIGPIPE
    signal.signal(signal.SIGPIPE, signal.SIG_DFL)
    try:
        fp = os.popen(cmd)
        if fp == None or fp == -1:
            return ICT_POPEN_FAILED, []
        for rline in fp:
            if rline and rline.endswith('\n'):
                rline = rline[:-1]
            dfout.append(rline)
            _dbg_msg('_cmd_out: stdout/stderr line=' + rline)
        status = fp.close()
    except StandardError:
        prerror('system error in launching shell cmd (' + cmd + ')')
        status = 1
    #restore original signal handler for SIGPIPE
    signal.signal(signal.SIGPIPE, orig_sigpipe)
    if status == None:
        status = 0
    if status != 0:
        write_log(ICTID, 'shell cmd (' + cmd + ') returned status ' +
                  str(status) + "\n")
    if DEBUGLVL >= LS_DBGLVL_INFO:
        print ICTID + ': _cmd_out status =', status, 'stdout/stderr=', dfout

    return status, dfout


def _cmd_status(cmd):
    '''execute a shell command using popen and return its exit status
    '''
    _dbg_msg('_cmd_status: executing cmd=' + cmd)
    exitstatus = None

    '''Since Python ignores SIGPIPE, according to Python issue 1652,
    UNIX scripts in subprocesses will also ignore SIGPIPE.
    Workaround is to save original signal handler, restore default handler,
    launch script, restore original signal handler
    '''
    #save SIGPIPE signal handler
    orig_sigpipe = signal.getsignal(signal.SIGPIPE)
    #restore default signal handler for SIGPIPE
    signal.signal(signal.SIGPIPE, signal.SIG_DFL)
    try:
        fp = os.popen(cmd)
        if fp == None or fp == -1:
            return ICT_POPEN_FAILED
        exitstatus = fp.close()
    except StandardError:
        prerror('unknown error in launching shell cmd (' + cmd + ')')
        prerror('Traceback:')
        prerror(traceback.format_exc())
        exitstatus = 1
    if exitstatus == None:
        exitstatus = 0
    # restore original signal handler for SIGPIPE
    signal.signal(signal.SIGPIPE, orig_sigpipe)
    _dbg_msg('_cmd_status: return exitstatus=' + str(exitstatus))
    return exitstatus


def info_msg(msg):
    '''
    send an informational message to logging service
    '''
    write_log(ICTID, msg + '\n')


#send informational debugging message to logging service, according to level
def _dbg_msg(msg):
    '''send informational debugging message to logging service,
    according to level
    '''
    if (DEBUGLVL >= LS_DBGLVL_INFO):
        write_dbg(ICTID, LS_DBGLVL_INFO, msg + '\n')


def _delete_temporary_file(filename):
    '''
    delete temporary file - suppress traceback on error
    '''
    try:
        os.unlink(filename)
    except StandardError:
        pass # ignore failure to delete temp file


class ICT(object):
    '''main class to support ICT
    ICT object must first be created and initialized
        basedir - root directory (only required parameter)
        debuglvl - debugging message level for liblogsvc
        bootenvrc - normal location of bootenv.rc
        autohome - normal location of autohome map
        loc_grubmenu - normal location of GRUB menu
        ai_sc_profile - SC profile generated by Automated Installer
        target_sc_profile = target SC profile
        sudoers - normal location of sudo configuration file


    class initializer will exit with error status if:
        - basedir is missing or empty
        - basedir is '/', in order to protect against accidental usage.
          For a live system this should be permitted.

    '''
    def __init__(self, basedir,
        debuglvl=-1,
        bootenvrc='/boot/solaris/bootenv.rc',
        autohome='/etc/auto_home',
        loc_grubmenu='/boot/grub/menu.lst',
        ai_sc_profile='/tmp/sc_manifest.xml',
        target_sc_profile='sc_profile.xml',
        sudoers='/etc/sudoers'):

        # determine whether we are doing AI install or slim install
        self.livecd_install = False
        self.auto_install = False
        self.text_install = False
        if os.access("/.livecd", os.R_OK):
            _dbg_msg('Determined to be doing Live CD install')
            self.livecd_install = True
        elif os.access("/.autoinstall", os.R_OK):
            _dbg_msg('Determined to be doing Automated Install')
            self.auto_install = True
        elif os.access("/.textinstall", os.R_OK):
            _dbg_msg("Determined to be doing Text Install")
            self.text_install = True

        if basedir == '':
            err_str = 'Base directory must be passed'
            prerror(err_str)
            raise ValueError(err_str)
        if basedir == '/':
            '''
            The code can be run on a live system but if we're not
            on a live system we should not support / for BASEDIR.
            '''
            if self.livecd_install or self.auto_install or self.text_install:
                err_str = 'Base directory cannot be root ' + \
                    '("/") during install'
                prerror(err_str)
                raise ValueError(err_str)

        self.basedir = basedir
        '''
        If we're running outside of an install we should not use
        the basedir here since that could be the mountpoint of a
        pool that we're creating the menu.lst file on.
        '''
        if self.livecd_install or self.auto_install or self.text_install:
            self.prependdir = basedir
        else:
            self.prependdir = ""

        self.bootenvrc = self.prependdir + bootenvrc

        #Is the current platform a SPARC system?
        self.is_sparc = (platform.platform().find('sparc') >= 0)

        global DEBUGLVL
        try:
            DEBUGLVL = int(os.getenv('LS_DBG_LVL', -1))
        except StandardError:
            prerror('Could not parse enviroment variable LS_DBG_LVL to ' +
                    'integer')
            DEBUGLVL = -1

        if DEBUGLVL == -1:
            DEBUGLVL = debuglvl
        if DEBUGLVL == -1:
            DEBUGLVL = LS_DBGLVL_ERR #default logging
        else:
            if DEBUGLVL != LS_DBGLVL_ERR and \
                init_log(DEBUGLVL) != 1: #set in logging service
                prerror('Setting logging service debug level to ' +
                    str(DEBUGLVL) + ' failed.')

        self.kbd_device = '/dev/kbd'
        self.kbd_layout_file = '/usr/share/lib/keytables/type_6/kbd_layouts'
        self.keyboard_layout = ''

        # determine whether we are installing to an iSCSI boot target
        self.iscsi_boot_install = False
        if os.access("/.iscsi_boot", os.R_OK):
            _dbg_msg('Determined to be doing iSCSI boot install')
            self.iscsi_boot_install = True

        #take root poolname from mnttab
        # Note there are TABs in the blow expression.
        # cmd = 'grep "^[^<TAB>]*<TAB>' + basedir +<TAB>' " /etc/mnttab | ' + \
        cmd = 'grep "^[^	]*	' + basedir + '	" /etc/mnttab | ' + \
              ' nawk \'{print $1}\' | sed \'s,/.*,,\''
        sts, rpa = _cmd_out(cmd)

        if len(rpa) == 0:
            prerror('Cannot determine root pool name. exit status=' +
                    str(sts) + ' command=' + cmd)
            sys.exit(ICT_GET_ROOTDEV_LIST_FAILED)
        self.rootpool = rpa[0]
        _dbg_msg('Root pool name discovered: ' + self.rootpool)

        if self.livecd_install or self.auto_install or self.text_install:
            #With the root pool pre-pended to /boot/grub/menu.lst
            self.grubmenu = '/' + self.rootpool + loc_grubmenu
            self.bootmenu_path_sparc = '/' + self.rootpool + '/boot'
        else:
            #With the basedir pre-pended to /boot/grub/menu.lst
            self.grubmenu = basedir + loc_grubmenu
            #With the basedir pre-pended for the SPARC boot menu
            self.bootmenu_path_sparc = basedir + '/boot'

        #/boot/menu.lst
        self.bootmenu_sparc = self.bootmenu_path_sparc + '/menu.lst'

        self.autohome = basedir + autohome
        self.sudoers = basedir + sudoers

        # System Configuration template used to assemble System Configuration
        # profile
        self.sc_template = '/usr/share/install/sc_template.xml'
        # path to System Configuration profile generated by Automated Installer
        self.ai_sc_profile = ai_sc_profile
        # name of target System Configuration profile
        self.sc_profile = target_sc_profile

    #support methods
    def _get_bootprop(self, property_id):
        '''support method - get property from bootenv.rc
        Parameter: property_id - bootenv.rc property ID
        The format of a line in bootenvrc is:
        # followed by a comment
            or
        setprop <prop name> <prop value>

        returns property value or '' if not found
        '''
        fp = open(self.bootenvrc)
        for rline in fp:
            # Ignore comment lines
            if rline.startswith('#'):
                continue
            try:
                # Store the property name in field
                # and the property value in value.
                (field, value) = rline.split()[1:3]
            except StandardError:
                continue
            if field == property_id:
                fp.close()
                return value
        fp.close()
        return ''

    def _delete_bootprop(self, property_id):
        '''support method  - from bootenv.rc, delete property
        Parameter: property_id - bootenv.rc property ID
        return 0 for success, otherwise ICT failure status code
        '''
        new_rc = self.bootenvrc + '.new'
        try:
            fp = open(self.bootenvrc)
            op = open(new_rc, 'w')
            for rline in op:
                if rline.startswith('#'):
                    op.write(rline)
                    continue
                try:
                    # Assign to field the token between seperator 1 and 2,
                    # this being the second token.
                    field = rline.split()[1:2]
                except ValueError:
                    op.write(rline)
                    continue
                if field == property_id:
                    continue
                op.write(rline)
            fp.close()
            op.close()
            os.rename(new_rc, self.bootenvrc)
        except OSError, (errno, strerror):
            prerror('Error in deleting property in ' + self.bootenvrc +
                    ': ' + strerror)
            prerror('Failure. Returning: ICT_DELETE_BOOT_PROPERTY_FAILURE')
            return ICT_DELETE_BOOT_PROPERTY_FAILURE
        except StandardError:
            prerror('Unexpected error when deleting property in ' +
                    self.bootenvrc)
            prerror(traceback.format_exc()) #traceback to stdout and log
            prerror('Failure. Returning: ICT_DELETE_BOOT_PROPERTY_FAILURE')
            return ICT_DELETE_BOOT_PROPERTY_FAILURE
        return 0

    def _update_bootprop(self, property_id, newvalue):
        '''support method - set bootenv.rc property_id to value
        Parameters:
                property_id - bootenv.rc property ID
                newvalue - value to assign to property_id
        return 0 for success or ICT status code
        '''
        new_rc = self.bootenvrc + '.new'
        return_status = 0
        fp = op = None
        try:
            fp = open(self.bootenvrc)
            op = open(new_rc, 'w')
            #copy all lines that do not contain the property_id
            for rline in fp:
                if rline.startswith('#'):
                    op.write(rline)
                    continue
                try:
                    # Assign to field the token between seperator 1 and 2,
                    # this being the second token.
                    field = rline.split()[1:2]
                except ValueError:
                    op.write(rline) #just copy
                    continue
                if field != property_id:
                    op.write(rline)

            #add the line with the updated property_id
            op.write('setprop ' + property_id + ' ' + newvalue + '\n')
            os.rename(new_rc, self.bootenvrc)
        except OSError, (errno, strerror):
            prerror('Update boot property failed. ' + strerror + ' file=' +
                    self.bootenvrc + ' property=' + property_id +
                    ' value=' + newvalue)
            prerror('Failure. Returning: ICT_UPDATE_BOOTPROP_FAILED')
            return_status = ICT_UPDATE_BOOTPROP_FAILED
        except StandardError:
            prerror('Unexpected error when updating boot property. file=' +
                    self.bootenvrc +
                    ' property=' + property_id + ' value=' + newvalue)
            prerror(traceback.format_exc()) #traceback to stdout and log
            prerror('Failure. Returning: ICT_UPDATE_BOOTPROP_FAILED')
            return_status = ICT_UPDATE_BOOTPROP_FAILED
        if fp != None:
            fp.close()
        if op != None:
            op.close()
        return return_status

    @staticmethod
    def set_boot_active(raw_slice):
        '''support method - set boot device as active in fdisk disk formatter
        Parameter: raw_slice is a /dev path
        launches fdisk -F <format file> /dev/rdsk/cXtXdXpX
         on partitions if changes required

        return 0 upon success, error code otherwise
        '''
        _register_task(inspect.currentframe())
        mtch = re.findall('p(\d+):boot$', raw_slice)
        if mtch and mtch[0]:
            p0 = raw_slice.replace('p' + mtch[0] + ':boot', 'p0')
        else:
            mtch = re.findall('s(\d+)$', raw_slice)
            if mtch and mtch[0]:
                p0 = raw_slice.replace('s' + mtch[0], 'p0')
            else:
                p0 = raw_slice

        # Note there are TABs in the blow expression.
        # cmd = 'fdisk -W - %s | grep -v \* | grep -v \'^[<TAB> ]*$\'' % (p0)
        cmd = 'fdisk -W - %s | grep -v \* | grep -v \'^[	 ]*$\'' % (p0)
        status, fdisk = _cmd_out(cmd)
        if status != 0:
            prerror('fdisk command fails to set ' + raw_slice + 
                    ' active. exit status=' + str(status))
            prerror('command was ' + cmd)
            prerror('Failure. Returning: ICT_FDISK_FAILED')
            return ICT_FDISK_FAILED
        # make sure there is a Solaris partition before doing anything
        has_solaris_systid = has_solaris_2_systid = False
        for ln in fdisk:
            if ln[0] == '*':
                continue
            cols = ln.split()
            if len(cols) < 2:
                continue
            if not has_solaris_systid:
                has_solaris_systid = (cols[0] == '130')
            if not has_solaris_2_systid:
                has_solaris_2_systid = (cols[0] == '191')
        if not has_solaris_systid and not has_solaris_2_systid:
            return 0 # no changes
        fdiskout = []
        made_fdisk_changes = False
        dont_change_active = False
        partition_number = 1
        for ln in fdisk:
            if ln[0] == '*':
                continue
            cols = ln.split()
            if len(cols) < 2:
                continue
            if has_solaris_2_systid:
                if cols[0] == '191':
                    #don't change active partiton if installing to logical
                    if partition_number > 4:
                        dont_change_active = True
                    if cols[1] != '128':
                        cols[1] = '128' #active partition
                        made_fdisk_changes = True
                else:
                    if cols[1] != '0':
                        cols[1] = '0'
                        made_fdisk_changes = True
            else: #systid Linux swap
                if cols[0] == '130':
                    if cols[1] != '128':
                        cols[1] = '128' #active partition
                        made_fdisk_changes = True
                else:
                    if cols[1] != '0':
                        cols[1] = '0'
                        made_fdisk_changes = True
            lnout = '  '
            for lno in cols:
                lnout += lno.ljust(6) + ' '
            lnout += '\n'
            fdiskout.append(lnout)
            partition_number = partition_number + 1
        if dont_change_active:
            _dbg_msg('Install partition is logical partition.'
                     ' Active partition not changed.')
            return 0
        if not made_fdisk_changes:
            _dbg_msg('No disk format changes - fdisk not run')
            return 0
        _dbg_msg('Disk format changes needed - fdisk will be run.')
        #write fdisk format to temporary file
        try:
            (fop, fdisk_tempfile) = tempfile.mkstemp('.txt', 'fdisk', '/tmp')
            for ln in fdiskout:
                os.write(fop, ln)
            os.close(fop)
        except OSError, (errno, strerror):
            prerror('Error in writing to temporary file. ' + strerror)
            prerror('Failure. Returning: ' +
                    'ICT_SET_BOOT_ACTIVE_TEMP_FILE_FAILURE')
            return ICT_SET_BOOT_ACTIVE_TEMP_FILE_FAILURE
        except StandardError:
            prerror('Unexpected error in writing to temporary file. ' +
                    strerror)
            prerror(traceback.format_exc()) #traceback to stdout and log
            prerror('Failure. Returning: ' +
                    'ICT_SET_BOOT_ACTIVE_TEMP_FILE_FAILURE')
            return ICT_SET_BOOT_ACTIVE_TEMP_FILE_FAILURE
        cmd = 'fdisk -F %s %s 2>&1' % (fdisk_tempfile, p0)
        status = _cmd_status(cmd)
        #delete temporary file
        try:
            os.unlink(fdisk_tempfile)
        except OSError:
            pass # ignore failure to delete temporary file
        if status != 0:
            prerror('Error executing ' + cmd + '. exit status=' + str(status))
            prerror('Failure. Returning: ICT_FDISK_FAILED')
            return ICT_FDISK_FAILED
        return 0

    def _get_osconsole(self):
        '''support method - determine console device
        returns console device found in bootenv.rc, from prtconf command,
        or default 'text'
        
        If osconsole is not set (initial/flash install), we set it here based
        on what the current console device is.
        '''
        osconsole = self._get_bootprop('output-device')
        if osconsole != '':
            return osconsole

        # get console setting from prtconf command - console
        cmd = 'prtconf -v /devices | ' + \
            'sed -n \'/console/{n;p;}\' | cut -f 2 -d \\\''
        sts, co = _cmd_out(cmd)
        if sts != 0:
            prerror('Error from command to get console. exit status=' +
                    str(sts))
            prerror('Command in error=' + cmd)
        if len(co) > 0:
            osconsole = co[0]
        if osconsole == '':
            # get console setting from prtconf command - output-device
            cmd = 'prtconf -v /devices | ' + \
                'sed -n \'/output-device/{n;p;}\' | cut -f 2 -d \\\''
            sts, co = _cmd_out(cmd)
            if sts != 0:
                prerror('Error from command to get console. exit status=' +
                        str(sts))
                prerror('Command in error=' + cmd)
            if len(co) > 0:
                osconsole = co[0]
            if osconsole == 'screen':
                osconsole = 'text'
        # default console to text
        if osconsole == '':
            osconsole = 'text'
        return osconsole

    def get_rootdev_list(self, dev_path):
        '''ICT and support method - get list of disks with zpools
        associated with root pool launch zpool iostat -v + rootpool

        dev_path is a Solaris /dev disk directory path
        (e.g. /dev/dsk or /dev/rdsk)
        return tuple:
                status - 0 for success, error code otherwise
                device list - list of device names: <dev_path>/cXtXdXsX,
                empty list if failure
        '''
        _register_task(inspect.currentframe())
        cmd = 'zpool iostat -v ' + self.rootpool
        sts, zpool_iostat = _cmd_out(cmd)
        if sts != 0:
            prerror('Error from command to get rootdev list. exit status=' +
                    str(sts))
            prerror('Command in error=' + cmd)
            prerror('Failure. Returning: ICT_GET_ROOTDEV_LIST_FAILED')
            return ICT_GET_ROOTDEV_LIST_FAILED, []
        i = 0
        rootdevlist = []
        while i < len(zpool_iostat):
            p1 = zpool_iostat[i].split()
            if len(p1) > 1 and p1[0] == self.rootpool:
                i += 1
                while i < len(zpool_iostat):
                    if len(zpool_iostat[i]) > 1 and zpool_iostat[i][0] == ' ':
                        la = zpool_iostat[i].split()
                        if len(la) > 1 and la[0] != 'mirror' and \
                            la[0][0] != '-':
                            rootdevlist.append(dev_path + la[0])
                    i += 1
            i += 1
        return 0, rootdevlist

    def _get_kbd_layout_name(self, layout_number):
        '''support method - given a keyboard layout number return the
        layout string.

        parameter layout_number - keyboard layout number from:
                /usr/share/lib/keytables/type_6/kbd_layouts
        We should not be doing this here, but unfortunately there
        is no interface in the keyboard API to perform
        this mapping for us - RFE.'''
        try:
            fh = open(self.kbd_layout_file, "r")
        except StandardError:
            prerror('keyboard layout file open failure: filename=' +
                self.kbd_layout_file)
            return ''
        kbd_layout_name = ''
        for line in fh: #read file until number matches
            if line.startswith('#'):
                continue
            if '=' not in line:
                continue
            (kbd_layout_name, num) = line.split('=')
            if int(num) == layout_number:
                fh.close()
                return kbd_layout_name
        fh.close()
        return ''

    def bootadm_update_menu(self, rdsk):
        '''ICT and support method - add failsafe menu entry for disk
        parameter rdsk - raw disk device name in ctds format:
        /dev/rdsk/cXtXdXsX
        Does bootadm update-menu -R basedir -Z -o <raw disk>
        returns 0 if command succeeded, error code otherwise
        '''
        _register_task(inspect.currentframe())
        cmd = 'bootadm update-menu -R %s -Z -o %s 2>&1' % (self.basedir, rdsk)
        info_msg('update GRUB boot menu on device ' + rdsk)
        _dbg_msg('editing GRUB menu: ' + cmd)
        status, cmdout = _cmd_out(cmd)
        if status != 0:
            prerror('Adding failsafe menu with command: %s failed. ' +
                    ' exit status=%d' % (cmd, status))
            for ln in cmdout:
                prerror('bootadm_update_menu output: ' + ln)
            prerror('Failure. Returning: ICT_ADD_FAILSAFE_MENU_FAILED')
            return ICT_ADD_FAILSAFE_MENU_FAILED
        for ln in cmdout:
            info_msg('bootadm_update_menu output: ' + ln)
        return 0

    @staticmethod
    def _get_root_dataset():
        '''support routine - using beadm list, get the root dataset of
        the root pool
        return root dataset active on reboot or '' if not found
        log error if not found
        '''
        _register_task(inspect.currentframe())
        cmd = 'beadm list -aH'
        status, belist = _cmd_out(cmd)
        if status != 0:
            prerror('BE list command %s failed. Exit status=%d' %
                    (cmd, status))
            for msg in belist:
                prerror(msg)
            return ''
        for ln in belist:
            arg = ln.split(';') #parse datasets
            if not arg[2]:
                continue
            if arg[2].find('R') != -1: #check if active on reboot
                _dbg_msg('found root dataset %s ' % arg[1])
                return arg[1]
        return ''

    #end support routines

    #Install Completion Tasks start here

    def remove_bootpath(self):
        '''ICT - no bootpath needed for zfs boot - remove property
        from bootenv.rc
        blatant hack:  _setup_bootblock should be fixed
        in the spmisvc library to not put bootpath in bootenv.rc
        in the first place for zfs boot
        returns 0 for success, error code otherwise
        '''
        _register_task(inspect.currentframe())
        #This ICT is not supported on SPARC platforms.
        #If invoked on a SPARC platform return ICT_INVALID_PLATFORM
        if self.is_sparc:
            prerror('This ICT is not supported on this hardware platform.')
            prerror('Failure. Returning: ICT_INVALID_PLATFORM')
            return ICT_INVALID_PLATFORM

        newbootenvrc = self.bootenvrc + '.tmp'
        bootpath = self._get_bootprop('bootpath')
        if bootpath != '':
            # Note there are TABs in the blow expression.
            #...'sed \'/^setprop[ <TAB>][ <TAB>]*' +
            #... 'bootpath[ <TAB>]/d\' ' +
            status = _cmd_status('sed \'/^setprop[ 	][ 	]*' +
                                 'bootpath[ 	]/d\' ' +
                                 self.bootenvrc + ' > ' +
                                 self.bootenvrc + '.tmp')
            if status != 0:
                prerror('bootpath not removed from bootenv.rc - ' +
                        ' exit status=' + str(status))
                prerror('Failure. Returning: ICT_REMOVE_BOOTPATH_FAILED')
                return ICT_REMOVE_BOOTPATH_FAILED
            if not _move_in_updated_config_file(newbootenvrc, self.bootenvrc):
                prerror('bootpath not removed from bootenv.rc')
                prerror('Failure. Returning: ICT_REMOVE_BOOTPATH_FAILED')
                return ICT_REMOVE_BOOTPATH_FAILED
        _dbg_msg('bootpath property removed from ' + self.bootenvrc)
        return 0

    def get_keyboard_layout(self):
        '''Get keyboard layout using ioctl KIOCLAYOUT on /dev/kbd
        return 0 for success, otherwise error code
        '''
        #ioctl codes taken from /usr/include/sys/kbio.h
        kioc = ord('k') << 8
        kioclayout = kioc | 20
        _dbg_msg("Opening keyboard device: " + self.kbd_device)
        try:
            kbd = open(self.kbd_device, "r+")
        except StandardError:
            prerror('Failure to open keyboard device ' + self.kbd_device)
            prerror('Failure. Returning: ICT_OPEN_KEYBOARD_DEVICE_FAILED')
            return ICT_OPEN_KEYBOARD_DEVICE_FAILED
        if kbd == None:
            prerror('Failure to open keyboard device ' + self.kbd_device)
            prerror('Failure. Returning: ICT_OPEN_KEYBOARD_DEVICE_FAILED')
            return ICT_OPEN_KEYBOARD_DEVICE_FAILED

        k = array.array('i', [0])
        
        try:
            status = fcntl.ioctl(kbd, kioclayout, k, 1)
        except IOError as err:
            status = err.errno
            if status == errno.EINVAL:
                kbd.close()
                info_msg("Failed to read keyboard device ioctl; Ignoring")
                return 0
        except StandardError:
            status = 1
        
        if status != 0:
            kbd.close()
            prerror('fcntl ioctl KIOCLAYOUT_FAILED: status=' + str(status))
            prerror('Failure. Returning: ICT_KIOCLAYOUT_FAILED')
            return ICT_KIOCLAYOUT_FAILED
        kbd_layout = k.tolist()[0]
        kbd.close()

        self.keyboard_layout = self._get_kbd_layout_name(kbd_layout)

        return 0

    def generate_sc_profile(self):
        ''' ICT - Assemble System Configuration (SC) profile

        Configured parameters:
          * keyboard layout - profile will configure keymap/layout SMF property
            of svc:/system/keymap:default SMF service.
        
        Following approach is taken:
         * Take template profile
         * Set value of keymap/layout SMF property to desired value (it is
           configured as 'US-English' in template
         * Store SMF profile into profile directory
           (/etc/svc/profile/)

        return 0 for success, ICT_GENERATE_SC_PROFILE_FAILED in case of failure
        '''

        _register_task(inspect.currentframe())

        sc_profile_src = self.basedir + self.sc_template
        sc_profile_dst = self.basedir + '/etc/svc/profile/' + \
                         self.sc_profile

        # Obtain desired keyboard layout.
        if self.get_keyboard_layout() != 0:
            prerror('get_keyboard_layout() failed, Returning: '
                    'ICT_GENERATE_SC_PROFILE_FAILED')
            return ICT_GENERATE_SC_PROFILE_FAILED

        #
        # If keyboard layout has not been identified,
        # go with default setting (US-English)
        #
        if self.keyboard_layout == '':
            info_msg('Keyboard layout has not been identified')
            info_msg('It will be configured to US-English.')
            return 0

        info_msg('Detected ' + self.keyboard_layout + ' keyboard layout')
        status = _cmd_status('/usr/bin/sed s/US-English/' + \
                             self.keyboard_layout + '/ ' + \
                             sc_profile_src + ' > ' + sc_profile_dst)
        if status != 0:
            try:
                os.unlink(sc_profile_dst)
            except OSError:
                pass
            prerror('Failure. Returning: ICT_GENERATE_SC_PROFILE_FAILED')
            return ICT_GENERATE_SC_PROFILE_FAILED

        info_msg('Created System Configuration profile ' + sc_profile_dst)

        return 0

    def delete_misc_trees(self):
        '''ICT - delete miscellanous directory trees used as work areas
        during installation
        always return success
        '''
        _register_task(inspect.currentframe())
        _cmd_status('rm -rf ' + self.basedir + '/var/tmp/*')
        _cmd_status('rm -rf ' + self.basedir + '/mnt/*')
        return 0

    def create_smf_repository(self):
        '''ICT - copies /lib/svc/seed/global.db to /etc/svc/repository.db
        returns 0 for success, otherwise error code
        '''
        _register_task(inspect.currentframe())
        src = self.basedir + '/lib/svc/seed/global.db'
        dst = self.basedir + '/etc/svc/repository.db'
        try:
            shutil.copyfile(src, dst)
            os.chmod(dst, S_IRUSR | S_IWUSR)
            os.chown(dst, 0, 3) # chown root:sys
        except OSError, (errno, strerror):
            prerror('Cannot create smf repository due to error in copying ' +
                    src + ' to ' + dst + ': ' + strerror)
            prerror('Failure. Returning: ICT_CREATE_SMF_REPO_FAILED')
            return ICT_CREATE_SMF_REPO_FAILED
        except StandardError:
            prerror('Unrecognized error - cannot create smf repository. ' +
                    'source=' + src + ' destination=' + dst)
            prerror(traceback.format_exc()) #traceback to stdout and log
            prerror('Failure. Returning: ICT_CREATE_SMF_REPO_FAILED')
            return ICT_CREATE_SMF_REPO_FAILED
        return 0

    def create_mnttab(self):
        '''ICT - create /etc/mnttab if it doesn't already exist and chmod
        returns 0 for success, otherwise error code
        '''
        _register_task(inspect.currentframe())
        mnttab = self.basedir + '/etc/mnttab'
        try:
            open(mnttab, 'w').close() # equivalent to touch(1)
            os.chmod(mnttab, S_IREAD | S_IRGRP | S_IROTH)
        except OSError, (errno, strerror):
            prerror('Cannot create ' + mnttab + ': ' + strerror)
            prerror('Failure. Returning: ICT_CREATE_MNTTAB_FAILED')
            return ICT_CREATE_MNTTAB_FAILED
        except StandardError:
            prerror('Unrecognized error - Cannot create ' + mnttab)
            prerror(traceback.format_exc()) #traceback to stdout and log
            prerror('Failure. Returning: ICT_CREATE_MNTTAB_FAILED')
            return ICT_CREATE_MNTTAB_FAILED
        return 0

    def add_splash_image_to_grub_menu(self):
        '''ICT - append splashimage and timeout commands to GRUB menu
        If console is redirected to serial line, don't enable GRUB
        splash screen.
        return 0 for success, otherwise error code
        '''
        _register_task(inspect.currentframe())
        #This ICT is not supported on SPARC platforms.
        #If invoked on a SPARC platform return ICT_INVALID_PLATFORM
        if self.is_sparc:
            prerror('This ICT is not supported on this hardware platform.')
            prerror('Failure. Returning: ICT_INVALID_PLATFORM')
            return ICT_INVALID_PLATFORM

        grubmenu = self.grubmenu
        try:
            fp = open(self.grubmenu, 'a+')
            if ((self._get_osconsole() == 'text') or
                (self._get_osconsole() == 'graphics')):

                fp.write('splashimage /boot/grub/splash.xpm.gz\n')
                fp.write('foreground 343434\n')
                fp.write('background F7FbFF\n')
                fp.write('default 0\n')
            else:
                info_msg('Console on serial line, GRUB splash image will ' +
                         'be disabled')

            fp.write('timeout 30\n')
            fp.close()
        except OSError, (errno, strerror):
            prerror('Error in appending splash image grub commands to ' +
                    grubmenu + ': ' + strerror)
            prerror('Failure. Returning: ICT_ADD_SPLASH_IMAGE_FAILED')
            return ICT_ADD_SPLASH_IMAGE_FAILED
        except StandardError:
            prerror('Unrecognized error in appending splash image grub ' +
                    'commands to ' + grubmenu)
            prerror(traceback.format_exc()) #traceback to stdout and log
            prerror('Failure. Returning: ICT_ADD_SPLASH_IMAGE_FAILED')
            return ICT_ADD_SPLASH_IMAGE_FAILED
        return 0

    def update_dumpadm_nodename(self):
        '''ICT - Update nodename in dumpadm.conf
        Note: This is just temporary solution, as dumpadm(1M) -r option\
        does not work.
        This issue is tracked by Bugster CR 6835106. Once this bug is fixed,
        dumpadm(1M) -r should be used for manipulating /etc/dumpadm.conf
        instead.

        returns 0 for success, error code otherwise
        '''
        _register_task(inspect.currentframe())
        nodename = self.basedir + '/etc/nodename'
        dumpadmfile = '/etc/dumpadm.conf'
        dumpadmfile_dest = self.basedir + dumpadmfile
        try:
            fnode = open(nodename, 'r')
            na = fnode.readlines()
            fnode.close()
        except OSError, (errno, strerror):
            prerror('Error in accessing ' + nodename + ': ' + strerror)
            prerror('Failure. Returning: ICT_UPDATE_DUMPADM_NODENAME_FAILED')
            return ICT_UPDATE_DUMPADM_NODENAME_FAILED
        except StandardError:
            prerror('Unrecognized error in accessing ' + nodename)
            prerror(traceback.format_exc()) #traceback to stdout and log
            prerror('Failure. Returning: ICT_UPDATE_DUMPADM_NODENAME_FAILED')
            return ICT_UPDATE_DUMPADM_NODENAME_FAILED
        nodename = na[0][:-1]

        status = _cmd_status('cat ' + dumpadmfile + ' | ' +
            'sed s/openindiana/' + nodename + '/ > ' + dumpadmfile_dest)
        if status != 0:
            try:
                os.unlink(dumpadmfile_dest)
            except OSError:
                pass
            prerror('Failure. Returning: ICT_UPDATE_DUMPADM_NODENAME_FAILED')
            return ICT_UPDATE_DUMPADM_NODENAME_FAILED

        return 0

    def explicit_bootfs(self):
        '''ICT - For libbe to be able to support the initial boot environment,
        we need an explicit bootfs value in our menu entry.  Add it
        to the entry before the ZFS-BOOTFS line.  This, along with the
        rest of the grub menu entry manipulation code in this file, will
        eventually need to get ripped out when we have support in libbe
        to create and activate the grub entry for the initial boot
        environment.
        returns 0 for success, error code otherwise
        '''
        _register_task(inspect.currentframe())
        #This ICT is not supported on SPARC platforms.
        #If invoked on a SPARC platform return ICT_INVALID_PLATFORM
        if self.is_sparc:
            prerror('This ICT is not supported on this hardware platform.')
            prerror('Failure. Returning: ICT_INVALID_PLATFORM')
            return ICT_INVALID_PLATFORM

        rootdataset = self._get_root_dataset()
        if rootdataset == '':
            prerror('Could not determine root dataset')
            prerror('Failure. Returning: ICT_EXPLICIT_BOOTFS_FAILED')
            return ICT_EXPLICIT_BOOTFS_FAILED
        newgrubmenu = self.grubmenu + '.new'

        # Note there are TABs in the blow expression.
        # sedcmd = 'sed \'/\-B[ <TAB>]*\\$ZFS-BOOTFS/ i\\\nbootfs ' +\
        sedcmd = 'sed \'/\-B[ 	]*\\$ZFS-BOOTFS/ i\\\nbootfs ' +\
            rootdataset + '\\\n\' ' + self.grubmenu + ' > ' + newgrubmenu
        status = _cmd_status(sedcmd)
        if status != 0:
            prerror('Adding bootfs command to grub menu fails. ' +
                    'exit status=' + int(status))
            prerror('Failure. Returning: ICT_EXPLICIT_BOOTFS_FAILED')
            return ICT_EXPLICIT_BOOTFS_FAILED
        try:
            shutil.copyfile(newgrubmenu, self.grubmenu)
            os.remove(newgrubmenu)
        except OSError, (errno, strerror):
            prerror('Moving GRUB menu ' + newgrubmenu + ' to ' +
                    self.grubmenu + ' failed. ' + strerror)
            prerror('Failure. Returning: ICT_EXPLICIT_BOOTFS_FAILED')
            return ICT_EXPLICIT_BOOTFS_FAILED
        except StandardError:
            prerror('Unrecognized error - cannot move GRUB menu ' +
                    newgrubmenu + ' to ' + self.grubmenu)
            prerror(traceback.format_exc())
            prerror('Failure. Returning: ICT_EXPLICIT_BOOTFS_FAILED')
            return ICT_EXPLICIT_BOOTFS_FAILED
        return 0

    def enable_happy_face_boot(self):
        '''ICT - Enable happy face boot
        Enable graphical Happy Face boot for the entries in the menu.lst file.
        If console is redirected to serial line, don't enable happy face boot.

        To enable Happy Face boot:
        above the ZFS-BOOTFS line add:
                splashimage /boot/solaris.xpm
                foreground FF0000
                background A8A8A8
        and to the end of the kernel line add: console=graphics

        returns 0 for success, error code otherwise
        '''
        _register_task(inspect.currentframe())
        #This ICT is not supported on SPARC platforms.
        #If invoked on a SPARC platform return ICT_INVALID_PLATFORM
        if self.is_sparc:
            prerror('This ICT is not supported on this hardware platform.')
            prerror('Failure. Returning: ICT_INVALID_PLATFORM')
            return ICT_INVALID_PLATFORM

        if self._get_osconsole() != 'text':
            info_msg('Console on serial line, happy face boot will' +
                     ' be disabled')
            return 0

        happy_face_splash = 'splashimage /boot/solaris.xpm'
        happy_face_foreground = 'foreground FF0000'
        happy_face_background = 'background A8A8A8'

        newgrubmenu = self.grubmenu + '.new'

        sedcmd = 'sed -e \'/^kernel.*\\$ZFS-BOOTFS/ i\\\n' +\
            happy_face_splash + '\\\n' +\
            happy_face_foreground + '\\\n' + happy_face_background +\
            '\' -e \'s/\\$ZFS-BOOTFS/\\$ZFS-BOOTFS,console=graphics/\' ' +\
            self.grubmenu + ' > ' + newgrubmenu
        status = _cmd_status(sedcmd)
        if status != 0:
            prerror('Adding happy face support to grub menu fails. ' +
                    'exit status=' + int(status))
            prerror('Failure. Returning: ICT_ENABLE_HAPPY_FACE_BOOT_FAILED')
            return ICT_ENABLE_HAPPY_FACE_BOOT_FAILED
        try:
            shutil.copyfile(newgrubmenu, self.grubmenu)
            os.remove(newgrubmenu)
        except OSError, (errno, strerror):
            prerror('Moving GRUB menu ' + newgrubmenu + ' to ' +
                self.grubmenu + ' failed. ' + strerror)
            prerror('Failure. Returning: ICT_ENABLE_HAPPY_FACE_BOOT_FAILED')
            return ICT_ENABLE_HAPPY_FACE_BOOT_FAILED
        except StandardError:
            prerror('Unrecognized error - cannot move GRUB menu ' +
                newgrubmenu + ' to ' + self.grubmenu)
            prerror(traceback.format_exc())
            prerror('Failure. Returning: ICT_ENABLE_HAPPY_FACE_BOOT_FAILED')
            return ICT_ENABLE_HAPPY_FACE_BOOT_FAILED
        return 0

    def setup_dev_namespace(self):
        '''ICT - Setup the dev namespace on the target using devfsadm(1M)
        if installing from IPS.

        Test if installing from IPS.
        launch devfsadm -R basedir
        return 0 for success, error code otherwise
        '''
        _register_task(inspect.currentframe())

        # launch devfsadm -R basedir
        cmd = '/usr/sbin/devfsadm -R ' + self.basedir + ' 2>&1'
        status, cmdout = _cmd_out(cmd)
        if status != 0:
            prerror('Setting up dev namespace fails. exit status=' +
                    str(status) + ' command=' + cmd)
            prerror('Failure. Returning: ICT_SETUP_DEV_NAMESPACE_FAILED')
            return ICT_SETUP_DEV_NAMESPACE_FAILED
        for ln in cmdout:
            info_msg('devfsadm command output: ' + ln)
        return 0

    def update_boot_archive(self):
        '''ICT - update archive using bootadm(1M)
        launch bootadm update-archive -R basedir
        return 0 for success, error code otherwise
        '''
        _register_task(inspect.currentframe())
        cmd = 'bootadm update-archive -R ' + self.basedir + ' 2>&1'
        status, cmdout = _cmd_out(cmd)
        info_msg('bootadm update-archive output: %s' % cmdout)
        if status != 0:
            prerror('Updating boot archive fails. exit status=' +
                    str(status) + ' command=' + cmd)
            prerror('Failure. Returning: ICT_UPDATE_ARCHIVE_FAILED')
            return ICT_UPDATE_ARCHIVE_FAILED
        else:
            return 0

    def copy_splash_xpm(self):
        '''ICT - copy splash file to grub directory in new root pool
        returns 0 for success or ICT status code
        '''
        _register_task(inspect.currentframe())
        #This ICT is not supported on SPARC platforms.
        #If invoked on a SPARC platform return ICT_INVALID_PLATFORM
        if self.is_sparc:
            prerror('This ICT is not supported on this hardware platform.')
            prerror('Failure. Returning: ICT_INVALID_PLATFORM')
            return ICT_INVALID_PLATFORM

        src = self.basedir + '/boot/grub/splash.xpm.gz'
        dst = '/' + self.rootpool + '/boot/grub/splash.xpm.gz'
        try:
            shutil.copy(src, dst)
        except OSError, (errno, strerror):
            prerror('Copy splash file ' + src + ' to ' + dst +
                    ' failed. ' + strerror)
            prerror('Failure. Returning: ICT_COPY_SPLASH_XPM_FAILED')
            return ICT_COPY_SPLASH_XPM_FAILED
        except StandardError:
            prerror('Unrecognized error - Could not copy splash file ' +
                    src + ' to ' + dst)
            prerror(traceback.format_exc())
            prerror('Failure. Returning: ICT_COPY_SPLASH_XPM_FAILED')
            return ICT_COPY_SPLASH_XPM_FAILED
        return 0

    def smf_correct_sys_profile(self):
        '''ICT - Point SMF at correct system profile
        return 0 if all files deleted and symlinks created,
        error status otherwise
        '''
        _register_task(inspect.currentframe())
        return_status = 0 #assume success until proven otherwise
        #delete and recreate links
        for src, dst in (
            ('generic_limited_net.xml',
             self.basedir + '/etc/svc/profile/generic.xml'),
            ('ns_dns.xml',
             self.basedir + '/etc/svc/profile/name_service.xml'),
            ('inetd_generic.xml',
             self.basedir + '/etc/svc/profile/inetd_services.xml'),
            (self.sc_profile,
             self.basedir + '/etc/svc/profile/site.xml')):

            try:
                os.unlink(dst)
            except OSError, (errno, strerror):
                if errno != 2: #file not found
                    prerror('Error deleting file ' + dst +
                        ' for smf profile. ' + strerror)
                    prerror('Failure. Returning: ' +
                            'ICT_SMF_CORRECT_SYS_PROFILE_FAILED')
                    return_status = ICT_SMF_CORRECT_SYS_PROFILE_FAILED
            except StandardError:
                prerror('Unrecognized error - could not delete file ' +
                    dst + ' for smf profile. ')
                prerror(traceback.format_exc())
                prerror('Failure. Returning: ' +
                        'ICT_SMF_CORRECT_SYS_PROFILE_FAILED')
                return_status = ICT_SMF_CORRECT_SYS_PROFILE_FAILED
            try:
                os.symlink(src, dst)
            except OSError, (errno, strerror):
                prerror('Error making symlinks for system profile. ' +
                        strerror)
                prerror('source=' + src + ' destination=' + dst)
                prerror('Failure. Returning: ' +
                        'ICT_SMF_CORRECT_SYS_PROFILE_FAILED')
                return_status = ICT_SMF_CORRECT_SYS_PROFILE_FAILED
            except StandardError:
                prerror('Unrecognized error making symlinks for ' +
                        'system profile.')
                prerror('source=' + src + ' destination=' + dst)
                prerror(traceback.format_exc())
                prerror('Failure. Returning: ' +
                        'ICT_SMF_CORRECT_SYS_PROFILE_FAILED')
                return_status = ICT_SMF_CORRECT_SYS_PROFILE_FAILED
        return return_status

    def add_sysidtool_sys_unconfig(self, more_entries=None):
        '''ICT - Add entries for sysidtool and sys-unconfig to run all
        known external apps.

        creates /etc/.sysidconfig.apps
        touches /etc/.UNCONFIGURED
        copy .sysIDtool.state to the target
        Parameter:
        more_entries - list of additional entries for .sysidconfig.apps

        return 0 if everything worked, error code if anything failed
        '''
        _register_task(inspect.currentframe())
        sys_unconfig_entries = [
                '/usr/sbin/sysidpm',
                ]
        return_status = 0
        try:
            sysidconfigapps = self.basedir + '/etc/.sysidconfig.apps'
            fp = open(sysidconfigapps, 'w')
            if more_entries:
                sys_unconfig_entries.extend(more_entries)
            for sys_unconfig_entry in sys_unconfig_entries:
                fp.write(sys_unconfig_entry + '\n')
            fp.close()
        except OSError, (errno, strerror):
            if errno != 2:
                prerror('Error creating ' + sysidconfigapps + ' - ' + strerror)
                prerror('Failure. Returning: ICT_SYSIDTOOL_ENTRIES_FAILED')
                return_status = ICT_SYSIDTOOL_ENTRIES_FAILED
        except IOError, (errno, strerror):
            if errno != 2:
                prerror('Error creating ' + sysidconfigapps + ' - ' + strerror)
                prerror('Failure. Returning: ICT_SYSIDTOOL_ENTRIES_FAILED')
                return_status = ICT_SYSIDTOOL_ENTRIES_FAILED
        except StandardError:
            prerror('Unrecognized error creating ' + sysidconfigapps)
            prerror(traceback.format_exc())
            prerror('Failure. Returning: ICT_SYSIDTOOL_ENTRIES_FAILED')
            return_status = ICT_SYSIDTOOL_ENTRIES_FAILED
        #touch /etc/.UNCONFIGURED
        try:
            unconfigured = self.basedir + '/etc/.UNCONFIGURED'
            open(unconfigured, 'w').close()
        except OSError, (errno, strerror):
            prerror('Error touching ' + unconfigured + ' - ' + strerror)
            prerror('Failure. Returning: ICT_SYSIDTOOL_ENTRIES_FAILED')
            return_status = ICT_SYSIDTOOL_ENTRIES_FAILED
        except StandardError:
            prerror('Unrecognized error touching ' + unconfigured)
            prerror(traceback.format_exc())
            prerror('Failure. Returning: ICT_SYSIDTOOL_ENTRIES_FAILED')
            return_status = ICT_SYSIDTOOL_ENTRIES_FAILED
        
        #copy .sysIDtool.state to the target
        try:
            src = '/etc/.sysIDtool.state'
            dst = self.basedir + '/etc/.sysIDtool.state'
            shutil.copy(src, dst)
        except OSError, (errno, strerror):
            prerror('Failed to copy the contents of file src to file dst' +
                strerror + ' src=' + src + '\n dst=' + dst + '\n')
            prerror('Failure. Returning: ICT_SYSIDTOOL_CP_STATE_FAILED')
            return_status = ICT_SYSIDTOOL_CP_STATE_FAILED
        except StandardError:
            prerror('Unexpected error during copy of src to dst' +
                ' src=' + src + '\n dst=' + dst + '\n')
            prerror(traceback.format_exc()) #traceback to stdout and log
            prerror('Failure. Returning: ICT_SYSIDTOOL_CP_STATE_FAILED')
            return_status = ICT_SYSIDTOOL_CP_STATE_FAILED

        return return_status

    def configure_nwam(self):
        '''ICT - configure nwam by creating /etc/nwam/llp with
                the preferred interface followed by dhcp in it.
                The perferred interface should be the interface
                used for the network installation.

        return 0, otherwise error status
        '''
        _register_task(inspect.currentframe())

        cmd = "/usr/sbin/ifconfig -au | /usr/bin/grep '[0-9]:' " \
            "| /usr/bin/grep -v 'LOOPBACK'"

        (status, output) = commands.getstatusoutput(cmd)
        if status != 0:
            prerror('ifconfig command to determine preferred network ' +
                'interface failed. command=' + cmd)
            prerror('Failure. Returning: ICT_CONFIGURE_NWAM_FAILED')
            return ICT_CONFIGURE_NWAM_FAILED

        interface = output.split(':')[0]

        llp_dir = self.basedir + '/etc/nwam'
        llp_file = llp_dir + '/llp'

        if not os.access(llp_dir, os.F_OK):
            os.makedirs(llp_dir)
            # chown root:root
            os.chown(llp_dir, 0, 0)
            # chmod 755
            os.chmod(llp_dir,
                    S_IREAD | S_IWRITE | S_IEXEC |
                    S_IRGRP | S_IXGRP |
                    S_IROTH | S_IXOTH)
        try:
            config_file = open(llp_file, 'w')
            # add the line with <interface> dhcp to it.
            config_file.write(interface + "\tdhcp\n")
            config_file.close()
            # chown root:root
            os.chown(llp_file, 0, 0)
            # chmod 644
            os.chmod(llp_file, S_IREAD | S_IWRITE | S_IRGRP | S_IROTH)
        except IOError, errno:
            # Unable to open the file
            prerror('Unexpected error writing to <target>/etc/nwam/llp' +
                ' to configure nwam. ' +
                ' file=' + llp_file + ' failed to add the lines:\n' +
                interface + '\tdhcp' + ' due to ' + str(errno))
            prerror(traceback.format_exc()) #traceback to stdout and log
            prerror('Failure. Returning: ICT_CONFIGURE_NWAM_FAILED')
            return ICT_CONFIGURE_NWAM_FAILED

        return 0

    def enable_nwam(self):
        '''ICT - Enable nwam service
        SVCCFG_DTD=basedir + '/usr/share/lib/xml/dtd/service_bundle.dtd.1'
        SVCCFG_REPOSITORY=basedir + '/etc/svc/repository.db'
        svccfg apply basedir + '/etc/svc/profile/network_nwam.xml'

        return 0, otherwise error status
        '''
        _register_task(inspect.currentframe())

        return_status = 0

        nwam_profile = self.basedir + '/etc/svc/profile/network_nwam.xml'
        os.putenv('SVCCFG_DTD', self.basedir +
                  '/usr/share/lib/xml/dtd/service_bundle.dtd.1')
        os.putenv('SVCCFG_REPOSITORY', self.basedir + '/etc/svc/repository.db')
        cmd = '/usr/sbin/svccfg apply ' + nwam_profile + ' 2>&1'
        status, oa = _cmd_out(cmd)
        if status != 0:
            prerror('Command to enable nwam failed. exit status=' +
                    str(status))
            prerror('Command to enable nwam was: ' + cmd)
            for ln in oa:
                prerror(ln)

            prerror('Failure. Returning: ICT_ENABLE_NWAM_FAILED')
            return_status = ICT_ENABLE_NWAM_FAILED

        return return_status

    def do_not_configure_network(self):
        '''ICT - Do not configure any network.  NWAM will be disabled.
        Net physical default will be enabled.
        SVCCFG_DTD=basedir + '/usr/share/lib/xml/dtd/service_bundle.dtd.1'
        SVCCFG_REPOSITORY=basedir + '/etc/svc/repository.db'
        svccfg -s network/physical:default setprop general/enabled = true
        svccfg -s network/physical:nwam setprop general/enabled = false

        return 0, otherwise error status
        '''
        _register_task(inspect.currentframe())

        return_status = 0

        os.putenv('SVCCFG_DTD', self.basedir +
                  '/usr/share/lib/xml/dtd/service_bundle.dtd.1')
        os.putenv('SVCCFG_REPOSITORY', self.basedir + '/etc/svc/repository.db')
        cmd = '/usr/sbin/svccfg -s network/physical:default setprop ' + \
              'general/enabled = true 2>&1'
        status, oa = _cmd_out(cmd)
        if status != 0:
            prerror('Command to disable network/physical:default failed. ' + \
                    'exit status=' + str(status))
            prerror('Command to disable network/physical:default was: ' + cmd)
            for ln in oa:
                prerror(ln)
            prerror('Failure. Returning: ICT_SVCCFG_FAILURE')
            return(ICT_SVCCFG_FAILURE)

        cmd = '/usr/sbin/svccfg -s network/physical:nwam setprop ' + \
              'general/enabled = false 2>&1'
        status, oa = _cmd_out(cmd)
        if status != 0:
            prerror('Command to disable nwam failed. exit status=' + \
                    str(status))
            prerror('Command to disable nwam was: ' + cmd)
            for ln in oa:
                prerror(ln)

            prerror('Failure. Returning: ICT_SVCCFG_FAILURE')
            return (ICT_SVCCFG_FAILURE)

        return return_status

    def remove_livecd_environment(self):
        '''ICT - Copy saved configuration files to remove vestiges of
        live CD environment
        return 0 for success, error code otherwise
        '''
        savedir = self.basedir + '/save'
        if not os.path.exists(savedir):
            info_msg('saved configuration files directory is missing')
            return 0 # empty - assume no config files to back up
        cmd = '(cd %s && find . -type f -print | \
            /bin/cpio -pmu %s > /dev/null 2>& 1) && rm -rf %s' \
            % (savedir, self.basedir, savedir)
        status = _cmd_status(cmd)
        if status == 0:
            return 0
        prerror('remove liveCD environment failed: exit status ' + str(status))
        prerror('command was ' + cmd)
        prerror('Failure. Returning: ICT_REMOVE_LIVECD_ENVIRONMENT_FAILED')
        return ICT_REMOVE_LIVECD_ENVIRONMENT_FAILED

    def remove_specific_packages(self, pkg_list):
        '''ICT - Remove install-specific packages
        launch pkg -R basedir uninstall PACKAGE
        Parameter: pkg_list - list of pkg names
        return 0 for success, error code otherwise
        '''
        _register_task(inspect.currentframe())
        return_status = 0
        for pkg in pkg_list:
            cmd = 'pkg -R %s uninstall -q --no-index %s 2>&1' % \
                (self.basedir, pkg)
            status, cmdout = _cmd_out(cmd)
            if status != 0:
                prerror('Removal of package %s failed.  pkg exit status =%d'
                        % (pkg, status))
                prerror('Failed package removal command=' + cmd)
                for ln in cmdout:
                    prerror(ln)
                prerror('Failure. Returning: ICT_PACKAGE_REMOVAL_FAILED')
                return_status = ICT_PACKAGE_REMOVAL_FAILED
        return return_status

    def set_flush_content_cache_false(self):
        '''ICT - The LiveCD can be configured to purge the IPS download cache.
        Restore the original IPS default to not purge the IPS download cache.
        Use the command line interface to IPS : pkg set-property
        return 0 upon success, error code otherwise
        '''
        _register_task(inspect.currentframe())

        cmd = 'pkg -R ' + self.basedir + \
            ' set-property flush-content-cache-on-success False'
        status = _cmd_status(cmd)
        if status != 0:
            prerror('Set property flush-content-cache-on-success ' +
                    'exit status = ' + str(status) + ', command was ' + cmd)
            prerror('Failure. Returning: ' +
                    'ICT_SET_FLUSH_CONTENT_CACHE_ON_SUCCESS_FAILED')
            return ICT_SET_FLUSH_CONTENT_CACHE_ON_SUCCESS_FAILED
        return 0

    def set_boot_device_property(self):
        '''ICT - update bootenv.rc 'console' property determines console
        boot device from bootenv.rc properties 'output-device' and 'console'
        updates 'console' bootenv.rc property
        return status = 0 if success, error code otherwise
        '''
        _register_task(inspect.currentframe())
        #This ICT is not supported on SPARC platforms.
        #If invoked on a SPARC platform return ICT_INVALID_PLATFORM
        if self.is_sparc:
            prerror('This ICT is not supported on this hardware platform.')
            prerror('Failure. Returning: ICT_INVALID_PLATFORM')
            return ICT_INVALID_PLATFORM

        # put it in bootenv.rc
        curosconsole = self._get_bootprop('output-device')
        osconsole = self._get_osconsole()
        if curosconsole != osconsole and osconsole != '':
            info_msg('Setting console boot device property to ' + osconsole)
            status = self._update_bootprop('console', osconsole)
            if status != 0:
                return status
        return 0

    def remove_livecd_coreadm_conf(self):
        '''ICT - Remove LiveCD-specific /etc/coreadm.conf config file.
        Coreadm will create its initial configuration on first boot
        see also coreadm(1m)
        returns 0 for success, otherwise error code
        '''
        _register_task(inspect.currentframe())
        filename = self.basedir + '/etc/coreadm.conf'
        try:
            os.unlink(filename)
        except OSError, (errno, strerror):
            if errno != 2: #file does not exist
                prerror('I/O error - cannot delete file ' + filename +
                        ': ' + strerror)
                prerror('Failure. Returning: ' +
                        ' ICT_REMOVE_LIVECD_COREADM_CONF_FAILURE')
                return ICT_REMOVE_LIVECD_COREADM_CONF_FAILURE
            else:
                _dbg_msg('coreadm config file already removed ' + filename)
        except StandardError:
            prerror('Unrecognized error - cannot delete file ' + filename)
            prerror(traceback.format_exc())
            prerror('Failure. Returning: ' +
                    'ICT_REMOVE_LIVECD_COREADM_CONF_FAILURE')
            return ICT_REMOVE_LIVECD_COREADM_CONF_FAILURE

        return 0

    def set_partition_active_sparc(self):
        '''support routine - set the Solaris partition active using eeprom
        return 0 if no errors for any drive, error code otherwise
        '''
        _register_task(inspect.currentframe())

        # Just in case this supporting routine was called directly.
        if not self.is_sparc:
            prerror('This supporting routine is not supported on this' +
                    ' hardware platform.')
            prerror('Failure. Returning: ICT_INVALID_PLATFORM')
            return ICT_INVALID_PLATFORM
        
        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

        # since the root device might be a metadevice, all the components
        # need to be located so each can be operated upon individually
        status = 0
        status, rdlist = self.get_rootdev_list('/dev/dsk/')
        if status != 0:
            prerror('get_rootdev_list() status=' + str(status))
            prerror('Failure. Returning: ICT_SET_PART_ACTIVE_FAILED')
            return ICT_SET_PART_ACTIVE_FAILED

        for rootdev in rdlist:
            _dbg_msg('root device: ' + rootdev)
            _dbg_msg('Opening prom device: ' + prom_device)
            try:
                prom = open(prom_device, "r")
            except StandardError:
                prom = None

            if prom == None:
                prerror('Failure to open prom device ' + prom_device)
                prerror('Failure. Returning: ICT_OPEN_PROM_DEVICE_FAILED')
                return ICT_OPEN_PROM_DEVICE_FAILED

            # 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 = (rootdev + "\0").ljust(oprommaxparam)
            buf = array.array('c', struct.pack('I%ds' % oprommaxparam,
                                               oprommaxparam, dev))

            # use ioctl to query the prom device.
            try:
                status = fcntl.ioctl(prom, opromdev2promname, buf, True)
            except StandardError:
                status = 1 # Force bad status for check below

            if status != 0:
                prom.close()
                prerror('ioctl OPROMDEV2PROMNAME ' + rootdev +
                        ' failed: status=' + str(status))
                prerror('Failure. Returning: ICT_IOCTL_PROM_FAILED')
                return ICT_IOCTL_PROM_FAILED

            prom.close()

            # 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]
            _dbg_msg('prom name:: ' + prom_name)

            # Set the boot device using eeprom
            status = _cmd_status('/usr/sbin/eeprom boot-device=' + prom_name)
            if status != 0:
                prerror('fcntl ioctl OPROMDEV2PROMNAME failed: status=' +
                        str(status))
                prerror('Failure. Returning: ICT_IOCTL_PROM_FAILED')
                return ICT_IOCTL_PROM_FAILED

        #if no errors encountered. Return 0 for success
        return 0

    def set_partition_active_x86(self):

        '''support routine - set the Solaris partition on the just
        installed drive to active rewrites disk format tables
        see set_boot_active()
        return 0 if no errors for any drive, error code otherwise
        '''
        _register_task(inspect.currentframe())

        # Just in case this supporting routine was called directly.
        if self.is_sparc:
            prerror('This supporting routine is not supported on this ' +
                    'hardware platform.')
            prerror('Failure. Returning: ICT_INVALID_PLATFORM')
            return ICT_INVALID_PLATFORM

        # since the root device might be a metadevice, all the components
        # need to be located so each can be operated upon individually
        return_status, rdlist = self.get_rootdev_list('/dev/rdsk/')
        if return_status == 0:
            for rootdev in rdlist:
                _dbg_msg('root device: ' + rootdev)
                status = self.set_boot_active(rootdev)
                if status != 0:
                    return_status = status
                status = self.bootadm_update_menu(rootdev)
                if status != 0:
                    return_status = status
        #if any operation fails, return error status
        return return_status

    def set_partition_active(self):
        '''ICT - set the Solaris partition active
        This ICT is implemented differently on SPARC and x86.
        Invoke the correct supporting routine based on platform.
        Bubble up status from supporting routine.
        return 0 if no errors for any drive, error code otherwise
        '''
        _register_task(inspect.currentframe())

        if self.is_sparc:
            return_status = self.set_partition_active_sparc()
        else:
            return_status = self.set_partition_active_x86()

        return return_status

    def get_special_grub_entry(self):
        '''Support function for the fix_grub_entry() function.
        Determines whether a special string is needed for the grub
        entry.  The special string, if specified, should be
        in the .image_info file.

        - return the special grub entry if one is found in .image_info.
        - return None if none is found
        '''

        if (not self.livecd_install and not self.auto_install and
            not self.text_install):
            # Not going to have .image_info file
            return None

        #
        # Check whether a specific title should be used for the
        # grub menu instead of the default one.  If a specific
        # title should be used, the Distribution Constructor
        # will put the special title in the /.cdrom/.image_info file
        #
        grub_title = None
        img_info_fd = None
        if (self.livecd_install or self.text_install):
            img_info_file = "/.cdrom/.image_info"
        else:
            img_info_file = "/tmp/.image_info"

        try:
            try:
                img_info_fd = open(img_info_file, "r")
                for line in img_info_fd:
                    if line.startswith("GRUB_TITLE="):
                        grub_title_line = line.rstrip('\n')
                        title_string = grub_title_line.split("=")
                        grub_title = title_string[1]
                        break
            except StandardError:
                # Should not get into this situation, but
                # it is harmless to continue, so, just
                # log it.
                _dbg_msg("No image file found. Use default grub title")
        finally:
            if (img_info_fd != None):
                img_info_fd.close()
        
        return grub_title
    
    def match_boot_entry(self, title):
        '''Returns True if the given line is the 'title' line that needs
        to be updated with the "special grub entry."
        
        For now, assume that boot_entry is the right entry, as there
        should be only one entry in the menu.lst file at this point
        in the install
        
        '''
        return title.startswith("title")
    
    def fix_grub_entry(self):
        '''ICT - Fix up the grub entry. If a special grub title entry
        is defined when the image is built by the Distribution
        Constructor, that special title will be used.
        
        return 0 on success, error code otherwise
        '''
        _register_task(inspect.currentframe())
        #This ICT is not supported on SPARC platforms.
        #If invoked on a SPARC platform return ICT_INVALID_PLATFORM
        if self.is_sparc:
            prerror('This ICT is not supported on this hardware platform.')
            prerror('Failure. Returning: ICT_INVALID_PLATFORM')
            return ICT_INVALID_PLATFORM
        
        new_title = self.get_special_grub_entry()
        if new_title is None:
            # No need to update the grub entry
            return 0
        
        newgrubmenu = self.grubmenu + '.new'
        try:
            with open(self.grubmenu, "r") as old_grub_fd:
                old_lines = old_grub_fd.readlines()
            
            with open(newgrubmenu, "w") as new_grub_fd:
                for line in old_lines:
                    if self.match_boot_entry(line):
                        # replace part of existing title
                        # with the specified new title
                        new_grub_fd.write("title %s\n" % new_title)
                    else:
                        new_grub_fd.write(line)
        except (OSError, IOError) as err:
            prerror('Error updating grub menu: ' + str(err))
            prerror('Failure. Returning: ICT_FIX_GRUB_ENTRY_FAILED')
            return ICT_FIX_GRUB_ENTRY_FAILED
        
        if not _move_in_updated_config_file(newgrubmenu,
                                            self.grubmenu):
            prerror('Failure. Returning: ICT_FIX_GRUB_ENTRY_FAILED')
            return ICT_FIX_GRUB_ENTRY_FAILED

        return 0

    def create_sparc_boot_menu(self, title=None):
        '''ICT - Create a boot menu.lst file on a SPARC system.
        return 0 on success, error code otherwise
        '''
        _register_task(inspect.currentframe())
        #This ICT is only supported on SPARC platforms.
        #If invoked on a non SPARC platform return ICT_INVALID_PLATFORM
        if not self.is_sparc:
            prerror('This ICT is not supported on this hardware platform.')
            prerror('Failure. Returning: ICT_INVALID_PLATFORM')
            return ICT_INVALID_PLATFORM

        # Attempt to create the path to where the menu.lst file will reside
        # Catch OSError and pass if the directory already exists.
        try:
            os.makedirs(self.bootmenu_path_sparc)
        except OSError:
            pass

        # Write to the menu.lst file
        rootdataset = self._get_root_dataset()
        if rootdataset == '':
            prerror('Could not determine root dataset')
            prerror('Failure. Returning: ICT_CREATE_SPARC_BOOT_MENU_FAILED')
            return ICT_CREATE_SPARC_BOOT_MENU_FAILED
        
        if title is None:
            title = MENU_LST_DEFAULT_TITLE
        sparc_title_line = 'title %s\n' % title
        bootfs_line = 'bootfs ' + rootdataset + '\n'

        try:
            op = open(self.bootmenu_sparc, 'w')
            op.write(sparc_title_line)
            op.write(bootfs_line)
            op.close()
            os.chmod(self.bootmenu_sparc,
                     S_IREAD | S_IWRITE | S_IRGRP | S_IROTH)
            os.chown(self.bootmenu_sparc, 0, 3)  # chown root:sys
        except OSError, (errno, strerror):
            prerror('Error when creating sparc boot menu.lst file ' +
                self.bootmenu_sparc + ': ' + strerror)
            prerror('Failure. Returning: ICT_CREATE_SPARC_BOOT_MENU_FAILED')
            return ICT_CREATE_SPARC_BOOT_MENU_FAILED
        except StandardError:
            prerror('Unexpected error when creating sparc boot ' +
                    ' menu.lst file ' + self.bootmenu_sparc)
            prerror(traceback.format_exc())  # traceback to stdout and log
            prerror('Failure. Returning: ICT_CREATE_SPARC_BOOT_MENU_FAILED')
            return ICT_CREATE_SPARC_BOOT_MENU_FAILED

        return 0

    def copy_sparc_bootlst(self):
        '''ICT - 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

        return 0 on success, error code otherwise
        '''
        _register_task(inspect.currentframe())

        # This ICT is only supported on SPARC platforms.
        # If invoked on a non SPARC platform return ICT_INVALID_PLATFORM
        if not self.is_sparc:
            prerror('This ICT is not supported on this hardware platform.')
            prerror('Failure. Returning: ICT_INVALID_PLATFORM')
            return ICT_INVALID_PLATFORM

        # Copy file bootlst from basedir to the rootpool
        bootlst_dir = '/' + self.rootpool + '/platform/' + platform.machine()
        bootlst_dst = bootlst_dir + '/bootlst'
        bootlst_src = self.basedir + '/platform/' + platform.machine() + \
            '/bootlst'

        # Create the destination directory if it does not already exist.
        # Catch OSError and pass if the directory already exists.
        try:
            os.makedirs(bootlst_dir)
        except OSError:
            pass

        # Copy the bootlst
        try:
            shutil.copyfile(bootlst_src, bootlst_dst)
            os.chmod(bootlst_dst, S_IREAD | S_IWRITE | S_IRGRP | S_IROTH)
            os.chown(bootlst_dst, 0, 3)  # chown root:sys

        except OSError, (errno, strerror):
            prerror('Error when copying the sparc bootlst file ' +
                    bootlst_src + ': ' + strerror)
            prerror('Failure. Returning: ICT_COPY_SPARC_BOOTLST_FAILED')
            return ICT_COPY_SPARC_BOOTLST_FAILED
        except StandardError:
            prerror('Unexpected error when copying the sparc bootlst file ' +
                    self.bootmenu_sparc)
            prerror(traceback.format_exc())  # traceback to stdout and log
            prerror('Failure. Returning: ICT_COPY_SPARC_BOOTLST_FAILED')
            return ICT_COPY_SPARC_BOOTLST_FAILED

        return 0

    def add_operating_system_grub_entry(self):
        '''ICT - add entries for other installed OS's to the grub menu
        Launch /sbin/mkmenu <target GRUB menu>
        return 0 on success, error code otherwise
        '''
        _register_task(inspect.currentframe())
        # This ICT is not supported on SPARC platforms.
        # If invoked on a SPARC platform return ICT_INVALID_PLATFORM
        if self.is_sparc:
            prerror('This ICT is not supported on this hardware platform.')
            prerror('Failure. Returning: ICT_INVALID_PLATFORM')
            return ICT_INVALID_PLATFORM

        cmd = '/sbin/mkmenu ' + self.grubmenu
        cwd_start = os.getcwd()
        os.chdir('/')
        status = _cmd_status(cmd)
        os.chdir(cwd_start)
        if status != 0:
            prerror('Add other OS to grub menu failed. command=' + cmd +
                ' exit status=' + str(status))
            prerror('Failure. Returning: ICT_MKMENU_FAILED')
            return ICT_MKMENU_FAILED
        return 0

    def do_clobber_files(self, flist_file):
        '''ICT - Given a file containing a list of pathnames,
        search for those entries in the alternate root and
        delete all matching pathnames from the alternate root that
        are symbolic links.
        This process is required because of the way the LiveCD env
        is constructed. Some of the entries in the boot_archive are
        symbolic links to files mounted off a compressed lofi file.
        This is done to drastically reduce space usage by the boot_archive.
        side effect: current directory changed to basedir
        returns 0 if all processing completed successfully,
        error code if any problems
        '''
        _register_task(inspect.currentframe())
        _dbg_msg('File with list of pathnames with symbolic links' +
                 ' to clobber: ' + flist_file)
        try:
            fh = open(flist_file, 'r')
            os.chdir(self.basedir)
        except OSError, (errno, strerror):
            prerror('I/O error - cannot access clobber list file ' +
                    flist_file + ': ' + strerror)
            prerror('Failure. Returning: ICT_CLOBBER_FILE_FAILED')
            return ICT_CLOBBER_FILE_FAILED
        except StandardError:
            prerror('Unrecognized error processing clobber list file ' +
                    flist_file)
            prerror(traceback.format_exc())
            prerror('Failure. Returning: ICT_CLOBBER_FILE_FAILED')
            return ICT_CLOBBER_FILE_FAILED
        return_status = 0
        for line in fh:
            line = line[:-1]
            try:
                mst = os.lstat(line)
                if S_ISLNK(mst.st_mode):
                    _dbg_msg("Unlink: " + line)
                    os.unlink(line)
            except OSError, (errno, strerror):
                if errno == 2:  # file does not exist
                    _dbg_msg('Pathname ' + line +
                             ' not found - nothing deleted')
                else:
                    prerror('I/O error - cannot delete soft link ' +
                            line + ': ' + strerror)
                    prerror('Failure. Returning: ICT_CLOBBER_FILE_FAILED')

                    # one or more items fail processing
                    return_status = ICT_CLOBBER_FILE_FAILED
            except StandardError:
                prerror('Unrecognized error during file ' + line + ' clobber')
                prerror(traceback.format_exc())
        fh.close()
        return return_status

    def copy_capability_file(self):
        '''ICT - copies grub capability file from microroot to grub
        directory in root pool.

        Parameters:
            none
        returns 0 for success, otherwise error code
        '''
        _register_task(inspect.currentframe())
        # This ICT is not supported on SPARC platforms.
        # If invoked on a SPARC platform return ICT_INVALID_PLATFORM
        if self.is_sparc:
            prerror('This ICT is not supported on this hardware platform.')
            prerror('Failure. Returning: ICT_INVALID_PLATFORM')
            return ICT_INVALID_PLATFORM
        try:
            shutil.copy2("/boot/grub/capability", "/" + self.rootpool +
                         "/boot/grub/capability")
        except (OSError, IOError) as err:
            prerror('Error copying /boot/grub/capability to ' + '/' +
                    self.rootpool + '/boot/grub/capability:' + str(err))
            return ICT_COPY_CAPABILITY_FAILED
        return 0

    def cleanup_unneeded_files_and_dirs(self,
                                        more_cleanup_files=None,
                                        more_cleanup_dirs=None):
        '''ICT - removes list of files and directories that should
        not have been copied If these are of appreciable size, they
        should be withheld from cpio file list instead

        Parameters:
                more_cleanup_files - list of additional files to delete
                more_cleanup_dirs - list of additional directories to delete
        returns 0 for success, otherwise error code
        '''
        _register_task(inspect.currentframe())
        # Cleanup the files and directories that were copied into
        # the basedir directory that are not needed by the installed OS.
        file_cleanup_list = ["/.livecd",
                             "/.volsetid",
                             "/.textinstall",
                             "/etc/sysconfig/language",
                             "/.liveusb"]
        dir_cleanup_list = ["/a", "/bootcd_microroot"]
        if more_cleanup_files:
            file_cleanup_list.extend(more_cleanup_files)
        if more_cleanup_dirs:
            dir_cleanup_list.extend(more_cleanup_dirs)
        return_status = 0
        for basefname in file_cleanup_list:
            fname = self.basedir + "/" + basefname
            _dbg_msg('Removing file ' + fname)
            try:
                os.remove(fname)
            except OSError, (errno, strerror):
                if errno == 2:  # file not found
                    _dbg_msg('File to delete was not found: ' + fname)
                else:
                    prerror('Error deleting file ' + fname + ': ' + strerror)
                    prerror('Failure. Returning: ICT_CLEANUP_FAILED')
                    return_status = ICT_CLEANUP_FAILED
            except StandardError:
                prerror('Unexpected error deleting directory.')
                prerror(traceback.format_exc())

        # Since pkg:/system/boot/grub delivers the reference grub menu file
        # (/boot/grub/menu.lst) we'll have to copy the menu.lst
        # file from the microroot into the installed system.
        # Since this file is for reference only if the copy
        # fails we don't want to stop the install for this but
        # we should log it.
        if not self.is_sparc:
            try:
                shutil.copy2("/boot/grub/menu.lst", self.basedir +
                             "/boot/grub/menu.lst")
            except (OSError, IOError) as err:
                prerror('Error copying /boot/grub/menu.lst to ' +
                        self.basedir + '/boot/grub/menu.lst :' + str(err))

        # The bootcd_microroot directory should be cleaned up in the
        # Distribution Constructor once they have finished the redesign.
        for basedname in dir_cleanup_list:
            dname = self.basedir + "/" + basedname
            _dbg_msg('removing directory' + dname)
            try:
                os.rmdir(dname)
            except OSError, (errno, strerror):
                if errno == 2:  # file not found
                    _dbg_msg('Path to delete was not found: ' + dname)
                else:
                    prerror('Error deleting directory ' + dname +
                            ': ' + strerror)
                    prerror('Failure. Returning: ICT_CLEANUP_FAILED')
                    return_status = ICT_CLEANUP_FAILED
            except StandardError:
                prerror('Unexpected error deleting file.')
                prerror(traceback.format_exc())
        return return_status

    def reset_image_uuid(self):
        '''ICT - reset pkg(1) image UUID for preferred publisher

        Obtain name of all publishers by parsing output of the following
        command: pkg -R basedir property -H publisher-search-order

        launch pkg -R basedir set-publisher --reset-uuid --no-refresh \
            <preferred_publisher>

        launch pkg -R basedir pkg set-property send-uuid True

        return 0 for success, otherwise error code
        '''
        _register_task(inspect.currentframe())
        cmd = '/usr/bin/pkg -R ' + self.basedir + ' property -H ' + \
            'publisher-search-order'
        status, co = _cmd_out(cmd)
        if status != 0:
            prerror('pkg(1) failed to obtain list of publishers - '
                    'exit status = ' + str(status) + ', command was ' + cmd)
            prerror('Failure. Returning: ICT_PKG_RESET_UUID_FAILED')
            return ICT_PKG_RESET_UUID_FAILED

        publisher_search_order = co[0].strip().split(None, 1)
        # strip off the leading '[' and trailing ']' characters
        for publisher in publisher_search_order[1:-1]:
            # strip off any ' or , characters
            publisher = publisher.strip("',")

            cmd = 'pkg -R ' + self.basedir + \
                ' set-publisher --reset-uuid --no-refresh ' + publisher
            status = _cmd_status(cmd)
            if status != 0:
                prerror('Reset uuid failed for publisher:  ' + publisher +
                        '- exit status = ' + str(status) +
                        ', command was ' + cmd)
                prerror('Failure. Returning: ICT_PKG_RESET_UUID_FAILED')
                return ICT_PKG_RESET_UUID_FAILED

        cmd = 'pkg -R ' + self.basedir + ' set-property send-uuid True'
        status = _cmd_status(cmd)
        if status != 0:
            prerror('Set property send uuid - exit status = ' + str(status) +
                ', command was ' + cmd)
            prerror('Failure. Returning: ICT_PKG_SEND_UUID_FAILED')
            return ICT_PKG_SEND_UUID_FAILED

        return 0

    def rebuild_pkg_index(self):
        '''ICT - rebuild pkg(1) index
        launch pkg -R basedir rebuild-index
        return 0 for success, otherwise error code
        '''
        _register_task(inspect.currentframe())
        cmd = 'pkg -R ' + self.basedir + ' rebuild-index'
        status = _cmd_status(cmd)
        if status == 0:
            return 0
        prerror('Rebuild package index failed - exit status = ' +
                str(status) + ', command was ' + cmd)
        prerror('Failure. Returning: ICT_REBUILD_PKG_INDEX_FAILED')
        return ICT_REBUILD_PKG_INDEX_FAILED

    def create_new_user(self, gcos, login, pw, gid, uid):
        '''ICT - create the new user.
        using IPS class PasswordFile from pkg.cfgfiles
        It is possible no new user was requested. If none was
        specified do nothing and return 0.
        return 0 on success, error code otherwise
        '''
        _register_task(inspect.currentframe())

        return_status = 0
        _dbg_msg('creating new user on target: ' + self.basedir)

        if not login:
            _dbg_msg('No login specified')
            return return_status

        try:
            pf = PasswordFile(self.basedir)
            nu = pf.getuser('nobody')
            nu['username'] = login
            nu['gid'] = gid
            nu['uid'] = uid
            nu['gcos-field'] = gcos
            nu['home-dir'] = '/home/' + login
            nu['login-shell'] = '/bin/bash'
            nu['password'] = pw
            pf.setvalue(nu)
            pf.writefile()

        except StandardError:
            prerror('Failure to modify the root password')
            prerror(traceback.format_exc())
            prerror('Failure. Returning: ICT_CREATE_NU_FAILED')
            return_status = ICT_CREATE_NU_FAILED

        return return_status

    def set_homedir_map(self, login):
        '''ICT - set the auto_home map entry on the specified install
        target.
        return 0 on success, error code otherwise
        '''
        _register_task(inspect.currentframe())

        return_status = 0

        temp_file = '/var/run/new_auto_home'

        if not login:
            _dbg_msg('No login specified')
            return return_status

        _dbg_msg('setting home dir in auto_home map: ' + self.basedir)

        try:
            with open(self.autohome, 'r') as fp:
                autohome_lines = fp.readlines()
            with open(temp_file, 'w') as fp_tmp:
                for l in autohome_lines:
                    if l.startswith("+auto_home"):
                        fp_tmp.write(login + '\tlocalhost:/export/home/&\n')
                    fp_tmp.write(l)
            os.remove(self.autohome)
            shutil.move(temp_file, self.autohome)
            os.chmod(self.autohome, S_IREAD | S_IWRITE | S_IRGRP | S_IROTH)
            os.chown(self.autohome, 0, 2)  # chown root:bin
        except IOError, (errno, strerror):
            prerror('Failure to add line in auto_home file')
            prerror(traceback.format_exc())
            prerror('Failure. Returning: ICT_SET_AUTOHOME_FAILED')
            return_status = ICT_SET_AUTOHOME_FAILED

        return return_status

    def set_root_password(self, newpw, expire=False):
        '''ICT - set the root password on the specified install target.
        using IPS class PasswordFile from pkg.cfgfiles.  Pre-expire password
        if expire is True
        return 0 on success, error code otherwise
        '''
        _register_task(inspect.currentframe())

        return_status = 0
        _dbg_msg('setting root password on target: ' + self.basedir)

        try:
            pf = PasswordFile(self.basedir)
            ru = pf.getuser('root')
            ru['password'] = newpw
            if expire:
                ru['lastchg'] = 0
            pf.setvalue(ru)
            pf.writefile()
        except StandardError:
            prerror('Failure to modify the root password')
            prerror(traceback.format_exc())
            prerror('Failure. Returning: ICT_SET_ROOT_PW_FAILED')
            return_status = ICT_SET_ROOT_PW_FAILED

        return return_status

    def apply_sysconfig_profile(self):
        '''ICT - apply system configuration SMF profile to the target.
        The SMF profile will be applied during first boot as part of
        Early Manifest Import process.

        Carry out only syntactic validation of SMF profile.

        return 0 on success, error code otherwise
        '''
        _register_task(inspect.currentframe())

        sc_profile_src = self.ai_sc_profile
        sc_profile_dst = self.basedir + '/etc/svc/profile/' + \
                         self.sc_profile
        os.environ["SVCCFG_DTD"] = self.basedir + \
                   '/usr/share/lib/xml/dtd/service_bundle.dtd.1'
        os.environ["SVCCFG_REPOSITORY"] = self.basedir + \
                   '/etc/svc/repository.db'
        cmd = '/usr/sbin/svccfg apply -n ' + sc_profile_src + ' 2>&1'
        status, oa = _cmd_out(cmd)

        if status == 0:
            write_log(ICTID, 'Syntactic validation of System configuration '
                      'profile succeeded\n')
        else:
            prerror('Syntactic validation of System configuration profile '
                    'failed. exit status=' + str(status))
            prerror('Command to validate configuration profile was: ' + cmd)
            for ln in oa:
                prerror(ln)

            prerror('Failure. Returning: ICT_APPLY_SYSCONFIG_FAILED')
            return ICT_APPLY_SYSCONFIG_FAILED

        # copy SMF profile to the target and make sure it can be read only
        # by root in order to protect encrypted password for configured
        # root and user accounts
        try:
            shutil.copyfile(sc_profile_src, sc_profile_dst)
            os.chmod(sc_profile_dst, S_IRUSR)  # read-only by user (root)
            os.chown(sc_profile_dst, 0, 3)  # chown root:sys

        except OSError, (errno, strerror):
            prerror('Error when copying System Configuration profile ' +
                    sc_profile_src + ' to ' + sc_profile_dst + ' : ' +
                    strerror)
            prerror('Failure. Returning: ICT_APPLY_SYSCONFIG_FAILED')
            return ICT_APPLY_SYSCONFIG_FAILED
        except StandardError:
            prerror('Unexpected error when copying System Configuration ' +
                    ' profile ' + sc_profile_src + ' to ' + sc_profile_dst)
            prerror(traceback.format_exc())  # traceback to stdout and log
            prerror('Failure. Returning: ICT_APPLY_SYSCONFIG_FAILED')
            return ICT_APPLY_SYSCONFIG_FAILED
        
        return 0

    def setup_rbac(self, login):
        '''ICT - configure user for root role, without any extra profiles and
        remove the jack user from user_attr
        return 0 on success, error code otherwise
        '''
        _register_task(inspect.currentframe())
        return_status = 0
        _dbg_msg('configuring RBAC in: ' + self.basedir)

        try:
            f = UserattrFile(self.basedir)
            # Remove jack if present
            if f.getvalue({'username' : 'jack'}):
                f.removevalue({'username' : 'jack'})
            
            rootentry = f.getvalue({'username' : 'root'})
            rootattrs = rootentry['attributes']
            
            # If we're creating a user, then ensure root is a role and
            # add the user.  Otherwise ensure that root is not a role.
            if login:
                rootattrs['type'] = ['role']
                rootentry['attributes'] = rootattrs
                f.setvalue(rootentry)
                
                # Attributes of a userattr entry are a dictionary of list values
                userattrs = dict({'roles' : ['root']})
                # An entry is a dictionary with username and attributes
                userentry = dict({'username' : login, 'attributes' : userattrs})
                f.setvalue(userentry)
            else:
                if 'type' in rootattrs:
                    del rootattrs['type']
                    rootentry['attributes'] = rootattrs
                    f.setvalue(rootentry)
            
            # Write the resulting file
            f.writefile()

        except StandardError:
            prerror('Failure to edit user_attr file')
            prerror(traceback.format_exc())
            prerror('Failure. Returning: ICT_SETUP_RBAC_FAILED')
            return_status = ICT_SETUP_RBAC_FAILED
        
        return return_status

    def setup_sudo(self, login):
        '''ICT - configure user for sudo access, removing jack user from sudoers
        return 0 on success, error code otherwise
        '''
        
        _register_task(inspect.currentframe())
        return_status = 0
        _dbg_msg('configuring RBAC in: ' + self.basedir)

        temp_file = '/var/run/sudoers'

        try:
            with open(self.sudoers, 'r') as fp:
                sudoers_lines = fp.readlines()
            with open(temp_file, 'w') as fp_tmp:
                for l in sudoers_lines:
                    if not l.startswith("jack"):
                        fp_tmp.write(l)
                if login:
                    fp_tmp.write(login + ' ALL=(ALL) ALL\n')

            os.remove(self.sudoers)
            shutil.move(temp_file, self.sudoers)
            os.chmod(self.sudoers, S_IREAD | S_IRGRP)
            os.chown(self.sudoers, 0, 0) # chown root:root
        except IOError, (errno, strerror):
            prerror('Failure to edit sudoers file')
            prerror(traceback.format_exc())
            prerror('Failure. Returning: ICT_SETUP_SUDO_FAILED')
            return_status = ICT_SETUP_SUDO_FAILED

        return return_status

    def ict_test(self, optparm=None):
        '''ICT - ict test
        This ict can be used to test the ICT object from the command line.
        It always returns 0
        '''
        _register_task(inspect.currentframe())

        info_msg('ict_test invoked')

        info_msg('optparm: ' + str(optparm))
        info_msg('auto_install: ' + str(self.auto_install))
        info_msg('basedir: ' + str(self.basedir))
        info_msg('bootenvrc: ' + str(self.bootenvrc))
        info_msg('grubmenu: ' + str(self.grubmenu))
        info_msg('is_sparc: ' + str(self.is_sparc))
        info_msg('livecd_install: ' + str(self.livecd_install))
        info_msg('text_install: ' + str(self.text_install))
        info_msg('kbd_device: ' + str(self.kbd_device))
        info_msg('kbd_layout_file: ' + str(self.kbd_layout_file))
        info_msg('rootpool: ' + str(self.rootpool))
        
        return 0

    #end Install Completion Tasks


def exec_ict(ict_name, basedir, debuglvl=None, optparm=None):
    '''run one ICT with a single command line using 'eval()'
    This will be called automatically if 2 or more command line arguments
    are provided
    ict_name - name of ICT in text string
    basedir - root directory of target
    debuglvl - logging service debugging level to override default
    optparm - parameter passed to ict_name if required by ict_name
    returns status of ict_name
    '''
    info_msg('Executing ICT=' + str(ict_name) + ' basedir=' + str(basedir))
    info_msg('    debuglvl=' + str(debuglvl) + ' optparm=' + str(optparm))

    if debuglvl != None:
        info_msg('setting debug level to' + debuglvl)
        myict = ICT(basedir, debuglvl)
    else:
        myict = ICT(basedir)
    if optparm != None:
        status = eval('myict.' + ict_name + '(optparm)')
    else:
        status = eval('myict.' + ict_name + '()')
    sys.exit(status)

if __name__ == '__main__':
    ''' The script is launched in order to run an individual ICT.

    Example command line:  # python ict.py ict_test /a  #runs test ICT
    '''
    # Invoke exec_ict() with the command line parameters, ignoring
    # the first argument, which is always the script name (ict.py).
    exec_ict(*sys.argv[1:])