#!/bin/sh

#######################################################################
# Copyright (C) 2008 by NetApp.  All Rights Reserved.
#
#######################################################################

#
# Command usage function
#
usage()
{
	if [ ${PRINT_USAGE} -eq 1 ]; then
		msg "Usage: INSTALL [-check] [-1] update|copy|install image1|image2 dir|tarball [dev]"
		msg "  -check - will perform everything up to the point of writing to the device"
		msg "  -1 - will cause install to install only on the specified image"
		msg "  update|copy|install - operation to perform"
		msg "  image1|image2 - the currently running image"
		msg "  dir|tarball - the location of the files to install or a gzipped tarball"
		msg "  dev - optional device containing the boot device used for the"
		msg "        install operation"
	fi
}


# For managing firmware environment variables!
. /etc/netapp_common.subr

#
# Register a handler for sigint to cleanup temp files
SIG="INT"
trap "sig_handler ${SIG}"  SIG${SIG}
SIG="TERM"
trap "sig_handler ${SIG}"  SIG${SIG}

# This is a list of files that will be installed to the boot device.  The
# list may be modified at run time.  The list will also be used in free space
# calculations.
INSTALL_FILES="BUILD CHECKSUM COMPAT.TXT INSTALL VERSION cap.xml fw.tgz kernel metadata.xml platform.ko rootfs.img"

# This is a list of files to be extracted to facilitate checks (metadata, free
# space, etc) before the actual installation
PRE_EXTRACT_FILES="BUILD CHECKSUM COMPAT.TXT VERSION kernel metadata.xml"

# Create a temp working directory
TMP=$(mktemp -d /tmp/install.tmp.XXXXX)

# While this is set, any encounterred errors will be treated as usage errors
PRINT_USAGE=1

FDISK=fdisk
DISKINFO=diskinfo
# Define a mount point for the boot device
if [ -n "${CFCARD_MNTPT}" ]; then
	CF_MNT=${CFCARD_MNTPT}
else
	# CFCARD_MNTPT might not be defined if coming from older (eg: tricky)
	# systems, so provide a default of /cfcard
	CF_MNT="/cfcard"
	SetCFcardMntPt "${CF_MNT}"
fi

#
# Convert to uppercase
#
toupper()
{
	echo $1 | sed y/abcdefghijklmnopqrstuvwxyz/ABCDEFGHIGKLMNOPQRSTUVWXYZ/
}

#
# Trim leading and trailing white space
#
trim()
{
	echo $1 | sed 's/^[ \t]*//' | sed 's/[ \t]*$//'
}

#
# Check for mounted file system
# Input: $1 --- name of file system, such as "/cfcard"
#
is_mounted()
{
        local file_system=$1

        if [ ! -d "$file_system" ]; then
                # error due to missing input for file system
                exit 1
        fi

        df -k $file_system | grep -q $file_system
}

#
# Get available disk space of a file system
# The available disk space is the fourth output column of 'df' command.
# Example:
#     or-094% df -k /cfcard
#     Filesystem 1024-blocks    Used   Avail Capacity  Mounted on
#     /dev/da0s1     7319208 1110840 6208368    15%    /cfcard
#
#     or-094% df -k /cfcard | grep /cfcard | awk '{print $4}'
#     6208368
#
# Input: $1 --- name of file system
#
fs_available_space()
{
        local file_system=$1

        if [ ! -d "$file_system" ]; then
                # error due to missing input for file system
                exit 1
        fi

        df -k $file_system | grep $file_system | awk '{print $4}'
}

#
# Get file system size
# The number of blocks of a file system is the second output column of 'df' command.
# Example:
#     or-094% df -k /cfcard
#     Filesystem 1024-blocks    Used   Avail Capacity  Mounted on
#     /dev/da0s1     7319208 1110840 6208368    15%    /cfcard
#
#     or-094% df -k /cfcard | grep /cfcard | awk '{print $2}'
#     7319208
#
# Input: $1 --- name of file system
#
fs_size()
{
        local file_system=$1

        if [ ! -d "$file_system" ]; then
                # error due to missing input for file system
                exit 1
        fi

        df -k $file_system | grep $file_system | awk '{print $2}'
}

#
# Make a directory
# NOTE: this now assumes it is passed an absolute path (which has
# always been true).  See burt 295028 for why this was changed.
#
create_dir()
{
	if [ ! -d $1 ]; then
		# Don't use mkdir -p on our /cfcard partitions due to burt 295028
		cd /
		for dir in `echo $1 | tr "/" " "`
		do
			mkdir $dir > /dev/null 2>&1
			cd $dir > /dev/null 2>&1
		done
		cd /
		if [ ! -d $1 ]; then
			msg "Unable to create the directory: $1"
			return 1
		fi
		msg "Directory $1 created"
	fi
	return 0
}

#
# Copy files( src, dest, file_list )
#
# If installing from a tarball, the souce directory is ignored.
copy_files()
{
	local srcdir=$1
	shift
	local dstdir=$1
	shift
	local file_list="$@"

	# Give the new partition table a chance to get to the device
	# and let any pending I/O settle down.
	msg "Syncing device..."
	sync ; sleep 3

	if [ $tarball ]; then
		msg "Extracting to $dstdir..."
		tar -xvzf $tarball -C $dstdir $file_list
		if [ $? -ne 0 ]; then
			msg "Unable to extract '$(basename $tarball)' to $dstdir - this is VERY bad"
			return 1
		fi
	else
		for file in $file_list; do
			msg "Copying $file from $srcdir to $dstdir..."
			cp $srcdir/$file $dstdir
			if [ $? -ne 0 ]; then
				msg "Unable to copy the file $srcdir/$file to $dstdir - this is VERY bad"
				return 1
			fi
		done
	fi
	touch $dstdir/_installtime
	return 0
}

write_fdisk_config()
{
	echo "${FDISK_SCRIPT}" > ${TMP_MNT}/dskfmt
}

#
# Get a kernel environment variable
#
get_kenv()
{
	kenv $1 2> /dev/null
	return $?
}

#
# Clean up some state, then exit.
# This allows install to be called again (specifically during
# a netboot), if it happens to fail the first time.
#
cleanup()
{
	if [  -d "${TMP}" ]; then
		rm -rf "${TMP}"
	fi

	# if $TMP_MNT has been used, try to unmmount it
	# depending on when this is called, it could be a
	# ramdisk, or a boot device, or not even mounted at all.
	if [ -n "$TMP_MNT" ]; then
		umount $TMP_MNT 2> /dev/null
	fi

	if [ "${op}" = "install" ]; then
		umount $CF_MNT 2> /dev/null
	fi
	sync
}

cleanup_and_exit()
{
	cleanup
	exit $1
}

#
# Error and exit function
#
err_exit()
{
	echo "ERROR: $1." >&2
	usage
	cleanup_and_exit 1
}

sig_handler()
{
	# Which signal did we catch?
	local sig=$1
	# unregister trap handler, revert to default handler.
	trap SIG${sig}

	msg "Exiting due to signal ${sig}."

	# delete temp files, etc
	cleanup

	# exit by invoking the default handler, which will tell the parent we
	# were killed by a signal.
	kill -s SIG${sig} $$
}

#
# Output a message
#
msg()
{
	echo "$1"
}

#
# Compute a numeric representation of the major and minor
# portions of the version string.
#
compute_version()
{
    local version=0
    local tmp=`echo $1 | sed 's/[a-zA-Z].*__//'`
    tmp=`echo $tmp | sed 's/[a-zA-Z].*//'`
    local major=`echo $tmp | awk -F. '{printf("%d", $1)}'`
    local minor=`echo $tmp | awk -F. '{printf("%d", $2)}'`

    if [ -z $major ]; then
            major=0
    fi

    # Data ONTAP 8(BR) is greater than 10(Tricky), so 8 becomes 80.
    # If there is a version 11 or greater, it must also be greater than
    # version 8, so 11  becomes 110.
    if [ $major -gt 7 -a \
         $major -lt 10 -o \
         $major -ge 11 ]; then
        major=`expr $major \* 10`
    fi
    version=`expr $version \* 100 + $major`


    if [ -z $minor ]; then
            minor=0
    fi
    version=`expr $version \* 100 + $minor`

    return $version
}

# Notify the dblade if a new version of Data ONTAP has been installed
# This information is used for NDU data mining.
notify_dblade()
{
	# Compare the new BUILD and VERSION files with what ever is in the
	# root directory of the currently running build.  Any differences
	# should trigger the "ndu_takeover" flag to be set within the dblade.

	diff -q /BUILD $otherdir/BUILD > /dev/null 2>&1
	rc_diff_build=$?
	diff -q /VERSION $otherdir/VERSION > /dev/null 2>&1
	rc_diff_vers=$?
	if [ $rc_diff_build -ne 0 -o \
	     $rc_diff_vers -ne 0 ]; then
		# OK to fail.  zsmcli might not exist (Tricky), or 
		# dblade might not be loaded (install from boot menu).
		# ignore any errors.
		zsmcli "system-set-new-ontap-version-downloaded" > /dev/null 2>&1 ||
		true
	fi
	return 0
}

#
# Brand Check is used to verify the software brand matches the
# branding of the underlying hardware.
#
# Input: BUILD file will be used to check for the software branding name
#
# The logic works as folows:
# 1) The BUILD file will be used to check for the software branding
# 2) The hardware branding is obtainied from the SYS_MODEL environment variable
# 3) If a mismatch is detected, abort the installation.
#
brand_check()
{
	buildfile=$1

	# The SwBrand, software branding, is obtained from the TYPE field in the BUILD file.
	# The TYPE field can be one of the following:
	# TYPE:
	# TYPE: IBM
	# TYPE: SIM
	# TYPE: REGULAR
	# TYPE: DEBUG
	# TYPE: DEBUG IBM
	# TYPE: DEBUG SIM
	# TYPE: DEBUG REGULAR
	# We only care about "IBM" brand. Every thing else is treated as null string.
	if grep 'TYPE:.\+IBM' ${buildfile} >/dev/null ; then
		SwBrand="IBM"
		SwType="IBM ONTAP"
	else
		SwType="NetApp ONTAP"
	fi

	# The HwBrand,  hardware branding, is obtained from the SYS_MODEL environment variable.
	# The SYS_MODEL can be one of the following:
	# IBM:
	#	IBM-XXXX
	# NetApp:
	#	FASXXXX
	# VSIM:
	#	SIMBOX
	# VSA:
	#	DOvXXX
	#	FCvXXX
	# We only care about "IBM" brand. Every thing else is treated as null string.
	if get_kenv SYS_MODEL | grep "IBM"  >/dev/null ; then
		HwBrand="IBM"
		HwType="product of IBM"
	else
		HwType="product of NetApp"
	fi

	# branding check
	if [ "${SwBrand}" != "${HwBrand}" ]; then
		err_exit "Cannot install ${SwType} on ${HwType}"
	fi
}

#
# Hostname Check is used to verify that the current node hostname
# does not contain any potentially problematic characters that are
# not supported by the new Data ONTAP version, preventing it from 
# booting.
#
# Specifically, this check limits the hostname to A-Z, a-z, 0-9,
# "-" and "_".
#
hostname_check()
{
	hostname=$(/bin/hostname)
	echo $hostname | grep -qE '[^A-Za-z0-9_-]'
	if [ $? -eq 0 ]; then
		err_exit "Invalid local hostname '$hostname'. The hostname can only contain the following characters: A-Z, a-z, 0-9, \"-\" and \"_\". Use the \"system node rename\" command to rename the node before installing this image"
	fi
}

#
# If it's a tarball then untar a few things to temp.
#
pre_extract()
{
	if [ "${tarball}" ]; then
		tar -xzf ${tarball} -C ${TMP} ${PRE_EXTRACT_FILES} ||
		    err_exit "Unable to extract '$(basename ${tarball})' to ${TMP}"
		newdir=${TMP}

		filetype=$(file ${TMP}/kernel)
		# The 'kernel' file is not required in /tmp after the file type
		# is determined.  It was fully extracted instead of read from a
		# pipe to avoid burt 295686.  Delete it now to reduce garbage in
		# /tmp.
		rm -f ${TMP}/kernel
	else
		filetype=$(file $newdir/kernel)
	fi
}

#
# Ensure that kernel matches the machine type
#
kernel_arch_check()
{
	case $machine in
	x86_64)
		echo $filetype | grep -q 'x86-64'
		if [ $? -eq 1 ]; then
			err_exit "Attempt to install an invalid kernel binary on target hardware: $filetype"
		fi
		;;
	x86)
		echo $filetype | grep -q 'Intel 80386'
		if [ $? -eq 1 ]; then
			err_exit "Attempt to install an invalid kernel binary on target hardware: $filetype"
		fi
		;;
	*)
		err_exit "Invalid machine type: $machine"
		;;
	esac
	msg "Kernel binary matches install machine type"
}

#
# Ensure that the file checksums match
#
# parameters are:
#	-package
#		verify checksums in the package
#	-dir path
#		verify the checksums in the specified directory path
#	-fw
#		verify checksums from the fw.tgz package
#
verify_checksums()
{
	if [ "$1" = "-package" ]; then
		check_package="1"
		source="Package"
		checksum_dir="$newdir"
		checksum_file="${checksum_dir}/CHECKSUM"

		# Grab just the file name from the list of MD5SUMs:
		# "MD5 (rootfs.img) = e5d7e73a4321cb1f941b14c9d0fc3644" --> "rootfs.img"
		files_to_checksum=$(cut -d' ' -f2  ${checksum_file}) ||
				err_exit "${checksum_file} file not found"
		files_to_checksum=$(echo ${files_to_checksum} | sed 's/[()]//g')

	elif [ "$1" = "-dir" ]; then
		check_package="0"
		source="Installed"
		checksum_dir="$2"
		checksum_file="${checksum_dir}/CHECKSUM"
		if [ ! -f "${checksum_file}" ]; then
			err_exit "${checksum_file} file not found"
		fi
		files_to_checksum="${INSTALL_FILES}"
	elif [ "$1" = "-fw" ]; then
		check_package="0"
		source="Firmware"
		checksum_dir="${CF_MNT}"
		checksum_file="${CF_MNT}/common/CHECKSUM"
		if [ ! -f "${checksum_file}" ]; then
			err_exit "${checksum_file} file not found"
		fi

		# Grab just the file name from the list of MD5SUMs:
		# "MD5 (rootfs.img) = e5d7e73a4321cb1f941b14c9d0fc3644" --> "rootfs.img"
		files_to_checksum=$(sed -e 's/^MD5 (\([^)]*\)).*$/\1/' \
		    ${checksum_file}) ||
		    err_exit "Error reading checksums from ${checksum_file}"
	else
		err_exit "Invalid parameters to verify_checksums"
	fi

	# Ensure the CHECKSUM file itself is not in the list.
	files_to_checksum=$(echo ${files_to_checksum} | sed 's/CHECKSUM //')

	for i in ${files_to_checksum}; do
		if [ "$check_package" = "1" -a $tarball ]; then
			tar -vtf "${tarball}" -O "${i}"  2>/dev/null ||
			    err_exit "Required file '${i}' not found in '$(basename ${tarball})'"
			md5sum=`tar -xvzf $tarball -O $i  2>/dev/null | md5`
		else
			[ -f "${checksum_dir}/${i}" ] ||
			    err_exit "Required file '${i}' not found in '${checksum_dir}'"
			md5sum=`md5 -q "${checksum_dir}/${i}"`
		fi
		# Look for the calculated MD5 in the list of stored checksums
		[ -n "${md5sum}" ] && grep -q "$md5sum" "${checksum_file}" 2> /dev/null
		if [ $? -ne 0 ]; then
			err_exit "MD5 checksum failure on ${source} file: '$i'"
		fi
	done
	msg "${source} MD5 checksums pass"
}

# This Function is used to 
# 1. Calculate free space along with directory(second arg) with first argument "size"
# 2. Delete directory(second arg) contents along with some stale images of 7G when first arg "delete"
delete_old_files()
{
        local option=$1
        local tmpdir=$2
        if [ "$option" = "size" ]; then
                local available=$(fs_available_space "/cfcard")
                for file in ${tmpdir}/*; do
                        if [ -e $file ]; then
                                temp=`du -k $file | awk '{print $1}'`
                                available=`expr $available + $temp`
                        fi
                done
                for file in ${olddir}/*; do
                        if [ -e $file ]; then
                                temp=`du -k $file | awk '{print $1}'`
                                available=`expr $available + $temp`
                        fi
                done
                echo $available
        elif [  "$option" = "delete" ]; then
                rm -rf ${tmpdir}/* 2> /dev/null
                if [ -f ${currentdir}/kernel ]; then
                        rm -rf ${olddir}/* 2> /dev/null
                fi
        fi

}

#
# If this is not an INSTALL operation, compute the available disk space
# available = the free space on the device + the space of the files to be replaced.
#
# NOTES:
#	This function expects INSTALL_FILES to have already been set
#	appropriately by the parse_metadata function.
#
free_space_check()
{
	if [ $op != 'INSTALL' ]; then
		if [ ! -d /cfcard ]; then
			err_exit "No /cfcard directory found"
		fi

                #
                # Check whether "/cfcard" is mounted.
                # This is to avoid wasting time to list all other unrelated
                # file systems.  If there is stale mounted file systems,
                # 'df -k | grep -q "cfcard"' will timeout when listing the staled file system.
                # See burt 912918
                #
                is_mounted "/cfcard"
		if [ $? != 0 ]; then
			err_exit "Boot device not mounted"
		fi

		available=$(delete_old_files size $otherdir)
		availableM=`expr $available / 1024`
		msg "Available space on boot device is $availableM MB"

		#
		# Compute the required disk space - the space of the new files.
		#
		required=0
		for file in ${INSTALL_FILES}; do
			if [ $tarball ]; then
				temp=`grep $file ${TMP}/filelist | awk '{print $5}'`
				temp=`expr $temp / 1024`
			else
				temp=`du -k $newdir/$file | awk '{print $1}'`
			fi
			required=`expr $required + $temp`
		done

		#
		# Ensure that the device will not be more than 95% full
		#
                slack=$(fs_size "/cfcard")
		slack=`expr $slack / 20`
		required=`expr $required + $slack`
		# convert to MB
		requiredM=`expr $required / 1024`

		msg "Required  space on boot device is $requiredM MB"
		if [ $required -gt $available ]; then
			err_exit "Insufficient space available on device"
		fi

	#
	# Otherwise check that the media size is at least 1GB
	#
	else
		msize=`$DISKINFO $cfdev` || err_exit "Unable to read diskinfo from device $cfdev"
		msize=`echo $msize | awk '{print $3}'`
		if [ $msize -lt 1000000000 ]; then
		msizeM=`expr $msize / 1024 / 1024`
			msg "The version of Data ONTAP you are attempting to install is not"
			msg "supported on a boot device of $msizeM MB capacity."
			err_exit "Size of boot device is less than 1GB"
		fi
	fi
}


#
# Rename the custom platfs to a generic platfs file name
# Parameters:
#	$1=directory		The path to the file to be renamed.
#	$2=cust_platfs		The name of the custom platfs to be renamed.
# Returns:
# 	0		success
#	1 or non-zero 	on error
#
rename_platfs()
{
	local directory=$1
	local cust_platfs=$2

	if [ ! -d "${directory}" ]; then
		return 1
	fi

	if [ ! -f "${directory}/${cust_platfs}" ]; then
		return 1
	fi

	if [ ${cust_platfs} = "platfs.img" ]; then
		# no need to rename
		return 0
	fi

	mv ${directory}/${cust_platfs} ${directory}/platfs.img

}

#
# Generate a temp file containing an XSLT.  The caller is expected to clean up
# the temp file.  The result of running this transform will be the "value" of
# the requested platform parameter.  For example, the name of the platfs.img
# file to be used on this platform.
#
# Input parameters:
#	$1= boardname identifier, eg: SB_XXX
#	$2= tag, eg: platfs
# Output:
#	The name of the temp file containing the XSLT.
xslt_gen()
{
	local boardname=$1
	local tag=$2
	local tmp_xslt=$(mktemp ${TMP}/xslt.XXXXX)
	cat <<-XSLT > ${tmp_xslt}
	<?xml version="1.0" encoding="ISO-8859-1"?>
	<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
		<xsl:output method="text" omit-xml-declaration="yes" />

		<xsl:template match="/">
			<xsl:apply-templates select="/package_metadata/platform[@name='${boardname}']/${tag}" />
		</xsl:template>

	</xsl:stylesheet>
	XSLT
	echo ${tmp_xslt}
}


# Prevent upgrade or install on platforms with a Flash Cache adaptor and 4GiB
# of memory or less.  See burts 693390 and 539729
flash_cache_check()
{
	# 5 GiB = 4 + 1 for NVMEM
	local fas3210_mem=$((5 * 1024 * 1024 * 1024))
        local virgo_devid="0x774c1275"
	local fc_override_var="ext-cache-4g-override"

        # If memsize <= FAS3210 memory && PCI vendor id for Virgo is found,
        # we have an illegal config
        local fc_memsize
	fc_memsize="$(sysctl hw.total_physmem)" || msg "Unable to read system memory information"
        fc_memsize="$(echo "${fc_memsize}" | awk '{print $2}')"
        if [ "${fc_memsize}" -gt "${fas3210_mem}" ]; then
		# System has enough memory, return success
		return 0
	fi

	if ! (pciconf -l | grep -q "${virgo_devid}" 2> /dev/null); then
		# flash cache is not present, return success
		return 0
	fi

	# We have a low mem system with flash cache, but
        # allow override by bootarg:
        local fc_override="$(get_kenv "${fc_override_var}")"
        if [ "${fc_override}" = "true" ]; then
		msg "'$(get_kenv SYS_MODEL)' does not support Flash Cache in this release of Data ONTAP,"
		msg "but '${fc_override_var}' is '${fc_override}'."
                return 0
        fi

	# If we haven't returned success by now, this is a failure
	err_exit "Model '$(get_kenv SYS_MODEL)' does not support Flash Cache in this release of Data ONTAP"
}


#
# Special case: versions are identical -> this becomes a copy operation
# Only do this if the operation is not an INSTALL.
#
# NOTES:
#	This function should be called as part of the compat_check() function,
#	since it could change an UPDATE op into a COPY op.  COPY is used to
#	intentionally bypass the compat check on what would otherwise be an
#	UPDATE operation.
#
op_should_be_COPY()
{
	if [ $op = 'INSTALL' ]; then
		return
	fi

	diff /VERSION ${newdir}/VERSION > /dev/null 2>&1
	if [ $? -eq 0 ]; then
		op='COPY'
	fi
}

# Check that the current VERSION file exists
# and that versions are compatible.
compat_check()
{
	op_should_be_COPY

	# COPY is used to intentionally bypass the compatibility check
	# INSTALL and UPDATE are subject to checks
	if [ $op = 'COPY' ]; then
		return
	fi

	# VERSION file must exist
	if [ ! -e /VERSION ]; then
		err_exit "/VERSION file does not exist"
	fi

	local error_string="The running version $(cat /VERSION)"
	error_string="${error_string} is not compatible with the package"
	error_string="${error_string} version $(cat ${newdir}/VERSION)"

	# Check basic compatibility
	# If a regex pattern in COMPAT.TXT matches the current /VERSION
	# string, then we are compatible.
	if ! grep -qEf ${newdir}/COMPAT.TXT /VERSION; then
		err_exit "${error_string}"
	fi
	msg "Versions are compatible"
}

#
# Given the platform boardname and a tag name, parse the metadata.xml file and
# return the value of the requested tag
#
# Input parameters:
#	$1= boardname identifier
#	$2= tag
# Output:
#	The content of "tag"
# Return:
#	The return code of the xsltproc(1) command
platform_xpath()
{
	local boardname=$1
	local tag=$2
	local xslt_file=$(xslt_gen ${boardname} ${tag})
	# Output is captuire by this function's caller
	xsltproc ${xslt_file} ${newdir}/metadata.xml
	local rc=$?
	rm -f ${xslt_file}
	return ${rc}

}

# Ensure the given command line tool exists
# Input paramters:
#	$1	The command to verify is present on the system and in the path.
# Return:
#	The script will exit if the command is not found.
tool_check()
{
	local tool_cmd="$1"

	local error_string="The running version $(cat /VERSION)"
	error_string="${error_string} does not have the tool set necessary to"
	error_string="${error_string} complete the ${op} operation"

	if ! which ${tool_cmd} > /dev/null; then
		msg "Command '${tool_cmd}' was not found."
		err_exit "${error_string}"
	fi
}


parse_metadata()
{
	# Ensure that the versions are compatible.  Do this early, otherwise
	# we might not have essential tools (such as xsltproc) or other
	# unexpected incompatibilities
	compat_check

	# verify xsltproc is available for use
	tool_check xsltproc

	# Which platform are we on?
	local boardname=$(get_kenv BOARDNAME)

	# BOARDNAME is readonly from the LOADER.  Allow a user to override
	# the BOARDNAME with an arbitrary value by setting BOARDNAME_OVERRIDE.
	local boardname_override=$(get_kenv BOARDNAME_OVERRIDE)
	if [ -n "${boardname_override}" ]; then
		msg "BOARDNAME_OVERRIDE='${boardname_override}' will replace the default value of BOARDNAME='${boardname}'."
		boardname="${boardname_override}"
	fi

	# Which platfs.img file should be used?
	# This is global, to allow for re-use during the rename to platfs.img
	PLATFS_NAME=$(platform_xpath ${boardname} platfs)

	# add platfs.img to the list of files to be installed:
	INSTALL_FILES="${INSTALL_FILES} ${PLATFS_NAME}"

	FDISK_SCRIPT="$(platform_xpath ${boardname} fdisk_script)"
	NEWFS_CMD="$(platform_xpath ${boardname} newfs_cmd)"
	MOUNT_CMD="$(platform_xpath ${boardname} mount_cmd)"


	# Make sure the metadata file provided all the values that we expect,
	# else we exit.
	if [ -z "${PLATFS_NAME}" -o \
	     -z "${FDISK_SCRIPT}" -o \
	     -z "${NEWFS_CMD}" -o \
	     -z "${MOUNT_CMD}" ]; then
		err_exit "Model '$(get_kenv SYS_MODEL)' is not supported by this package"
	fi
}

#
# Ensure the files for the new image exist
#
# NOTES:
#	This function expects INSTALL_FILES to have already been set
#	appropriately by the parse_metadata function.
#
file_presence_check()
{
	if [ $tarball ]; then
		for file in ${INSTALL_FILES}; do
			if ! grep -q "${file}" ${TMP}/filelist; then
				err_exit "'$file' does not exist in $tarball"
			fi
		done
	else
		for file in ${INSTALL_FILES}; do
			if [ ! -e $newdir/$file ]; then
				err_exit "'$file' does not exist in $newdir"
			fi
		done
	fi
}


#
# For the INSTALL operation check for the existance of the device and
# ensure that it is not currently mounted.
#
device_mount_check()
{
	if [ $op != 'INSTALL' ]; then
		return
	fi

	if [ ! -c $cfdev ]; then
		err_exit "Cannot find boot device file $cfdev"
	fi

	mount | grep -q $cfdev
	if [ $? = 0 ]; then
		err_exit "Cannot initialize a mounted filesystem"
	fi
	msg "Install device found"

	if [ $oneonly ]; then
		msg "Install command will only setup $current"
	fi
}


#
# Validate the machine type
#
machine=`uname -m`
case $machine in
	amd64)	machine=x86_64
		def_cfdev=/dev/ad0
	;;
	i386)	machine=x86
		def_cfdev=/dev/ad4
	;;
	*)	err_exit "Unrecognized machine type: $machine"
	;;
esac

# If this is not a LOADER based system, then this will be empty
LOADER_VERSION=`get_kenv LOADER_VERSION`

#
# Validate the number of paramters
#
if [ `toupper $1`null = "-CHECK"null ]; then
	checkonly=1
	shift
	msg "INSTALL running in check only mode"
fi

if [ $1null = "-1"null ]; then
	oneonly=1
	shift
fi

if [ $# -lt 3 -o $# -gt 4 ]; then
	err_exit "Invalid number of parameters specified"
fi

op=`toupper $1`
current=$2
location=$3
cfdev=$4

#
# Only accept copy, update and install as operations
#
if [ $op != 'COPY' -a $op != 'UPDATE'  -a $op != 'INSTALL' ]; then
	err_exit "Invalid operation specified"
fi
msg "Mode of operation is $op"

#
# Only accept image1 and image2 as the location of the currently running image
#
if [ `toupper $current` = 'IMAGE1' ]; then
	current=image1
	other=image2
elif [ `toupper $current` = 'IMAGE2' ]; then
	current=image2
	other=image1
else
	err_exit "Invalid current image specified"
fi

#
# Ensure the that currently running image is really the currently running image.
#
imagestr=`get_kenv bootarg.init.rootimage`
if [ $? -ne 0 ]; then
	imagestr=`get_kenv kernelname`
fi
if [ $imagestr ]; then
	echo $imagestr | grep -q $other 2> /dev/null
	if [ $? -eq 0 ]; then
		err_exit "Cannot write to a currently running image"
	fi
fi

currentdir=/cfcard/$machine/freebsd/$current
otherdir=/cfcard/$machine/freebsd/$other
olddir=/cfcard/backup/$machine/kernel

msg "Current image is $current"
msg "Alternate image is $other"

#
# Is it a directory or a tarball?
#
if [ ! -d $location ]; then
	tar -tzf $location > /dev/null
	if [ $? -ne 0 ]; then
		err_exit "Invalid location specified - must be a directory or a gzipped tarball"
	fi
	tarball=$location
	tar -tvzf $tarball > ${TMP}/filelist
else
	newdir=$location
fi

#
# If this in an INSTALL operation then check for a valid optional device
#
if [ $op = "INSTALL" ]; then
	if [ $cfdev ]; then
		if [ ! -c $cfdev ]; then
			err_exit "Device $cfdev does not exist"
		fi
		cfpart=$cfdev"s1"
	else
		cfdev=`get_kenv ntap.init.cfdevice`
		if [ $cfdev ]; then
			cfpart=$cfdev
			cfdev=`echo $cfdev | sed 's/s.*//'`
		else
			cfdev=$def_cfdev
			cfpart=$cfdev"s1"
		fi
	fi

	msg "Using device $cfdev for install"
fi

# After this point, we assume we've been invoked correctly, so don't print the
# script usage.
PRINT_USAGE=0


pre_extract
verify_checksums -package
parse_metadata
file_presence_check
free_space_check
kernel_arch_check
hostname_check
flash_cache_check
brand_check "${newdir}/BUILD"
device_mount_check

# Cleanup temp files
rm -rf ${TMP}

#
# If running as check only then stop here.
#
if [ $checkonly ]; then
	cleanup_and_exit 0
fi
msg "Getting ready to install image"

#
# If this is an INSTALL operation then format the device
#
if [ $op = 'INSTALL' ]; then


	# whipup a ramdisk to place a file specifing partitioning info for fdisk
	mddev=`mdconfig -a -tmalloc -s1024`
	if [ $? != 0 ]; then
		err_exit "Unable to allocate a small ramdisk"
	fi

	newfs /dev/$mddev
	if [ $? != 0 ]; then
		mdconfig -d -u$mddev
		err_exit "Unable to newfs ramdisk"
	fi
	sync

	TMP_MNT=`mktemp -d /tmp/mnt.XXXXX`
	mount /dev/$mddev $TMP_MNT
	if [ $? != 0 ]; then
		mdconfig -d -u$mddev
		err_exit "Unable to mount ramdisk"
	fi

	write_fdisk_config

	# partition the disk
	$FDISK -i -f $TMP_MNT/dskfmt $cfdev
	if [ $? != 0 ]; then
		umount $TMP_MNT
		mdconfig -d -u$mddev
		err_exit "Unable to initialize the boot device partition table"
	fi
	sync ; sleep 3;
	msg "Partitioning of boot device complete"

	# toss out the ramdisk
	umount $TMP_MNT
	mdconfig -d -u$mddev
	sync

	# create a new filesystem on partition 1
	$(printf "${NEWFS_CMD}" "${cfpart}")
	if [ $? != 0 ]; then
		err_exit "Unable to create a filesystem on the boot device"
	fi
	msg "New boot device filesystem created"
	sync

	# mount the new filesystem for copying over files
	$(printf "${MOUNT_CMD}" "${cfpart}" "${CF_MNT}")
	if [ $? != 0 ]; then
		err_exit "Unable to mount boot device filesystem"
	fi
	sync
	msg "New filesystem mounted"

        if [ "$LOADER_VERSION" != "" ]; then
		# Restore loader's environment to the boot device since we blew
		# it away when the boot device was formatted
		SaveLoaderEnv ||
			msg "Warning: Could not preserve Loader environment."
		sync
	fi

	currentdir=${CF_MNT}/$machine/freebsd/$current
	otherdir=${CF_MNT}/$machine/freebsd/$other
	olddir=${CF_MNT}/backup/$machine/kernel
fi

#
# If destination directory does not exist then create it.
#
create_dir $otherdir
if [ $? != 0 ]; then
	cleanup_and_exit 1
fi
if [ $op = 'INSTALL' ]; then
	create_dir $currentdir
	if [ $? != 0 ]; then
		cleanup_and_exit 1
	fi
fi

#
# Copy the files
#
if [ $op = 'INSTALL' -a $oneonly ]; then
	delete_old_files delete $currentdir
	copy_files "${newdir}" "${currentdir}" "${INSTALL_FILES}"
	if [ $? != 0 ]; then
		cleanup_and_exit 1
	fi
	verify_checksums -dir $currentdir
	rename_platfs "${currentdir}" "${PLATFS_NAME}"

	# Swap for fw install
	otherdir=$currentdir
else
	if [ $op = 'INSTALL' ]; then
		delete_old_files delete $currentdir
		copy_files "${newdir}" "${currentdir}" "${INSTALL_FILES}"
		if [ $? != 0 ]; then
			cleanup_and_exit 1
		fi
		verify_checksums -dir $currentdir
		rename_platfs "${currentdir}" "${PLATFS_NAME}"
		# we don't need two copies of fw.tgz
		rm -f ${currentdir}/fw.tgz
	fi

	delete_old_files delete $otherdir 
	copy_files "${newdir}" "${otherdir}" "${INSTALL_FILES}"
	if [ $? != 0 ]; then
		cleanup_and_exit 1
	fi
	verify_checksums -dir $otherdir
	rename_platfs "${otherdir}" "${PLATFS_NAME}"
fi

#
# Check for fw.tgz in this package and install it
#
if [ -f $otherdir/fw.tgz ]; then
	# See if we have enough space to install this.
	# Assume we need twice the size of the tarball
	fw_needed=`ls -l $otherdir/fw.tgz | awk '{print $5}'`
	fw_needed=`expr $fw_needed \* 2 / 1024`
	avail=`df -k $otherdir | grep -v Avail | awk '{print $4}'`
	avail=`expr $avail \* 90 / 100` # 10% head room

	if [ $fw_needed -gt $avail ]; then
		msg "Not enough space to install diagnostics and firmware files"
	else
		msg "Installing diagnostic and firmware files"
		# use the directory hierarchy built into the tar file
		tar -xzf $otherdir/fw.tgz -C ${CF_MNT}
		if [ $? != 0 ]; then
			err_exit "Failed to install diagnostic and firmware files"
		fi

		verify_checksums -fw
		# Successfully extracted, no need to keep the package
		rm -f ${otherdir}/fw.tgz
	fi
fi

# After the install, and before we unmount and exit,
# tell the dblade to set a flag.
notify_dblade

#
# If this was an install operation then unmount the boot device.
#
if [ $op = "INSTALL" ]; then
	umount ${CF_MNT}
	if [ $? != 0 ]; then
		err_exit "Unable to unmount the boot device"
	fi
	sync
fi

cleanup_and_exit 0
