#! /bin/ksh
#
# ident "@(#)scinstall_common.ksh 1.33     02/02/07 SMI"
#
# Copyright 2002 Sun Microsystems, Inc.  All rights reserved.
# Use is subject to license terms.
#

#####################################################
#
# setfile() filename
#
#	Set the file mode, ownership, and group of
#	the given read-only root "filename".
#
#	Return:
#		zero		Success
#		non-zero	Failure
#
#####################################################
setfile()
{
	typeset -r filename=$1

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

	# set the file mode, owner, group
	chmod 0444 ${filename} || return 1
	chown root ${filename} || return 1
	chgrp sys  ${filename} || return 1

	return 0
}

#####################################################
#
# duplicate() [args ...]
#
#	args - a list of 0-n arguments
#
#	This function returns non-zero if any two
#	arguments in the list match.   That is,
#	if duplicate args are found.
#
#####################################################
duplicate()
{
	typeset arglist;  set -A arglist $*

	integer i=0
	integer j

	while [[ -n "${arglist[i]}" ]]
	do
		((j = i + 1))
		while [[ -n "${arglist[j]}" ]]
		do
			if [[ "${arglist[i]}" == "${arglist[j]}" ]]; then
				return 1
			fi
			((j += 1))
		done
		((i += 1))
	done

	return 0
}

#####################################################
#
# check_opts() given_opts legal_opts required_opts
#
#	given_opts	- comma separated list of given subopts
#	legal_opts	- comma separated list of legal subopts
#	required_opts	- comma separated list of required subopts
#
#	This function returns non-zero if there are "given_opts"
#	which are not in the "legal_opts" list, or if there
#	are "required_opts" which are not in the "given_opts" list.
#
#	Suboption checking is incomplete at this stage, since we cannot
#	check properties until the property lists are installed.
#	scrconf(1M) will do a more complete job, once we install it.
#	And, after the pkgadds, we go through our options and let
#	scrconf(1M) re-check for usage.
#
#	Return:
#		zero		Success
#		non-zero	Failure
#
#####################################################
check_opts()
{
	typeset given_opts;  set -A given_opts $(IFS=, ; echo $1)
	typeset legal_opts;  set -A legal_opts $(IFS=, ; echo $2)
	typeset required_opts;  set -A required_opts $(IFS=, ; echo $3)

	integer i
	integer j

	typeset buffer
	typeset opt

	if [[ $# -ne 3 ]]; then
		printf "$(gettext '%s:  Internal error - bad call to check_opts()')\n" ${PROG} >&2
		return 1
	fi

	# strip values from the given opts
	let i=0
	buffer=
	while [[ -n "${given_opts[i]}" ]]
	do
		opt=$(expr "${given_opts[i]}" : '\(.*\)=.*')
		if [[ -z "${opt}" ]]; then
			opt=${given_opts[i]}
		fi
		buffer="${buffer} ${opt}"
		((i += 1))
	done
	set -A given_opts ${buffer}

	# Make sure that all of the given options are legal
	if [[ -n "${legal_opts}" ]]; then
		let i=0
		while [[ -n "${given_opts[i]}" ]]
		do
			let j=0
			while [[ -n "${legal_opts[j]}" ]]
			do
				if [[ "${given_opts[i]}" = "${legal_opts[j]}" ]]; then
					break
				fi
				((j += 1))
			done
			if [[ -z "${legal_opts[j]}" ]]; then
				return 1
			fi
			((i += 1))
		done
	fi

	# Make sure that all of the required options are given
	if [[ -z "${given_opts}" ]] && [[ -n "${required_opts}" ]]; then
		return 1
	fi

	let i=0
	while [[ -n "${required_opts[i]}" ]]
	do
		let j=0
		while [[ -n "${given_opts[j]}" ]]
		do
			if [[ "${required_opts[i]}" = "${given_opts[j]}" ]]; then
				break
			fi
			((j += 1))
		done
		if [[ -z "${given_opts[j]}" ]]; then
			return 1
		fi
		((i += 1))
	done

	return 0
}

#####################################################
#
# check_optslist() given_optslist opt_type legal_opts required_opts
#
#	given_optslist	- full list of options & suboptions for given opt_type
#	opt_type	- option letter
#	legal_opts	- comma separated list of legal subopts
#	required_opts	- comma separated list of required subopts
#
#	This function returns non-zero if there are suboptions in
#	the"given_optslist" which are not in the "legal_opts" list,
#	or if there are "required_opts" which are not in the
#	"given_optslist" list.
#
#	Suboption checking is incomplete at this stage, and we cannot
#	check properties until the property lists are installed.
#	scrconf(1M) will do a more complete job, once we install it.
#
#	Return:
#		zero		Success
#		non-zero	Failure
#
#####################################################
check_optslist()
{
	typeset -r given_optslist="$1"
	typeset -r opt_type="$2"
	typeset -r legal_opts"$3"
	typeset -r required_opts="$4"

	typeset c

	if [[ $# -ne 4 ]]; then
		printf "$(gettext '%s:  Internal error - bad call to check_optslist()')\n" ${PROG} >&2
		return 1
	fi

	set - ${given_optslist}
	OPTIND=1
	while getopts ${opt_type}: c 2>/dev/null
	do
		case ${c} in
		${opt_type})
			check_opts "${OPTARG}" "${legal_opts}" "${required_opts}" || return 1
			;;

		*)
			return 1
			;;
		esac
	done

	return 0
}

#####################################################
#
# check_cable_opts() "cable_opts" "installnode" "adapter_opts"
#
#	cable_opts	- comma separated list of cable options
#	installnode	- the name of the node being installed
#
#	This function returns non-zero if any of the cable options
#	are illegal.
#
#	The following things are checked:
#
#		- each -m option must have exactly two endpoints
#		- at least one nodename for an adapter in a pair of
#			adapter endpoints must be "NULL" or equal
#			to the name of this node
#		- each cable must connect this node to the cluster
#		- each adapter may be given only once
#
#	Return:
#		zero		Success
#		non-zero	Failure
#
#####################################################
check_cable_opts()
{
	typeset -r cable_opts="$1"
	typeset -r installnode="$2"
	typeset -r adapter_opts="$3"

	typeset c
	typeset subopts
	typeset value
	typeset nodenames
	typeset foo
	typeset bar
	typeset adapter
	typeset adapters
	typeset myadapters

	integer i
	integer countme
	integer found

	# If no cable options, there is nothing to do
	if [[ -z "${cable_opts}" ]]; then
		return 0
	fi

	# Turn adapter_opts into list of adapters
	adapters="$(print_subopt_values "${adapter_opts}" "name")"

	#
	# For each opt,
	#
	myadapters=
	OPTIND=1
	while getopts m: c ${cable_opts} 2>/dev/null
	do
		if [[ "${c}" != "m" ]]; then
			printf "$(gettext '%s:  Internal error in check_cable_opts()')\n" "${PROG}" >&2
			return 1
		fi

		# for each subopt ...
		set -A subopts $(IFS=, ; echo ${OPTARG})
		let i=0
		let countme=0
		while [[ -n "${subopts[i]}" ]]
		do
			# get value (endpoint=<value>)
			value=$(expr "${subopts[i]}" : 'endpoint=\(.*\)')
			if [[ -z "${value}" ]]; then
				printf "$(gettext '%s:  Unrecognized suboption given with -m')\n" "${PROG}" | logerr
				return 1
			fi

			# get nodename, if there is one ([nodename]:adapter)
			nodenames[i]="-"
			if [[ "${value}" = *:* ]]; then

				# Set the nodename portion
				nodenames[i]=$(expr "${value}" : '\(.*\)\:.*')
				if [[ -z "${nodenames[i]}" ]]; then
					nodenames[i]=${installnode}
				fi

				# Is it my adapter?
				if [[ "${nodenames[i]}" = "${installnode}" ]]; then
					((countme += 1))
					adapter=$(expr "${value}" : '.*:\(.*\)')
					if [[ "${adapter}" != *@* ]]; then
						adapter="${adapter}@0"
					fi
					myadapters="${myadapters} ${adapter}"
					bar=$(expr "${adapter}" : '\([^@]*\).*')
					let found=0
					for foo in ${adapters}
					do
						if [[ "${foo}" = "${bar}" ]]; then
							let found=1
							break
						fi
					done
					if [[ ${found} -ne 1 ]]; then
						printf "$(gettext '%s:  Cable endpoint must match one of the adapters')\n" "${PROG}" | logerr
						return 1
					fi
				fi
			fi
			((i += 1))
		done

		# there must be two endpoints
		if [[ ${i} -ne 2 ]]; then
			printf "$(gettext '%s:  There must be two endpoints per cable')\n" "${PROG}" | logerr
			return 1
		fi

		# One of the nodenames must be me
		if [[ ${countme} -lt 1 ]]; then
			printf "$(gettext '%s:  At least one end of each cable must attach to this node')\n" "${PROG}" | logerr
			return 1
		fi

		# And, only one
		if [[ ${countme} -gt 1 ]]; then
			printf "$(gettext '%s:  At least one end of each cable must attach to this node')\n" "${PROG}" | logerr
			return 1
		fi
	done

	# Make sure that there are no duplicate adapters
	duplicate ${myadapters}
	if [[ $? -ne 0 ]]; then
		printf "$(gettext '%s:  One or more of the adapters for this node is cabled more than once')\n" "${PROG}" | logerr
		return 1
	fi

	return 0
}

#####################################################
#
# print_subopt_values() suboptlist subopt
#
#	suboptlist		comma separated list of "suboptions"
#	subopt			name of the suboption
#
#	Print the values of the given "subopt", as found
#	in the "suboptlist".  If option letters are included,
#	they are skipped
#
#	This function always returns zero.
#
#####################################################
print_subopt_values()
{
	typeset -r suboptlist="$(IFS=, ; echo $1)"
	typeset -r subopt=$2

	typeset foo

	# Check arg
	if [[ $# -ne 2 ]]; then
		return 0
	fi

	for foo in ${suboptlist}
	do
		# Strip out any option letters
		if [[ "${foo}" = -* ]]; then
			continue
		fi

		if [[ "${foo}" = *=* ]]; then
			foo="$(expr "${foo}" : ${subopt}'=\(.*\)')"
		fi
		if [[ -n "${foo}" ]]; then
			echo ${foo}
		fi
	done

	return 0
}

#####################################################
#
# new_separator <new_separator> "<space_separated_list>"
#
#	Replace the spaces in the "space_separated_list" with
#	the "new_separator", and print the results.
#
#	This function always returns zero.
#
#####################################################
new_separator()
{
	typeset new_separator=${1};  shift
	typeset space_separated_list="${*}"

	typeset item
	typeset newlist=

	for item in ${space_separated_list}
	do
		if [[ -z "${newlist}" ]]; then
			newlist=${item}
		else
			newlist=${newlist}:${item}
		fi
	done

	echo ${newlist}
}

#####################################################
#
# setlock()
#
#	Check for the "lockfile".  If it already
#	exists, print an error, and return with non-zero.
#	Otherwise, create a lockfile with our pid inside.
#
#####################################################
setlock()
{
	# If we already set our lock, return
	if [[ ${SC_LOCK_ISSET} -eq 1 ]]; then
		return 0
	fi

	# Check for lockfile
	if [[ -f ${lockfile} ]]; then
		printf "$(gettext '%s:  Another instance of this program may already be running')\n" "${PROG}" >&2
		printf "$(gettext '%s:  If not, remove %s and try again')\n" "${PROG}" "${lockfile}" >&2
		return 1
	fi

	# Create lockfile
	echo $$ >${lockfile} || return 1

	# Set the lock flag
	SC_LOCK_ISSET=1

	return 0
}

#####################################################
#
# is_os_okay
#
#	Return ${SC_FALSE} if the OS cannot support cluster software
#	Return ${SC_TRUE} if the OS might have support for cluster software
#
#####################################################
is_os_okay()
{
	if [[ -x /usr/sbin/clinfo ]]; then
		return ${SC_TRUE}
	fi

	return ${SC_FALSE}
}

#####################################################
#
# is_cluster_member()
#
#	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
#
#####################################################
is_cluster_member()
{
	if [[ -x /usr/sbin/clinfo ]]; then
		/usr/sbin/clinfo > /dev/null 2>&1
		if [[ $? -eq 0 ]]; then
			return ${SC_TRUE}
		fi
	fi

	return ${SC_FALSE}
}

#####################################################
#
# verify_d_option() [cdimagebasedir]
#
#	If "cdimagebasedir" is not set or does not include .cdtoc,
#	print an error message, and return non-zero.
#
#####################################################
verify_d_option()
{
	typeset -r cdimagebasedir=$1

	typeset foo
	integer found

	if [[ -z "${cdimagebasedir}" ]]; then
		printf "$(gettext '%s:  Please use -d to specify the location of the CDROM')\n" "${PROG}" | logerr
		return 1
	fi

	# Search for .cdtoc in cdimagebasedir or its children
	let found=0
	for foo in ${cdimagebasedir} ${cdimagebasedir}/*
	do
		if [[ -f "${foo}/${SC_CDTOC}" ]]; then
			let found=1
			break
		fi
	done
	if [[ ${found} -eq 0 ]]; then
		printf "$(gettext '%s:  Cannot find a \"%s\" file')\n" "${PROG}" "${SC_CDTOC}" | logerr
		printf "$(gettext '%s:  %s has unexpected layout')\n" "${PROG}" "${cdimagebasedir}" | logerr
		return 1
	fi

	return 0
}

#####################################################
#
# verify_G_option() [global]
#
#	verify -G option.   If there is a failure, print message
#	and return non-zero.
#
#	If global_fs is not given, the default is assumed.
#
#####################################################
verify_G_option()
{
	typeset -r global=$1

	# Print message ...
	printf "\n" | logmsg
	printf "$(gettext 'Checking device to use for global devices file system ... ')" | logmsg

	# Check device or filesystem
	if [[ -n "${global}" ]] && [[ -c "${global}" ]];  then
		is_globalcspecial_okay ${global} || return 1
	else
		is_globalfs_okay "${global}" || return 1
	fi
		
	# ... done message
	printf "%s\n" ${SC_DONE} | logmsg

	return 0
}

#####################################################
#
# admin()
#
#	Creates "adminfile" for pkgadd and pkgrm.
#
#	Returns non-zero on error.
#
#####################################################
admin()
{
	# Create file
	cat >${adminfile} <<END
basedir=default
mail=
runlevel=quit
conflict=nocheck
setuid=nocheck
action=nocheck
partial=quit
instance=unique
idepend=quit
rdepend=nocheck
space=quit
END

	return $?
}

#####################################################
#
# getproduct() cdtoc productname cluster flag
#
#	cdtoc			name of cdtoc(4) file
#	productname		product name (PRODNAME)
#	cluster			name of software cluster or metacluster
#	flag			set to "dir" or "rel"
#
#	Search the given "cdtoc" file for a "productname" with
#	a matching software "cluster".  If "productname" is NULL,
#	all products listed in the "cdtoc" file are considered in
#	order.
#
#	When a match is found, the absolute path to the product
#	directory is returned (if "dir");   or, the release is
#	is returned (if "rel").
#
#	Return:
#		zero		Success
#		non-zero	Failure
#
#####################################################
getproduct()
{
	typeset -r cdtoc=$1
	typeset -r productname="$2"
	typeset -r cluster=$3
	typeset -r flag=$4

	typeset cdtocdir
	typeset clustertoc
	typeset pname
	typeset pvers
	typeset pdir
	typeset line
	typeset value

	# Check arg
	if [[ $# -ne 4 ]]; then
		printf "$(gettext '%s:  Internal error - bad call to getproduct()')\n" ${PROG} >&2
		return 1
	fi

	# Make sure we have and absolute path for "cdtoc"
	if [[ "${cdtoc}" != /* ]]; then
		printf "$(gettext '%s:  Internal error - bad cdtoc in getproduct()')\n" "${PROG}" >&2
		return 1
	fi

	# Make sure we have a "cdtoc" file
	if [[ ! -f "${cdtoc}" ]]; then
		printf "$(gettext '%s:  Cannot find \"%s\"')\n" "${PROG}" "${cdtoc}" | logerr
		return 1
	fi

	# Get the base directory
	cdtocdir=${cdtoc%/*}

	# Read the file, looking for a matching product
	pname=
	pvers=
	while read line
	do
		case "${line}" in
		PRODNAME=*)
			# reset
			pname=
			pvers=
			pdir=

			# PRODNAME=<value>
			value=$(expr "${line}" : 'PRODNAME=\(.*\)')

			# set pname if no productname or productname match
			if [[ -z "${productname}" ]] ||
			    [[ "${productname}" = "${value}" ]]; then
				pname="${value}"
			fi
			;;

		PRODVERS=*)
			# reset
			pvers=

			# if pname is not set, skip it
			if [[ -z "${pname}" ]]; then
				break
			fi

			# PRODVERS=<value>
			pvers=$(expr "${line}" : 'PRODVERS=\(.*\)')

			# if pdir is set, and flag is "rel", we are done
			if [[ -n "${pdir}" ]] && [[ "${flag}" = "rel" ]]; then
				echo ${pvers}
				return 0
			fi
			;;

		PRODDIR=*)
			# reset
			pdir=

			# if pname is not set, skip it
			if [[ -z "${pname}" ]]; then
				break
			fi

			# PRODDIR=<value>
			value=$(expr "${line}" : 'PRODDIR=\(.*\)')

			# try to find the software cluster
			if [[ "${cdtocdir}" = "${SC_DOT_DIR}" ]]; then
				clustertoc="${SC_DOT_CLUSTERTOC}"
			else
				clustertoc="${cdtocdir}/${value}/${SC_CLUSTERTOC}"
			fi
			egrep '^CLUSTER='${cluster}'[ 	]*$|^METACLUSTER='${cluster}'[	 ]*$' ${clustertoc} >/dev/null 2>&1
			if [[ $? -eq 0 ]]; then
				pdir=${value}

				# if flag is "dir", we are done
				if [[ "${flag}" = "dir" ]]; then
					echo ${cdtocdir}/${pdir}
					return 0
				fi

				# if pvers is set, and flag is "rel", done
				if [[ -n "${pvers}" ]] && [[ "${flag}" = "rel" ]]; then
					echo ${pvers}
					return 0
				fi
			fi
			;;
		esac

	done < ${cdtoc}

	# not found
	printf "$(gettext '%s:  Cannot find \"%s\"')\n" "${PROG}" "${cluster}" | logerr

	return 1
}

#####################################################
#
# print_clustertoc() clustertocfile cluster flag [maxdepth]
#
#	clustertofile 		name of clustertoc(4) file
#	cluster			name of sofware cluster or metacluster
#	flag			either "packages" or "description"
#	maxdepth		maximum number of recursive calls
#
#	If the flag is set to "packages", list the the names of the
#	packages for the given software "cluster" defined in the given
#	"clustertocfile".  The package names are not listed in any
#	particular order, and any given	package may actually be listed
#	more than once.
#
#	If the flag is set to "description", print the description of
#	the software "cluster".
#
#	The format of the "clustertocfile" is described on the
#	clustertoc(4) man page.
#
#	This function may call itself recursively.   If "maxdepth" is
#	given, an error will be returned when it is equal to zero.
#	If it is not set, ${DEFAULT_MAXDEPTH} is used.
#
#	Return:
#		zero		Success
#		1		The "cluster" name is not found
#		> 1		Other failure
#
#####################################################
print_clustertoc()
{
	integer -r DEFAULT_MAXDEPTH=10

	integer -r STATE_INIT=1
	integer -r STATE_PROCESSING=2
	integer -r STATE_ENDED=3

	typeset -r clustertocfile=$1
	typeset -r cluster=$2
	typeset -r flag=$3
	typeset -r smaxdepth=$4
	integer maxdepth=${smaxdepth:-${DEFAULT_MAXDEPTH}}

	typeset state=${STATE_INIT}
	typeset line
	typeset pkglist

	#
	# METACLUSTER=<value>
	# CLUSTER=<value>
	# SUNW_CSRMEMBER=<thething>
	# SUNW_CSRMBRIFF=(<thetest> <thearg>)<thething>
	#
	typeset value
	typeset thething
	typeset thetest
	typeset thearg

	if [[ $# -ne 3 && $# -ne 4 ]]; then
		printf "$(gettext '%s:  Internal error - bad call to print_clustertoc()')\n" ${PROG} >&2
		return 2
	fi

	if [[ ${maxdepth} -eq 0 ]]; then
		printf "$(gettext '%s:  Bad .clustertoc file (\"%s\") - too deep')\n" ${PROG} ${clustertocfile} | logerr
		return 2
	fi

	if [[ ! -r ${clustertocfile} ]]; then
		printf "$(gettext '%s:  Cannot open \"%s\"')\n" ${PROG} ${clustertocfile} | logerr
		return 2
	fi

	#
	# Read the file, first searching for our "CLUSTER".
	#
	while read line
	do
		# Reset the metacluster/cluster/pkg name
		thething=

		case "${line}" in

		#
		# Look for our "METACLUSTER" or "CLUSTER" record.
		#
		METACLUSTER=* | CLUSTER=*)
			# Make sure the state is correct
			if [[ ${state} -eq ${STATE_PROCESSING} ]]; then
				printf "$(gettext '%s:  Bad .clustertoc file (\"%s\") - CLUSTER within CLUSTER')\n" ${PROG} ${clustertocfile} | logerr
				return 2
			fi

			# METACLUSTER=<value>
			# CLUSTER=<value>
			value=$(expr "${line}" : 'CLUSTER=\(.*\)')
			if [[ -z "${value}" ]]; then
				value=$(expr "${line}" : 'METACLUSTER=\(.*\)')
			fi

			# Make sure there is a value
			if [[ -z "${value}" ]]; then
				printf "$(gettext '%s:  Bad .clustertoc file (\"%s\") - CLUSTER error')\n" ${PROG} ${clustertocfile} | logerr
				return 2
			fi

			# If this is a match, change the state
			if [[ "${value}" = "${cluster}" ]]; then
				state=${STATE_PROCESSING}
			fi
			;;

		#
		# Process all SUNW_CSRMEMBER records
		#
		SUNW_CSRMEMBER=*)
			# Make sure we are supposed to be processing
			if [[ ${state} -ne ${STATE_PROCESSING} ]]; then
				continue
			fi

			# Looking for packages?
			if [[ "${flag}" != "packages" ]]; then
				continue
			fi

			# SUNW_CSRMEMBER=<thething>
			thething=$(expr "${line}" : 'SUNW_CSRMEMBER=\(.*\)')

			# Make sure there is a value
			if [[ -z "${thething}" ]]; then
				printf "$(gettext '%s:  Bad .clustertoc file (\"%s\") - SUNW_CSRMEMBER error')\n" ${PROG} ${clustertocfile} | logerr
				return 2
			fi
			;;

		#
		# Process all SUNW_CSRMBRIFF records
		#
		SUNW_CSRMBRIFF=*)
			# Make sure we are supposed to be processing
			if [[ ${state} -ne ${STATE_PROCESSING} ]]; then
				continue
			fi

			# Looking for packages?
			if [[ "${flag}" != "packages" ]]; then
				continue
			fi

			# SUNW_CSRMBRIFF=(<thetest> <thearg>)<thething>
			thetest=$(expr "${line}" : 'SUNW_CSRMBRIFF=(\(.*\)[ ].*')
			thearg=$(expr "${line}" : 'SUNW_CSRMBRIFF=(.*[ ][ ]*\(.*\)).*')
			thething=$(expr "${line}" : 'SUNW_CSRMBRIFF=(.*)\(.*\)')

			# Make sure all three are set
			if [[ -z "${thetest}" || \
			    -z "${thearg}" || \
			    -z "${thething}" ]]; then
				printf "$(gettext '%s:  Bad .clustertoc file (\"%s\") - SUNW_CSRMBRIFF error')\n" ${PROG} ${clustertocfile} | logerr
				return 1
			fi
	
			# We only support a "match" test
			case "${thetest}" in
			mach)
				# if not our machine type, skip it
				if [[ "${thearg}" != "${SC_ARCH}" ]]; then
					continue
				fi
				;;

			*)
				printf "$(gettext '%s:  Bad .clustertoc file (\"%s\") - unknown SUNW_CSRMBRIFF test')\n" ${PROG} ${clustertocfile} | logerr
				return 1
				;;
			esac

			;;
		#
		# Process the DESC
		#
		DESC=*)
			# Make sure we are supposed to be processing
			if [[ ${state} -ne ${STATE_PROCESSING} ]]; then
				continue
			fi

			# Looking for description?
			if [[ "${flag}" != "description" ]]; then
				continue
			fi

			# DESC=<ththing>
			thething=$(expr "${line}" : 'DESC=\(.*\)')

			echo ${thething}

			return 0
			;;

		#
		# Look for "END" record to match our "CLUSTER" 
		#
		END)
			# If processing, change the state
			if [[ ${state} = ${STATE_PROCESSING} ]]; then
				state=${STATE_ENDED}
			fi
			;;

		esac

		# If a membership record, look for the pkg or cluster
		if [[ -n "${thething}" ]]; then
			pkglist=$(print_clustertoc ${clustertocfile} ${thething} "packages" $((maxdepth - 1)))
			case $? in
			0)	# found cluster - list packages within cluster
				echo ${pkglist}
				;;

			1)	# not a cluster - list itself as a package
				echo ${thething}
				;;

			2)	# error
				return 2
				;;

			esac
		fi

		# Done?
		if [[ ${state} -eq ${STATE_ENDED} ]]; then
			break;
		fi

	done < ${clustertocfile}

	#
	# If still in INIT state, then we did not find CLUSTER entry,
	# so return 1.   This may or may not be considered an error by
	# the caller.   If we are still in the PROCESSING state, then
	# we never found a matching "END" to the "CLUSTER" keyword
	# in the .clustertoc file.
	#
	if [[ ${state} -eq ${STATE_INIT} ]]; then
		return 1
	elif [[ ${state} -eq ${STATE_PROCESSING} ]]; then
		printf "$(gettext '%s:  Bad .clustertoc file (\"%s\") - no END to CLUSTER')\n" ${PROG} ${clustertocfile} | logerr

	fi

	return 0
}

#####################################################
#
# order_packages() orderfile "pkglist"
#
#	List the the names, in order, of the packages in the
#	"pkglist" found in the given "orderfile".   Each package
#	is only listed once.
#
#	The format of the "orderfile" is described on the
#	order(4) man page.
#
#	Return:
#		zero		Success
#		non-zero	Failure
#
#####################################################
order_packages()
{
	typeset -r orderfile=$1
	typeset pkglist; set -A pkglist $2

	typeset pkg
	typeset foo
	integer i

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

	# read the order file, checking for pkgs in pkglist
	while read pkg foo
	do
		# Better be just one package per line
		if [[ -n "${foo}" ]]; then
			printf "$(gettext '%s:  Bad .order file (\"%s\") - bad line')\n" ${PROG} ${orderfile} | logerr
			return 1
		fi

		if [[ -z "${pkg}" ]]; then
			continue
		fi

		# if pkg is in pkglist, print it
		let i=0
		while [[ -n "${pkglist[i]}" ]]; do
			if [[ "${pkglist[i]}" = "${pkg}" ]]; then
				echo ${pkg}
				pkglist[i]="-"
				break
			fi
			((i += 1))
		done
	done < ${orderfile}

	# make sure we got everything from our package list
	let i=0
	while [[ -n "${pkglist[i]}" ]]; do
		if [[ "${pkglist[i]}" != "-" ]]; then
			printf "$(gettext '%s:  Bad .order file (\"%s\") - missing package(s)')\n" "${PROG}" "${orderfile}" | logerr
			return 1
		fi
		((i += 1))
	done

	return 0
}

#####################################################
#
# rev_order_packages() "pkglist"
#
#	Reverse the list.
#
#####################################################
rev_order_packages()
{
	typeset pkglist; set -A pkglist $1
	integer i

	if [[ $# -ne 1 ]]; then
		return 0
	fi

	let i=$(set -- ${pkglist[*]};  echo $#)
	while [[ ${i} -ne 0 ]]
	do
		((i -= 1))
		echo ${pkglist[i]}
	done

	return 0
}

#####################################################
#
# find_cdimagebasedir() directory productname cluster
#
#	directory		place to begin search of cdtoc
#	productname		product name (PRODNAME)
#	cluster			name of software cluster or metacluster
#
#	Attempt to find the cdimagebasedir for the given "product"
#	and "release" among the directories given in "directory".
#	Upon success, print the new cdimagebasedir to stdout.
#
#	Return:
#		zero		Success
#		non-zero	Failure
#
#####################################################
find_cdimagebasedir()
{
	typeset -r directory=$1
	typeset -r productname=$2
	typeset -r cluster=$3

	typeset foo

	# Check args
	if [[ $# -ne 3 ]] ||
	    [[ -z "${directory}" ]] ||
	    [[ ! -d "${directory}" ]]; then
		printf "$(gettext '%s:  Internal error - bad call to find_cdimagebasedir()')\n" ${PROG} >&2
		return 1
	fi

	# check each possible dir
	for foo in ${directory} ${directory}/*
	do
		if [[ -f "${foo}/${SC_CDTOC}" ]]; then
			getproduct ${foo}/${SC_CDTOC} "${productname}" "${cluster}" "dir" >/dev/null
			if [[ $? -eq 0 ]]; then
				echo ${foo}
				return 0
			fi
		fi
	done

	# not found
	printf "$(gettext '%s:  Cannot find \"%s\"')\n" "${PROG}" "${cluster}" | logerr

	return 1
}

#####################################################
#
# install_packages() productdir "pkglist" flag ["description"]
#
#	productdir		location of packages
#	"pkglist"		list of package names
#	flag			may be set to "framework" or NULL
#	"description"		description
#
#	Install the given list of packages.
#
#	Partially installed packages are removed, then re-installed.
#	Already installed packages are skipped.
#
#	If the flag is set to "framework", we accept an exit code of
#	10 from pkgadd.   Exit code 10 says reboot required.   And,
#	we do reboot on any "framework" install/upgrade.
#
#	Return:
#		zero		Success
#		non-zero	Failure
#
#####################################################
install_packages()
{
	typeset -r productdir=$1
	typeset pkglist;  set -A pkglist $2
	typeset -r flag=$3
	typeset -r description="$4"

	integer i
	integer j
	integer result
	typeset cmdstring
	typeset rootarg=
	typeset pstamp1
	typeset pstamp2
	typeset buffer

	# Check args
	if [[ $# -ne 3 && $# -ne 4 ]]; then
		printf "$(gettext '%s:  Internal error - bad call to install_packages()')\n" ${PROG} >&2
		return 1
	fi

	if [[ -n "${SC_BASEDIR}" ]] && [[ "${SC_BASEDIR}" != "/" ]]; then
		rootarg="-R ${SC_BASEDIR}"
	fi

	# introduce the set
	printf "\n" | logmsg

	# Print description, if there is one
	if [[ -n "${description}" ]]; then
		printf "** $(gettext 'Installing %s') **\n" "${description}" | logmsg
	fi

	# for each package
	let i=0
	while [[ -n "${pkglist[i]}" ]]
	do
		# Make sure we can find the package
		if [[ ! -d ${productdir}/${pkglist[i]} ]]; then
			printf "$(gettext '%s:  Unable to find \"%s\"')\n" "${PROG}" "${pkglist[i]}" | logerr
			return 1
		fi

		# See if the package is already installed
		pkginfo ${rootarg} ${pkglist[i]} >/dev/null 2>&1

		# It IS installed
		if [[ $? -eq 0 ]]; then

			# if only partially, back it out
			pkginfo -p ${rootarg} ${pkglist[i]} >/dev/null 2>&1
			if [[ $? -eq 0 ]]; then
				printf "$(gettext 'Removing partially installed package \"%s\"')\n" "${pkglist[i]}" | logmsg

				# Package remove
				cmdstring="pkgrm -n -a ${adminfile} ${rootarg} ${pkglist[i]}"
				printf "${cmdstring}" >>${install_log}
				${cmdstring} >${tmperrs} 2>&1
				let result=$?
				cat ${tmperrs} >>${install_log}
				if [[ ${result} -ne 0 ]]; then
					printf "$(gettext '%s:  Failed to remove \"%s\"')\n" "${PROG}" "${pkglist[i]}" | logerr
					return 1
				fi

			# otherwise, compare PSTAMPS
			else
				pstamp1=$(pkgparam -d ${productdir} ${pkglist[i]} PSTAMP)
				pstamp2=$(pkgparam ${rootarg} ${pkglist[i]} PSTAMP)
				if [[ -z "${pstamp1}" || -z "${pstamp2}" ]]; then
					printf "$(gettext '%s:  No PSTAMP for \"%s\"')\n" "${PROG}" "${pkglist[i]}" | logerr
					return 1
				fi

				# issue skip message ...
				printf "$(gettext 'Skipping \"%s\" - already installed')\n" "${pkglist[i]}" | logmsg

				# if PSTAMPs don't match, issue warning
				if [[ "${pstamp1}" != "${pstamp2}" ]]; then
					printf "$(gettext '%s:  WARNING:  but, the installed version is not the expected version!')\n" "${PROG}" | logmsg
				fi

				# Skip it
				((i += 1))
				continue
			fi
		fi

		#
		# Install the package
		#
		# The "<pkg>.....done." message is printed in the same
		# style as used by JumpStart.
		#
		let j=$(expr ${pkglist[i]} : .\*)
		((j = 12 - j))
		buffer=${pkglist[i]}
		while [[ ${j} -gt 0 ]]
		do
			buffer="${buffer}."
			((j -= 1))
		done
		printf "\t%s" "${buffer}"
		printf "\n\t%s\n" "${pkglist[i]}" >>${install_log}


		# Package add
		cmdstring="pkgadd -S -d ${productdir} -n -a ${adminfile} ${rootarg} ${pkglist[i]}"
		${PRINTDEBUG} ${cmdstring}
		printf "${cmdstring}" >>${install_log}
		${cmdstring} >${tmperrs} 2>&1
		let result=$?

		#
		# Package install scripts are supposed
		# to use exit codes 0 through 3 to indicate
		# success or failure.  In addition, 10 is supposed
		# to be added to the exit code if the system is to be
		# rebooted after all selected packages are installed.
		# Or, 20 should be added to the code if the system
		# needs to be rebooted immediately.
		#
		# So, we accept 10 as a successful exit code when
		# flag is set to "framework", since a reboot is always
		# provided for after a "framework" install.
		#
		if [[ ${result} -eq 10 ]] && [[ "${flag}" = "framework" ]]; then
			let result=0
		fi
		if [[ ${result} -ne 0 ]]; then
			printf "%s\n" ${SC_FAILED} | logmsg
			cat ${tmperrs} >>${install_log}
			printf "$(gettext '%s:  Installation of \"%s\" failed')\n" "${PROG}" "${pkglist[i]}" | logerr
			return 1
		fi
		printf "%s\n" ${SC_DONE} | logmsg
		cat ${tmperrs} >> ${install_log}

		# reboot requested on framework install
		if [[ "${flag}" = "framework" ]]; then
			reboot_requested=${SC_TRUE}
		fi

		# next
		((i += 1))
	done

	if [[ -n "${description}" ]]; then
		printf "\n" | logmsg
	fi

	return 0
}

#####################################################
#
# remove_patches() package
#
#	package		a package
#
#	Make sure that the desired package is not currently patched. Otherwise,
#	remove all patches against the package.
#
#	The following global variable is used:
#		install_log	tmperrs
#
#	Return:
#		zero		Success
#		non-zero	Failure
#
#####################################################
remove_patches()
{
	# check args
	if [[ $# -ne 1 ]]; then
		printf "%s:  $(gettext 'Internal error - bad call to remove_patches()')\n" ${PROG} >&2
		return 2
	fi

	typeset -r package=$1

	typeset -r patchdb="/var/sadm/patch"
	typeset revpatchlist; set -A revpatchlist ""
	typeset patchlist; set -A patchlist ""
	typeset patchid
	typeset cmd
	integer result

	# obtain the list of patches that have been put on this package.
	set -A patchlist $(pkgparam ${package} PATCHLIST 2> /dev/null)

	if [[ -z ${patchlist} ]]; then
		return 0
	fi

	# remove patches from newest to oldest
	revpatchlist=$(rev_order_packages "${patchlist[*]}")
	for patchid in ${revpatchlist[*]}
	do
		if [[ -d ${patchdb}/${patchid} ]]; then
			printf "\t$(gettext 'Removing') %s..." ${patchid}
			printf "\n\t$(gettext 'Removing') %s\n" ${patchid} >>${install_log}
			if [[ -x /usr/sbin/patchrm ]]; then
				cmd="patchrm -f ${patchid}"
				print ${cmd} >>${install_log}
				${cmd} >${tmperrs} 2>&1
				let result=$?
				cat ${tmperrs} >>${install_log}
				if (( result != 0 )); then
					printf "%s\n" ${SC_FAILED}
					printf "%s:  $(gettext 'Failed to remove') \"%s\"\n" ${PROG} ${patchid} | logerr
					return 1
				fi
			elif [[ -x ${patchdb}/${patchid}/backoutpatch ]]; then
				cmd="${patchdb}/${patchid}/backoutpatch ${patchid}"
				print ${cmd} >>${install_log}
				${cmd} >${tmperrs} 2>&1
				let result=$?
				cat ${tmperrs} >>${install_log}
				if (( result != 0 )); then
					printf "%s\n" ${SC_FAILED}
					printf "%s:  $(gettext 'Failed to remove') %s\n" ${PROG} ${patchid} | logerr
					return 1
				fi
			else
				printf "%s\n" ${SC_FAILED}
				printf "%s:  $(gettext 'Cannot find mechanism to remove patch') %s.\n" ${patchid} | logerr
				return 1
			fi
			printf "%s\n" ${SC_DONE}
		fi
	done

	return 0
}

#####################################################
#
# remove_packages() "pkglist" ["heading"] [nopatchrm]
#
#	"pkglist"		list of package names
#	"heading"		optional heading
#	nopatchrm		if set, do not remove patches
#
#	Remove the given list of packages.  Packages not installed
#	are ignored.
#
#	It should not be necessary to remove patches before removing
#	packages.  Patch information is automatically removed with packages.
#	The old remove_patches() function remains for 2.2 upgrad,
#	where the old style of patches was still possible.
#
#	Return:
#		zero		Success
#		non-zero	Failure
#
#####################################################
remove_packages()
{
	typeset pkglist;  set -A pkglist ${1}
	typeset heading="${2}"
	typeset nopatchrm=${3}

	typeset buffer
	typeset cmdstring
	typeset rootarg=

	integer i
	integer j
	integer found

	# Check args
	if [[ $# -lt 1 ]]; then
		printf "$(gettext '%s:  Internal error - bad call to remove_packages()')\n" ${PROG} >&2
		return 1
	fi

	if [[ -n "${SC_BASEDIR}" ]] && [[ "${SC_BASEDIR}" != "/" ]]; then
		rootarg="-R ${SC_BASEDIR}"
	fi

	# Make sure that there is at least one package remove
	let i=0
	let found=0
	while [[ -n "${pkglist[i]}" ]]
	do
		# See if the package is installed
		pkginfo ${rootarg} ${pkglist[i]} >/dev/null 2>&1

		# Found one
		if [[ $? -eq 0 ]]; then
			let found=1
			break
		fi
		((i += 1))
	done

	# if nothing to remove, return now
	if [[ ${found} -eq 0 ]]; then
		return 0
	fi

	# introduce the remove set
	printf "\n" | logmsg

	# Print action
	if [[ -z "${heading}" ]]; then
		heading="$(gettext 'Removing packages')"
	fi
	printf "** %s **\n" "${heading}" | logmsg

	# for each package
	let i=0
	while [[ -n "${pkglist[i]}" ]]
	do
		# See if the package is installed
		pkginfo ${rootarg} ${pkglist[i]} >/dev/null 2>&1
		if [[ $? -ne 0 ]]; then
			((i += 1))
			continue
		fi

		# remove any patches made against ${pkglist[i]}
		if [[ -z "${nopatchrm}" ]]; then
			remove_patches ${pkglist[i]} || return 1
		fi

		# print message
		let j=$(expr ${pkglist[i]} : .\*)
		((j = 12 - j))
		buffer=${pkglist[i]}
		while [[ ${j} -gt 0 ]]
		do
			buffer="${buffer}."
			((j -= 1))
		done
		printf "\t$(gettext 'Removing %s')" "${buffer}"
		printf "\n\t$(gettext 'Removing %s')" "${pkglist[i]}" >>${install_log}

		# Package remove
		cmdstring="pkgrm -n -a ${adminfile} ${rootarg} ${pkglist[i]}"
		printf "${cmdstring}" >>${install_log}
		${cmdstring} >${tmperrs} 2>&1
		if [[ $? -ne 0 ]]; then
			printf "%s\n" ${SC_FAILED} | logmsg
			cat ${tmperrs} >>${install_log}
			printf "$(gettext '%s:  Failed to remove \"%s\"')\n" "${PROG}" "${pkglist[i]}" | logerr
		else
			printf "%s\n" ${SC_DONE} | logmsg
			cat ${tmperrs} >>${install_log}
		fi
		((i += 1))
	done

	return 0
}

#####################################################
#
# installframework() cdimagebasedir
#
#	cdimagebasedir		location of .cdtoc file
#
#	Install the framework packages.
#
#	Partially installed packages are removed, then re-installed.
#	Already installed packages are skipped.
#
#	Return:
#		zero		Success
#		non-zero	Failure
#
#####################################################
installframework()
{
	typeset -r cdimagebasedir=$1

	typeset realcdimage
	typeset productdir
	typeset productrel
	typeset pkglist
	typeset revpkglist

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

	#
	# Note that the cdimagebasedir may be given as either the
	# directory containing the .cdtoc we are looking for OR
	# the directory above that.  This call to find_cdimagebasedir()
	# will reset the cdimagebasedir to be the directory containing
	# our .cdtoc.
	# 
	realcdimage=$(find_cdimagebasedir "${cdimagebasedir}" "${SC_PRODUCT}" "${SC_CLUSTER}") || return 1

	# get the name of the product directory
	productdir=$(getproduct ${realcdimage}/${SC_CDTOC} "${SC_PRODUCT}" ${SC_CLUSTER} "dir") || return 1

	# get the product release
	productrel=$(getproduct ${realcdimage}/${SC_CDTOC} "${SC_PRODUCT}" ${SC_CLUSTER} "rel") || return 1

	# get the list of packages
	pkglist="$(print_clustertoc ${productdir}/${SC_CLUSTERTOC} ${SC_CLUSTER} "packages")" || return 1

	# order the list of packages
	pkglist="$(order_packages ${productdir}/${SC_ORDER} "${pkglist}")" || return 1

	# install packages
	install_packages ${productdir} "${pkglist}" "framework" "${SC_PRODUCT} ${productrel}" || return 1
	let SC_FRAMEWORK_INSTALLED=1

        # make sure next reboot is a reconfig reboot
	if [[ -z "${SC_BASEDIR}" ]]; then
		touch /reconfigure
	fi       

	return 0
}

#####################################################
#
# uninstallframework() clustertoc order [all]
#
#	clustertoc		name of the clustertoc file to use
#	order			name of the order file to use
#	all			if not NULL, uninstall everything in order file
#
#	Uninstall the framework packages.
#
#	Return:
#		zero		Success
#		non-zero	Failure
#
#####################################################
uninstallframework()
{
	typeset -r clustertoc=$1
	typeset -r order=$2
	typeset -r all=$3

	typeset pkglist
	typeset revpkglist
	typeset heading

	# Check args
	if [[ $# -lt 2 ]] ||
	    [[ "${clustertoc}" != /* ]] ||
	    [[ "${order}" != /* ]];  then
		printf "$(gettext '%s:  Internal error - bad call to uninstallframework()')\n" ${PROG} >&2
		return 1
	fi

	# If "all", use everything in the order file
	if [[ -n "${all}" ]]; then
		pkglist="$(cat ${order})"
	else
		# get the list of packages
		pkglist="$(print_clustertoc ${clustertoc} ${SC_CLUSTER} "packages")" || return 1

		# order the list of packages
		pkglist="$(order_packages ${order} "${pkglist}")" || return 1
	fi

	# reverse the order
	revpkglist="$(rev_order_packages "${pkglist}")"

	# remove the packages
	heading="$(printf "$(gettext 'Removing %s packages')" "Sun Cluster")"
	remove_packages "${revpkglist}" "${heading}" no || return 1
	let SC_FRAMEWORK_INSTALLED=0

	return 0
}

#####################################################
#
# installservices() cdimagebasedir "services"
#
#	cdimagebasedir		location of .cdtoc file
#	"services"		list of services
#
#	Install the service packages from the "services" list.
#
#	Partially installed packages are removed, then re-installed.
#	Already installed packages are skipped.
#
#	Each Sun cluster "data service" has its own individual
#	software cluster name associated with it.  The name
#	has the format "SUNWC_DS_<srvcname>", where "srvcname"
#	is the name given to scinstall(1M).
#
#	Return:
#		zero		Success
#		non-zero	Failure
#
#####################################################
installservices()
{
	typeset -r cdimagebasedir=$1
	typeset -r services="${2}"

	integer j
	typeset realcdimage
	typeset service
	typeset productdir
	typeset description
	typeset pkglist

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

	# for each service
	let i=0
	for service in ${services}
	do
		#
		# Note that the cdimagebasedir may be given as either the
		# directory containing the .cdtoc we are looking for OR
		# the directory above that.  This call to find_cdimagebasedir()
		# will reset the cdimagebasedir to be the directory containing
		# our .cdtoc.
		# 
		realcdimage=$(find_cdimagebasedir "${cdimagebasedir}" "" "${SC_SERVICE}${service}") || return 1

		# get the name of the product directory
		productdir=$(getproduct ${realcdimage}/${SC_CDTOC} "" ${SC_SERVICE}${service} "dir" 2>/dev/null)
		if [[ $? -ne 0 ]]; then
			printf "$(gettext 'Cannot find data service \"%s\" - skipping')\n" "${service}" | logmsg
			continue
		fi

		# get the product description
		description="$(print_clustertoc ${productdir}/${SC_CLUSTERTOC} ${SC_SERVICE}${service} "description" 2>/dev/null)"
		if [[ -z "${description}" ]]; then
			description="Data service ${service}"
		fi

		# get the list of packages
		pkglist="$(print_clustertoc ${productdir}/${SC_CLUSTERTOC} ${SC_SERVICE}${service} "packages")"
		if [[ $? -ne 0 ]]; then
			printf "$(gettext 'Cannot get package list for data service \"%s\" - skipping')\n" "${service}" | logmsg
			continue
		fi

		# if more than one package in cluster, find the order
		let j=$(set -- ${pkglist};  echo $#)
		if [[ ${j} -gt 1 ]]; then
			pkglist="$(order_packages ${productdir}/${SC_ORDER} "${pkglist}")"
			if [[ $? -ne 0 ]]; then
				printf "$(gettext 'Unable to determine package order for data service \"%s\" - skipping')\n" "${service}" | logmsg
				continue
			fi
		fi

		# install packages
		install_packages ${productdir} "${pkglist}" "" "${description}"

		# next
	done

	return 0
}

#####################################################
#
# uninstallservices() [exceptions]
#
#	exceptions		list of packages NOT to unininstall
#
#	Uninstall the SUNW data services packages.  The list of packages
#	to uninstall is gleened from the "pkglist" in rtreg files.
#	To prevent the uninstall of framework packages,	an "exceptions"
#	list can be given.
#
#	The order in which these packages are removed should not be
#	important, since rdepend=nocheck is specified in the admin file.
#
#	Return:
#		zero		Success
#		non-zero	Failure
#
#####################################################
uninstallservices()
{
	typeset -r exceptions="${1}"

	typeset pkg
	typeset pkglist
	typeset heading
	typeset file
	typeset services_pkglist
	typeset key
	typeset foo

	# Get an ordered list of packages from these SUNW resource types
	services_pkglist="$(
		for file in /usr/cluster/lib/rgm/rtreg/SUNW*
		do
                	# Get the list of packages
                	pkglist="$(
                        	LANG=C; export LANG;
                        	key="$(grep -i '^[ 	]*pkglist' ${file} | sed -n 's/^[ 	]*\([^ 	=]*\).*/\1/p')"
                        	key=$(set -- ${key}; echo ${1})
                        	sed -n 's/^[ 	]*'${key}'[ 	]*=[ 	"]*\([^";]*\).*/\1/p' ${file}
			)"
			pkglist="$(IFS=" 	," ; set -- ${pkglist}; echo $*)"

			# Reverse the order
			rev_order_packages "${pkglist}"
		done
	)"

	# Weed out the exceptions
	pkglist=
	if [[ -n "${exceptions}" ]]; then
		for pkg in ${services_pkglist}
		do
			for foo in ${exceptions}
			do
				if [[ "${foo}" == "${pkg}" ]]; then
					continue 2
				fi
			done
			pkglist="${pkglist} ${pkg}"
		done
	fi
	services_pkglist="${pkglist}"

	# remove the packages
	heading="$(printf "$(gettext 'Removing %s data services packages')" "Sun Cluster")"
	remove_packages "${pkglist}" "${heading}" no || return 1

	return 0
}

#####################################################
#
# expand_adapter_options() "adapter_options"
#
#	Expand the adapter options from the default.
#	If only an adapter name is given, "trtype" is added.
#
#	"setdefaults()" should always be called before
#	calling this function.
#
#	This function always returns 0.
#
#####################################################
expand_adapter_options()
{
	typeset -r adapter_options="$1"

	typeset new_options=
	typeset subopts

	integer i
	typeset c

	# make sure options are given as expected
	if [[ $# -ne 1 ]]; then
		echo $*
		return 0
	fi

	# make sure required defaults are set
	if [[ -z "${SC_DFLT_TRANSPORT_TYPE}" ]]; then
		echo "${adapter_options}"
		return 0
	fi

	set - ${adapter_options}
	OPTIND=1
	while getopts A: c 2>/dev/null
	do
		case ${c} in
		A)	# Adapter
			subopts="${OPTARG}"

			# check for single suboption
			let i=$(IFS=, ; set - ${subopts};  echo $#)
			if [[ ${i} -ne 1 ]]; then
				new_options="${new_options} -A ${subopts}"
				continue
			fi

			# is it a standalone adapter name?
			if [[ "${subopts}" != *=* ]]; then
				subopts="name=${subopts}"
			fi

			# make sure single option is "name"
			if [[ "${subopts}" != name=* ]]; then
				continue
			fi

			# add other required suboption(s)
			subopts="${subopts},trtype=${SC_DFLT_TRANSPORT_TYPE}"

			# add it to the list
			new_options="${new_options} -A ${subopts}"

			;;

		*)	# Error
			echo "${adapter_options}"
			return 0
			;;
		esac
	done

	if [[ -n "${new_options}" ]]; then
		echo "${new_options}"
	else
		echo "${adapter_options}"
	fi

	return 0
}

#####################################################
#
# strip_type_direct() "bb_options"
#
#	Strip out "type=direct" from the bb_options.
#
#	This function always returns 0.
#
#####################################################
strip_type_direct()
{
	typeset -r bb_options="$1"

	typeset new_options=
	typeset new_subopts
	typeset subopts
	typeset subopt

	integer i
	typeset c

	# make sure options are given as expected
	if [[ $# -ne 1 ]]; then
		echo $*
		return 0
	fi

	set - ${bb_options}
	OPTIND=1
	while getopts B: c 2>/dev/null
	do
		case ${c} in
		B)	# Blackbox
			subopts="${OPTARG}"

			# Remove, from subopts
			subopts="$(IFS=, ; set - ${subopts};  echo $*)"

			new_subopts=
			for subopt in ${subopts}
			do
				if [[ "${subopt}" != "type=direct" ]]; then
					if [[ -n "${new_subopts}" ]]; then
						new_subopts="${new_subopts},${subopt}"
					else
						new_subopts="${subopt}"
					fi
				fi
			done

			if [[ -n "${new_subopts}" ]]; then
				new_options="${new_options} -B ${new_subopts}"
			fi

			;;

		*)	# Error
			echo "${bb_options}"
			return 0
			;;
		esac
	done

	echo "${new_options}"

	return 0
}

#####################################################
#
# expand_bb_options() "bb_options"
#
#	Expand the blackbox options from the default.
#	If only a blackbox name is given, "type" is added.
#
#	"setdefaults()" should always be called before
#	calling this function.
#
#	This function always returns 0.
#
#####################################################
expand_bb_options()
{
	typeset -r bb_options="$1"

	typeset new_options=
	typeset subopts

	integer i
	typeset c

	# make sure options are given as expected
	if [[ $# -ne 1 ]]; then
		echo $*
		return 0
	fi

	# make sure required defaults are set
	if [[ -z "${SC_DFLT_JUNCTION_TYPE}" ]]; then
		echo "${bb_options}"
		return 0
	fi

	set - ${bb_options}
	OPTIND=1
	while getopts B: c 2>/dev/null
	do
		case ${c} in
		B)	# Blackbox
			subopts="${OPTARG}"

			# check for single suboption
			let i=$(IFS=, ; set - ${subopts};  echo $#)
			if [[ ${i} -ne 1 ]]; then
				new_options="${new_options} -B ${subopts}"
				continue
			fi

			# is it a standalone blackbox name?
			if [[ "${subopts}" != *=* ]]; then
				subopts="name=${subopts}"
			fi

			# make sure single option is "name"
			if [[ "${subopts}" != name=* ]]; then
				continue
			fi

			# add other required suboption(s)
			subopts="${subopts},type=${SC_DFLT_JUNCTION_TYPE}"

			# add it to the list
			new_options="${new_options} -B ${subopts}"

			;;

		*)	# Error
			echo "${bb_options}"
			return 0
			;;
		esac
	done

	if [[ -n "${new_options}" ]]; then
		echo "${new_options}"
	else
		echo "${bb_options}"
	fi

	return 0
}

#####################################################
#
# print_default_cable() "adapter_options" "bb_options" "installnode"
#
#	Using the given "adapter_options" and "bb_options",
#	print an option for a default cable.  If there is
#	more than one adapter or bb, there is no default.
#
#	This function always returns 0.
#
#####################################################
print_default_cable()
{
	typeset adapter_options="$1"
	typeset bb_options="$2"
	typeset installnode="$3"

	typeset subopts;  set -A subopts ""
	typeset adapter_name=
	typeset bb_name=

	integer a
	integer b

	typeset c

	# make sure that adapter options are set
	if [[ -z "${adapter_options}" ]]; then
		if [[ -n "${SC_DFLT_JUNCTION_OPTS}" ]]; then
			adapter_options="-A ${SC_DFLT_ADAPTER_OPTS}"
		else
			return 0
		fi
	fi

	# if bb_options not set, use the default bb name
	if [[ -z "${bb_options}" ]]; then
		if [[ -n "${SC_DFLT_JUNCTION_OPTS}" ]]; then
			bb_options="-B ${SC_DFLT_JUNCTION_OPTS}"
		else
			return 0
		fi
	fi

	# remove the "-A" and "-B"
	let a=0
	let b=0
	set - ${adapter_options} ${bb_options}
	OPTIND=1
	while getopts A:B: c 2>/dev/null
	do
		case ${c} in
		A)	# Adapter
			adapter_options=${OPTARG}
			((a += 1))
			;;

		B)	# Junction
			bb_options=${OPTARG}
			((b += 1))
			;;

		*)
			return 0
			;;
		esac
	done

	# if more than 1 -A or -B, we are done
	if [[ ${a} -gt 1 || ${b} -gt 1 ]]; then
		return 0
	fi

	# make sure they are both still set
	if [[ -z "${adapter_options}" || -z "${bb_options}" ]]; then
		return 0
	fi

	# get the adapter name
	let a=0
	set -A subopts $(IFS=, ; echo ${adapter_options})
	while [[ -n "${subopts[a]}" ]]
	do
		adapter_name=$(expr "${subopts[a]}" : 'name=\(.*\)')
		if [[ -n "${adapter_name}" ]]; then
			break
		fi
		((a += 1))
	done

	# and, the bb name
	let a=0
	set -A subopts $(IFS=, ; echo ${bb_options})
	while [[ -n "${subopts[a]}" ]]
	do
		bb_name=$(expr "${subopts[a]}" : 'name=\(.*\)')
		if [[ -n "${bb_name}" ]]; then
			break
		fi
		((a += 1))
	done

	# make sure both names are set
	if [[ -z "${adapter_name}" && -z "${bb_name}" ]]; then
		return 0
	fi

	# print default cable
	echo "-m endpoint=${installnode}:${adapter_name},endpoint=${bb_name}"

	return 0
}

#####################################################
#
# initialize_cluster() "clustername" "auth_options" "adapter_options"
#    "bb_options" "cable_options" "netaddr_options"
#
#	"clustername"			cluster name
#	"auth_options"			"-T <authentication_options>"
#	"adapter_options"		"-A <adapter_options> ..."
#	"bb_options"			"-B <blackbox_options> ..."
#	"cable_options"			"-m <cable_options> ..."
#	"netaddr_options"		"-w <network_options"
#
#	Initialize the local CCR database.
#
#	Return:
#		zero		Success
#		non-zero	Failure
#
#####################################################
initialize_cluster()
{
	typeset -r clustername=$1
	typeset -r auth_options=$2
	typeset -r adapter_options=$3
	typeset -r bb_options=$4
	typeset -r cable_options=$5
	typeset -r netaddr_options=$6

	integer result
	typeset c
	typeset message
	typeset cluster_options
	typeset infrastructure
	typeset cmd
	typeset cmdarg
	typeset cmdargs=

	# Check args
	if [[ $# -ne 6 ]]; then
		printf "$(gettext '%s:  Internal error - bad call to initialize_cluster()')\n" ${PROG} >&2
		return 1
	fi

	# Set cluster options
	cluster_options=
	if [[ -n "${clustername}" ]]; then
		cluster_options="-C ${clustername}"
	fi

	# Check for scrconf usage errors
	OPTIND=1
	set - ${cluster_options} ${auth_options} ${adapter_options} ${bb_options} ${cable_options} ${netaddr_options}
	while getopts C:T:A:B:m:w: c 2>/dev/null
	do
		rm -f ${tmpconfig}
		cmdarg="-${c} ${OPTARG}"
		if [[ ${c} = A ]]; then
			cmdarg="${cmdarg},node=${mynodename}"
		fi
		cmdargs="${cmdargs} ${cmdarg}"
		cmd="scrconf -U -f ${tmpconfig} ${cmdargs}"
		${PRINTDEBUG} ${cmd}
		${cmd} 2>${tmperrs}
		let result=$?
		if [[ ${result} -eq ${SC_SCCONF_EUSAGE} ]]; then
			printf "$(gettext '%s:  Bad %s option (\"%s\")')\n" "${PROG}" "-${c}" "${cmdarg}" | logerr
		elif [[ ${result} -ne 0 ]]; then
			if [[ -s "${tmperrs}" ]]; then
				cat ${tmperrs} | logerr
			fi
			printf "$(gettext '%s:  Unexpected result (%d) from scrconf during option check (\"%s\")')\n" "${PROG}" "${result}" "${cmdarg}" | logerr
		fi
		if [[ ${result} -ne 0 ]]; then
			rm -f ${tmpconfig}
			return 1
		fi
	done

	printf "\n" | logmsg

	# Iteratively create a tmp config file
	OPTIND=1
	cmdargs="-h node=${mynodename}"
	set - ${cluster_options} ${auth_options} ${adapter_options} ${bb_options} ${cable_options} ${netaddr_options}
	while getopts C:T:A:B:m:w: c 2>/dev/null
	do
		rm -f ${tmpconfig}
		cmdarg="-${c} ${OPTARG}"
		if [[ ${c} = A ]]; then
			cmdarg="${cmdarg},node=${mynodename}"
		fi
		cmdargs="${cmdargs} ${cmdarg}"

		case ${c} in
		C)	# clustername
			message="$(printf "$(gettext 'Initializing cluster name to \"%s\" ... ')" "${OPTARG}")"
			;;

		T)	# authentication
			message="$(printf "$(gettext 'Initializing authentication options ... ')")"
			;;


		h)	# nodename
			message="$(printf "$(gettext 'Initializing configuration for node \"%s\" ... ')" "${mynodename}")"
			;;

		A)	# adapter
			message="$(printf "$(gettext 'Initializing configuration for adapter \"%s\" ... ')" "$(print_subopt_values "${OPTARG}" "name")")"
			;;

		B)	# blackbox
			message="$(printf "$(gettext 'Initializing configuration for junction \"%s\" ... ')" "$(print_subopt_values "${OPTARG}" "name")")"
			;;

		m)	# cable
			message="$(printf "$(gettext 'Initializing configuration for cable ... ')")"
			;;

		w)	# netaddr
			message="$(printf "$(gettext 'Initializing private network address options ... ')")"
			;;

		*)	message="??? ... "
			;;
		esac

		# printf first part of message
		printf "${message}" | logmsg

		cmd="scrconf -f ${tmpconfig} ${cmdargs}"
		${PRINTDEBUG} ${cmd}
		${cmd} 2>${tmperrs}
		let result=$?
		if [[ ${result} -ne 0 ]]; then
			printf "%s\n" ${SC_FAILED} | logmsg
			if [[ -s "${tmperrs}" ]]; then
				cat ${tmperrs} | logerr
			fi
			printf "$(gettext '%s:  Failed to initialize cluster configuration (\"%s\")')\n" "${PROG}" "${cmdarg}" | logerr
			rm -f ${tmpconfig}
			return 1
		fi
		printf "%s\n" ${SC_DONE} | logmsg
	done

	# add checksum
	ccradm -i ${tmpconfig} -o
	if [[ $? -ne 0 ]]; then
		printf "$(gettext '%s:  Failed to add CCR checksum to infrastructure table')\n" "${PROG}" | logerr 
		return 1
	fi

	# Now, move file into place
	mv ${tmpconfig} ${SC_CONFIG}
	if [[ $? -ne 0 ]]; then
		printf "$(gettext '%s:  Failed to create cluster configuration file')\n" "${PROG}" | logerr
		rm -f ${tmpconfig}
		return 1
	fi

	# Set attributes
	setfile "${SC_CONFIG}"
	if [[ $? -ne 0 ]]; then
		printf "$(gettext '%s:  Cannot set attributes of the cluster config file (\"%s\")')\n" "${PROG}" "${SC_CONFIG}" | logerr
		return 1
	fi

	# If there is no CCR table directory, add it now
	if [[ ! -f "${SC_CCRDIR}" ]]; then
		touch "${SC_CCRDIR}"
		if [[ $? -ne 0 ]]; then
			printf "$(gettext '%s:  Cannot create the cluster config table directory (\"%s\")')\n" "${PROG}" "${SC_CCRDIR}" | logerr
			return 1
		fi
		setfile "${SC_CCRDIR}"
		if [[ $? -ne 0 ]]; then
			printf "$(gettext '%s:  Cannot set attributes of the cluster config table directory (\"%s\")')\n" "${PROG}" "${SC_CCRDIR}" | logerr
			return 1
		fi
	fi

	# Add the infrastructure table to the CCR table directory
	infrastructure=${SC_CONFIG##*/}
	grep '^'${infrastructure} ${SC_CCRDIR} >/dev/null
	if [[ $? -ne 0 ]]; then
		echo ${infrastructure} >>${SC_CCRDIR}
		if [[ $? -ne 0 ]]; then
			printf "$(gettext '%s:  Cannot update the cluster config table directory (\"%s\")')\n" "${PROG}" "${SC_CCRDIR}" | logerr
			return 1
		fi
	fi

	# add checksum
	ccradm -i ${SC_CCRDIR} -o
	if [[ $? -ne 0 ]]; then
		printf "$(gettext '%s:  Failed to add CCR checksum to table directory')\n" "${PROG}" | logerr 
		return 1
	fi

	return 0
}

#####################################################
#
# addnode_tocluster() sponsornode "clustername_options" "adapter_options"
#    "bb_options" "cable_options"
#
#	sponsornode			remote node for connection
#	clustername			verify cluster name
#	"adapter_options"		"-A <adapter_options> ..."
#	"bb_options"			"-B <blackbox_options> ..."
#	"cable_options"			"-m <cable_options> ..."
#
#	Initialize the local CCR database from remote cluster.
#
#	Return:
#		zero		Success
#		non-zero	Failure
#
#####################################################
addnode_tocluster()
{
	typeset -r sponsornode=$1
	typeset -r clustername=$2
	typeset -r adapter_options=$3
	typeset -r bb_options=$4
	typeset -r cable_options=$5

	integer result
	integer bad_options=${SC_FALSE}
	integer firstime
	typeset c
	typeset cmd
	typeset cmdarg
	typeset buffer
	typeset lastbusy
	typeset busynode
	typeset sc_config

	# Check args
	if [[ $# -ne 5 ]]; then
		printf "$(gettext '%s:  Internal error - bad call to addnode_tocluster()')\n" ${PROG} >&2
		return 1
	fi

	#
	# Wait for sponsor node to join cluster
	#
	cmd="scrconf -x 10 -N ${sponsornode}"
	${PRINTDEBUG} ${cmd}
	${cmd} >${tmperrs} 2>&1
	let result=$?
	if [[ ${result} -eq ${SC_SCCONF_ETIMEDOUT} ]]; then
		printf "\n" | logmsg
		printf "$(gettext 'Current time - %s')\n" "$(date)" | logmsg
		printf "$(gettext 'Waiting for \"%s\" to join the cluster ... ')" "${sponsornode}" | logmsg
		cmd="scrconf -x ${SC_WTIMEOUT} -N ${sponsornode}"
		${PRINTDEBUG} ${cmd}
		${cmd} >${tmperrs} 2>&1
		let result=$?
		if [[ ${result} -eq ${SC_SCCONF_ETIMEDOUT} ]]; then
			printf "$(gettext 'timed out')\n" | logmsg
		elif [[ ${result} -ne 0 ]]; then
			printf "%s\n" ${SC_FAILED} | logmsg
		else
			printf "$(gettext 'established')\n" | logmsg
		fi
		printf "$(gettext 'Current time - %s')\n" "$(date)" | logmsg
	fi
	if [[ ${result} -ne 0 ]] &&
	    [[ ${result} -ne ${SC_SCCONF_ETIMEDOUT} ]]; then
		if [[ -s "${tmperrs}" ]]; then
			cat ${tmperrs} | logerr
		fi
		printf "$(gettext '%s:  Failed communications with \"%s\"')\n" "${PROG}" "${sponsornode}" | logerr
	fi
	if [[ ${result} -ne 0 ]]; then
		return 1
	fi

	# Verify that we are talking to the right cluster
	if [[ -n "${clustername}" ]]; then
		cmd="scrconf -a -N ${sponsornode} -C ${clustername}"
		${PRINTDEBUG} ${cmd}
		${cmd} >${tmperrs} 2>&1
		let result=$?
		if [[ ${result} -eq ${SC_SCCONF_ENOCLUSTER} ]]; then
			printf "$(gettext '%s:  \"%s\" does not belong to cluster \"%s\"')\n" "${PROG}" "${sponsornode}" "${clustername}" | logerr
		elif [[ ${result} -eq ${SC_SCCONF_ENOEXIST} ]]; then
			printf "$(gettext '%s:  The cluster to which \"%s\" belongs does not have a name')\n" "${PROG}" "${sponsornode}" | logerr
		elif [[ ${result} -ne 0 ]]; then
			if [[ -s "${tmperrs}" ]]; then
				cat ${tmperrs} | logerr
			fi
			printf "$(gettext '%s:  Failed to establish the name of the cluster')\n" "${PROG}" | logerr
		elif [[ -n "${SC_DEBUG}" ]] && [[ -s "${tmperrs}" ]]; then
			cat ${tmperrs} | logerr
		fi
		if [[ ${result} -ne 0 ]]; then
			return 1
		fi
	fi


	# Check for scrconf usage errors
	OPTIND=1
	set - ${adapter_options} ${bb_options} ${cable_options}
	while getopts A:B:m: c 2>/dev/null
	do
		cmdarg="-${c} ${OPTARG}"
		if [[ ${c} = A ]]; then
			cmdarg="${cmdarg},node=${mynodename}"
		fi
		cmd="scrconf -U -a -N ${sponsornode} ${cmdarg}"
		${PRINTDEBUG} ${cmd}
		${cmd} 2>${tmperrs}
		let result=$?
		if [[ ${result} -eq ${SC_SCCONF_EUSAGE} ]]; then
			printf "$(gettext '%s:  Bad %s option (\"%s\")')\n" "${PROG}" "-${c}" "${cmdarg}" | logerr
			let bad_options=${SC_TRUE}
		elif [[ ${result} -ne 0 ]]; then
			if [[ -s "${tmperrs}" ]]; then
				cat ${tmperrs} | logerr
			fi
			printf "$(gettext '%s:  Unexpected result (%d) from scrconf during option check (\"%s\")')\n" "${PROG}" "${result}" "${cmdarg}" | logerr
			let bad_options=${SC_TRUE}
		fi
		if [[ ${result} -ne 0 ]]; then
			rm -f ${tmpconfig}
		fi
	done

	# If one or more bad options, we are done
	if [[ ${bad_options} -eq ${SC_TRUE} ]]; then
		return 1
	fi

	printf "\n" | logmsg

	# Update cluster config
	OPTIND=1
	set - -h node=${mynodename} ${adapter_options} ${bb_options} ${cable_options} 2>/dev/null
	while getopts h:A:B:m: c 2>/dev/null
	do
		cmdarg="-${c} ${OPTARG}"
		if [[ ${c} = A ]]; then
			cmdarg="${cmdarg},node=${mynodename}"
		fi
		case ${c} in
		h)	# nodename
			buffer="$(printf "$(gettext 'node \"%s\"')" "${mynodename}")"
			;;

		A)	# adapter
			buffer="$(printf "$(gettext 'adapter \"%s\"')" "$(print_subopt_values "${OPTARG}" "name")")"
			;;

		B)	# blackbox
			buffer="$(printf "$(gettext 'junction \"%s\"')" "$(print_subopt_values "${OPTARG}" "name")")"
			;;

		m)	# cable
			buffer="$(gettext 'cable')"
			;;

		*)	buffer="?"
			;;
		esac

		printf "$(gettext 'Adding %s to the cluster configuration ... ')" "${buffer}" | logmsg

		rm -f ${tmperrs}
		cmd="scrconf -a -N ${sponsornode} ${cmdarg}"
		${PRINTDEBUG} ${cmd}
		if [[ ${c} = h ]]; then
			lastbusy=
			firsttime=${SC_TRUE}
			while true
			do
				#
				# If SC_SCCONF_EBUSY, scrconf prints
				# busy node name to stdout.
				#
				busynode="$(${cmd} 2>${tmperrs2})"
				let result=$?
				if [[ ${result} -ne ${SC_SCCONF_EBUSY} ]]; then
					if [[ ${firsttime} -ne ${SC_TRUE} ]]; then
						printf "\n$(gettext 'Attempt to add %s to the configuration ... ')" "${buffer}" | logmsg
						cat ${tmperrs2} >>${tmperrs}
					fi
					break;
				elif [[ -s ${tmperrs2} ]]; then
					cat ${tmperrs2} >>${tmperrs}
				fi
				rm -f ${tmperrs2}
				if [[ -z "${busynode}" ]]; then
					busynode="."
				fi
				if [[ ${firsttime} -eq ${SC_TRUE} ]]; then
					firsttime=${SC_FALSE}
					printf "$(gettext 'busy')\n" | logmsg
				fi
				if [[ "${lastbusy}" != "${busynode}" ]]; then
					if [[ "${busynode}" = "." ]]; then
						printf "\n$(gettext 'Waiting for all configured nodes to join the cluster ... ')" | logmsg
					else
						printf "\n$(gettext 'Waiting for \"%s\" to join the cluster ... ')" "${busynode}" | logmsg
					fi
					lastbusy=${busynode}
				fi

				# sleep
				sleep 20
			done
		else
			${cmd} >>${tmperrs} 2>&1
			let result=$?
		fi
		if [[ ${result} -eq ${SC_SCCONF_EEXIST} ]]; then
			printf "$(gettext 'skipped')\n" | logmsg
			printf "$(gettext 'Skipped %s - already configured')\n\n" "${buffer}" | logmsg

		elif [[ ${result} -ne 0 ]]; then
			printf "%s\n" ${SC_FAILED} | logmsg
			if [[ -s "${tmperrs}" ]]; then
				cat ${tmperrs} | logerr
			fi
			printf "$(gettext '%s:  Failed to update cluster configuration (\"%s\")')\n\n" "${PROG}" "${cmdarg}" | logerr
			return 1
		else
			printf "%s\n" ${SC_DONE} | logmsg
			if [[ -n "${SC_DEBUG}" ]] && [[ -s "${tmperrs}" ]]; then
				cat ${tmperrs} | logerr
			fi
		fi
	done

	# remove old tmp files
	rm -f ${tmpconfig}
	rm -f ${tmperrs}

	# get a copy of the file
	if [[ -z "${SC_BASEDIR}" ]]; then
		sc_config=${SC_CONFIG}
	else
		sc_config="$(expr "${SC_CONFIG}" : ${SC_BASEDIR}'\(.*\)')"
	fi
	printf "\n" | logmsg
	printf "$(gettext 'Copying the config from \"%s\" ... ')" "${sponsornode}" | logmsg
	cmd="scrconf -g -N ${sponsornode} ${sc_config} ${tmpconfig}"
	${PRINTDEBUG} ${cmd}
	${cmd} >${tmperrs} 2>&1
	let result=$?
	if [[ ${result} -ne 0 ]]; then
		printf "%s\n" ${SC_FAILED} | logmsg
		if [[ -s "${tmperrs}" ]]; then
			cat ${tmperrs} | logerr
		fi
		printf "$(gettext '%s:  Unable to retrieve a copy of the cluster config')\n" "${PROG}" | logerr
		return 1
	fi
	printf "%s\n" ${SC_DONE} | logmsg
	if [[ -n "${SC_DEBUG}" ]] && [[ -s "${tmperrs}" ]]; then
		cat ${tmperrs} | logerr
	fi

	# Now, move file into place
	mv ${tmpconfig} ${SC_CONFIG}
	if [[ $? -ne 0 ]]; then
		printf "$(gettext '%s:  Failed to create cluster configuration file')\n" "${PROG}" | logerr
		rm -f ${tmpconfig}
		return 1
	fi

	# Set attributes
	setfile "${SC_CONFIG}"
	if [[ $? -ne 0 ]]; then
		printf "$(gettext '%s:  Cannot set attributes of the cluster config file (\"%s\")')\n" "${PROG}" "${SC_CONFIG}" | logerr
		return 1
	fi

	# If there is no CCR table directory, add it now
	if [[ ! -f "${SC_CCRDIR}" ]]; then
		touch "${SC_CCRDIR}"
		if [[ $? -ne 0 ]]; then
			printf "$(gettext '%s:  Cannot create the cluster config table directory (\"%s\")')\n" "${PROG}" "${SC_CCRDIR}" | logerr
			return 1
		fi
		setfile "${SC_CCRDIR}"
		if [[ $? -ne 0 ]]; then
			printf "$(gettext '%s:  Cannot set attributes of the cluster config table directory (\"%s\")')\n" "${PROG}" "${SC_CCRDIR}" | logerr
			return 1
		fi
	fi

	# Add the infrastructure table to the CCR table directory
	infrastructure=${SC_CONFIG##*/}
	grep '^'${infrastructure} ${SC_CCRDIR} >/dev/null
	if [[ $? -ne 0 ]]; then
		echo ${infrastructure} >>${SC_CCRDIR}
		if [[ $? -ne 0 ]]; then
			printf "$(gettext '%s:  Cannot update the cluster config table directory (\"%s\")')\n" "${PROG}" "${SC_CCRDIR}" | logerr
			return 1
		fi
	fi

	# add checksum
	ccradm -i ${SC_CCRDIR} -o
	if [[ $? -ne 0 ]]; then
		printf "$(gettext '%s:  Failed to add CCR checksum to table directory')\n" "${PROG}" | logerr 
		return 1
	fi

	return 0
}

#####################################################
#
# set_nodeid nodeidfile
#
#	Create the nodeid file.
#
#	Return:
#		zero		Success
#		non-zero	Failure
#
#####################################################
set_nodeid()
{
	typeset -r nodeidfile=$1

	typeset nodeid
	integer result

	# Check args
	if [[ $# -ne 1 ]]; then
		printf "$(gettext '%s:  Internal error - bad call to set_nodeid()')\n" "${PROG}" >&2
		return 1
	fi

	# create the nodeid file
	printf "\n" | logmsg
	printf "$(gettext 'Setting the node ID for \"%s\" ... ')" "${mynodename}" | logmsg
	rm -f ${nodeidfile}
	nodeid=$(scrconf -p -h node=${mynodename} < ${SC_CONFIG} 2>${tmperrs})
	let result=$?
	echo ${nodeid} >${nodeidfile}
	if [[ $? -ne 0 ]] || [[ ${result} -ne 0 ]] || [[ -z "${nodeid}" ]]; then
		printf "%s\n" ${SC_FAILED} | logmsg
		if [[ -s "${tmperrs}" ]]; then
			cat ${tmperrs} | logerr
		fi
		printf "$(gettext '%s:  Unable to create the node ID file (\"%s\")')\n" "${PROG}" "${nodeidfile}" | logerr
		rm -f ${nodeidfile}
		return 1
	fi
	printf "%s" ${SC_DONE} | logmsg
	printf "$(gettext ' (id=%d)')\n" "${nodeid}" | logmsg

	# Set attributes
	setfile "${nodeidfile}"
	if [[ $? -ne 0 ]]; then
		printf "$(gettext '%s:  Cannot set attributes of the node ID file (\"%s\")')\n" "${PROG}" "${nodeidfile}" | logerr
		return 1
	fi

	return 0
}

#####################################################
#
# is_globaldevfs nodeid
#
#	nodeid				Node ID
#
#	Check to see if there is a mount entry in either
#	${SC_BASEDIR}/etc/vfstab or /etc/mnttab for
#	${SC_GLOBALDEVDIR}/node@<nodeid>.
#
#	Return:
#		0		There is no mount entry
#		1		There is a mount entry
#		-1		Error
#
#####################################################
is_globaldevfs()
{
	typeset -r nodeid=$1

	typeset -r globaldevmountp=${SC_GLOBALDEVDIR}/node@${nodeid}
	typeset special
	typeset fsckdev
	typeset mountp
	typeset foo

	# Check args
	if [[ $# -ne 1 ]]; then
		printf "$(gettext '%s:  Internal error - bad call to is_globaldevfs()')\n" "${PROG}" >&2
		return 1
	fi

	# Check ${SC_BASEDIR}/etc/vfstab for the global mount
	while read special fsckdev mountp foo
	do
		case ${special} in
		'#'* | '')	# Ignore comments, empty lines
				continue
				;;
		esac

		if [[ "${mountp}" = "${globaldevmountp}" ]]; then
			return 1
		fi
	done < ${SC_BASEDIR}/etc/vfstab
	if [[ $? -ne 0 ]]; then
		printf "$(gettext '%s:  Error reading %s')\n" "${PROG}" "${SC_BASEDIR}/etc/vfstab" | logerr
		return 1
	fi

	# Check /etc/mnttab for the mount
	while read special mountp foo
	do
		if [[ "${mountp}" = "${globaldevmountp}" ]]; then
			return 1
		fi
	done < /etc/mnttab
	if [[ $? -ne 0 ]]; then
		printf "$(gettext '%s:  Error reading %s')\n" "${PROG}" "/etc/mnttab" | logerr
		return 1
	fi

	return 0
}

#####################################################
#
# is_globalfs_okay [global_fs] [msgtype]
#
#	global_fs			File system name used with -G
#	msgtype				Type of error message to print
#		1				stderr format w/ "failed"
#		2				stderr format w/out "failed"
#		3				stdout format (interactive)
#
#	If msgtype is not given, default is type "1".
#
#	If global_fs is not given, the default is assumed.
#
#	Check to see if the file system name to use for the global
#	devices filesystem is okay to use. 
#
#	Return:
#		0		Okay to use
#		1		Does not begin with /
#		2		Is not a directory
#		3		Is not a mount point in /etc/vfstab
#		4		Error reading /etc/vfstab
#		5		Is not a mount point in /etc/mnttab
#		6		Error reading /etc/mnttab
#		7		Is not empty
#		8		Does not include lost+found
#		9		Is under /global
#		-1		Other error
#
#####################################################
is_globalfs_okay()
{
	typeset global_fs=$1
	typeset msgtype=$2

	typeset failed_msg=:
	typeset logcmd=cat
	typeset prefix=
	typeset special
	typeset fsckdev
	typeset mountp
	typeset foo
	typeset dir

	integer found

	# Check args
	if [[ $# -gt 2 ]]; then
		printf "$(gettext '%s:  Internal error - bad call to is_globalfs_okay()')\n" "${PROG}" >&2
		return -1
	fi

	# Setup message type
	if [[ -z "${msgtype}" ]]; then
		msgtype=1
	fi
	case ${msgtype} in
	2)	# stderr format w/out "failed"
		logcmd=logerr
		prefix="${PROG}:  "
		;;

	3)	# stdout format (interactive)
		;;

	*)	# stderr format w/ "failed"
		failed_msg="printf "%s\n" ${SC_FAILED} | logmsg"
		logcmd=logerr
		prefix="${PROG}:  "
		;;
	esac

	# If "global_fs" is not set, use default
	if [[ -z "${global_fs}" ]]; then
		global_fs=${SC_GLOBALDEVFS}
	fi

	# Make sure it begins with /
	if [[ ${global_fs} != /* ]]; then
		${failed_msg}
		printf "$(gettext '%sGlobal file system device name must begin with slash (\"/\").')\n" "${prefix}" | ${logcmd}
		return 1
	fi

	# Make sure it is a directory
	if [[ ! -d ${SC_BASEDIR}${global_fs} ]]; then
		${failed_msg}
		printf "$(gettext '%s%s is not a directory or file system mount point.')\n" "${prefix}" "${SC_BASEDIR}${global_fs}" | ${logcmd}
		return 2
	fi

	# Under /global?
	if [[ ${global_fs} == /global/* ]]; then
		${failed_msg}
		printf "$(gettext '%s\"%s\" is in %s.')\n" "${prefix}" "${global_fs}" "/global" | ${logcmd}
		return 9
	fi

	#
	# Check for "global_fs" in ${SC_BASEDIR}/etc/vfstab
	#
	let found=0
	while read special fsckdev mountp foo
	do
		case ${special} in
		'#'* | '')	# Ignore comments, empty lines
				continue
				;;
		esac

		if [[ "${mountp}" = "${global_fs}" ]]; then
			let found=1
			break
		fi
	done < ${SC_BASEDIR}/etc/vfstab
	if [[ $? -ne 0 ]]; then
		${failed_msg}
		printf "$(gettext '%sError reading %s.')\n" "${prefix}" "${SC_BASEDIR}/etc/vfstab" | ${logcmd}
		return 4
	fi

	# If not found in vfstab, error
	if [[ ${found} -eq 0 ]]; then
		${failed_msg}
		printf "$(gettext '%s%s is not a mount point in %s.')\n" "${prefix}" "${global_fs}" "${SC_BASEDIR}/etc/vfstab" | ${logcmd}
		return 3
	fi

	# Make sure it is actually mounted
	let found=0
	while read special mountp foo
	do
		if [[ "${mountp}" == "${SC_BASEDIR}${global_fs}" ]]; then
			let found=1
			break
		fi
	done < /etc/mnttab
	if [[ $? -ne 0 ]]; then
		${failed_msg}
		printf "$(gettext '%sError reading %s.')\n" "${prefix}" "/etc/mnttab" | ${logcmd}
		return 6
	fi

	# If not found in mnttab, error
	if [[ ${found} -eq 0 ]]; then
		${failed_msg}
		printf "$(gettext '%s%s is not mounted.')\n" "${prefix}" "${global_fs}" | ${logcmd}
		return 5
	fi

	# Make sure it is an empty file system
	let found=0
	for dir in ${SC_BASEDIR}${global_fs}/.* ${SC_BASEDIR}${global_fs}/*
	do
		case ${dir} in
		${SC_BASEDIR}${global_fs}/.)
			;;

		${SC_BASEDIR}${global_fs}/..)
			;;

		${SC_BASEDIR}${global_fs}/lost+found)
			((found += 1))
			;;

		*)
			${failed_msg}
			printf "$(gettext '%s\"%s\" is not empty.')\n" "${prefix}" "${SC_BASEDIR}${global_fs}" | ${logcmd}
			return 7
			;;
		esac
	done

	# No lost+found?
	if [[ ${found} -eq 0 ]]; then
		${failed_msg}
		printf "$(gettext '%s\"%s\" does not include a %s directory.')\n" "${prefix}" "${SC_BASEDIR}${global_fs}" "lost+found" | ${logcmd}
		return 8
	fi

	return 0
}

#####################################################
#
# is_globalcspecial_okay global_cspecial [msgtype]
#
#	global_cspecial			Character special device used w/ -G
#	msgtype				Type of error message to print
#		1				stderr format w/ "failed"
#		2				stderr format w/out "failed"
#		3				stdout format (interactive)
#
#	If msgtype is not given, default is type "1".
#
#	Check to see if the given character special device is okay to use for 
#	the global devices filesystem.
#
#	Return:
#		0		Okay to use
#		1		Does not begin with /
#		2		Is not a character special device
#		3		Is already in use by /etc/vfstab
#		4		Error reading /etc/vfstab
#		-1		Other error
#
#####################################################
is_globalcspecial_okay()
{
	typeset -r global_cspecial=$1
	typeset msgtype=$2

	typeset failed_msg=:
	typeset logcmd=cat
	typeset prefix=
	typeset special
	typeset fsckdev
	typeset mountp
	typeset foo

	integer found

	# Check args
	if [[ $# -gt 2 ]]; then
		printf "$(gettext '%s:  Internal error - bad call to is_globalcspecial_okay()')\n" "${PROG}" >&2
		return -1
	fi

	# Setup message type
	if [[ -z "${msgtype}" ]]; then
		msgtype=1
	fi
	case ${msgtype} in
	2)	# stderr format w/out "failed"
		logcmd=logerr
		prefix="${PROG}:  "
		;;

	3)	# stdout format (interactive)
		;;

	*)	# stderr format w/ "failed"
		failed_msg="printf "%s\n" ${SC_FAILED} | logmsg"
		logcmd=logerr
		prefix="${PROG}:  "
		;;
	esac

	# Make sure it begins with /
	if [[ ${global_cspecial} != /* ]]; then
		${failed_msg}
		printf "$(gettext '%sGlobal device name must begin with slash (\"/\").')\n" "${prefix}" | ${logcmd}
		return 1
	fi

	# Make sure it is a character special device
	if [[ ! -c ${SC_BASEDIR}${global_cspecial} ]]; then
		${failed_msg}
		printf "$(gettext '%sGlobal device is not a character special device.')\n" "${prefix}" | ${logcmd}
		return 2
	fi

	# Make sure it is not given in vfstab
	let found=0
	while read special fsckdev mountp foo
	do
		case ${special} in
		'#'* | '')	# Ignore comments, empty lines
				continue
				;;
		esac

		if [[ "${special}" = "${global_cspecial}" ]] ||
		    [[ "${fsckdev}" = "${global_cspecial}" ]]; then
			let found=1
			break
		fi
	done < ${SC_BASEDIR}/etc/vfstab
	if [[ $? -ne 0 ]]; then
		${failed_msg}
		printf "$(gettext '%sError reading %s.')\n" "${prefix}" "${SC_BASEDIR}/etc/vfstab" | ${logcmd}
		return 4
	fi

	# If found in vfstab, error
	if [[ ${found} -ne 0 ]]; then
		${failed_msg}
		printf "$(gettext '%s\"%s\" is a mount device in %s.')\n" "${prefix}" "${global_cspecial}" "${SC_BASEDIR}/etc/vfstab" | ${logcmd}
		return 3
	fi

	return 0
}

#####################################################
#
# create_globaldevfs nodeid [global] [checkflag]
#
#	nodeid				ID of this node
#	global				file system or special device
#	nocheckflag			if given, do not re-check "global"
#
#	Create a file system, and mount it on
#	${SC_BASEDIR}${SC_GLOBALDEVDIR}/node@<id>.
#
#	If "global" is given and is the name of a mounted file system,
#	a check is made to verify that it is empty.  If it is, it
#	is unmounted, and a new file system created, if needed.  An
#	entry is added to ${SC_BASEDIR}/etc/vfstab, and it it mounted.
#
#	If "global" is not given, ${SC_GLOBALDEVFS} is used as the default.
#
#	"global" may also be given as the name of a special device.
#	There must not be a mount entry for the device.
#
#	Return:
#		zero		Success
#		non-zero	Failure
#
#####################################################
create_globaldevfs()
{
	typeset -r nodeid=$1
	typeset global=$2
	typeset -r nocheckflag=$3

	typeset global_fs=
	typeset global_cspecial=
	typeset global_bspecial=
	typeset -r globaldevmountp=${SC_GLOBALDEVDIR}/node@${nodeid}

	typeset special
	typeset fsckdev
	typeset mountp
	typeset foo

	typeset dir
	typeset vfstabline
	integer result
	integer found

	# Check args
	if [[ $# -lt 1 ]] || [[ $# -gt 3 ]]; then
		printf "$(gettext '%s:  Internal error - bad call to create_globaldevfs()')\n" "${PROG}" >&2
		return 1
	fi

	# See if the globaldevmountp already exists
	printf "$(gettext 'Checking for global devices global file system ... ')" "${globaldevmountp}" | logmsg
	is_globaldevfs ${nodeid} 2>${tmperrs}
	case $? in
	0)	# Not found
		printf "%s\n" ${SC_DONE} | logmsg
		;;

	1)	# Found it
		printf "%s\n" ${SC_DONE} | logmsg
		printf "$(gettext 'Already mounted - %s')\n" "${globaldevmountp}"
		return 0
		;;

	*)	# Error
		printf "%s\n" ${SC_FAILED} | logmsg
		if [[ -s "${tmperrs}" ]]; then
			cat ${tmperrs} | logerr
		fi
		printf "$(gettext '%s:  Cannot determine if global mount already exists')\n" "${PROG}" | logerr
		return 1
		;;
	esac

	#
	# But, be sure that our globaldev mount point exists;
	# it should have been created by the Sun Cluster packages.
	#
	if [[ ! -d "${SC_BASEDIR}${globaldevmountp}" ]];  then
		printf "$(gettext '%s:  %s is not found')\n" "${PROG}" "${SC_BASEDIR}${globaldevmountp}" | logerr
		return 1
	fi

	# If "global" is not set, use default
	if [[ -z "${global}" ]]; then
		global=${SC_GLOBALDEVFS}
	fi

	#
	# If "global" is a character special device, set global_cspecial
	# otherwise, set global_fs.
	#
	if [[ -c "${global}" ]]; then
		global_cspecial=${global}
	else
		global_fs=${global}
	fi

	#
	# If it is a file system, check to see if all requirements are met.
	# This check should be redundant, as it is also called when command
	# line options are processed.
	#
	if [[ -n "${global_fs}" ]]; then

		# Check device?
		if [[ -z "${nocheckflag}" ]]; then
			printf "$(gettext 'Checking device to use for global devices file system ... ')" | logmsg
			is_globalfs_okay ${global_fs}
			if [[ $? -ne 0 ]]; then
				return 1
			fi
			printf "%s\n" ${SC_DONE} | logmsg
		fi

		# Attempt unmount
		umount ${SC_BASEDIR}${global_fs} 2>&1 | logerr
		if [[ $? -ne 0 ]]; then
			printf "$(gettext '%s:  Cannot unmount \"%s\"')\n" "${PROG}" "${SC_BASEDIR}${global_fs}" | logerr
			return 1
		fi

		# Set global_cspecial
		while read special fsckdev mountp foo
		do
			case ${special} in
			'#'* | '')	# Ignore comments, empty lines
					continue
					;;
			esac

			if [[ "${mountp}" = "${global_fs}" ]]; then
				global_cspecial=${fsckdev}
				break
			fi
		done < ${SC_BASEDIR}/etc/vfstab
		if [[ -z "${global_cspecial}" ]]; then
			printf "$(gettext '%s:  Error reading %s')\n" "${PROG}" "${SC_BASEDIR}/etc/vfstab" | logerr
			return 1
		fi

	elif [[ -n "${global_cspecial}" ]]; then

		# Check device?
		if [[ -z "${nocheckflag}" ]]; then
			printf "$(gettext 'Checking device to use for global devices file system ... ')" | logmsg
			is_globalcspecial_okay ${global_cspecial}
			if [[ $? -ne 0 ]]; then
				return 1
			fi
			printf "%s\n" ${SC_DONE} | logmsg
		fi
	else
		printf "$(gettext '%s:  Internal error - create_globaldevfs')\n" "${PROG}" | logerr
		return 1
	fi

	# Set global_bspecial
	foo=$(expr "${global_cspecial}" : '/..*\(/rdsk/\)')
	if [[ "${foo}" != "/rdsk/" ]]; then
		printf "$(gettext '%s:  %s is not a raw disk device')\n" "${PROG}" "${OPTARG}" | logerr
		return 1
	fi
	global_bspecial=$(echo "${global_cspecial}" | sed 's/rdsk/dsk/')
	if [[ ! -b "${global_bspecial}" ]]; then
		printf "$(gettext '%s:  \"%s\" is not a block special device')\n" "${PROG}" "${global_bspecial}" | logerr
		return 1
	fi

	# Search /etc/mnttab for "global_bspecial";  it must not be mounted
	while read special mountp foo
	do
		if [[ "${special}" = "${global_cspecial}" ]] ||
		    [[ "${special}" = "${global_bspecial}" ]];  then
			printf "$(gettext '%s:  \"%s\" is already mounted')\n" "${PROG}" "${special}" | logerr
			return 1
		fi
	done < /etc/mnttab
	if [[ $? -ne 0 ]]; then
		printf "$(gettext '%s:  Error reading %s')\n" "${PROG}" "/etc/mnttab" | logerr
		return 1
	fi

	# If not a filesystem, create one
	if [[ -z "${global_fs}" ]]; then
		printf "$(gettext 'Creating global devices file system on %s ... ')" "${global_cspecial}" | logmsg
		newfs ${global_cspecial} </dev/null 2>${tmperrs} >/dev/null
		if [[ $? -ne 0 ]]; then
			printf "%s\n" ${SC_FAILED} | logmsg
			if [[ -s "${tmperrs}" ]]; then
				cat ${tmperrs} | logerr
			fi
			printf "$(gettext '%s:  Failed to create global devices file system (%s)')\n" "${PROG}" "${global_cspecial}" | logerr
			return 1
		fi
		printf "%s\n" ${SC_DONE} | logmsg
	fi

	# Edit vfstab
	printf "$(gettext 'Updating %s ... ')" "vfstab" | logmsg
	vfstabline="$(echo "${global_bspecial}\t${global_cspecial}\t${globaldevmountp}\tufs\t2\tno\tglobal")"
	grep '^'${global_bspecial}'[ 	]' ${SC_BASEDIR}/etc/vfstab >/dev/null 2>&1
	if [[ $? -eq 0 ]]; then
		foo=$(echo ${global_bspecial} | sed 's#/#\\/#g')
		ed -s ${SC_BASEDIR}/etc/vfstab << EOF >/dev/null 2>&1
/^${foo}/s/^/#/
\$a
${vfstabline}
.
w
q
EOF
		let result=$?
	else
		echo "${vfstabline}" >>${SC_BASEDIR}/etc/vfstab
		let result=$?
	fi
	if [[ ${result} -ne 0 ]]; then
		printf "%s\n" ${SC_FAILED} | logmsg
		printf "$(gettext '%s:  Failed to update %s')\n" "${PROG}" "${SC_BASEDIR}/etc/vfstab" | logerr
		return 1
	fi
	printf "%s\n" ${SC_DONE} | logmsg

	# Attempt to remove the old mount point
	if [[ -n "${global_fs}" ]] && [[ -d "${global_fs}" ]]; then
		rmdir ${global_fs} 2>/dev/null
	fi

	return 0
}

#####################################################
#
# configure_did_entry       sponsornode
#
#		if ${sponsornode} = ${mynodename}
#		this is the first node
#
#	Obtain and make proper entry for did driver in
#	/etc/name_to_major. If first node calculate best value;
#	if subsequent node, query sponsor for value to use.
#	Update /etc/minor_perm.
#
#	Return:
#		zero		Success
#		non-zero	Failure
#
#####################################################
configure_did_entry()
{
	typeset -r sponsornode=$1

	typeset -r file=${SC_BASEDIR}/etc/name_to_major
	typeset -r perm_file=${SC_BASEDIR}/etc/minor_perm
	typeset -r driver=did

	integer -r SC_MAX_MAJOR_NUMBER=16383	# Maximum major number allowed
	integer -r default_major=300
	integer -r delta=15	
	integer  highest_major=
	integer  preferred_major=
	integer  sponsor_major=

	typeset sponsor_did_major=
	typeset status=

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

	# see if did driver entry already exists in name_to_major
	sponsor_did_major=$(grep -w ${driver} ${file}) 2>/dev/null
	if [[ -z ${sponsor_did_major} ]]; then
	# no did entry

		# obtain major number to use
		printf "$(gettext 'Setting the major number for the \"%s\" driver ... ')" ${driver} | logmsg
		# am I first node?
		if [[ "${sponsornode}" = ${mynodename} ]]; then
			# yes, am first node

			# figure out preferred major number: either default or the
			# highest number currently in use plus ${delta}, whichever is greater
			# this number is logically guaranteed not to be in use

			# find highest major number in use
			sort -nrk 2,2 ${file} | read ignore highest_major

			preferred_major=$((${highest_major} + ${delta}))
			if [[ ${preferred_major} -lt ${default_major} ]]; then
				preferred_major=${default_major}
			fi

			# is preferred_major too big?
			if [[ ${preferred_major} -gt ${SC_MAX_MAJOR_NUMBER} ]]; then
				# no available major to use:  FATAL
				printf "%s\n" ${SC_FAILED} | logmsg
				printf "$(gettext '%s: no available major number for \"%s\" driver')\n" ${PROG} ${driver} | logerr
				return 1
			fi

		else
		# not first node; request major from sponsor

			printf "\n$(gettext 'Obtaining the major number for the \"%s\" driver from \"%s\" ... ')" ${driver} ${sponsornode} | logmsg

			sponsor_did_major=$(scrconf -d ${driver} -N ${sponsornode} 2>&1)
			# scrconf -d -N returns its value on stderr, not stdout
			if [[ $? -ne 0 ]]; then
				# no response from sponsor: FATAL
				printf "%s\n" ${SC_FAILED} | logmsg
				printf "$(gettext '%s:  unable to get \"%s\" driver major number from \"%s\"')\n" ${PROG} ${driver} ${sponsornode} | logmsg
				return 1
			fi
			preferred_major=$(echo ${sponsor_did_major} | awk -F',' '{print $1}')
			if [[ ${preferred_major} -eq -1 ]]; then
				# did driver not registered on sponsor: FATAL
				printf "%s\n" ${SC_FAILED} | logmsg
				printf "$(gettext '%s: \"%s\" driver not registered on \"%s\" ')\n" ${PROG} ${driver} ${sponsornode} | logerr
				return 1
			fi
		fi
		# now have preferred_major

		# make entry in /etc/name_to_major file
		echo "${driver} ${preferred_major}" >> ${file}

		# make entry in /etc/minor_perm file based on pattern
		# of 'st' devices (if present)
		modestr=$(grep "^st:" ${perm_file} | awk '{printf "%s %s %s", $2, $3, $4}')
		if [[ -n "${modestr}" ]]; then
			echo "${driver}:*,tp ${modestr}" >> ${perm_file}
		fi

		# reconfig reboot will perform registrations and create all dev links
		printf "%s\n" ${SC_DONE} | logmsg
		printf "$(gettext '\"%s\" driver major number set to %d')\n" ${driver} ${preferred_major} | logmsg

	else
	# did entry already exists

		# if I'm first node just accept existing entry
		# if I'm not first node make sure entry matches my sponsor node
		if [[ "${sponsornode}" != ${mynodename} ]]; then
			printf "$(gettext 'Verifying the major number for the \"%s\" driver with \"%s\" ... ')" ${driver} ${sponsornode} | logmsg

			# save value of my existing entry
			preferred_major=$(echo ${sponsor_did_major} | awk '{print $2}')

			# and ask the sponsor for its entry
			sponsor_did_major=$(scrconf -d ${driver} -N ${sponsornode} 2>&1)
			# scrconf -d -N returns its value on stderr, not stdout
			if [[ $? -ne 0 ]]; then
				# no response from sponsor: FATAL
				printf "%s\n" ${SC_FAILED} | logmsg
				printf "$(gettext '%s:  unable to compare \"%s\" driver major number with \"%s\"')\n" ${PROG} ${driver} ${sponsornode} | logmsg
				return 1
			fi
			sponsor_major=$(echo ${sponsor_did_major} | awk -F',' '{print $1}')
			if [[ ${sponsor_major} -eq -1 ]]; then
				# did driver not registered on sponsor: FATAL
				printf "%s\n" ${SC_FAILED} | logmsg
				printf "$(gettext '%s: \"%s\" driver not registered on \"%s\" ')\n" ${PROG} ${driver} ${sponsornode} | logerr
				return 1
			fi
			if [[ ${sponsor_major} -ne ${preferred_major} ]]; then
				# existing self entry does not match entry on sponsor: FATAL
				printf "%s\n" ${SC_FAILED} | logmsg
				printf "$(gettext '%s: existing entry for \"%s\" driver differs from major number in use on \"%s\" ')\n" ${PROG} ${driver} ${sponsornode} | logerr
				return 1
			fi
			printf "%s\n" ${SC_DONE} | logmsg

		fi
	fi # check for did entry in /etc/name_to_major
	return 0
}

#####################################################
#
# ntp_config
#
#	Setup a default NTP configuration.
#
#	Return:
#		zero		Success
#		non-zero	Failure
#
#####################################################
ntp_config()
{
	typeset -r file=${SC_BASEDIR}/etc/inet/ntp.conf

	# See if /etc/inet/ntp.conf already exists
	printf "$(gettext 'Verifying that NTP is configured ... ')" | logmsg
	if [[ -f ${file} ]]; then
		printf "%s\n" ${SC_DONE} | logmsg
		return 0
	fi
	printf "%s\n" ${SC_DONE} | logmsg

	# If ntp.conf does not exist, copy ntp.cluster to ntp.conf.cluster
	printf "$(gettext 'Installing a default NTP configuration ... ')" | logmsg
	cp ${SC_BASEDIR}/etc/inet/ntp.cluster ${file}.cluster 2>${tmperrs}
	if [[ $? -ne 0 ]]; then
		printf "%s\n" ${SC_FAILED} | logmsg
		if [[ -s "${tmperrs}" ]]; then
			cat ${tmperrs} | logerr
		fi
		printf "$(gettext '%s:  Unable to install a default %s file')\n" "${PROG}" "ntp.conf.cluster" | logerr
		return 1
	fi
	printf "%s\n" ${SC_DONE} | logmsg

	# More configuration required
	printf "$(gettext 'Please complete the NTP configuration after %s has finished.')\n" "${PROG}" | logmsg

	return 0
}

#####################################################
#
# nsswitch_addcluster db
#
#	db		- nss "database" (e.g., hosts or netmasks)
#
#	Add "cluster" switch to entry for "db" in nsswitch.conf.
#
#	Return:
#		zero		Success
#		non-zero	Failure
#
#####################################################
nsswitch_addcluster()
{
	typeset -r db=$1

	typeset -r file=${SC_BASEDIR}/etc/nsswitch.conf
	typeset dbline
	typeset numlines

	# See if "cluster" switch already exists for the "db" entry
	printf "$(gettext 'Verifying that \"%s\" is set for \"%s\" in %s ... ')" "cluster" "${db}" "nsswitch.conf" | logmsg
	grep '^'${db}':[ 	].*[ 	]cluster[ 	]..*' ${file} 2>${tmperrs} >/dev/null
	case $? in
	2)	# Read error
		printf "%s\n" ${SC_FAILED} | logmsg
		if [[ -s "${tmperrs}" ]]; then
			cat ${tmperrs} | logerr
		fi
		printf "$(gettext '%s:  error reading %s')\n" "${PROG}" "${file}" | logerr
		return 1
		;;

	0)	# Found it!
		printf "%s\n" ${SC_DONE} | logmsg
		return 0
		;;

	*)	# Not found
		printf "%s\n" ${SC_DONE} | logmsg
		;;
	esac

	# Add the "cluster" switch
	printf "$(gettext 'Adding the \"%s\" switch to \"%s\" in %s ... ')" "cluster" "${db}" "nsswitch.conf" | logmsg

	# Make sure that there is a single "db" entry
	numlines=$(grep -c '^'${db}':[ 	]' ${file} 2>/dev/null)
	case ${numlines} in
	0)	# No lines
		printf "%s\n" ${SC_FAILED} | logmsg
		printf "$(gettext '%s:  %s does not include a \"%s\" entry')\n" "${PROG}" "${file}" "${db}" | logerr
		return 1
		;;

	1)	# One line is good
		;;
		
	*)	# More than one line is not good
		printf "%s\n" ${SC_FAILED} | logmsg
		printf "$(gettext '%s:  %s has %s \"%s\" entries!')\n" "${PROG}" "${file}" "${numlines}" "${db}" | logerr
		return 1
		;;
	esac

	# Get the line
	dbline=$(grep '^'${db}':[ 	]' ${file} 2>/dev/null)

	# Add the entry
	ed -s ${file} << EOF >/dev/null 2>&1
/^${db}:[ 	]/a
${dbline}
.
-2
/^${db}:[ 	]/s/^/#/
/^${db}:[ 	]/s/^${db}:[ 	]*/${db}:      cluster /
w
q
EOF

	# Report errors
	if [[ $? -ne 0 ]]; then
		printf "%s\n" ${SC_FAILED} | logmsg
		printf "$(gettext '%s:  Failed to update %s')\n" "${PROG}" "${file}" | logerr
		return 1
	fi

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

	return 0
}

#####################################################
#
# nsswitch_config
#
#	Add "cluster" switch to hosts entry in nsswitch.conf.
#	Add "cluster" switch to netmasks entry in nsswitch.conf.
#
#	Return:
#		zero		Success
#		non-zero	Failure
#
#####################################################
nsswitch_config()
{
	# hosts
	nsswitch_addcluster hosts || return 1

	# netmasks
	printf "\n" | logmsg
	nsswitch_addcluster netmasks || return 1

	return 0
}

#####################################################
#
# powerm_off
#
#	Turn off power management.
#
#	Return:
#		zero		Success
#		non-zero	Failure
#
#####################################################
powerm_off()
{
	typeset -r file=${SC_BASEDIR}/etc/power.conf
	typeset suffix

	# Don't let /etc/init.d/sysid.sys ask us to turn it on again
	rm -f ${SC_BASEDIR}/etc/.PM_RECONFIGURE 2>/dev/null

	# See if /etc/power.conf exists
	printf "$(gettext 'Verifying that power management is NOT configured ... ')" | logmsg
	if [[ ! -f ${file} ]]; then
		printf "%s\n" ${SC_DONE} | logmsg
		return 0
	fi
	printf "%s\n" ${SC_DONE} | logmsg

	# If it does exist, re-name it
	suffix=$(date +'%'m'%'d'%'y'%'H'%'M'%'S)
	printf "$(gettext 'Unconfiguring power management ... ')" | logmsg
	mv ${file} ${file}.${suffix} 2>${tmperrs}
	if [[ $? -ne 0 ]]; then
		printf "%s\n" ${SC_FAILED} | logmsg
		if [[ -s "${tmperrs}" ]]; then
			cat ${tmperrs} | logerr
		fi
		printf "$(gettext '%s:  Unable to disable power management')\n" "${PROG}" | logerr
		return 1
	fi
	printf "%s\n" ${SC_DONE} | logmsg

	# More configuration required
	printf "$(gettext '%s has been renamed to %s')\n" "${file}" "${file}.${suffix}" | logmsg
	printf "$(gettext 'Power management is incompatible with the HA goals of the cluster.')\n" | logmsg
	printf "$(gettext 'Please do not attempt to re-configure power management.')\n" | logmsg

	return 0
}

#####################################################
#
# router_disable
#
#	Ensure that node will not act as a router.
#
#	Return:
#		zero		Success
#		non-zero	Failure
#
#####################################################
router_disable()
{
	typeset -r file=${SC_BASEDIR}/etc/notrouter

	# See if /etc/notrouter already exists
	printf "$(gettext 'Ensure network routing is disabled ... ')" ${file} | logmsg
	if [[ ! -f ${file} ]]; then
		(umask 022; touch ${file} 2>${tmperrs})
		if [[ $? -ne 0 ]]; then
			printf "%s\n\n" ${SC_FAILED} | logmsg
			if [[ -s "${tmperrs}" ]]; then
				cat ${tmperrs} | logerr
			fi
			printf "$(gettext '%s:  Unable to disable network routing')\n" "${PROG}" | logerr
			return 1
		fi
		printf "%s\n" ${SC_DONE} | logmsg

		# Explanation
		printf "$(gettext 'Network routing has been disabled on this node by creating %s.')\n" "${file}" | logmsg
		printf "$(gettext 'Having a cluster node act as a router is not supported by Sun Cluster.')\n" | logmsg
		printf "$(gettext 'Please do not re-enable network routing.')\n" | logmsg
	else
		printf "%s\n" ${SC_DONE} | logmsg
	fi

	return 0
}

#####################################################
#
# print_errorexit_msg [beep]
#
#	beep				beep
#
#	Print the error exit message
#
#	This function always returns 0.
#
#####################################################
print_errorexit_msg()
{
	typeset beep=$1

	printf "\n" >&2
	printf "$(gettext '%s:  %s did NOT complete successfully!')\n\n" "${PROG}" "${PROG}" >&2
	if [[ -s "${install_log}" ]]; then
		printf "\n" >>${install_log}
		printf "$(gettext '%s:  %s did NOT complete successfully!')\n\n" "${PROG}" "${PROG}" >>${install_log}
	fi

	# beep
	if [[ -n "${beep}" ]]; then
		echo "\a\c"
	fi

	return 0
}

#####################################################
#
# print_release [verbose]
#
#	verbose				print verbose
#
#	Print release information
#
#	Return:
#		zero		Success
#		non-zero	Failure
#
#####################################################
print_release()
{
	typeset -r verbose=$1

	typeset productrel
	typeset pkglist
	typeset pkgversion
	typeset rootarg=

	integer i
	integer result=0

	#
	# If not installed, print message when verbose.
	# It is not an error to not be installed.
	#
	if [[ ! -f ${SC_DOT_CDTOC} ]] ||
	    [[ ! -f ${SC_DOT_CLUSTERTOC} ]] ||
	    [[ ! -f ${SC_DOT_ORDER} ]]; then
		if [[ -n "${verbose}" ]]; then
			printf "$(gettext '%s is not installed')\n" "${SC_PRODUCT}"
		fi
		return 0
	fi
			
	if [[ -n "${SC_BASEDIR}" ]] && [[ "${SC_BASEDIR}" != "/" ]]; then
		rootarg="-R ${SC_BASEDIR}"
	fi

	# get the product release
	productrel=$(getproduct ${SC_DOT_CDTOC} "${SC_PRODUCT}" ${SC_CLUSTER} "rel") || return 1

	# if not verbose, we are done
	if [[ -z "${verbose}" ]]; then
		echo ${productrel}
		return 0
	fi

	# get the list of packages
	pkglist="$(print_clustertoc ${SC_DOT_CLUSTERTOC} ${SC_CLUSTER} "packages")"
	if [[ $? -ne 0 ]]; then
		return 1
	fi

	# order the list of packages
	pkglist="$(order_packages ${SC_DOT_ORDER} "${pkglist}")"
	if [[ $? -ne 0 ]]; then
		return 1
	fi

	# print the release
	echo ${SC_PRODUCT} ${productrel}

	#
	let i=0
	set -A pkglist ${pkglist}
	while [[ -n "${pkglist[i]}" ]]
	do
		# get version, if installed
		pkgversion="$(pkgparam ${pkglist[i]} VERSION 2>/dev/null)"

		# if not set, there is an error
		if [[ -z "${pkgversion}" ]]; then
			let result=1
		fi

		# partially installed?
		if [[ -z "${pkgversion}" ]]; then
			pkginfo ${rootarg} -p ${pkglist[i]} >/dev/null 2>&1
			if [[ $? -eq 0 ]]; then
				pkgversion="$(gettext 'Package is only partially installed!')"
			fi
		fi

		# not installed, or no VERSION?
		if [[ -z "${pkgversion}" ]]; then 
			pkginfo ${rootarg} -i ${pkglist[i]} >/dev/null 2>&1
			if [[ $? -eq 0 ]]; then
				pkgversion="$(gettext 'Package version not found!')"
			else
				pkgversion="$(gettext 'Package not installed!')"
			fi
		fi

		# print result
		printf "%-14s %s\n" "${pkglist[i]}:" "${pkgversion}"
		((i += 1))
	done

	return ${result}
}

#####################################################
#
# find_and_install_patches cdimagebasedir productname cluster
#		"service"
#
#	cdimagebasedir		CD image base directory
#
#	productname		Name of product to be upgraded
#
#	cluster			Software cluster for product
#				from .clustertoc file
#
#	service			Data service to be upgraded.
#				If null, core packages are to
#				be upgraded.
#
#	silent			Determines whether failure to
#				find patch is error condition.
#
#	Locates a set of patches on a CD and installs them.
#
#	Return:
#		zero		Patch data found and install
#				attempted.
#		non-zero	Patch data not found.
#
#
#####################################################
find_and_install_patches()
{

	typeset -r cdimagebasedir=$1
	typeset -r productname=$2
	typeset -r cluster=$3
	typeset -r service=$4
	integer -r silent=$5
	typeset realcdimage
	typeset productdir
	typeset patchdir
	typeset patchlist
	typeset cmdstring
	typeset Roptionstring=
	integer result

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

	# find the product directory
	realcdimage=$(find_cdimagebasedir ${cdimagebasedir} "${productname}" "${cluster}") || return 1
	productdir=$(getproduct ${realcdimage}/${SC_CDTOC} "${productname}" "${cluster}" "dir")

	if [[ $? -ne 0 ]]; then

		# missing product directory for core packages
		if [[ -z "${service}" ]]; then
			printf "%s:  $(gettext 'Cannot locate product directory %s')\n" ${PROG} "${productdir}" | logerr

		# missing product directory for data service packages
		else
			printf "%s:  $(gettext 'Cannot find data service \"%s\"')\n" ${PROG} "${service}" | logerr
		fi
		return 1
	fi

	# find the patch directory and the patch order file
	patchdir=${productdir}/${SC_UPGD_RELPATCHDIR}
	if [[ ! -d ${patchdir} ]]; then
		if [[ ${silent} -ne ${SC_TRUE} ]]; then
			printf "%s:  $(gettext 'Unable to find patch directory %s')\n" ${PROG} ${patchdir} | logerr
			return 1
		else
			return 0
		fi
	fi

	patchlist=${SC_UPGD_PATCHORDERFILE}
	if [[ ! -f ${patchdir}/${patchlist} ]] || [[ ! -s ${patchdir}/${patchlist} ]]; then
	printf "\n$(gettext 'Nothing to Upgrade!')\n" | logmsg
		if [[ ${silent} -ne ${SC_TRUE} ]]; then
			printf "\n%s:  $(gettext 'Empty or non-existant patch order file %s')\n" ${PROG} ${patchlist} | logerr
			return 1
		else
			return 0
		fi
	fi

	# install the patches
	printf "$(gettext 'Beginning update release upgrade of %s using ')\n %s" ${productname} ${patchdir} | logmsg
	if [[ -n ${SC_BASEDIR} ]]; then
		Roptionstring="-R ${SC_BASEDIR}"
	fi

	cmdstring="patchadd -M ${Roptionstring} ${patchdir} ${patchlist}"
	printf "\n${cmdstring}\n" | logmsg

	${cmdstring} >${tmperrs} 2>&1
	let result=$?
	cat ${tmperrs} | logmsg

	if [[ ${result} -ne 0 ]]; then
		printf "%s:  $(gettext 'Installation of one or more patches in %s failed')\n" ${PROG} ${patchdir} | logerr
	fi

	return 0
}

#####################################################
#
# find_ds_list cdimagebasedir
#
#	cdimagebasedir		CD image base directory
#
#	Builds list of all data services found on CD.
#
#	Return:
#		zero		List created.
#
#		non-zero	Unable to create list.
#
#####################################################
find_ds_list()
{
	typeset -r cdimagebasedir=$1
	typeset foo
	typeset cdtoc
	typeset cdtocdir
	typeset clustertoc
	typeset service
	typeset services=
	typeset line
	typeset value
	integer found

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

	# find directory containing the .cdtoc file
	for foo in ${cdimagebasedir} ${cdimagebasedir}/*
	do
		if [[ -f "${foo}/${SC_CDTOC}" ]]; then
			cdtocdir=${foo}
			cdtoc=${foo}/${SC_CDTOC}
		fi
	done

	if [[ -z ${cdtocdir} ]]; then
		printf "%s:  $(gettext 'Cannot find a %s file')\n" "${PROG}" "${SC_CDTOC}" | logerr
		return 1
	fi

	# Read the file, looking for product directories
	while read line
	do
		case "${line}" in
		PRODDIR=*)

			# PRODDIR=<value>
			value=$(expr "${line}" : 'PRODDIR=\(.*\)')

			# try to find the software cluster
			if [[ "${cdtocdir}" = "${SC_DOT_DIR}" ]]; then
				clustertoc="${SC_DOT_CLUSTERTOC}"
			else
				clustertoc="${cdtocdir}/${value}/${SC_CLUSTERTOC}"
			fi

			# read the service from the clustertoc file
			service=$(egrep '^CLUSTER='${SC_SERVICE}'|^METACLUSTER='${SC_SERVICE} ${clustertoc} 2>&1 | head -1 | awk -F_ '{ print $3 }')
			if [[ -n ${service} ]]; then

				# check to see that one service is installed before
				# adding to patch list
				let found=${SC_FALSE}
				for foo in $(dirname ${clustertoc})/*
				do
					if [[ -d ${foo} ]]; then
						pkginfo $(basename ${foo}) >/dev/null 2>&1
						if [[ $? -eq 0 ]]; then
							found=${SC_TRUE}
							break;
						fi
					fi
				done
				if [[ found -eq ${SC_TRUE} ]]; then
					services="${services} ${service}"
				fi
			fi
			;;
		esac

	done < ${cdtoc}

	# make sure we found something
	if [[ -z ${services} ]]; then
		printf "%s:  $(gettext 'Cannot find services')\n" "${PROG}" | logerr
		return 1
	fi

	echo ${services}
	return 0
}
