#! /bin/ksh -p
#
# ident "@(#)scinstall_upgrade.ksh 1.34     02/02/01 SMI"
#
# Copyright 2002 Sun Microsystems, Inc.  All rights reserved.
# Use is subject to license terms.
#

#####################################################
#
# The following information is extracted from the various configuration
# files of the cluster release being upgraded. This information is then
# stored in SC_UPGD_CONFIGFILE for use at different times in the upgrade
# process.
#
# The specific content of the file is:
#
# SC_UPGD_CONFIG_CLUSTER="product_version:cluster_name:volume_manager:\
#	transport_type:number_of_members:location_of_old_configuration"
#
# SC_UPGD_CONFIG_MEMBERS="\
#	node_name_1:private_netif_1,private_netif_2:\
#	    private_ipaddr_1,private_ipaddr_2:virtual_private_ipaddr \
#	node_name_2:private_netif_1,private_netif_2:\
#	    private_ipaddr_1,private_ipaddr_2:virtual_private_ipaddr \
#	... \
# "
#
# SC_UPGD_CONFIG_LHOSTS="\
#	logical_host_hostname_1:disk_group_1,disk_group_2,...:
#	    primary_master,secondary_master,...:\
#	    logical_host_netname1(primary_netif,secondary_netif);
#		logical_host_netname2(primary_netif,secondary_netif);...:
#	    administrative_file_system:failback:data_service \
#	logical_host_hostname_2:disk_group_1,disk_group_2,...:
#	    primary_master,secondary_master,...:\
#	    logical_host_netname1(primary_netif,secondary_netif);
#		logical_host_netname2(primary_netif,secondary_netif);...:
#	    administrative_file_system:failback:
#	    data_service_1,data_service_2,... \
#	... \
# "
#
#####################################################

#####################################################
#
# upgd_get_release() prod_var prod_vers_var
#
#	prod_var			The name of the variable which will
#					contain the product name and version
#					string.
#	prod_vers_var			The name of the variable which will
#					contain a number identifying the
#					product:
#
#					 0	None found
#					2.x	SC2.x
#
#	This function tries to determine the product release that is being
#	upgraded by using the VERSION parameter found in pkginfo(4). In
#	this release, SC2.2 is only supported.
#
#	Return:
#		zero		Success
#		non-zero	Unable to upgrade
#
#####################################################
upgd_get_release()
{
	# check arg
	if [[ $# != 2 ]]; then
		printf "%s:  $(gettext 'Internal error - bad call to upgd_get_release()')\n" ${PROG} >&2
		return 2
	fi

	typeset -r prod_var=$1
	typeset -r prod_vers_var=$2

	typeset dirlist;  set -A dirlist ""
	typeset prodname
	typeset prodversion prodrelease
	integer pkgstatus=1

	eval ${prod_var}=\"\"
	eval ${prod_vers_var}=0

	printf "$(gettext 'Determining cluster release that is being upgraded') ... " | logmsg

	# see if the version has already been determined
	set -A dirlist $(ls -d ${SC_UPGD_PRESERVEDIR}/[1-9]\.* 2>/dev/null)
	if [[ ${#dirlist[*]} > 1 ]]; then
		printf "%s\n\n" ${SC_FAILED} | logmsg
		printf "%s:  $(gettext 'The cluster release could not be determined,')\n" ${PROG} | logerr
		printf "\t$(gettext 'more than one directory beginning with an integer')\n" | logerr
		printf "\t$(gettext 'was found in') \"%s\".\n" ${SC_UPGD_PRESERVEDIR} | logerr
		return 1
	fi
	if [[ ${dirlist[0]##*/} == 2.* ]]; then
		prodversion=${dirlist[0]##*/}
	fi

	#
	# In case upgrade dies during package removal, check last package to be
	# removed if first one has already been removed
	#
	if [[ -z "${prodversion}" ]]; then
		pkgstatus=$(pkginfo -q SUNWscins >/dev/null 2>&1; echo $?)
		if [[ ${pkgstatus} == 0 ]]; then
			prodversion=$(pkgparam SUNWscins VERSION | cut -d, -f1)
		fi
	fi

	if [[ -z "${prodversion}" ]]; then
		printf "%s\n\n" ${SC_FAILED} | logmsg
		printf "%s:  $(gettext 'The cluster release could not be determined.')\n" ${PROG} | logerr
		return 1
	fi

	if [[ "${prodversion}" == 2\.* ]]; then
		prodname="Sun Cluster ${prodversion}"
		prodrelease=2.x
	else
		printf "%s\n" ${SC_DONE} | logmsg
		printf "%s:  $(gettext 'Unsupported upgrade') - Sun Cluster %d\n" ${PROG} ${prodversion} | logmsg
		return 1
	fi

	eval ${prod_var}=\"${prodname}\"
	eval ${prod_vers_var}=${prodrelease}

	SC_UPGD_SAVEDIR=${SC_UPGD_PRESERVEDIR}/${prodversion}

	printf "%s (release=%s)\n" ${SC_DONE} ${prodversion} | logmsg

	return 0
}

#####################################################
#
# upgd_get_2Xclustername() clustname_var
#
#	clustname_var			The name of the variable that will
#					contain the name of the cluster
#
#	Get the cluster name of a SC2.x cluster, set it as the default
#	"clustername", and set the CDBFILE variable.
#
#	Return:
#		zero		Success
#		non-zero	Failure
#
#####################################################
upgd_get_2Xclustername()
{
	# check arg
	if [[ $# != 1 ]]; then
		printf "%s:  $(gettext 'Internal error - bad call to upgrade_get_2Xclustername()')\n" ${PROG} >&2
		return 2
	fi

	typeset -r clustname_var=$1

	typeset clustname_file=
	typeset clustname

	printf "$(gettext 'Determining the cluster name') ... " | logmsg

	if [[ -s ${SC_UPGD_CONFIGROOT}/SUNWcluster/conf/default_clustername ]]; then
		clustname_file=${SC_UPGD_CONFIGROOT}/SUNWcluster/conf/default_clustername
	elif [[ -s ${SC_UPGD_SAVEDIR}/SUNWcluster/conf/default_clustername ]]; then
		clustname_file=${SC_UPGD_SAVEDIR}/SUNWcluster/conf/default_clustername
	else
		printf "%s\n\n" ${SC_FAILED} | logmsg
		printf "%s:  $(gettext 'Unable to determine the name of the cluster.')\n" ${PROG} | logerr
		return 1
	fi

	clustname=$(cat ${clustname_file})
	SC_UPGD_CDBFILE="${clustname}.cdb"

	printf "%s (clustername=%s)\n" ${SC_DONE} ${clustname} | logmsg
	if [[ "${clustname}" != "$(eval print \$\{${clustname_var}\})" && \
	    -n "$(eval print \$\{${clustname_var}\})" ]]; then
		print "" | logmsg
		printf "$(gettext 'Using \"%s\" in place of \"%s\" for the clustername.')\n\n" ${clustname} $(eval print \$\{${clustname_var}\}) | logmsg
	fi

	eval ${clustname_var}=${clustname}

	return 0
}

#####################################################
#
# upgd_is_cluster_member() product_vers prod_name
#
#	prod_vers			The version of the product
#	prod_name			The name of the product
#
#	Return ${SC_FALSE} if this node is NOT a member of the cluster
#	Return ${SC_TRUE} if this node is a member of the cluster
#
#####################################################
upgd_is_cluster_member()
{
	# check arg
	if [[ $# != 2 ]]; then
		printf "%s:  $(gettext 'Internal error - bad call to upgd_is_cluster_member()')\n" ${PROG} >&2
		return 2
	fi

	typeset -r prod_vers=$1
	typeset -r prod_name="$2"

	typeset -r timed_run=${SC_UPGD_SC22BINDIR}/timed_run

	printf "$(gettext 'Ensuring node is not a cluster member') ... " | logmsg

	if [[ "${prod_vers}" == "2.x" ]]; then
		# make sure the package is installed
		pkginfo SUNWsc >/dev/null 2>&1

		# it IS installed
		if [[ $? == 0 ]]; then
			# check if clustd is really running
			if [[ ! -x ${timed_run} ]]; then
				printf "%s\n\n" ${SC_FAILED} | logmsg
				printf "%s:  $(gettext 'Unable to determine cluster membership, assuming it is a member')\n" ${PROG} | logerr
				return ${SC_TRUE}
			fi
			${timed_run} -q 3 clustm getstate ${clustername} >/dev/null 2>&1
			if [[ $? == 0 ]]; then
				printf "%s\n\n" ${SC_DONE} | logmsg
				printf "$(gettext 'The node is running') \"%s\".\n" "${prod_name}" | logmsg
				return ${SC_TRUE}
			fi
		fi
	else
		printf "%s\n\n" ${SC_FAILED} | logmsg
		printf "%s:  $(gettext 'Unable to determine cluster membership, assuming it is a member')\n" ${PROG} | logerr
		return ${SC_TRUE}
	fi

	printf "%s\n" ${SC_DONE} | logmsg

	return ${SC_FALSE}
}

#####################################################
#
# upgd_saveconfig() prod_vers
#
#	prod_vers			The version of the product
#
#	Save the framework and data service configuration files of the
#	release being upgraded. The files will be saved under
#	SC_UPGD_SAVEDIR.
#
#	Return:
#		zero		Success
#		non-zero	Unable to save files
#
#####################################################
upgd_saveconfig()
{
	# check arg
	if [[ $# != 1 ]]; then
		printf "%s:  $(gettext 'Internal error - bad call to upgd_saveconfig()')\n" ${PROG} >&2
		return 2
	fi

	typeset -r prod_version=$1

	typeset proddir
	typeset cmd1 cmd2

	printf "$(gettext 'Copying configuration files to \"%s\"') ... " ${SC_UPGD_SAVEDIR}
	printf "$(gettext 'Copying configuration files to \"%s\"')\n\n" ${SC_UPGD_SAVEDIR} >>${install_log}

	mkdir -p -m 0755 ${SC_UPGD_SAVEDIR} 2>/dev/null
	mkdir -m 0755 ${SC_UPGD_SAVEDIR}/pnm 2>/dev/null
	chgrp sys ${SC_UPGD_SAVEDIR} || return 1

	# save framework configuration files
	cd ${SC_UPGD_CONFIGROOT}
	if [[ "${prod_version}" = "2.x" ]]; then
		proddir="SUNWsc*"

		SC_UPGD_FMWKDIR=${SC_UPGD_SAVEDIR}/SUNWcluster/conf

		cmd1="find SUNWcluster -print"
		cmd2="cpio -pdm ${SC_UPGD_SAVEDIR}"
		print ${cmd1}" | "${cmd2} >>${install_log}
		${cmd1} 2>/dev/null | ${cmd2} >/dev/null 2>&1

		cmd1="cp /etc/pnmconfig ${SC_UPGD_SAVEDIR}/pnm"
		print ${cmd1} >>${install_log}
		${cmd1} >/dev/null 2>&1

		cmd1="cp /opt/SUNWpnm/bin/staticroutes/staticroutes.nafo[0-9]* ${SC_UPGD_SAVEDIR}/pnm"
		print ${cmd1} >>${install_log}
		${cmd1} >/dev/null 2>&1
	else
		proddir="SUNWha*"

		SC_UPGD_FMWKDIR=${SC_UPGD_SAVEDIR}/SUNWhadf/hadf
	fi

	if ls ${proddir} >/dev/null 2>&1
	then
		cmd1="find ${proddir} -print"
		cmd2="cpio -pdm ${SC_UPGD_SAVEDIR}"
		print ${cmd1}" | "${cmd2} >>${install_log}
		${cmd1} 2>/dev/null | ${cmd2} >/dev/null 2>&1
	fi

	if [[ -f ${SC_UPGD_22DIDCONF} ]]; then
		cmd1="cp ${SC_UPGD_22DIDCONF} ${SC_UPGD_SAVEDIR}"
		print ${cmd1} >>${install_log}
		${cmd1} >/dev/null 2>&1
	fi

	cd ${OLDPWD}

	printf "%s\n" ${SC_DONE}
	print "" >>${install_log}

	return 0
}

#####################################################
#
# upgd_gethostname()
#
#	hostaddr		The IP address of the host
#
#	Call a perl script to retrive and print a host name
#	from an IP address.
#
#	Return:
#		zero		Success
#		non-zero	Failure
#
#####################################################
upgd_gethostname()
{
	# check arg
	if [[ $# != 1 ]]; then
		printf "%s:  $(gettext 'Internal error - bad call to upgd_gethostname()')\n" ${PROG} >&2
		return 2
	fi

	typeset hostaddr=${1}

	/usr/bin/perl -s -e '
		use Socket;
		$_ = $ARGV[0];
		if (!/^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)$/) {
			print "$_\n";
			exit(0);
		}
		$iaddr = inet_aton($1);
		$name = gethostbyaddr($iaddr, AF_INET) || exit(1);
		print "$name\n";
		exit(0);
	' ${hostaddr}
	
	return $?
}

#####################################################
#
# upgd_parse2Xconfig()
#
#	prod_vers			The version of the product
#
#	Retrieves the framework configuration information required to
#	set-up the SC3.0 of this cluster. The information will be
#	stored in SC_UPGD_CONFIGFILE for use later upgrade phases. The
#	general format of the data will be
#	TYPE_OF_INFORMATION="data1:data2:data3:...".
#
#	Return:
#		zero		Success
#		non-zero	Failure
#
#####################################################
upgd_parse2Xconfig()
{
	# check arg
	if [[ $# != 1 ]]; then
		printf "%s:  $(gettext 'Internal error - bad call to upgd_parse2Xconfig()')\n" ${PROG} >&2
		return 2
	fi

	typeset -r prod_vers=$1

	typeset vol_mgr
	integer num_nodes
	integer pdbapps
	integer i
	integer j
	typeset nodename pvt_ipaddrs pvt_netif vipaddr
	typeset lname dgname masters netifs dsname admin_fs
	integer maintstate failback
	typeset lhost_row tmp_lhost_row logifs logif
	typeset logip_list;  set -A logip_list ""
	typeset tmp_pdbapps
	typeset tmp_pvt_netif
	typeset tmp_pvt_ipaddr
	typeset tmp_lhost_name

	printf "$(gettext 'Extracting configuration data') ... " | logmsg

	#
	# Extract the following information:
	#
	# SC_UPGD_CONFIG_CLUSTER="product_version:cluster_name:volume_manager:\
	#	transport_type:number_of_members:location_of_old_configuration"
	#

	# retrieve PDBAPPS bit vector
	tmp_pdbapps=$(egrep "^cluster.pdbapps" ${SC_UPGD_FMWKDIR}/${SC_UPGD_CDBFILE} | sed -e "s|.*:[ 	]*||")
	if [[ $? != 0 ]]; then
		printf "%s\n\n" ${SC_FAILED} | logmsg
		printf "%s:  $(gettext 'Problem parsing configuration file'): %s\n" ${PROG} ${SC_UPGD_CDBFILE} | logerr
		return 1
	fi
	let pdbapps=${tmp_pdbapps}

	# extract volume manager being used
	if (( pdbapps & (1 << 0) )); then
		printf "%s\n\n" ${SC_FAILED} | logmsg
		printf "%s:  $(gettext 'Oracle Parallel Server upgrades are not supported')\n" ${PROG} | logerr
		return 1
	elif (( pdbapps & (1 << 3) )); then
		printf "%s\n\n" ${SC_FAILED} | logmsg
		printf "%s:  $(gettext 'CVM upgrades are not supported')\n" ${PROG} | logerr
		return 1
	elif (( pdbapps & (1 << 4) )); then
		vol_mgr=${SC_UPGD_KEY_SSVM}
	elif (( pdbapps & (1 << 5) )); then
		vol_mgr=${SC_UPGD_KEY_SDS}
	else
		printf "%s\n\n" ${SC_FAILED} | logmsg
		printf "%s:  $(gettext 'Unable to determine volume manager from \"cluster.pdbapps\"')\n" ${PROG} | logerr
		return 1
	fi

	# extract transport type
	transport_type=$(egrep "^cluster.net_if_type" ${SC_UPGD_FMWKDIR}/${SC_UPGD_CDBFILE} | sed -e "s|.*:[ 	]*||")
	if [[ $? != 0 ]]; then
		printf "%s\n\n" ${SC_FAILED} | logmsg
		printf "%s:  $(gettext 'Problem parsing configuration file'): %s\n" ${PROG} ${SC_UPGD_CDBFILE} | logerr
		return 1
	fi

	# extract number of nodes
	let num_nodes=$(egrep "^cluster.number.nodes" ${SC_UPGD_FMWKDIR}/${SC_UPGD_CDBFILE} | sed -e "s|.*:[ 	]*||")
	if [[ $? != 0 ]]; then
		printf "%s\n\n" ${SC_FAILED} | logmsg
		printf "%s:  $(gettext 'Problem parsing configuration file'): %s\n" ${PROG} ${SC_UPGD_CDBFILE} | logerr
		return 1
	fi

	#
	# product_version:cluster_name:volume_manager:transport_type:\
	#	number_of_members:location_of_old_configuration
	#
	${PRINTFDEBUG} "SC_UPGD_CONFIG_CLUSTER=\"%s:%s:%s:%s:%d:%s\"\n" ${prod_vers} ${clustername} ${vol_mgr} ${transport_type} ${num_nodes} ${SC_UPGD_SAVEDIR}
	printf "SC_UPGD_CONFIG_CLUSTER=\"%s:%s:%s:%s:%d:%s\"\n" ${prod_vers} ${clustername} ${vol_mgr} ${transport_type} ${num_nodes} ${SC_UPGD_SAVEDIR} > ${tmp_upgdfile}

	#
	# Extract the following information:
	#
	# SC_UPGD_CONFIG_MEMBERS="\
	#	node_name_1:private_netif_1,private_netif_2:\
	#	    private_ipaddr_1,private_ipaddr_2:virtual_private_ipaddr \
	#	node_name_2:private_netif_1,private_netif_2:\
	#	    private_ipaddr_1,private_ipaddr_2:virtual_private_ipaddr \
	#	... \
	# "
	#

	${PRINTDEBUG}
	print "" >> ${tmp_upgdfile}
	${PRINTFDEBUG} "SC_UPGD_CONFIG_MEMBERS=\"\\\\\n"
	printf "SC_UPGD_CONFIG_MEMBERS=\"\\\\\n" >> ${tmp_upgdfile}
	let i=0
	while (( i < ${num_nodes} )); do
		nodename=$(egrep "^cluster.node.${i}.hostname" ${SC_UPGD_FMWKDIR}/${SC_UPGD_CDBFILE} | sed -e "s|.*:[ 	]*||")
		if [[ $? != 0 ]]; then
			printf "%s\n\n" ${SC_FAILED} | logmsg
			printf "%s:  $(gettext 'Problem parsing configuration file'): %s\n" ${PROG} ${SC_UPGD_CDBFILE} | logerr
			return 1
		fi

		vipaddr=$(egrep "^cluster.node.${i}.hahost[ 	]" ${SC_UPGD_FMWKDIR}/${SC_UPGD_CDBFILE} | sed -e "s|.*:[ 	]*||")
		if [[ $? != 0 ]]; then
			printf "%s\n\n" ${SC_FAILED} | logmsg
			printf "%s:  $(gettext 'Problem parsing configuration file'): %s\n" ${PROG} ${SC_UPGD_CDBFILE} | logerr
			return 1
		fi

		pvt_netif=""
		pvt_ipaddrs=""
		let j=0
		while (( j < 2 ))
		do
			tmp_pvt_netif=$(egrep "^cluster.node.${i}.if.${j}" ${SC_UPGD_FMWKDIR}/${SC_UPGD_CDBFILE} | sed -e "s|.*:[ 	]*||")
			if [[ $? != 0 ]]; then
				printf "%s\n\n" ${SC_FAILED} | logmsg
				printf "%s:  $(gettext 'Problem parsing configuration file'): %s\n" ${PROG} ${SC_UPGD_CDBFILE} | logerr
				return 1
			fi

			# replace "scid" with "sci"
			if [[ "${tmp_pvt_netif}" == scid* ]]; then
				tmp_pvt_netif="sci${tmp_pvt_netif##scid}"
			fi

			tmp_pvt_ipaddr=$(egrep "^cluster.node.${i}.phost.${j}" ${SC_UPGD_FMWKDIR}/${SC_UPGD_CDBFILE} | sed -e "s|.*:[ 	]*||")
			if [[ $? != 0 ]]; then
				printf "%s\n\n" ${SC_FAILED} | logmsg
				printf "%s:  $(gettext 'Problem parsing configuration file'): %s\n" ${PROG} ${SC_UPGD_CDBFILE} | logerr
				return 1
			fi

			if (( j == 0 )); then
				pvt_netif=${tmp_pvt_netif}
				pvt_ipaddrs=${tmp_pvt_ipaddr}
			else
				pvt_netif=${pvt_netif}","${tmp_pvt_netif}
				pvt_ipaddrs=${pvt_ipaddrs}","${tmp_pvt_ipaddr}
			fi

			((j += 1))
		done

		#
		# node_name:private_netif_1,private_netif_2:\
		#	private_ipaddr_1,private_ipaddr_2:virtual_private_ipaddr
		#
		${PRINTFDEBUG} "%s:%s:%s:%s \\\\\n" ${nodename} ${pvt_netif} ${pvt_ipaddrs} ${vipaddr}
		printf "%s:%s:%s:%s \\\\\n" ${nodename} ${pvt_netif} ${pvt_ipaddrs} ${vipaddr} >> ${tmp_upgdfile}

		((i += 1))
	done

	${PRINTFDEBUG} "\"\n"
	printf "\"\n" >> ${tmp_upgdfile}

	#
	# Extract the following information:
	#
	# SC_UPGD_CONFIG_LHOSTS="\
	#	logical_host_hostname_1:disk_group_1,disk_group_2,...:
	#	    primary_master,secondary_master,...:\
	#	    logical_host_netname1(primary_netif,secondary_netif);
	#	    logical_host_netname2(primary_netif,secondary_netif);...:
	#	    administrative_file_system:failback:data_service \
	#	logical_host_hostname_2:disk_group_1,disk_group_2,...:
	#	    primary_master,secondary_master,...:\
	#	    logical_host_netname1(primary_netif,secondary_netif);
	#	    logical_host_netname2(primary_netif,secondary_netif);...:
	#	    administrative_file_system:failback:data_service \
	#	... \
	# "
	#
	${PRINTDEBUG}
	print "" >> ${tmp_upgdfile}
	${PRINTFDEBUG} "SC_UPGD_CONFIG_LHOSTS=\"\\\\\n"
	printf "SC_UPGD_CONFIG_LHOSTS=\"\\\\\n" >> ${tmp_upgdfile}

	# extract administrative file system root
	adminfs_root=$(egrep "^cluster.haadmindir" ${SC_UPGD_FMWKDIR}/${SC_UPGD_CDBFILE} | sed -e "s|.*:[ 	]*||")
	if [[ $? != 0 ]]; then
		printf "%s\n\n" ${SC_FAILED} | logmsg
		printf "%s:  $(gettext 'Problem parsing configuration file'): %s\n" ${PROG} ${SC_UPGD_CDBFILE} | logerr
		return 1
	fi
		
	logip_list=$(egrep "^LOGIP:" ${SC_UPGD_FMWKDIR}/${SC_UPGD_CCDFILE} 2>/dev/null)
	egrep "^LOGHOST:" ${SC_UPGD_FMWKDIR}/${SC_UPGD_CCDFILE} 2>/dev/null | while read lhost_row
	do
		if (( $(IFS=: ; set -- ${lhost_row}; print $#) != 6 )); then
			printf "%s\n\n" ${SC_FAILED} | logmsg
			printf "%s:  $(gettext 'Problem parsing configuration file'): %s\n" ${PROG} ${SC_UPGD_CCDFILE} | logerr
			return 1
		fi

		lname=$(IFS=: ; set -- ${lhost_row}; print $2)
		dgname=$(IFS=: ; set -- ${lhost_row}; print $4)
		masters=$(IFS=: ; set -- ${lhost_row}; print $3)
		logifs=$(IFS=: ; set -- ${lhost_row}; print $5)
		let failback=$(IFS=: ; set -- ${lhost_row}; print $6)

		let maintstate=$(egrep "^LOGHOST_MSTATE:${lname}:" ${SC_UPGD_FMWKDIR}/${SC_UPGD_CCDFILE} | sed -e "s|.*:[ 	]*||")
		if (( maintstate == 0 )); then
			printf "%s\n\n" ${SC_FAILED} | logmsg
			printf "%s:  $(gettext '"%s" is in the maintenance state - aborting upgrade')\n" ${PROG} ${lname} | logerr
			return 1
		fi

		netifs=""
		for logif in $(IFS=, ; set -- ${logifs}; print $*)
		do
			for tmp_lhost_row in ${logip_list}
			do
				if (( $(IFS=: ; set -- ${tmp_lhost_row}; print $#) != 5 )); then
					printf "%s\n\n" ${SC_FAILED} | logmsg
					printf "%s:  $(gettext 'Problem parsing configuration file'): %s\n" ${PROG} ${SC_UPGD_CCDFILE} | logerr
					return 1
				fi

				if [[ $(IFS=: ; set -- ${tmp_lhost_row}; print $5) != ${logif} ]]; then
					continue
				fi

				tmp_lhost_name=$(IFS=: ; set -- ${tmp_lhost_row}; upgd_gethostname $4)
				if [[ $? != 0 ]]; then
					printf "%s\n\n" ${SC_FAILED} | logmsg
					printf "%s:  $(gettext 'Problem parsing configuration file'): %s\n" ${PROG} ${SC_UPGD_CCDFILE} | logerr
					printf "%s:  $(gettext 'Invalid logical host name')\n" ${PROG} | logerr
				fi
				if [[ -z ${netifs} ]]; then
					netifs="${tmp_lhost_name}("$(IFS=: ; set -- ${tmp_lhost_row}; print $3)")"
				else
					netifs="${netifs};${tmp_lhost_name}("$(IFS=: ; set -- ${tmp_lhost_row}; print $3)")"
				fi
			done
		done

		#
		# This loop retrieves logical host-data service associations.
		#
		# NOTE: There may be more than one data service associated
		#	with a logical host
		#
		egrep "^LOGHOST_DS:" ${SC_UPGD_FMWKDIR}/${SC_UPGD_CCDFILE} 2>/dev/null | while read tmp_lhost_row
		do
			if (( $(IFS=: ; set -- ${tmp_lhost_row}; print $#) != 3 )); then
				printf "%s\n\n" ${SC_FAILED} | logmsg
				printf "%s:  $(gettext 'Problem parsing configuration file'): %s\n" ${PROG} ${SC_UPGD_CCDFILE} | logerr
				return 1
			fi

			if [[ "$(IFS=: ; set -- ${tmp_lhost_row}; print $2)" == "${lname}" ]]; then
				if [[ -z ${dsname} ]]; then
					dsname=$(IFS=: ; set -- ${tmp_lhost_row}; print $3)
				else
					dsname="${dsname},$(IFS=: ; set -- ${tmp_lhost_row}; print $3)"
				fi
			fi
		done

		#
		# logical_host_hostname_1:disk_group_1,disk_group_2,...:
		#	primary_master,secondary_master,...:
		#	logical_host_netname1(primary_netif,secondary_netif);
		#	logical_host_netname2(primary_netif,secondary_netif);...
		#	:administrative_file_system:failback:data_service
		#
		${PRINTFDEBUG} "%s:%s:%s:%s:%s:%d:%s \\\\\n" ${lname} ${dgname} ${masters} ${netifs} "${adminfs_root}${lname}" ${failback} ${dsname}
		printf "%s:%s:%s:%s:%s:%d:%s \\\\\n" ${lname} ${dgname} ${masters} ${netifs} "${adminfs_root}${lname}" ${failback} ${dsname} >> ${tmp_upgdfile}
	done
	${PRINTFDEBUG} "\"\n"
	printf "\"\n" >> ${tmp_upgdfile}

	if [[ -s ${SC_UPGD_CONFIGFILE} ]]; then
		rm -f ${SC_UPGD_CONFIGFILE}
	fi
	mv ${tmp_upgdfile} ${SC_UPGD_CONFIGFILE} >/dev/null 2>&1

	printf "%s\n" ${SC_DONE} | logmsg

	return 0
}

#####################################################
#
# upgd_validate_sys() clust_config "clust_mbrs" "lhost_list"
#
#	"clust_config"			contents of SC_UPGD_CONFIG_CLUSTER
#	"clust_mbrs"			array form of SC_UPGD_CONFIG_MEMBERS
#	"lhost_list"			array form of SC_UPGD_CONFIG_LHOSTS
#
#	Ensure the system is capable of running SC3.0. The system type,
#	types of private interconnect adapters, and the OS are verified
#	against lists of valid configurations found in scinstall_validate.
#	This check can be overidden by using '-O'.
#
#	Return:
#		zero		Success
#		non-zero	Unable to upgrade
#
#####################################################
upgd_validate_sys()
{
	# check arg
	if [[ $# != 3 ]]; then
		printf "%s:  $(gettext 'Internal error - bad call to upgd_validate_sys()')\n" ${PROG} >&2
		return 2
	fi

	typeset -r clust_config=$1
	typeset clust_mbrs;  set -A clust_mbrs $2
	typeset lhost_list;  set -A lhost_list $3

	#
	# Variables which will be set from the scinstall upgrade_lists file
	#
	typeset upgd_valid_os			# array of valid OS'
	typeset upgd_valid_plat			# array of valid platforms
	typeset upgd_valid_pvt_adap		# array of private adapters

	typeset -r prodvers=$(IFS=: ; set -- ${clust_config}; print $1)
	typeset -r xprttype=$(IFS=: ; set -- ${clust_config}; print $4)
	typeset -r volmgr=$(IFS=: ; set -- ${clust_config}; print $3)
	typeset -r myos="$(uname -r)"
	typeset -r mysys="$(uname -i)"
	typeset errbuf;  set -A errbuf ""
	typeset msgbuf tmpmsgbuf
	integer validflag
	integer i
	integer errbufidx=0
	integer errbufsiz
	integer fooint
	typeset entry entry2
	typeset node adapters adptr

	printf "$(gettext 'Determining validity of system') ... " | logmsg

	loadlib ${SC_SCLIBDIR}/${SC_HW_LISTS} ${SC_LOADED_HW}
	let SC_LOADED_HW=${SC_TRUE}

	# validate os
	let i=0
	let validflag=0
	for entry in ${upgd_valid_os[*]}
	do
		if [[ "${entry}" == "${myos}" ]]; then
			if [[ -x /usr/sbin/clinfo ]]; then
				((validflag += 1))
			fi
			break
		fi
	done
	if (( validflag == 0 )); then
		errbuf[errbufidx]="$(gettext 'Invalid OS'): \"${myos}\""
		((errbufidx += 1))
	fi

	# validate server
	let validflag=0
	for entry in ${upgd_valid_plat[*]}
	do
		if [[ "${entry}" == "${mysys}" ]]; then
			((validflag += 1))
			break
		fi
	done
	if (( validflag == 0 )); then
		errbuf[errbufidx]="$(gettext 'Invalid platform'): \"${mysys}\""
		((errbufidx += 1))
	fi

	#
	# validate storage
	# NOTE: This is a next to impossible task to accomplish so only this
	#	check for SSAs will be performed.
	#
	cmd1="find /devices -type d -print"
	cmd2="egrep SUNW,pln"
	${PRINTDEBUG} "${cmd1} | ${cmd2}"
	${cmd1} 2>/dev/null | ${cmd2} >/dev/null 2>&1
	if [[ $? == 0 ]]; then
		errbuf[errbufidx]="$(gettext 'Invalid storage'): \"SSA\""
		((errbufidx += 1))
	fi

	# validate private interconnect
	if [[ "${xprttype}" == "ether" ]]; then
		let validflag=0
		tmpmsgbuf=""
		for entry in ${clust_mbrs[*]}
		do
			node=$(IFS=: ; set -- ${entry}; print $1)
			adapters=$(IFS=: ; set -- ${entry}; print $2)

			for adptr in $(IFS=, ; set -- ${adapters}; print $*)
			do
				for entry2 in ${upgd_valid_pvt_adap[*]}
				do
					if [[ "${entry2}" == "${adptr%%[0-9]*}" ]]; then
						((validflag += 1))
						break
					fi
				done
				if (( validflag == 0 )); then
					if [[ -z ${tmpmsgbuf} ]]; then
						tmpmsgbuf=${adptr}
					else
						tmpmsgbuf="${tmpmsgbuf}, ${adptr}"
					fi
				fi
			done
			if [[ -n ${tmpmsgbuf} ]]; then
				if [[ -z ${msgbuf} ]]; then
					msgbuf="${node}(${msgbuf})"
				else
					msgbuf="${msgbuf}, ${node}(${msgbuf})"
				fi
			fi
		done
		if [[ -n ${msgbuf} ]]; then
			errbuf[errbufidx]="$(gettext 'Invalid public network adapters'): ${msgbuf}"
			((errbufidx += 1))
			errbuf[errbufidx]="\t${msgbuf}"
			((errbufidx += 1))
		fi
	fi

	# in the case of DiskSuite, maker sure it's been upgraded
	if [[ "${volmgr}" == "${SC_UPGD_KEY_SDS}" ]]; then
		whence ${SC_UPGD_SDS_SETCONVERT} >/dev/null 2>&1
		if [[ $? != 0 ]]; then
			errbuf[errbufidx]="$(gettext 'Incorrect release of volume manager')"
			((errbufidx += 1))
		fi
	fi

	# print out summary of errors
	if [[ -n ${errbuf[*]} ]]; then
		printf "%s\n\n" ${SC_FAILED} | logmsg
		printf "$(gettext 'The following problems were discovered'):\n" | logmsg

		let errbufsiz=${errbufidx}
		let i=0
		while (( i < errbufsiz ))
		do
			printf "\t\t- %s\n" "${errbuf[i]}" | logmsg
			((i += 1))
		done
		return 1
	fi

	printf "%s\n" ${SC_DONE} | logmsg

	return 0
}

#####################################################
#
# upgd_rm22pkgs() prod_name
#
#	prod_name			The name of the product
#
#	Remove all SC2.x software. All SC2.x data service, client, and
#	framework packages found on the system will be removed.
#
#	Return:
#		zero		Success
#		non-zero	Failure
#
#####################################################
upgd_rm22pkgs()
{
	# check arg
	if [[ $# != 1 ]]; then
		printf "%s:  $(gettext 'Internal error - bad call to upgd_rm22pkgs()')\n" ${PROG} >&2
		return 2
	fi

	typeset -r prod_name="$1"

	typeset rmpkgs;  set -A rmpkgs ""
	integer result
	integer i j

	# all possible 2.x framework packages; order is important
	typeset pkgs_framework;  set -A pkgs_framework "\
	    SUNWscds SUNWscpro SUNWscidx SUNWsmax SUNWsma SUNWscid SUNWsci \
	    SUNWudlmx SUNWudlm SUNWdidx SUNWdid SUNWmondx SUNWsclbx SUNWffx \
	    SUNWscmgr SUNWsccf SUNWscman SUNWpnm SUNWmond SUNWff SUNWcmm \
	    SUNWccd SUNWsc SUNWsclb SUNWscins"

	# all possible 2.x client packages; order is important
	typeset pkgs_client;  set -A pkgs_client "\
	    SUNWscmgr SUNWscsdb SUNWcsnmp SUNWccp SUNWccon SUNWscch"

	#
	# All data service packages depend on SUNWsccf. This section of
	# code determines the data service packages that are installed
	# by attempting to remove SUNWsccf and parsing the error output
	# for the list of packages that depend on SUNWsccf; these will
	# be the data service packages. The expected format of the error
	# message is:
	#
	# [...]
	# WARNING:
	#     The <SUNWschtt> package depends on the package
	#     currently being removed.
	# [...]
	#
	let i=0
	pkginfo SUNWscds >/dev/null 2>&1
	if [[ $? == 0 ]]; then
		pkgrm -n -a ${adminfile} SUNWsccf 2>&1 | while read line
		do
			case "${line}" in
			'WARNING:')
				read line
				if [[ "${line}" == *depends* ]]; then
					rmpkgs[i]=$(expr "${line}" : '.*<\(SUNW.*\)>')
					((i += 1))
				fi
				;;
			*)
				;;
			esac
		done
	fi

	if [[ -n "${rmpkgs[*]}" ]]; then
		print "" | logmsg
		printf "$(gettext 'Removing "%s" HA Data Service packages')\n" "${prod_name}" | logmsg
		remove_packages "${rmpkgs[*]}"
		if [[ $? != 0 ]]; then
			printf "%s:  $(gettext 'Could not remove all HA Data Service packages').\n" ${PROG} | logerr
			return 1
		fi
	fi

	#
	# remove framework packages
	#
	pkginfo SUNWscins >/dev/null 2>&1
	if [[ $? == 0 ]]; then

		# remove client packages if installed
		pkginfo SUNWscch >/dev/null 2>&1
		if [[ $? == 0 ]]; then
			print "" | logmsg
			printf "$(gettext 'Removing "%s" Client packages')\n" "${prod_name}" | logmsg
			remove_packages "${pkgs_client[*]}" || return 1
		fi

		# remove framework packages
		print "" | logmsg
		printf "$(gettext 'Removing "%s" Framework packages')\n" "${prod_name}" | logmsg
		remove_packages "${pkgs_framework[*]}" || return 1
	fi

	# remove any remaining SC2.x-specific files and directories
	rm -f /etc/pnmconfig /etc/sma.config >/dev/null 2>&1
	rm -fr /etc/opt/SUNWcluster >/dev/null 2>&1
	rmdir /var/opt/oracle >/dev/null 2>&1

	return 0
}

#####################################################
#
# upgd_migrate_pnm() prod_vers
#
#	prod_vers			The version of the product
#
#	Move PNM configuration files to SC3.0 location
#
#	Return:
#		zero		Success
#		non-zero	Failure
#
#####################################################
upgd_migrate_pnm()
{
	# check arg
	if [[ $# != 1 ]]; then
		printf "%s:  $(gettext 'Internal error - bad call to upgd_migrate_pnm()')\n" ${PROG} >&2
		return 2
	fi

	typeset -r prod_vers=$1

	printf "$(gettext 'Migrating PNM configuration') ... " | logmsg

	if [[ "${prod_vers}" == "2.x" ]]; then
		cp ${SC_UPGD_SAVEDIR}/pnm/pnmconfig ${SC_UPGD_PNMDIR}/${SC_UPGD_PNMCONFIG} >/dev/null 2>&1
		cp ${SC_UPGD_SAVEDIR}/pnm/staticroutes.nafo[0-9]* ${SC_UPGD_PNMDIR} >/dev/null 2>&1
		cat << EOC >${SC_UPGD_PNMDIR}/${SC_UPGD_PNMPARAMS}
#
# PNM Tuneable parameters.  See pnmd(1M) man page for further details.
#

EOC
		egrep "^pnmd." ${SC_UPGD_FMWKDIR}/${SC_UPGD_CDBFILE} >> ${SC_UPGD_PNMDIR}/${SC_UPGD_PNMPARAMS}
	fi

	printf "%s\n" ${SC_DONE} | logmsg

	return 0
}

#####################################################
#
# upgd_transport() "clust_members" sponsornode clustname transp_type num_nodes adapter_opts_var bb_opts_var cable_opts_var
#
#	"clust_members"			array form of SC_UPGD_CONFIG_MEMBERS
#	sponsornode			remote node for connection
#	clustname			name of the cluster
#	transp_type			type of transport ("ether" or "SCI")
#	num_nodes			number of cluster members
#	adapter_opts_var		name of variable containing
#					"-A <adapter_options> ..."
#	bb_opts_var			name of variable containing
#					"-B <blackbox_options> ..."
#	cable_opts_var			name of variable containing
#					"-m <cable_options> ..."
#
#	Using the relevant data from the SC2.x configuration, generate
#	the necessary adapter and cable connections for the
#	transport. The output will be in the same forms supplied to
#	initialize_framework() which will be eventually used by upgrade.
#
#	Return:
#		zero		Success
#		non-zero	Failure
#
#####################################################
upgd_transport()
{
	if [[ $# != 8 ]]; then
		printf "%s:  $(gettext 'Internal error - bad call to upgd_transport()')\n" ${PROG} >&2
		return 2
	fi

	typeset clust_members;  set -A clust_members $1
	typeset -r sponsornode=$2
	typeset -r clustname=$3
	typeset -r transp_type=$4
	integer -r num_nodes=$5
	typeset -r adapter_opts_var=$6
	typeset -r bb_opts_var=$7
	typeset -r cable_opts_var=$8

	typeset nodename=
	typeset adapt_opts bbox_opts cbl_opts
	typeset adapters sadapters adapter1 adapter2 sadapter1 sadapter2
	typeset cbl_opt1 cbl_opt2
	typeset sciport=
	integer i

	printf "$(gettext 'Migrating private cluster transport') ... " | logmsg

	# 2-node cluster
	if (( num_nodes == 2 )); then 
		let i=0
		while (( i < ${num_nodes} ))
		do
			nodename=$(IFS=: ; set -- ${clust_members[i]}; print $1)

			# this is first node, only specify adapters
			if [[ ${sponsornode} == ${mynodename} ]]; then
				
				if [[ ${nodename} == ${mynodename} ]]; then
					adapters=$(IFS=: ; set -- ${clust_members[i]}; print $2)
					adapt_opts="-A "$(IFS=, ; set -- ${adapters}; print $1)" -A "$(IFS=, ; set -- ${adapters}; print $2)
					adapt_opts="$(expand_adapter_options "${adapt_opts}")"

					break
				fi

			# this is second node, specify adapters and cables
			else
				if [[ ${nodename} == ${sponsornode} ]]; then
					sadapters=$(IFS=: ; set -- ${clust_members[i]}; print $2)
					sadapter1=$(IFS=,; set -- ${sadapters}; print $1)
					sadapter2=$(IFS=,; set -- ${sadapters}; print $2)
				elif [[ ${nodename} == ${mynodename} ]]; then
					adapters=$(IFS=: ; set -- ${clust_members[i]}; print $2)
					adapter1=$(IFS=, ; set -- ${adapters}; print $1)
					adapter2=$(IFS=, ; set -- ${adapters}; print $2)
					adapt_opts="-A "${adapter1}" -A "${adapter2}
					adapt_opts="$(expand_adapter_options "${adapt_opts}")"
				fi

				if [[ -n ${sadapters} && -n ${adapters} ]]; then
					cbl_opt1="-m endpoint=${sponsornode}:${sadapter1},endpoint=${mynodename}:${adapter1}"
					cbl_opt2="-m endpoint=${sponsornode}:${sadapter2},endpoint=${mynodename}:${adapter2}"
					cbl_opts="${cbl_opt1} ${cbl_opt2}"
					break
				fi
			fi

			((i += 1))
		done

	# greater than 2-node cluster
	elif (( num_nodes > 2 )); then

		# only set junctions if running on first node
		if [[ ${sponsornode} == ${mynodename} ]]; then
			bbox_opts="-B ${SC_DFLT_JUNCTION_NAME1} -B ${SC_DFLT_JUNCTION_NAME2}"
			bbox_opts="$(expand_bb_options "${bbox_opts}")"
			eval ${bb_opts_var}=\"${bbox_opts}\"
		fi

		let i=0
		while (( i < ${num_nodes} ))
		do
			nodename=$(IFS=: ; set -- ${clust_members[i]}; print $1)
				
			if [[ ${nodename} == ${mynodename} ]]; then
				adapters=$(IFS=: ; set -- ${clust_members[i]}; print $2)
				adapter1=$(IFS=, ; set -- ${adapters}; print $1)
				adapter2=$(IFS=, ; set -- ${adapters}; print $2)
				adapt_opts="-A "${adapter1}" -A "${adapter2}
				adapt_opts="$(expand_adapter_options "${adapt_opts}")"

				#
				# SCI Dolphin switches, the ones used by
				# SC2.x clusters, have ports that range
				# from 0-3.  Also, these switches are
				# dumb so don't have to be "correct"
				# with connections.
				#
				if [[ "${transp_type}" == "SCI" ]]; then
					sciport="@${i}"
				fi

				cbl_opt1="-m endpoint=${nodename}:${adapter1},endpoint=${SC_DFLT_JUNCTION_NAME1}${sciport}"
				cbl_opt2="-m endpoint=${nodename}:${adapter2},endpoint=${SC_DFLT_JUNCTION_NAME2}${sciport}"
				cbl_opts="${cbl_opt1} ${cbl_opt2}"

				sciport=""

				break
			fi
			((i += 1))
		done
	fi

	eval ${adapter_opts_var}=\"${adapt_opts}\"
	eval ${cable_opts_var}=\"${cbl_opts}\"

	printf "%s\n" ${SC_DONE} | logmsg

	return 0
}

#####################################################
#
# upgd_begin() cdimagebasedir skip_hw clustname_var adapt_options_var bbox_options_var cbl_options_var
#
#	cdimagebasedir			CD image base directory
#	skip_hw				boolean that controls hardware
#					validation
#	clustname_var			name of the variable that contains the
#					name of the cluster
#	adapt_options_var		name of the variable that contains
#					"-A <adapter_options> ..."
#	bbox_options_var		name of the variable that contains
#					"-B <blackbox_options> ..."
#	cbl_options_var			name of the variable that contains
#					"-m <cable_options> ..."
#
#	Execute the first phase of upgrading a cluster:
#		- Release being upgraded is determined
#		- Configuration files are saved
#		- Extract relevant framework configuration information
#		- Validation checks
#		- Remove packages of old release
#		- Install SC3.0 packages
#		- Prepare data for initializing cluster
#
#	Programming Note: SC_UPGD_SAVEDIR is only set in
#		upgd_get_release() and used by various upgd_begin()
#		functions. It is not used by any second phase
#		functions. It should either be replaced with local
#		variables or the second phase functions should set it
#		and use it or leave everything alone just because it is
#		easier this way.
#
#	Return:
#		zero		Success
#		non-zero	Failure
#
#####################################################
upgd_begin()
{
	# check arg
	if [[ $# != 6 ]]; then
		printf "%s:  $(gettext 'Internal error - bad call to upgd_begin()')\n" ${PROG} >&2
		return 2
	fi

	typeset -r cdimagebasedir=$1
	integer -r skip_hw=$2
	typeset -r clustname_var=$3
	typeset -r adapt_options_var=$4
	typeset -r bbox_options_var=$5
	typeset -r cbl_options_var=$6

	typeset cname=$(eval print \$\{${clustname_var}\})
	typeset product_string product_vers
	typeset a_opts b_opts c_opts
	typeset transp_type
	integer i
	integer num_nodes
	typeset clust_members; set -A clust_members ""
	typeset tmp_string

	# create the upgrade flag file
	if [[ ! -f ${SC_UPGD_FLAGFILE} ]]; then
		touch ${SC_UPGD_FLAGFILE}
	fi

	# determine current version of cluster software
	upgd_get_release product_string product_vers || return 1

	#
	# ensure node being upgraded is not part of cluster
	#
	# if upgrading from SC2.x, use SC2.x cname
	#
	if [[ "${product_vers}" == "2.x" ]]; then
		upgd_get_2Xclustername cname || return 1
	fi
	eval ${clustname_var}=${cname}

	# make sure not a cluster member
	upgd_is_cluster_member ${product_vers} "${product_string}"
	if [[ $? == ${SC_TRUE} ]]; then 
		return 1
	fi

	# preserve configuration files
	upgd_saveconfig "${product_vers}" || return 1

	#
	# parse configuration
	#
	# If SC_UPGD_CONFIGFILE exists, it is assumed that a previous invocation
	# of ${PROG} created it and parsing the old configuration will be
	# skipped.
	#
	if [[ ! -s ${SC_UPGD_CONFIGFILE} ]]; then
		if [[ "${product_vers}" == "2.x" ]]; then
			upgd_parse2Xconfig ${product_vers} || return 1
		fi

	fi

	# retrieve configuration
	loadlib ${SC_UPGD_CONFIGFILE} ${SC_LOADED_CONFIGFILE}
	let SC_LOADED_CONFIGFILE=${SC_TRUE}

	# add path to volume manager executables
	if [[ "$(IFS=: ; set -- ${SC_UPGD_CONFIG_CLUSTER}; print $3)" == "${SC_UPGD_KEY_SSVM}" ]]; then
		PATH=/etc/vx/bin:${PATH}
	fi

	# validate hardware and OS
	if (( skip_hw == SC_FALSE )); then
		upgd_validate_sys "${SC_UPGD_CONFIG_CLUSTER}" "${SC_UPGD_CONFIG_MEMBERS}" "${SC_UPGD_CONFIG_LHOSTS}"  || return 1
	fi

	cname=$(IFS=: ; set -- ${SC_UPGD_CONFIG_CLUSTER}; print $2)
	transp_type=$(IFS=: ; set -- ${SC_UPGD_CONFIG_CLUSTER}; print $4)
	let num_nodes=$(IFS=: ; set -- ${SC_UPGD_CONFIG_CLUSTER}; print $5)

	# handle private interconnect transition
	set -A clust_members ${SC_UPGD_CONFIG_MEMBERS}
	if [[ "${product_vers}" == "2.x" ]]; then
		print "" | logmsg
		printf "$(gettext 'There are several entries in \"/.rhosts\" that you may want to')\n" | logmsg
		printf "$(gettext 'remove for security purposes. They look like the following:')\n\n" | logmsg
		let i=0
		while (( i < ${num_nodes} ))
		do
			printf "\t%s root\n" $(IFS=: ; set -- ${clust_members[i]}; print $4) | logmsg
			tmp_string=$(IFS=: ; set -- ${clust_members[i]}; print $3)
			printf "\t%s root\n" $(IFS=, ; set -- ${tmp_string}; print $1) | logmsg
			printf "\t%s root\n" $(IFS=, ; set -- ${tmp_string}; print $2) | logmsg

			((i += 1))
		done
	fi
	print "" | logmsg

	# install software
	if [[ "${product_vers}" == "2.x" ]]; then
		upgd_rm22pkgs "${product_string}" || return 1
	fi
	installframework ${cdimagebasedir} || return 1

	#
	# root was removed from sysadmin in /etc/group by the removal of
	# the SUNWsccf package.  Put root back.
	#
	if [[ "$(IFS=: ; set -- ${SC_UPGD_CONFIG_CLUSTER}; print $3)" == "${SC_UPGD_KEY_SDS}" ]]; then
		printf "$(gettext 'Ensuring "root" is in the "sysadmin" group') ... " | logmsg
		sed '/^sysadmin:/s/,root$//
		    /^sysadmin:/s/:root$/:/
		    /^sysadmin:/s/\([:,]\)root,/\1/
		    /^sysadmin:[^:]*:14:..*$/s/$/,root/
		    /^sysadmin:[^:]*:14:$/s/$/root/' /etc/group > ${tmp_upgdfile}
		if [[ $? != 0 ]]; then
			printf "%s\n\n" ${SC_FAILED} | logerr
			printf "%s:  $(gettext 'Problem adding "root" to the sysadmin group in /etc/group.')\n" ${PROG} | logerr
			printf "\t$(gettext 'Please add it manually and re-run this command.')\n" | logerr
			return 1
		fi
		cp ${tmp_upgdfile} /etc/group >/dev/null 2>&1
		if [[ $? != 0 ]]; then
			printf "%s\n\n" ${SC_FAILED} | logerr
			printf "%s:  $(gettext 'Problem adding "root" to the sysadmin group in /etc/group.')\n" ${PROG} | logerr
			printf "\t$(gettext 'Please add it manually and re-run this command.')\n" | logerr
			return 1
		fi
		rm -f ${tmp_upgdfile}
		printf "%s\n" ${SC_DONE} | logmsg
	fi

	# migrate DID configuration
	if [[ -s ${SC_UPGD_22DIDCONF} ]]; then
		printf "$(gettext 'Migrating DID configuration') ... " | logmsg

		# migration will fail if SC_UPGD_30DIDCONF exists
		if [[ -f ${SC_UPGD_30DIDCONF} ]]; then
			rm -f ${SC_UPGD_30DIDCONF} >/dev/null 2>&1
		fi

		cmd="scdidadm -U"
		${PRINTDEBUG} ${cmd}
		${cmd} >${tmperrs} 2>&1
		if [[ $? != 0 && $? != 2 ]]; then
			printf "%s\n\n" ${SC_FAILED} | logmsg
			printf "%s:  $(gettext 'Problem migrating DID configuration')\n" ${PROG} | logerr
			cat ${tmperrs}

			return 1
		else
			rm -f ${SC_UPGD_22DIDCONF} >/dev/null 2>&1
			printf "%s\n" ${SC_DONE} | logmsg
		fi
	fi

	# restore PNM configuration
	upgd_migrate_pnm ${product_vers} || return 1

	# generate transport arguments used by initialize_cluster
	upgd_transport "${SC_UPGD_CONFIG_MEMBERS}" ${sponsornode} ${cname} ${transp_type} ${num_nodes} a_opts b_opts c_opts || return 1
	eval ${adapt_options_var}=\"${a_opts}\"
	eval ${bbox_options_var}=\"${b_opts}\"
	eval ${cbl_options_var}=\"${c_opts}\"

	return 0
}

#####################################################
#
# upgd_prefinish_checks() "clust_members" "lhost_list" num_mbrs
#
#	"clust_members"			array form of SC_UPGD_CONFIG_MEMBERS
#	"lhost_list"			array form of SC_UPGD_CONFIG_LHOSTS
#	num_mbrs			number of cluster members
#
#	Ensure that conditions are right to complete second phase:
#		- No logical hosts should be up
#		- All cluster members should be present and "Online"
#
#	Return:
#		zero		Success
#		non-zero	Unable to upgrade
#
#####################################################
upgd_prefinish_checks()
{
	# check arg
	if [[ $# != 3 ]]; then
		printf "%s:  $(gettext 'Internal error - bad call to upgd_prefinish_checks()')\n" ${PROG} >&2
		return 2
	fi

	typeset clust_members;  set -A clust_members $1
	typeset lhost_list;  set -A lhost_list $2
	typeset -r num_mbrs=$3

	typeset errbuf
	typeset entry entry2 key
	integer i j
	typeset nodestatus;  set -A nodestatus ""
	typeset mbrlist;  set -A mbrlist ""

	printf "$(gettext 'Validating system') ... " | logmsg

	#
	# number of "Node Name:"-"Online" pairs in 'scstat -n' output should
	# match num_nodes
	#
	let i=0
	for entry in ${clust_members[*]}
	do
		mbrlist[i]=$(IFS=: ; set -- ${entry}; print $1)
		((i += 1))
	done

	let i=0
	LANG=C scstat -n 2>/dev/null | while read entry 
	do
		case "${entry}" in
		'Cluster node:'*)
			key=$(set -- ${entry}; print "$3")
			nodestatus[i]=$(set -- ${entry}; print "$3:$4")
			
			# remove any node that is in a cluster member from array
			let j=0
			while [[ ${j} < ${#mbrlist[*]} ]]
			do
				if [[ "${mbrlist[j]}" == "${key}" ]]; then
					mbrlist[j]=""
				fi
				((j += 1))
			done
			((i += 1))
			;;
		*)
			;;
		esac
	done

	errbuf=""
	for entry in ${mbrlist[*]}
	do
		if [[ -n ${entry} ]]; then
			if [[ -z ${errbuf} ]]; then
				errbuf=${entry}
			else
				errbuf="${errbuf}, ${entry}"
			fi
		fi
	done
	if [[ -n ${errbuf} ]]; then
		printf "%s\n" ${SC_DONE} | logmsg
		printf "$(gettext 'Upgrade cannot continue - these nodes are not cluster members'):\n" | logmsg
		printf "\t%s\n" "${errbuf}" | logmsg
		return 1
	fi

	let i=0
	errbuf=""
	for entry in ${clust_members[*]}
	do
		key=$(IFS=: ; set -- ${entry}; print $1)

		for entry2 in ${nodestatus[*]}
		do
			if [[ "$(IFS=: ; set -- ${entry2}; print $1)" == "${key}" ]]; then
				if [[ "$(IFS=: ; set -- ${entry2}; print $2)" != "Online" ]]; then
					if [[ -z ${errbuf} ]]; then
						errbuf=${key}
					else
						errbuf=${errbuf}", "${key}
					fi
				fi
			fi
		done
		((i += 1))
	done
	if [[ -n ${errbuf} ]]; then
		printf "%s\n" ${SC_DONE} | logmsg
		printf "$(gettext 'Upgrade cannot continue - these nodes are in the wrong state'):\n" | logerr
		printf "\t%s\n" "${errbuf}" | logmsg
		return 1
	fi

	printf "%s\n" ${SC_DONE} | logmsg

	return 0
}

#####################################################
#
# upgd_get_30config() numnodes pvt_node_list_var low_node_list_var
#
#	numnodes			number of cluster members
#	pvt_node_list_var		name of the variable that is the array
#					containing the list of cluster members
#					and their private host names
#	low_node_list_var		name of the variable that is the array
#					containing the list of cluster members
#					and their node IDs in ascending order
#
#	From scconf(1M) output:
#		- associate each cluster member with its private host name
#		- determine the node with the lowest node ID
#
#	Return:
#		zero		Success
#		non-zero	Failure
#
#####################################################
upgd_get_30config()
{
	# check arg
	if [[ $# != 3 ]]; then
		printf "%s:  $(gettext 'Internal error - bad call to upgd_get_30config()')\n" ${PROG} >&2
		return 2
	fi

	integer -r numnodes=$1
	typeset -r pvt_node_list_var=$2
	typeset -r low_node_list_var=$3

	typeset entry
	typeset nidlist;  set -A nidlist ""
	typeset foo
	integer i j jth jnxt

	let i=0
	let j=0
	LANG=C scconf -pv | while read entry
	do
		case "${entry}" in
		*'Node private hostname:'*)
			eval ${pvt_node_list_var}[i]=$(expr "${entry}" : '(\([a-z]*.*\))')":"$(IFS=: ; set -- ${entry}; print $2 | sed -e 's: ::g')
			((i += 1))
			;;
		*'Node ID:'*)
			nidlist[j]=$(expr "${entry}" : '(\([a-z]*.*\))')":"$(set -- ${entry}; print $4)
			((j += 1))
			;;
		esac

		# skip device group info
		if (( i == numnodes && j == numnodes )); then
			break
		fi
	done
	if [[ $? != 0 ]]; then
		printf "%s:  $(gettext 'Error - could not determine cluster configuration').\n" ${PROG} | logerr
		return 1
	fi

	# sort array in ascending order by node ID
	let i=0
	while (( i < numnodes - 1 ))
	do
		let j=0
		while (( j < numnodes - i - 1 ))
		do
			let jth=$(IFS=: ; set -- ${nidlist[j]}; print $2)
			let jnxt=$(IFS=: ; set -- ${nidlist[j+1]}; print $2)
			if (( jth > jnxt )); then
				foo=${nidlist[j]}
				nidlist[j]=${nidlist[j+1]}
				nidlist[j+1]=${foo}
			fi
			((j += 1))
		done
		((i += 1))
	done

	# set return variable
	let i=0
	while (( i < numnodes ))
	do
		eval ${low_node_list_var}[i]=${nidlist[i]}
		((i += 1))
	done

	return 0
}

#####################################################
#
# upgd_get_low_nodeid() "nodelist" "nodeid_list"
#
#	"nodelist"			comma delimited list of nodenames
#	"nodeid_list"			array containing cluster members and
#					their node IDs
#
#	Return the nodename from "nodelist" which has the lowest node ID.
#
#	Return:
#		zero		Success
#		non-zero	Failure
#
#####################################################
upgd_get_low_nodeid()
{
	# check arg
	if [[ $# != 2 ]]; then
		printf "%s:  $(gettext 'Internal error - bad call to upgd_get_low_nodeid()')\n" ${PROG} >&2
		return 2
	fi

	typeset nodelist;  set -A nodelist $(IFS=, ; print $1)
	typeset nid_list;  set -A nid_list $2

	typeset node thenode
	integer nodeid low_id
	integer i

	let i=0
	while [[ -n ${nodelist[i]} ]]
	do
		for entry in ${nid_list[*]}
		do
			node=$(IFS=: ; set -- ${entry}; print $1)
			nodeid=$(IFS=: ; set -- ${entry}; print $2)

			if [[ ${node} == ${nodelist[i]} ]]; then
				if [[ -z ${thenode} ]]; then
					let low_id=nodeid
					thenode=${node}
					break
				elif (( nodeid < low_id )); then
					let low_id=nodeid
					thenode=${node}
				fi
			fi
		done
		((i += 1))
	done
	print ${thenode}

	return 0
}


#####################################################
#
# upgd_disk_groups() vol_mgr low_node "dskgrp_list" "nodeid_list"
#
#	vol_mgr				the volume manager in use
#	low_node			cluster member with the lowest node ID.
#					it will be overridden if vol_mgr=SSVM
#	"dskgrp_list"			array form of SC_UPGD_CONFIG_LHOSTS
#	"nodeid_list"			array containing cluster members and
#					their node IDs
#
#	Integrate disk groups into 3.0 environment. Device groups will
#	be added for each disk group and the node preferences will be
#	set in the order specified for the logical host.
#
#	Return:
#		zero		Success
#		non-zero	Failure
#
#####################################################
upgd_disk_groups()
{
	# check arg
	if [[ $# != 4 ]]; then
		printf "%s:  $(gettext 'Internal error - bad call to upgd_disk_groups()')\n" ${PROG} >&2
		return 2
	fi

	typeset -r vol_mgr=$1
	typeset low_node=$2
	typeset dskgrp_list;  set -A dskgrp_list $3
	typeset n_id_list;  set -A n_id_list $4

	typeset dskgrps masters
	typeset failback preference
	typeset entry key masters
	typeset cmd
	typeset setconvert_debug
	integer result msgflag

	# create device groups for each SSVM disk group
	if [[ "${vol_mgr}" == "${SC_UPGD_KEY_SSVM}" ]]; then
		let msgflag=0
		for entry in ${dskgrp_list[*]}
		do
			dskgrps=$(IFS=: ; set -- ${entry}; print $2)
			masters=$(IFS=: ; set -- ${entry}; print $3)

			if [[ $(IFS=: ; set -- ${entry}; print $6) == 0 ]]; then
				failback="enabled"
				preference="true"
			else
				failback="disabled"
				preference="false"
			fi

			#
			# For >2 node clusters, it may not be possible to master
			# all disk groups on the node with the lowest node
			# ID. Instead the node with the lowest node ID that can
			# master a given disk group will do the work.
			#
			low_node=$(upgd_get_low_nodeid ${masters} "${n_id_list[*]}")
			if [[ -z ${low_node} ]]; then
				printf "%s:  $(gettext 'Internal error - upgd_disk_groups()')\n" ${PROG} >&2
				return 2
			fi

			if [[ "${low_node}" != "${mynodename}" ]]; then
				return 0
			fi

			if (( msgflag == 0 )); then
				printf "$(gettext 'Converting disk groups') ... "
				printf "$(gettext 'Converting disk groups')\n\n" >>${install_log}
				((msgflag += 1))
			fi

			for key in $(IFS=, ; set -- ${dskgrps}; print $*)
			do

				#
				# temporary import to avoid triggering VxVM's
				# auto-import
				#
				cmd="vxdg -t import ${key}"
				print "${cmd}">>${install_log}
				${cmd} >${tmperrs} 2>&1
				result=$?
				cat ${tmperrs} >>${install_log}
				if (( result != 0 )); then
					printf "%s\n\n" ${SC_FAILED}
					printf "%s:  $(gettext 'Problem mastering') \"%s\".\n" ${PROG} ${key} | logerr
					return 1
				fi

				cmd="scconf -a -D type=vxvm,name=${key},nodelist=$(print ${masters} | tr ',' ':'),preferenced=${preference},failback=${failback}"
				print "${cmd}" >>${install_log}
				${cmd} > ${tmperrs} 2>&1
				result=$?
				cat ${tmperrs} >>${install_log}
				if (( result != SC_SCCONF_NOERR && \
				    result != SC_SCCONF_EEXIST )); then
					printf "%s\n\n" ${SC_FAILED}
					printf "%s:  $(gettext 'Problem adding \"%s\" as a disk device group').\n" ${PROG} ${key} | logerr
					return 1
				fi

#XXX				cmd="vxdg deport ${key}"
#XXX				print "${cmd}">>${install_log}
#XXX				${cmd} >${tmperrs} 2>&1
#XXX				result=$?
#XXX				cat ${tmperrs} >>${install_log}
#XXX				if (( result != 0 )); then
#XXX					printf "%s\n\n" ${SC_FAILED}
#XXX					printf "%s:  $(gettext 'Problem deporting') \"%s\".\n" ${PROG} ${key} | logerr
#XXX					return 1
#XXX				fi
			done
		done

	# create device groups for each DiskSuite diskset
	elif [[ "${vol_mgr}" == "${SC_UPGD_KEY_SDS}" ]]; then
		for entry in ${dskgrp_list[*]}
		do
			dskgrps=$(IFS=: ; set -- ${entry}; print $2)
			masters=$(IFS=: ; set -- ${entry}; print $3)

			if [[ $(IFS=: ; set -- ${entry}; print $6) == 0 ]]; then
				failback="enabled"
			else
				failback="disabled"
			fi

			if [[ "${low_node}" != "${mynodename}" ]]; then
				return 0
			fi

			if (( msgflag == 0 )); then
				printf "$(gettext 'Converting disksets') ... "
				printf "$(gettext 'Converting disksets')\n\n" >>${install_log}

				# turn on setconvert's verbose mode if debugging
				if [[ -n "${SC_DEBUG}" ]]; then
					setconvert_debug="-v"
				fi

				((msgflag += 1))
			fi

			for key in $(IFS=, ; set -- ${dskgrps}; print $*)
			do
				cmd="${SC_UPGD_SDS_SETCONVERT} -a ${setconvert_debug} -s ${key}"
				${PRINTDEBUG} "${cmd}"
				${cmd} > ${tmperrs} 2>&1
				if [[ $? != 0 ]]; then
					printf "%s\n\n" ${SC_FAILED}
					printf "%s:  $(gettext 'Problem converting') \"%s\".\n" ${PROG} ${key} | logerr
					if [[ -s ${tmperrs} ]]; then
						cat ${tmperrs} | logerr
					fi
					return 1
				fi

				cmd="scconf -c -D name=${key},nodelist=$(print ${masters} | tr ',' ':'),preferenced=true,failback=${failback}"
				print "${cmd}">>${install_log}
				${cmd} >${tmperrs} 2>&1
				result=$?
				cat ${tmperrs} >>${install_log}
				if (( result != SC_SCCONF_NOERR && \
				    result != SC_SCCONF_EEXIST )); then
					printf "%s\n\n" ${SC_FAILED}
					printf "%s:  $(gettext 'Problem updating properties of') \"%s\".\n" ${PROG} ${key} | logerr
					return 1
				fi
			done
		done
	else
		printf "%s\n\n" ${SC_FAILED}
		printf "%s:  $(gettext 'The volume manager is not supported').\n" ${PROG} | logerr
		return 1
	fi

	printf "%s\n" ${SC_DONE}
	print "" >>${install_log}

	return 0
}

#####################################################
#
# upgd_migrate_vfstab() "lhost_list" prod_vers savedir [flag]
#
#	"lhost_list"			array form of SC_UPGD_CONFIG_LHOSTS
#	prod_vers			release of the product
#	savedir				location of saved configuration files
#	flag				if SC_TRUE, then don't print status
#					message
#
#	Add the entries for any logical host mount points to SC3.0's
#	vfstab (SC_UPGD_30VFSTAB), verify that the administrative file
#	system mount point exists, and add 'global' to the
#	mount point options for each logical host mount point.
#
#	Return:
#		zero		Success
#		non-zero	Failure
#
#####################################################
upgd_migrate_vfstab()
{
	if [[ $# != 3 && $# != 4 ]]; then
		printf "%s:  $(gettext 'Internal error - bad call to upgd_migrate_vfstab()')\n" ${PROG} >&2
		return 2
	fi

	typeset lhost_list;  set -A lhost_list $1
	typeset -r prod_vers=$2
	typeset -r savedir=$3
	integer flag=${SC_FALSE}
	if [[ $# == 4 ]]; then
		let flag=$4
	fi

	typeset -r sc30vfstab=${SC_UPGD_30VFSTAB}
	typeset fmwkdir
	typeset entry lhost masters
	typeset admin_fs
	typeset lhostvfstab
	typeset cmd cmd2
	typeset special fsckdev mountp fstype fsckpass automnt mntopts
	typeset mntopt
	typeset globopt=
	integer repeatflag=${SC_FALSE}
	typeset mountpts;  set -A mountpts ""
	integer i j
	integer mountptssize
	integer jth jnxt
	typeset foo

	rm -f ${tmp_upgdfile}

	if (( flag == SC_FALSE )); then
		printf "$(gettext 'Migrating logical host vfstab entries') ... " | logmsg
	fi

	if [[ "${prod_vers}" == "2.x" ]]; then
		fmwkdir=${savedir}/SUNWcluster/conf/hanfs
	fi

	if [[ -f ${sc30vfstab} ]]; then
		cp ${sc30vfstab} ${tmp_upgdfile} >/dev/null 2>&1
	fi

	let i=0
	for entry in ${lhost_list[*]}
	do
		# this has already been done
		if (( repeatflag == SC_TRUE )); then
			break
		fi

		lhost=$(IFS=: ; set -- ${entry}; print $1)
		admin_fs=$(IFS=: ; set -- ${entry}; print $5)

		# ensure administrative file system mount point exists
		if [[ ! -d ${admin_fs} ]]; then
			mkdir -p ${admin_fs} 2>/dev/null
		fi

		lhostvfstab=${fmwkdir}/vfstab.${lhost}
		if [[ ! -s ${lhostvfstab} ]]; then

			# attempt to retrieve file from another node
			masters=$(IFS=: ; set -- ${entry}; print $3)
			for foo in $(IFS=, ; set -- ${masters}; print $*)
			do
				if [[ "${foo}" == "${mynodename}" ]]; then
					continue
				fi

				cmd="scrconf -g -N ${foo} ${lhostvfstab} ${lhostvfstab}"
				${PRINTDEBUG} "${cmd}"
				${cmd} >${tmperrs} 2>&1
				if [[ $? != ${SC_SCCONF_NOERR} && \
				    $? != ${SC_SCCONF_ENOEXIST} ]]; then
					printf "%s:  $(gettext 'Internal error - problem retrieving %s').\n" ${PROG} ${lhostvfstab} >&2
					if [[ -s ${tmperrs} ]]; then
						cat ${tmperrs} | logerr
					fi
					return 2
				fi
			done

			# file really doesn't exist so continue
			if [[ $? == ${SC_SCCONF_ENOEXIST} ]]; then
				continue
			fi
		fi

		while read special fsckdev mountp fstype fsckpass automnt mntopts
		do
			case ${special} in
			'#'* | '')	# ignore comments, empty lines
				continue
				;;
			'-')		# ignore no-action lines
				continue
				;;
			esac

			# make sure this hasn't already been done
			egrep "${mountp}" ${sc30vfstab} >/dev/null 2>&1
			if [[ $? == 0 ]]; then
				let repeatflag=${SC_TRUE}
				break
			fi

			# save all mount points for later use
			mountpts[i]=${mountp}
			((i += 1))

			# add "global" to mount options as needed
                        globopt="global"
                        if [[ "${mntopts}" != "-" ]]; then
                                globopt=",${globopt}"
                                for mntopt in $(IFS=, ; set -- ${mntopts}; print
 $*)
                                do
                                        if [[ ${mntopt} == 'global' ]]; then
                                                globopt=""
                                        fi
                                done
                                mntopts="${mntopts}${globopt}"
                        else
                                mntopts="${globopt}"
                        fi

			printf "%s\t%s\t%s\t%s\t%s\tyes\t%s\n" ${special} ${fsckdev} ${mountp} ${fstype} ${fsckpass} ${mntopts} >> ${tmp_upgdfile}
		done < ${lhostvfstab}
	done

	if (( repeatflag == SC_FALSE )); then
		cmd="mv ${sc30vfstab} ${sc30vfstab}.${prod_vers}"
		cmd2="mv ${tmp_upgdfile} ${sc30vfstab}"
		${PRINTDEBUG} "${cmd}; ${cmd2}"
		(${cmd}; ${cmd2}) 2> ${tmperrs}
		if [[ $? != 0 ]]; then
			if (( flag == SC_FALSE )); then
				printf "%s\n\n" ${SC_FAILED} | logmsg
			fi
			printf "%s:  $(gettext 'Problem migrating vfstab').\n" ${PROG} | logerr
			if [[ -s ${tmperrs} ]]; then
				cat ${tmperrs} | logerr
			fi
			return 1
		fi
	fi

	#
	# Determine root-level mount points by:
	#	1) Sort all paths by depth in ascending order
	#	2) Loop through sorted list in order, selecting first item in
	#	   list
	#	   a) If the chosen item matches a following item then remove
	#	      the following item from the list
	#	   b) Repeat for all remaining items in the list
	#	3) Those items remaining in the list are the mount
	#	   points. Create the mount point for each item in the
	#	   list that doesn't already exist.
	#

	#	1) Sort all paths by depth in ascending order
	let mountptssize=${#mountpts[*]}
	let i=0
	while (( i < mountptssize - 1 ))
	do
		let j=0
		while (( j < mountptssize - i - 1 ))
		do
			let jth=$(IFS=/ ; set -- ${mountpts[j]}; print $#)
			let jnxt=$(IFS=/ ; set -- ${mountpts[j+1]}; print $#)
			if (( jth > jnxt )); then
				foo=${mountpts[j]}
				mountpts[j]=${mountpts[j+1]}
				mountpts[j+1]=${foo}
			fi
			((j += 1))
		done
		((i += 1))
	done

	#
	#	2) Loop through sorted list in order, selecting first item in
	#	   list
	#	   a) If the chosen item matches a following item then remove
	#	      the following item from the list
	#	   b) Repeat for all remaining items in the list
	#
	let i=0
	for foo in ${mountpts[*]}
	do
		if [[ -n ${foo} ]]; then
			let j="i + 1"
			while (( j < mountptssize ))
			do
				if [[ "${mountpts[j]}" == ${foo}* ]]; then
					mountpts[j]=""
				fi
				((j += 1))
			done
		fi
		((i += 1))
	done

	#
	#	3) Those items remaining in the list are the mount
	#	   points. Create the mount point for each item in the
	#	   list that doesn't already exist.
	#
	for foo in ${mountpts[*]}
	do
		if [[ ! -d ${foo} ]]; then
			mkdir -p ${foo} 2>/dev/null
		fi
	done

	if (( flag == SC_FALSE )); then
		printf "%s\n" ${SC_DONE} | logmsg
		print "" >>${install_log}
	fi

	return 0
}

#####################################################
#
# upgd_mount_fsys()
#
#	Mount logical host file systems. The bulk of this function came
#	from MOUNTGFSYS which can be found in /etc/init.d.
#
#	Return:
#		zero		Success
#		non-zero	Failure
#
#####################################################
upgd_mount_fsys()
{
	# check arg
	if [[ $# != 0 ]]; then
		printf "%s:  $(gettext 'Internal error - bad call to upgd_mount_fsys()')\n" ${PROG} >&2
		return 2
	fi

	typeset special fsckdev mountp fstype fsckpass automnt mntopts
	typeset g mntlist

	printf "$(gettext 'Mounting logical host file systems') ... "
	printf "$(gettext 'Mounting logical host file systems')\n" >>${install_log}

	while read special fsckdev mountp fstype fsckpass automnt mntopts
	do
		case ${special} in
		'#'* | '')	#  Ignore comments, empty lines
		continue ;;
		'-')		#  Ignore no-action lines
			continue
		esac

		# Skip entries that aren't enabled to be mounted.
		if [[ "${automnt}" == "no" || \
		    "${fstype}" == "-" ]]; then
			continue
		fi

		# Skip entries that have don't the "global" option.
		g=$(print ${mntopts} | grep '\<global\>')
		if [[ "${fstype}" == "cachefs" || \
		    "${fstype}" == "nfs" || \
		    -z ${g} ]]; then
			continue
		fi

		#
		# Try to get the fsck/mount lock for this device.
		# We won't get the lock if it is already mounted or
		# some other node beats us.
		#
		cmd="clconfig -l ${special}"
		${PRINTDEBUG} "${cmd}"
		${cmd} >/dev/null 2>&1
		if [[ $? != 0 ]]; then
			continue
		fi

		OPTIONS=""

		#
		# Can't fsck if no fsckdev is specified
		#
		if [[ "${fsckdev}" == "-" ]]; then
			mntlist="${mntlist} ${mountp}"
			continue
		fi
		#
		# For fsck purposes, we make a distinction between ufs
		# and other file systems
		# 
		if [ "${fstype}" = "ufs" ]; then
			mntlist="${mntlist} ${mountp}"
			continue
		fi
	done < ${SC_UPGD_30VFSTAB}

	if [[ -n ${mntlist} ]]; then
		cmd="mount -a ${mntlist}"
		print "${cmd}" >>${install_log}
		${cmd} > ${tmperrs} 2>&1
		result=$?
		cat ${tmperrs} >>${install_log}
		if (( result != 0 )); then
			printf "%s\n\n" ${SC_FAILED}
			printf "%s:  $(gettext 'Problem mounting logical host file systems').\n" ${PROG} | logerr
			return 1
		fi
	fi

	printf "%s\n" ${SC_DONE}
	print "" >>${install_log}

	return 0
}

#####################################################
#
# upgd_create_lhosts() "lhost_list"
#
#	"lhost_list"			array form of SC_UPGD_CONFIG_LHOSTS
#
#	Re-create logical hosts
#
#	Return:
#		zero		Success
#		non-zero	Failure
#
#####################################################
upgd_create_lhosts()
{
	# check arg
	if [[ $# != 1 ]]; then
		printf "%s:  $(gettext 'Internal error - bad call to upgd_create_lhosts()')\n" ${PROG} >&2
		return 2
	fi

	typeset lhost_list;  set -A lhost_list $1

	typeset entry
	typeset key masters admin_fs failback
	typeset cmd
	integer result
	typeset ngrp niflist
	typeset master_list;  set -A master_list ""
	typeset netif_list;  set -A netif_list ""
	typeset netnameset netname
	integer i

	printf "$(gettext 'Re-creating logical hosts') ... "
	printf "$(gettext 'Re-creating logical hosts')\n" >>${install_log}

	# create RG for each logical host and set Failback property
	for entry in ${lhost_list[*]}
	do
		lhrgname="$(IFS=: ; set -- ${entry}; print $1)-lh"
		masters=$(IFS=: ; set -- ${entry}; print $3)
		admin_fs=$(IFS=: ; set -- ${entry}; print $5)

		if [[ $(IFS=: ; set -- ${entry}; print $6) == 0 ]]; then
			failback="TRUE"
		else
			failback="FALSE"
		fi

		cmd="scrgadm -F -a -g ${lhrgname} -y Nodelist=${masters} -y Pathprefix=${admin_fs} -y Failback=${failback} -y Maximum_primaries=1 -y Desired_primaries=1"
		print "${cmd}" >>${install_log}
		${cmd} > ${tmperrs} 2>&1
		result=$?
		cat ${tmperrs} >>${install_log}
		if (( result != SCHA_ERR_NOERR && \
		    result != SCHA_ERR_RG )); then
			printf "%s\n\n" ${SC_FAILED}
			printf "%s:  $(gettext 'Problem creating Resource Group for') \"%s\".\n" ${PROG} ${lhrgname} | logerr
			return 1
		fi

		set -A master_list $(IFS=, ; print ${masters})

		# create logical host Resources
		for netnameset in $(IFS=\; ; print $(IFS=: ; set -- ${entry}; print $4))
		do
			netname=$(expr "${netnameset}" : '\([a-z]*.*\)(.*)')
			set -A netif_list $(IFS=, ; print $(expr "${netnameset}" : '.*(\([a-z]*.*\))'))

			# construct netif list
			let i=0
			while (( i < ${#master_list[*]} ))
			do
				if (( i == 0 )); then
					niflist=${netif_list[i]}"@"${master_list[i]}
				else
					niflist=${niflist}","${netif_list[i]}"@"${master_list[i]}
				fi
				((i += 1))
			done

			# create Resource
			cmd="scrgadm -F -a -L -g ${lhrgname} -l ${netname} -n ${niflist}"
			print "${cmd}" >>${install_log}
			${cmd} > ${tmperrs} 2>&1
			result=$?
			cat ${tmperrs} >>${install_log}
			if (( result != SCHA_ERR_NOERR && \
			    result != SCHA_ERR_RSRC )); then
				printf "%s\n\n" ${SC_FAILED}
				printf "%s:  $(gettext 'Problem creating logical host Resource for') \"%s\".\n" ${PROG} ${lhrgname} | logerr
				return 1
			fi
		done

		# bring RG Online
		cmd="scswitch -Z -g ${lhrgname}"
		print "${cmd}" >>${install_log}
		${cmd} >${tmperrs} 2>&1
		if [[ $? != 0 ]]; then
			printf "%s\n\n" ${SC_FAILED}
			printf "%s:  $(gettext 'Problem putting "%s" under RGM management.')\n" ${PROG} ${lhrgname} | logerr
			if [[ -s ${tmperrs} ]]; then
				cat ${tmperrs} | logerr
			fi
			return 1
		fi
	done

	printf "%s\n" ${SC_DONE}
	print "" >>${install_log}

	return 0
}

#####################################################
#
# upgd_finish()
#
#	"quorum_opts"			Quorum options
#
#	Execute the last phase of upgrading cluster framework. Everything
#	except upgrading data services is performed.
#		- Verify that remaining SC2.2 nodes are down
#		- Verify that entire SC3.0 cluster is up on the node.
#		- [Lowest Node ID] Reserve all shared disks
#		- [Lowest Node ID] If the volume manager is SDS, convert
#		  disk groups to use DID names
#		- [Lowest Node ID] Create a device group for each disk
#		  group with the appropriate node preferences list
#		- Move SC2.2 logical host file system information into
#		  /etc/vfstab.
#		- [Lowest Node ID] Mount the administrative and logical
#		  host file systems on preferred nodes
#		- [Lowest Node ID] Create SC3.0 logical host (LH) Resource
#		  Group (RG) counterpart to SC2.2 LHs in the Offline state
#		- [Lowest Node ID] Add quorum device
#		- [Lowest Node ID] Disable installmode
#		- [Lowest Node ID] Remove the upgrade flag file on all nodes
#
#	Return:
#		zero		Success
#		non-zero	Failure
#
#####################################################
upgd_finish()
{
	# check arg
	if [[ $# != 1 ]]; then
		printf "%s:  $(gettext 'Internal error - bad call to upgd_finish()')\n" ${PROG} >&2
		return 2
	fi

	typeset -r quorum_opts="$1"

	typeset cname
	integer num_nodes
	typeset product_vers
	typeset upgd_savedir
	typeset nodeid_list;  set -A nodeid_list ""
	typeset pvt_host_list;  set -A pvt_host_list ""
	typeset members
	typeset low_nodeid_node
	typeset mypvtnodename
	typeset cmd
	integer result
	typeset foo
	typeset quorumdsk
	typeset dskhost

	if [[ ! -s ${SC_UPGD_CONFIGFILE} ]]; then
		printf "%s:  $(gettext 'Configuration data missing')!\n" ${PROG} | logerr
		return 1
	fi

	# retrieve configuration
	loadlib ${SC_UPGD_CONFIGFILE} ${SC_LOADED_CONFIGFILE}
	let SC_LOADED_CONFIGFILE=${SC_TRUE}

	cname=$(IFS=: ; set -- ${SC_UPGD_CONFIG_CLUSTER}; print $2)
	let num_nodes=$(IFS=: ; set -- ${SC_UPGD_CONFIG_CLUSTER}; print $5)
	product_vers=$(IFS=: ; set -- ${SC_UPGD_CONFIG_CLUSTER}; print $1)
	upgd_savedir=$(IFS=: ; set -- ${SC_UPGD_CONFIG_CLUSTER}; print $6)

	# 2-node clusters MUST have a shared quorum device
	if [[ ${num_nodes} == 2 && -z ${quorum_opts} ]]; then
		printf "%s:  $(gettext '2-node clusters require a quorum device').\n" ${PROG} | logerr
		return 1
	fi

	# the quorum device must be on a shared disk.
	if [[ ${num_nodes} == 2 ]]; then
		quorumdsk=${quorum_opts#globaldev=}
		set -A dskhost $(scdidadm -Lo host ${quorumdsk})
		if [[ ${#dskhost[*]} < 2  ]]; then
			printf "%s:  $(gettext 'The quorum device must be a share disk').\n" ${PROG} | logerr
			return 1
		fi
	fi

	# pre-finish checks
	upgd_prefinish_checks "${SC_UPGD_CONFIG_MEMBERS}" "${SC_UPGD_CONFIG_LHOSTS}" ${num_nodes} || return 1

	# migrate logical host file system entries to SC_UPGD_30VFSTAB
	upgd_migrate_vfstab "${SC_UPGD_CONFIG_LHOSTS[*]}" ${product_vers} ${upgd_savedir} || return 1

	# since the flag file doesn't exist everything else has been done
	if [[ ! -f ${SC_UPGD_FLAGFILE} ]]; then
		return 0
	fi

	# get cluster configuration
	upgd_get_30config ${num_nodes} pvt_host_list nodeid_list || return 1

	# get cluster member with lowest node ID
	for foo in ${SC_UPGD_CONFIG_MEMBERS}
	do
		if [[ -z ${members} ]]; then
			members=$(IFS=: ; set -- ${foo}; print $1)
		else
			members=${members}","$(IFS=: ; set -- ${foo}; print $1)
		fi
	done
	low_nodeid_node=$(upgd_get_low_nodeid "${members}" "${nodeid_list[*]}")
	if [[ -z ${low_nodeid_node} ]]; then
		printf "%s:  $(gettext 'Internal error - upgd_finish()')\n" ${PROG} >&2
		return 2
	fi

	if [[ "${mynodename}" == "${low_nodeid_node}" ]]; then

		# reserve all accessible shared disks
		printf "$(gettext 'Reserving disks') ... " | logmsg
		cmd="/usr/cluster/lib/sc/run_reserve -c node_join"
		${PRINTDEBUG} "${cmd}"
		${cmd} > ${tmperrs} 2>&1
		if [[ $? != 0 ]]; then
			printf "%s\n\n" ${SC_FAILED} | logmsg
			printf "%s:  $(gettext 'Problem reserving disks').\n" ${PROG} | logerr
			if [[ -s ${tmperrs} ]]; then
				cat ${tmperrs} | logerr
			fi
			return 1
		fi
		printf "%s\n" ${SC_DONE} | logmsg
	fi

	# convert disk groups
	upgd_disk_groups $(IFS=: ; set -- ${SC_UPGD_CONFIG_CLUSTER}; print $3) ${low_nodeid_node} "${SC_UPGD_CONFIG_LHOSTS[*]}" "${nodeid_list[*]}" || return 1

	if [[ "${low_nodeid_node}" == "${mynodename}" ]]; then

		# mount logical host file systems
		upgd_mount_fsys || return 1

		# create logical hosts
		upgd_create_lhosts "${SC_UPGD_CONFIG_LHOSTS}" || return 1

		# add quorum device
		if [[ -n ${quorum_opts} ]]; then
			printf "$(gettext 'Adding quorum device') ... "
			printf "$(gettext 'Adding quorum device')\n" >>${install_log}

			cmd="scconf -a -q ${quorum_opts}"
			print "${cmd}">>${install_log}
			${cmd} >${tmperrs} 2>&1
			result=$?
			cat ${tmperrs} >>${install_log}
			if (( result != SC_SCCONF_NOERR && \
			    result != SC_SCCONF_EEXIST )); then
				printf "%s\n\n" ${SC_FAILED}
				printf "%s:  $(gettext 'Problem adding quorum device').\n" ${PROG} | logerr
				return 1
			fi
			printf "%s\n" ${SC_DONE}
		fi

		# disable installmode
		printf "$(gettext 'Disabling installmode') ... "
		printf "$(gettext 'Disabling installmode')\n" >>${install_log}
		cmd="scconf -c -q reset"
		print "${cmd}" >>${install_log}
		${cmd} >${tmperrs} 2>&1
		result=$?
		cat ${tmperrs} >>${install_log}
		if (( result != SC_SCCONF_NOERR && \
		    result != SC_SCCONF_EEXIST )); then
			printf "%s\n\n" ${SC_FAILED}
			printf "%s:  $(gettext 'Problem disabling installmode').\n" ${PROG} | logerr
			return 1
		fi
		printf "%s\n" ${SC_DONE}
		print "" >>${install_log}

		#
		# Remove upgrade flag file on cluster member with lowest node ID
		# after removing it on all other cluster members. This ensures
		# that if this portion of the "finish" process is interrupted at
		# this point, this section of code will be executed again when
		# the command is re-run.
		#
		for foo in ${pvt_host_list[*]}
		do
			if [[ "$(IFS=: ; set -- ${foo}; print $1)" != "${mynodename}" ]]; then
				cmd="scrconf -r -f ${SC_UPGD_FLAGFILE} -N $(IFS=: ; set -- ${foo}; print $2)"
				${PRINTDEBUG} "${cmd}"
				${cmd} >${tmperrs} 2>&1
				if [[ $? != ${SC_SCCONF_NOERR} && \
				    $? != ${SC_SCCONF_ENOEXIST} ]]; then
					printf "%s:  $(gettext 'Internal error - problem removing upgrade flag file').\n" ${PROG} >&2
					if [[ -s ${tmperrs} ]]; then
						cat ${tmperrs} | logerr
					fi
					return 2
				fi
			else
				mypvtnodename=$(IFS=: ; set -- ${foo}; print $2)
			fi
		done
		cmd="scrconf -r -f ${SC_UPGD_FLAGFILE} -N ${mypvtnodename}"
		${PRINTDEBUG} "${cmd}"
		${cmd} >${tmperrs} 2>&1
		if [[ $? != ${SC_SCCONF_NOERR} && \
		    $? != ${SC_SCCONF_ENOEXIST} ]]; then
			printf "%s:  $(gettext 'Internal error - problem removing upgrade flag file').\n" ${PROG} >&2
			if [[ -s ${tmperrs} ]]; then
				cat ${tmperrs} | logerr
			fi
			return 2
		fi
	fi

	return 0
}

#####################################################
#
# upgd_data_services() "services" cdimagebasedir
#
#	"services"			A list of the data services to be
#					upgraded
#	cdimagebasedir			CD image base directory
#
#	Upgrade data services
#		- Install new data service packages for services
#		  identified by the `-s' option
#		- Register Sun-supplied Resource Types (RTs) based on
#		  services supplied by `-s' option
#		- Create SC3.0 data service (DS) Resource counterpart to
#		  SC2.2 DS instances based on services supplied by `-s'
#		  option
#
#	Return:
#		zero		Success
#		non-zero	Failure
#
#####################################################
upgd_data_services()
{
	# check arg
	if [[ $# != 2 ]]; then
		printf "%s:  $(gettext 'Internal error - bad call to upgd_data_services()')\n" ${PROG} >&2
		return 2
	fi

	typeset services;  set -A services $1
	typeset -r cdimagebasedir=$2

	typeset prod_vers
	typeset upgd_savedir
	typeset svc_basedir svc_script
	typeset cmd
	integer result
	typeset dstype
	integer i
	typeset buffer
	typeset entry foo key
	integer regflg

	if [[ ! -s ${SC_UPGD_CONFIGFILE} ]]; then
		printf "%s:  $(gettext 'Configuration data missing')!\n" ${PROG} | logerr
		return 1
	fi

	# retrieve configuration
	loadlib ${SC_UPGD_CONFIGFILE} ${SC_LOADED_CONFIGFILE}
	let SC_LOADED_CONFIGFILE=${SC_TRUE}

	prod_vers=$(IFS=: ; set -- ${SC_UPGD_CONFIG_CLUSTER}; print $1)
	upgd_savedir=$(IFS=: ; set -- ${SC_UPGD_CONFIG_CLUSTER}; print $6)

	# install services
	installservices "${cdimagebasedir}" "${services[*]}"
	if [[ $? != 0 ]]; then
		return 1
	fi

	printf "** $(gettext 'Upgrading Data Services') **\n" | logmsg

	# upgrade data services
	for dstype in ${services[*]}
	do

		#
		# Register and upgrade the data service package
		#
		# The "<pkg>.....done." message is printed in the same
		# style as used by JumpStart.
		#
		let i=$(expr ${dstype} : .\*)
		((i = 12 - i))
		buffer=${dstype}
		while (( i > 0 ))
		do
			buffer="${buffer}."
			((i -= 1))
		done

		printf "\t%s" "${buffer}"
		printf "\n\t%s:\n" "${dstype}" >>${install_log}

		# if previously registered, then create RT
		let regflg=${SC_FALSE}
		for entry in ${SC_UPGD_CONFIG_LHOSTS}
		do
			foo=$(IFS=: ; set -- ${entry}; print $7)
			for key in $(IFS=, ; set -- ${foo}; print $*)
			do
				if [[ "${key}" == "${dstype}" ]]; then
					let regflg=${SC_TRUE}
				fi
			done
		done

		if (( regflg == SC_FALSE )); then
			printf "$(gettext 'aborted')\n"
			printf "$(gettext 'Skipping "%s" - not registered in previous release')\n" ${dstype} | logmsg
			continue
		fi


		# register RT for entire cluster
		cmd="scrgadm -F -a -t SUNW.${dstype}"
		${PRINTDEBUG} "${cmd}"
		print "${cmd}" >>${install_log}
		${cmd} >${tmperrs} 2>&1
		result=$?
		cat ${tmperrs} >>${install_log}
		if (( result == SCHA_ERR_FILE )); then
			printf "$(gettext 'not registered')"
			continue
		elif (( result != SCHA_ERR_NOERR && \
		    result != SCHA_ERR_RT )); then
			printf "%s\n\n" ${SC_FAILED}
			printf "%s:  $(gettext 'Problem registering') \"%s\".\n" ${PROG} ${dstype} | logerr
			return 1
		fi
		printf "$(gettext 'registered')...."

		# get RT_BASEDIR for service
		cmd="scha_resourcetype_get -T SUNW.${dstype} -O RT_BASEDIR"
		${PRINTDEBUG} ${cmd}
		print "${cmd}" >>${install_log}
		svc_basedir=$(${cmd} 2>${tmperrs})
		if [[ $? != ${SCHA_ERR_NOERR} ]]; then
			printf "%s\n\n" ${SC_FAILED}
			printf "%s:  $(gettext 'Unable to retrieve RT_BASEDIR for') \"SUNW.%s\".\n" ${PROG} ${dstype} | logerr
			if [[ -s ${tmperrs} ]]; then
				cat ${tmperrs} | logerr
			fi
			return 1
		fi

		# execute upgrade script
		svc_script=${svc_basedir}/${dstype}_upgrade
		if [[ -x ${svc_script} ]]; then
			cmd="${svc_script} -v ${upgd_savedir##*/} -d ${upgd_savedir}"
			${PRINTDEBUG} "${cmd}"
			${cmd} >${tmperrs} 2>&1
			result=$?
			cat ${tmperrs} >>${install_log}
			if (( result != 0 )); then
				printf "%s\n\n" ${SC_FAILED}
				printf "%s:  $(gettext 'Problem upgrading "%s" - please check the log.')\n" ${PROG} ${dstype} | logerr
				return 1
			fi

			printf "%s\n" ${SC_DONE}
		else
			printf "$(gettext 'incomplete')\n"
			printf "$(gettext 'Upgrade method not found - manual upgrade required')\n" ${PROG} | logmsg
		fi
		print "" >>${install_log}
	done

	return 0
}

#####################################################
#
# patch_upgd() "services" cdimagebasedir
#
#	"services"		A list of data services to be
#				upgraded; null if core packages
#				are being upgraded
#	cdimagebasedir		CD image base directory
#
#	Upgrades core packages or data services from a CD
#	via patch installation.
#
#	Return:
#		zero		Success
#		non-zero	Failure
#
#####################################################
patch_upgd()
{
	typeset services=$1
	typeset -r cdimagebasedir=$2
	typeset service
	typeset silent

	# check arg
	if [[ $# != 2 ]]; then
		printf "%s:  $(gettext 'Internal error - bad call to patch_upgd()')\n" ${PROG} >&2
		return 2
	fi

	# must be upgrading core packages if services not specified
	if [[ -z ${services} ]]; then
		find_and_install_patches ${cdimagebasedir} "${SC_PRODUCT}" "${SC_CLUSTER}" "" "${SC_FALSE}" || return 1

	# upgrading data services
	else
		if [[ "${services}" = "all" ]]; then
			silent=${SC_TRUE}
			services=$(find_ds_list ${cdimagebasedir})
			if [[ $? -ne 0 ]]; then
				printf "%s:  $(gettext 'Cannot find services')\n" "${PROG}" | logerr
				return 1
			fi
		else
			silent=${SC_FALSE}
		fi

		for service in ${services}
		do
			find_and_install_patches ${cdimagebasedir} "" "${SC_SERVICE}${service}" ${service} ${silent} || return 1
		done
	fi
	return 0
}
