#!/bin/ksh

# Copyright 2009 Sun Microsystems, Inc.  All rights reserved. 
# Use is subject to license terms.
#
# ident	"@(#)root_archive.ksh	1.8	09/02/27 SMI"

# utility to pack and unpack a boot/root archive
# both ufs and hsfs (iso9660) format archives are unpacked
# only ufs archives are generated
#
# usage: pack   <archive> <root>
#        unpack <archive> <root>
#        packmedia   <solaris_image> <root>
#        unpackmedia <solaris_image> <root>
#
#   Where <root> is the directory to unpack to and will be cleaned out
#   if it exists.
#
#   In the case of (un)packmedia, the image is packed or unpacked to/from
#   Solaris media and all the things that don't go into the ramdisk image
#   are (un)cpio'd as well
#

usage()
{
	printf "usage: root_archive pack <archive> <root>\n"
	printf "       root_archive unpack <archive> <root>\n"
	printf "       root_archive packmedia   <solaris_image> <root>\n"
	printf "       root_archive unpackmedia <solaris_image> <root>\n"
}

cleanup()
{
	if [ -d $MNT ] ; then
		umount $MNT 2> /dev/null
		rmdir $MNT
	fi

	lofiadm -d "$TMR" 2>/dev/null
        if [ "$REALTHING" != true ] ; then
		rm -f "$TMR"
	fi
	rm -f "$TMR.gz"
}

archive_lu()
{
	MEDIA="$1"
	MINIROOT="$2"

	RELEASE=`/bin/ls -d "$MEDIA/Solaris_"*`
	RELEASE=`basename "$RELEASE"`

	CPIO_DIR="$MEDIA/$RELEASE/Tools/Boot"

	(
		cd "$MINIROOT"
		find usr/lib/install usr/snadm usr/sbin | \
		    cpio -ocmPuB 2> /dev/null | bzip2 > "$CPIO_DIR"/lu.cpio.bz2
		ls platform > "$CPIO_DIR/lu.platforms"
	)
}

packmedia()
{
	MEDIA="$1"
	MINIROOT="$2"

	RELEASE=`ls -d ${MEDIA}/Solaris_*`
	RELEASE=`basename ${RELEASE}`

	mkdir -p ${MEDIA}/${RELEASE}/Tools/Boot
	mkdir -p ${MEDIA}/boot

	cd ${MINIROOT}

	# archive package databases to conserve memory
	#
	find tmp/root/var/sadm/install tmp/root/var/sadm/pkg -print | \
	    cpio -ocmPuB 2> /dev/null | bzip2 > \
	    ${MEDIA}/${RELEASE}/Tools/Boot/pkg_db.cpio.bz2

	rm -rf ${MINIROOT}/tmp/root/var/sadm/install
	rm -rf ${MINIROOT}/tmp/root/var/sadm/pkg

	if [ -d "$MINIROOT/platform/i86pc" ] ; then
		#
		# clear out 64 bit support to conserve memory
		#
		find ${MINIROOT} -name amd64 -type directory | xargs rm -rf
	fi

	# create the graphics and non-graphics X archive
	#
	cd ${MINIROOT}/usr
	find openwin dt -print | cpio -ocmPuB 2> /dev/null | bzip2 > \
	    ${MEDIA}/${RELEASE}/Tools/Boot/X.cpio.bz2

	find openwin/bin/mkfontdir \
	     openwin/lib/installalias \
	     openwin/server/lib/libfont.so.1 \
	     openwin/server/lib/libtypesclr.so.0 \
	         -print | cpio -ocmPuB 2> /dev/null | bzip2 > \
	         ${MEDIA}/${RELEASE}/Tools/Boot/X_small.cpio.bz2

	rm -rf dt openwin
	ln -s ../tmp/root/usr/dt
	ln -s ../tmp/root/usr/openwin
	cd ../..

	if [ -d "$MINIROOT/platform/i86pc" ] ; then
		cp ${MINIROOT}/platform/i86pc/multiboot ${MEDIA}/boot

		# XXX fix as soon as we deliver boot/grub/install_menu
		#
		if [ -f "${MINIROOT}/boot/grub/install_menu" ] ; then
			cp ${MINIROOT}/boot/grub/install_menu \
			    ${MEDIA}/boot/grub/menu.lst
		elif [ -f "/ws/boot-gate/usr/src/grub/menu.lst.cd_dvd" ] ; then
			cp /ws/boot-gate/usr/src/grub/menu.lst.cd_dvd \
			    ${MEDIA}/boot/grub/menu.lst
		elif [ -f "/ws/boot-gate/usr/src/grub/install_menu" ] ; then
			cp /ws/boot-gate/usr/src/grub/install_menu \
			    ${MEDIA}/boot/grub/menu.lst
		fi

		cd ${MEDIA}/${RELEASE}/Tools/Boot
		ln -sf ../../../boot/x86.miniroot
		ln -sf ../../../boot/multiboot
		ln -sf ../../../boot/grub/pxegrub
	fi

	if [ -d "$MINIROOT/platform/sun4u" ] ; then
		mkdir -p "$MEDIA/boot"
		dd if="$MINIROOT/platform/sun4u/lib/fs/hsfs/bootblk" \
		    of="$MEDIA/boot/hsfs.bootblock" \
		    bs=1b oseek=1 count=15 conv=sync 2> /dev/null
	fi

	for arch in sun4u sun4v sun4us ; do
		if [ -d "$MINIROOT/platform/$arch" ] ; then
			archdir="$MEDIA/$RELEASE/Tools/Boot/platform/$arch"
			mkdir -p $archdir
			ln -sf ../../../../../boot/sparc.miniroot \
			    "$archdir/boot_archive"
			cp "$MINIROOT/usr/platform/$arch/lib/fs/nfs/inetboot" \
			    "$archdir"
			cp "$MINIROOT/platform/$arch/wanboot" \
			    "$archdir"
			mkdir -p "$MEDIA/platform/$arch"
			ln -sf ../../boot/sparc.miniroot \
			    "$MEDIA/platform/$arch/boot_archive"
			ln -sf ../../$RELEASE/Tools/Boot/platform/$arch/wanboot \
			    "$MEDIA/platform/$arch/wanboot"
		fi
	done

	if [ -d "$MINIROOT/kernel/drv/sparcv9" ] ; then
		archive_lu "$MEDIA" "$MINIROOT"
	fi

	#
	# copy the install menu to menu.lst so we have a menu
	# on the install media
	#
	if [ -f "$MINIROOT/boot/grub/install_menu" ] ; then
		cp $MINIROOT/boot/grub/install_menu \
		    $MEDIA/boot/grub/menu.lst
	fi

	# May be removed once SUNWgzip is included in the miniroot
	if [ ! -f "$MINIROOT/usr/bin/gzip" ] ; then
		cp /usr/bin/gzip $MINIROOT/usr/bin
	fi

	#
	# jumpstart utilities in usr/sbin/install.d
	#
	if [ -d "$MINIROOT/usr/sbin/install.d" ] ; then
		(
		cd ${MINIROOT}
		find usr/sbin/install.d/chkprobe \
		    -print | cpio -ocmPuB 2> /dev/null | bzip2 > \
		    ${MEDIA}/${RELEASE}/Tools/Boot/usr_sbin_install_d.cpio.bz2
		)
	fi
}

unpackmedia()
{
	MEDIA="$1"
	UNPACKED_ROOT="$2"

	RELEASE=`/bin/ls -d "$MEDIA/Solaris_"*`
	RELEASE=`basename "$RELEASE"`

	# unpack X
	#
	cd ${UNPACKED_ROOT}/usr
	rm -rf dt openwin
	bzcat ${MEDIA}/${RELEASE}/Tools/Boot/X.cpio.bz2 | cpio -icdmu \
	    2> /dev/null

	# unpack package databases
	#
	cd $UNPACKED_ROOT
	bzcat ${MEDIA}/${RELEASE}/Tools/Boot/pkg_db.cpio.bz2 | cpio -icdmu \
	    2> /dev/null
}

do_unpack()
{
	rm -rf "$UNPACKED_ROOT"
	mkdir -p "$UNPACKED_ROOT"
	(
		cd $MNT
		find . -print | cpio -pdum "$UNPACKED_ROOT" 2> /dev/null
	)
	umount $MNT
}

unpack()
{
	if [ ! -f "$MR" ] ; then
		usage
		exit 1
	fi

	if [ `basename $MR` = x86.miniroot ] ; then
		gzcat "$MR" > $TMR
	else
		REALTHING=true ; export REALTHING
		TMR="$MR"
	fi

	LOFIDEV=`/usr/sbin/lofiadm -a $TMR`
	if [ $? != 0 ] ; then
		echo lofi plumb failed
		exit 2
	fi

	mkdir -p $MNT

	FSTYP=`fstyp $LOFIDEV`

	if [ "$FSTYP" = ufs ] ; then
		/usr/sbin/mount -o ro,nologging $LOFIDEV $MNT
		do_unpack
	elif [ "$FSTYP" = hsfs ] ; then
		/usr/sbin/mount -F hsfs -o ro $LOFIDEV $MNT
		do_unpack
	else
		printf "invalid root archive\n"
	fi


	rmdir $MNT
	lofiadm -d $TMR ; LOFIDEV=
	if [ "$REALTHING" != true ] ; then
		rm $TMR
	fi
}

compress()
{
	SRC=$1
	DST=$2

	(
		cd $SRC
		filelist=`find .`

		for file in $filelist ; do

			file=`echo $file | sed s#^./##`

			# copy all files over to preserve hard links
			#
			echo $file | cpio -pdum $DST 2> /dev/null

			if [ -f $file ] && [ -s $file ] && [ ! -h $file ] ; then
				fiocompress -mc $file $DST/$file &
			fi

		done

		# now re-copy a couple of uncompressed files
		#

		find kernel platform -name unix | cpio -pdum $DST 2> /dev/null
		find kernel platform -name genunix | cpio -pdum $DST \
		    2> /dev/null
		find kernel platform -name platmod | cpio -pdum $DST \
		    2> /dev/null
		find `find kernel platform -name cpu` | cpio -pdum $DST \
		    2> /dev/null
		find `find kernel platform -name kmdb\*` | cpio -pdum $DST \
		    2> /dev/null
		find kernel/misc/sparcv9/ctf kernel/fs/sparcv9/dcfs \
		    etc/system etc/name_to_major etc/path_to_inst \
		    etc/name_to_sysnum | cpio -pdum $DST 2> /dev/null
	)
}

root_is_ramdisk()
{
	grep -v "set root_is_ramdisk=" "$UNPACKED_ROOT"/etc/system | \
	    grep -v "set ramdisk_size=" > /tmp/system.$$
	cat /tmp/system.$$ > "$UNPACKED_ROOT"/etc/system
	rm /tmp/system.$$

	echo set root_is_ramdisk=1 >> "$UNPACKED_ROOT"/etc/system
	echo set ramdisk_size=$1 >> "$UNPACKED_ROOT"/etc/system
}

pack()
{
	if [ ! -d "$UNPACKED_ROOT" -o -z "$MR" ] ; then
		usage
		exit 1
	fi

	# always compress on sparc if fiocompress exists
	#
	if [ -d "$UNPACKED_ROOT/kernel/drv/sparcv9" ] && \
	    [ -x /usr/sbin/fiocompress ] ; then
		COMPRESS=true
	fi

	# Estimate image size and add %10 overhead for ufs stuff.
	# Note, we can't use du here in case $UNPACKED_ROOT is on a filesystem,
	# e.g. zfs, in which the disk usage is less than the sum of the file
	# sizes.  The nawk code
	#
	#	{t += ($7 % 1024) ? (int($7 / 1024) + 1) * 1024 : $7}
	#
	# below rounds up the size of a file/directory, in bytes, to the
	# next multiple of 1024.  This mimics the behavior of ufs especially
	# with directories.  This results in a total size that's slightly
	# bigger than if du was called on a ufs directory.
	#
	# If the operation in turn is compressing the files the amount
	# of typical shrinkage is used to come up with a useful archive
	# size. This number is impacted by the number of files that need
	# to remain uncompressed and grows (less compression) with additional
	# platforms that all supply their own unix, genunix and the like (which
	# are all larger files).
	#
	size=$(find "$UNPACKED_ROOT" -ls | nawk '
	    {t += ($7 % 1024) ? (int($7 / 1024) + 1) * 1024 : $7}
	    END {print int(t * 1.10 / 1024)}')
	if [ "$COMPRESS" = true ] ; then
		size=`echo $size | nawk '{s = $1} END {print int(s * .55)}'`
	fi
	(( size= (($size + (1200 - 1)) / 1200) * 1200 ))

	/usr/sbin/mkfile ${size}k "$TMR"

	LOFIDEV=`/usr/sbin/lofiadm -a "$TMR"`
	if [ $? != 0 ] ; then
		echo lofi plumb failed
		exit 2
	fi

	RLOFIDEV=`echo $LOFIDEV | sed s/lofi/rlofi/`
	newfs $RLOFIDEV < /dev/null 2> /dev/null
	mkdir -p $MNT
	mount -o nologging $LOFIDEV $MNT
	rmdir $MNT/lost+found

	if [ -d "$UNPACKED_ROOT/kernel/drv/sparcv9" ] ; then
		root_is_ramdisk $size
	fi

	(
		cd "$UNPACKED_ROOT"
		if [ "$COMPRESS" = true ] ; then
			compress . $MNT
		else
			find . -print | cpio -pdum $MNT 2> /dev/null
		fi
	)
	lockfs -f $MNT
	umount $MNT
	rmdir $MNT

	if [ -d "$UNPACKED_ROOT/kernel/drv/sparcv9" ] ; then
		"$UNPACKED_ROOT/usr/sbin/installboot" \
		    "$UNPACKED_ROOT/platform/sun4u/lib/fs/ufs/bootblk" \
		    $RLOFIDEV
	fi

	lofiadm -d $LOFIDEV
	LOFIDEV=

	rm -f "$TMR.gz"

	if [ -d "$UNPACKED_ROOT/kernel/drv/sparcv9" ] ; then
		mv "$TMR" "$MR"
	else
		gzip -f "$TMR"
		mv "$TMR.gz" "$MR"
	fi

	chmod a+r "$MR"
}

# main
#

EXTRA_SPACE=0
STRIP_AMD64=
COMPRESS=

PATH=/usr/sbin:/usr/bin:/opt/sfw/bin ; export PATH

while getopts s:6c opt ; do
	case $opt in
	s)	EXTRA_SPACE="$OPTARG"
		;;
	6)	STRIP_AMD64=false
		;;
	c)	COMPRESS=true
		;;
	*)	usage
		exit 1
		;;
	esac
done
shift `expr $OPTIND - 1`

if [ $# != 3 ] ; then
	usage
	exit 1
fi

UNPACKED_ROOT="$3"
BASE="`pwd`"
MNT=/tmp/mnt$$
TMR=/tmp/mr$$
LOFIDEV=
MR="$2"

if [ "`dirname $MR`" = . ] ; then
	MR="$BASE/$MR"
fi
if [ "`dirname $UNPACKED_ROOT`" = . ] ; then
	UNPACKED_ROOT="$BASE/$UNPACKED_ROOT"
fi


MEDIA="$MR"

trap cleanup EXIT

case $1 in
	packmedia)
		if [ -d "$UNPACKED_ROOT/kernel/drv/sparcv9" ] ; then
			ARCHIVE=sparc.miniroot
		else
			ARCHIVE=x86.miniroot
		fi
		MR="$MEDIA/boot/$ARCHIVE"
		packmedia "$MEDIA" "$UNPACKED_ROOT"
		pack
		;;
	unpackmedia)
		if [ -f "$MEDIA/boot/sparc.miniroot" ] ; then
			ARCHIVE=sparc.miniroot
		else
			ARCHIVE=x86.miniroot
		fi
		MR="$MEDIA/boot/$ARCHIVE"
		unpack
		unpackmedia "$MEDIA" "$UNPACKED_ROOT"
		;;
	pack)	pack
		;;
	unpack)	unpack
		;;
	*)	usage
		;;
esac
