#!/bin/sh

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

#
# Set a few things
#
files='kernel rootfs.img VERSION COMPAT.TXT INSTALL CHECKSUM'
print_usage=1

#
# 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]*//'
}

#
# Make a directory
#
create_dir()
{
	if [ ! -d $1 ]; then
		mkdir -p $1
		if [ $? -eq 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 )
#
copy_files()
{
	local srcdir=$1
	shift
	local dstdir=$1
	shift

	if [ $tarball ]; then
		msg "Untarring to $dstdir..."
		tar -xvzf $tarball -C $dstdir
		if [ $? -ne 0 ]; then
			msg "Unable to untar $tarball to $dstdir - this is VERY bad"
			return 1
		fi
	else
		for file in $@; 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
}

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

#
# 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 CF card used for the"
		msg "        install operation"
	fi
}

#
# Error and exit function
#
err_exit()
{
	if [ $tmp ]; then
		rm -rf $tmp
	fi
	echo "ERROR: $1." >&2
	usage
	exit 1
}

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

#
# Compute the a numeric representation of the major and minor
# portions of the version string.
#
compute_version()
{
	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
	if [ -z $minor ]; then
	        minor=0
	fi

	local version=`expr $major \* 1000 + $minor`
	return $version
}

#
# 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

#
# 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 ntap.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

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
	tmp=`mktemp -d /tmp/install.tmp.XXXXX`
	tar -tvzf $tarball > $tmp/filelist
else
	newdir=$location
fi

#
# If it's a tarball then untar a few things to temp
#

#
# 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
print_usage=0

#
# Ensure the files for the new image exist
#
if [ $tarball ]; then
	for file in $files; do
		cat $tmp/filelist | grep -q $file
		if [ $? -ne 0 ]; then
			err_exit "'$file' does not exist in $tarball"
		fi
	done
else
	for file in $files; do
		if [ ! -e $newdir/$file ]; then
			err_exit "'$file' does not exist in $newdir"
		fi
	done
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.
#
if [ $op != 'INSTALL' ]; then
	if [ ! -d /cfcard ]; then
		err_exit "No /cfcard directory found"
	fi

	df -k | grep -q cfcard
	if [ $? != 0 ]; then
		err_exit "CF card not mounted"
	fi

	available=`df -k | grep cfcard | awk '{print $4}'`
	for file in $otherdir/*; do
		if [ -e $file ]; then
			temp=`du -k $file | awk '{print $1}'`
			available=`expr $available + $temp`
		fi
	done
	msg "Available space on device is $available 1k blocks"

	#
	# Compute the required disk space - the space of the new files.
	#
	required=0
	for file in $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
	msg "Required space for install is $required 1k blocks"

	#
	# Ensure that the device will not be more than 90% full
	#
	total=`df -k | grep cfcard | awk '{print $2}'`
	total=`expr $total / 10`
	total=`expr $available - $total`
	if [ $required -gt $total ]; then
		err_exit "Insufficient space available on device"
	fi

#
# Otherwise check that the media size in at least 1G
#
else
	msize=`diskinfo $cfdev | awk '{print $3}'`
	if [ $msize -lt 1000000000 ]; then
		err_exit "Size of CF card is less than 1G"
	fi
fi

#
# If it's a tarball then extract the VERSION, COMPAT.TXT and CHECKSUM files to temp
#
if [ $tarball ]; then
	tar -xzf $tarball --include='[V|C]*' -C $tmp
	newdir=$tmp
	filetype=`tar -xzf $tarball -O kernel | file -`
else
	filetype=`file $newdir/kernel`
fi

#
# Ensure that kernel matches the machine type
#
case $machine in
	x86_64)	echo $filetype | grep -q 'AMD 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
#
clist=`cut -d' ' -f2  $newdir/CHECKSUM | sed 's/[()]//g'`
files=$clist
clist=`echo $clist | sed 's/CHECKSUM //'`
for i in $clist; do
	if [ $tarball ]; then
		md5sum=`tar -xvzf $tarball -O $i  2>/dev/null | md5`
	else
		md5sum=`md5 -q $newdir/$i`
	fi
	grep -q $md5sum $newdir/CHECKSUM 2> /dev/null
	if [ $? -eq 1 ]; then
		err_exit "MD5 checksum failure on file: $i"
	fi
done
msg "MD5 checksums pass"

#
# Special case: versions are identical -> this becomes a copy operation
# Again, only do this if the operation is not an INSTALL.
#
if [ $op != 'INSTALL' ]; then
	diff $currentdir/VERSION $newdir/VERSION > /dev/null 2>&1
	if [ $? -eq 0 ]; then
		op='COPY'
	fi
fi

#
# For the UPDATE operation, check that the current version is supported
# by the newer version or vis vera.
#
if [ $op = 'UPDATE' ]; then

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

	# checking basic compatibility
	compute_version `cat $currentdir/VERSION`
	curversion=$?
	compute_version `cat $newdir/VERSION`
	newversion=$?
	if [ $curversion -ne $newversion ]; then

		# is this an UPGRADE?
		version=`cat $currentdir/VERSION`
		version=`trim $version`
		grep -Eq '[[:space:]]*'$version'[[:space:]]*$' $newdir/COMPAT.TXT

		# is this a DOWNGRADE?
		if [ $? -eq 1 ]; then
			if [ ! -e $currentdir/COMPAT.TXT ]; then
				err_exit "$current COMPAT.TXT file does not exist"
			fi
			version=`cat $newdir/VERSION`
			version=`trim $version`
			grep -Eq '[[:space:]]*'$version'[[:space:]]*$' $currentdir/COMPAT.TXT
			if [ $? -eq 1 ]; then
				err_exit "Versions are not compatible"
			else
				msg "This looks like a downgrade"
			fi
		else
			msg "This looks like an upgrade"
		fi
	else
		msg "Major version levels are identical"
	fi
	msg "Versions are compatible"

#
# For the INSTALL operation check for the existance of the device and
# ensure that it is not currently mounted.
#
elif [ $op = 'INSTALL' ]; then
	if [ ! -c $cfdev ]; then
		err_exit "Cannot find CF device file"
	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

#
# For the COPY operations, don't need to do much at all.
#
else
fi

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

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

	# compute the start sector and size of the first partition
	msize=`diskinfo $cfdev | awk '{print $4}'`
	cylstart=`diskinfo $cfdev | awk '{print $7}'`
	psize=`expr $msize - $cylstart`

        # Save loader's environment somewhere so we can
        # restore it after reboot
        if [ $machine = 'x86_64' ]; then
                LOADER_ENV_SAVED=0
                TMP_ENV=`mktemp -d /tmp/env.XXXXX`
                TMP_MNT=`mktemp -d /tmp/mnt.XXXXX`
                mount -t msdos $cfpart $TMP_MNT
                if [ $? -eq 0 ]; then
                        msg "Backing up loader environment."
                        if [ -d $TMP_MNT/env ]; then
                                cp -r $TMP_MNT/env $TMP_ENV/
                        fi
                        if [ -d $TMP_MNT/env_bak ]; then
                                cp -r $TMP_MNT/env_bak $TMP_ENV/
                        fi
                        umount $TMP_MNT
                        LOADER_ENV_SAVED=1
                fi
                rmdir $TMP_MNT
        fi

	# 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

	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

	echo "p 1 6 $cylstart $psize" > $TMP_MNT/dskfmt
	echo "a 1" >> $TMP_MNT/dskfmt

	# 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 CF card partition table"
	fi
	msg "Partitioning of CF card complete"

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

	# create a new filesystem on partition 1
	newfs_msdos -F16 -O"MSDOS.5" -m0xf8 -o$cylstart $cfpart
	if [ $? != 0 ]; then
		err_exit "Unable to create an MSDOS filesystem on the CF card"
	fi
	msg "New MSDOS filesystem created"

	# mount the new filesystem for copying over files
	mount -t msdos $cfpart $TMP_MNT
	if [ $? != 0 ]; then
		err_exit "Unable to mount MSDOS filesystem on the CF card"
	fi
	msg "New filesystem mounted"

	currentdir=$TMP_MNT/$machine/freebsd/$current
	otherdir=$TMP_MNT/$machine/freebsd/$other

	# Restore loader's environment to the CF card since we blew it away!
	if [ $machine = 'x86_64' ]; then
                if [ $LOADER_ENV_SAVED -eq 1 ]; then
                        msg "Restoring loader environment to the CF card."
                        cp -r $TMP_ENV/ $TMP_MNT/
                else
			SetCFcardMntPt "$TMP_MNT"
			SaveLoaderEnv
		fi
	fi
fi

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

#
# Copy the files
#
if [ $op = 'INSTALL' -a $oneonly ]; then
	rm -rf $currentdir/* 2> /dev/null
	copy_files $newdir $currentdir $files
	if [ $? != 0 ]; then
		exit 1
	fi

	# Swap for diags install
	otherdir=$currentdir
else
	rm -rf $otherdir/* 2> /dev/null
	copy_files $newdir $otherdir $files
	if [ $? != 0 ]; then
		exit 1
	fi
	if [ $op = 'INSTALL' ]; then
		rm -rf $currentdir/* 2> /dev/null
		copy_files $newdir $currentdir $files
		if [ $? != 0 ]; then
			exit 1
		fi
	fi
fi

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

	if [ $diags_needed -gt $avail ]; then
		msg "Not enough space to install diagnostics and firmware files"
	else
		# This the directory hierarchy built into the tar file
		msg "Installing diagnostics and firmware files"
		if [ $op = 'INSTALL' ]; then
			tar -xzf $otherdir/diags.tgz -C $TMP_MNT
		else
			tar -xzf $otherdir/diags.tgz -C /cfcard
		fi

		if [ $? != 0 ]; then
			msg "Failed to install diagnostics and firmware files"
			# ignore!
		fi
	fi
fi

#
# If this was an install operation then unmount the CF card.
#
if [ $op = "INSTALL" ]; then
	umount $TMP_MNT
	rmdir $TMP_MNT
	if [ $? != 0 ]; then
		err_exit "Unable to umount the CF card"
	fi
fi

exit 0
