#!/bin/ksh -p
#
# nbld-saferoot - second phase bootstrap: miniroot => safe miniroot
#
##############################################################################

#
# Copyright (C) 2005-2008 Nexenta Systems, Inc.
# All rights reserved.
#
##############################################################################

test ! -f /etc/nbld.cf && echo "Error: can't find /etc/nbld.cf" && exit 1
. /etc/nbld.cf

PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/ucb:/usr/ccs/bin export PATH
umask 0222

#
# CUSTOM_DIR specifies the location where customized LiveCD
# files are located at; it defaults to ${WORKING_COPY}/livecd.
# By default CUSTOM_DIR is set to /usr/lib/nbld and can
# be overridden if necessary.
#
CUSTOM_DIR="$NBLD_LIBDIR"

#
# VOLID specifies the CD-ROM volume ID of the LiveCD;
# by default it is set to "Elatte_LiveCD".
#
VOLID=${VOLID:=Elatte_LiveCD}

FAKEROOT="$NBLD_MINIROOT"

NODENAME=nexenta_safemode

#
# The following variables are used internally and should
# not be set by the user under normal circumstances.
#
SYSIDTOOL_XML=${CUSTOM_DIR}/nexenta-sysidtool.xml
SYSIDTOOL_NET=${CUSTOM_DIR}/nexenta-sysidtool-net
SYSIDTOOL_SYSTEM=${CUSTOM_DIR}/nexenta-sysidtool-system
SMF_PROFILE=${CUSTOM_DIR}/nexenta_livecd.xml
MINI_FILELIST=${CUSTOM_DIR}/filelist_saferoot.txt
CD_DIRLIST=.cd_dirlist
DHCP_EVENTHOOK=${CUSTOM_DIR}/eventhook
MINIROOT_STAGING=${NBLD_TMP}/miniroot_staging.tmp
RAMDISK_FILE=${NBLD_TMP}/miniroot.$$
RAMDISK_MNT=${NBLD_TMP}/miniroot_mount.$$
REPO_FILE=${NBLD_TMP}/repository.db.$$
TMP_FILE=${NBLD_TMP}/tmp_file.$$
CPIO=/usr/sun/bin/cpio

function aborted
{
	cleanup
	echo "Exiting."
	exit 0
}

trap 'aborted' INT HUP EXIT

function fatal_error
{
	cleanup
	echo
	echo "Fatal error."
	exit 1
}

function cleanup
{
	rm -f ${REPO_FILE} 2> /dev/null
	umount -f ${RAMDISK_MNT} 2>/dev/null
	lofiadm -d ${RAMDISK_FILE} 2>/dev/null
	rm -Rf ${RAMDISK_FILE} ${RAMDISK_FILE}.gz ${RAMDISK_MNT} 2> /dev/null
	rm -f ${TMP_FILE} 2> /dev/null
	rm -Rf ${MINIROOT_STAGING} 2> /dev/null
}

function create_dir
{
	dir=$1
	mode=$2
	owner=$3

	mkdir -p ${dir} || fatal_error
	chmod ${mode} ${dir} || fatal_error
	chown ${owner} ${dir} || fatal_error
}

function copy_file
{
	src=$1
	dst=$2
	mode=$3
	owner=$4

	cp ${src} ${dst} || fatal_error
	chmod ${mode} ${dst} || fatal_error
	chown ${owner} ${dst} || fatal_error
}

function bootstrap_repository
{
	# Grab the list of all manifests under var/svc/manifest
	alt_root=$1

	echo "  bootstrapping smf repository \c"

	# inetd-upgrade needs to be removed so to avoid dependencies
	# failures show up on svcs -vx command.
	rm -f ${alt_root}/lib/svc/manifest/network/inetd-upgrade.xml

	manifest_list=`find ${alt_root}/lib/svc/manifest/* \
	    -type f -name "*.xml" -print`

	set -- ${manifest_list}
        backup=`echo "[$#/$#] ... " | sed 's/.//g'`
        fwidth=`echo "$#\c" | wc -c`

	CONFIGD=/lib/svc/bin/svc.configd
	SVCCFG=/usr/sbin/svccfg
	DTD=${FAKEROOT}/usr/share/lib/xml/dtd/service_bundle.dtd.1
	SVCENV="NLU_ENABLED=1 LD_NOAUXFLTR=1 LD_LIBRARY_PATH=/tmp/nlulib \
		LD_LIBRARY_PATH_64=/tmp/nlulib/64 SVCCFG_CHECKHASH=1 \
		PKG_INSTALL_ROOT=${alt_root} SVCCFG_DTD=${DTD} \
		SVCCFG_REPOSITORY=${REPO_FILE} SVCCFG_CONFIGD_PATH=${CONFIGD}"

	# Create the repository with smf/manifest property
	eval "${SVCENV} ${SVCCFG} add smf/manifest"

	i=1; n=$#
	while [ $# -gt 0 ]; do
		printf "[%${fwidth}s/%${fwidth}s] ... " $i $n

		# Import manifests into the repository
		eval "${SVCENV} ${SVCCFG} import $1"

		i=`expr $i + 1`
		shift
		if [ $# -ne 0 ]; then
			echo "${backup}\c"
		fi
	done

	plat=`uname -i`

	cd ${alt_root}/etc/svc/profile
	rm -f inetd_services.xml
	ln -s inetd_generic.xml inetd_services.xml

	rm -f name_service.xml
	ln -s ns_dns.xml name_service.xml

	rm -f platform.xml
	ln -s platform_none.xml platform.xml

	rm -f generic.xml
	ln -s generic_limited_net.xml generic.xml
	sed -i -e "s/\(vt[0-9].*enabled=.\)true/\1false/" generic.xml
	sed -i -e "/system\/coreadm/{n; s/true/false/}" generic.xml
	cd ${OLDPWD}

	eval "${SVCENV} ${SVCCFG} apply ${alt_root}/etc/svc/profile/generic.xml"

	eval "${SVCENV} ${SVCCFG} apply ${alt_root}/etc/svc/profile/platform.xml"

	# Apply LiveCD profile
	eval "${SVCENV} ${SVCCFG} apply ${SMF_PROFILE}"

	# Store the repository under etc/svc/repository.db
	chown root:sys ${REPO_FILE}
	mv ${REPO_FILE} ${alt_root}/etc/svc/repository.db

	echo "done"
}

function customize_mini
{
	oldpwd=${PWD}

	echo "  customizing mini stage ... \c"

	# This is where the LiveCD gets mounted at
	mkdir $1/.livecd
	chown root:sys $1/.livecd

	echo "${VOLID}" > $1/.volid
	chown root:sys $1/.volid

	# We need device reconfiguration
	touch $1/reconfigure

	# Tell NFS4 to not prompt us for default domain
	touch $1/etc/.NFS4inst_state.domain

	# Prepare system to use DNS
	if [ -f $1/etc/resolv.conf ]; then
		rm $1/etc/resolv.conf
	fi
	touch $1/etc/resolv.conf
	cp $1/etc/nsswitch.dns $1/etc/nsswitch.conf
	copy_file ${DHCP_EVENTHOOK} \
	    $1/etc/dhcp/`basename ${DHCP_EVENTHOOK}` 0755 root:sys

	# Bootstrap /etc/hosts entry
	node_fqdn="${NODENAME}.localdomain"
	hosts_entry="127.0.0.1\t${NODENAME}\t${node_fqdn}\tloghost"
	echo "${hosts_entry}" >> $1/etc/hosts

	echo "${NODENAME}" > $1/etc/nodename
	chmod 0644 $1/etc/nodename
	chown root:root $1/etc/nodename

	echo "setprop prealloc-chunk-size 0x2000" >> $1/boot/solaris/bootenv.rc

	# Make sure root special device is known at boot time
	echo "/devices/ramdisk:a\t-\t/\t\tufs\t1\tno\trw" >> $1/etc/vfstab

	# Get us as much swap space as possible
	echo "set tmpfs:tmpfs_minfree=1" >> $1/etc/system
	echo "set swapfs_minfree=0x40" >> $1/etc/system

	# Copy over customized SMF manifests and methods
	copy_file ${SYSIDTOOL_XML} \
	    $1/lib/svc/manifest/system/`basename ${SYSIDTOOL_XML}` 0444 root:sys

	copy_file ${SYSIDTOOL_NET} \
	    $1/lib/svc/method/`basename ${SYSIDTOOL_NET}` 0555 root:bin

	copy_file ${SYSIDTOOL_SYSTEM} \
	    $1/lib/svc/method/`basename ${SYSIDTOOL_SYSTEM}` 0555 root:bin

	# Set root password to "empty"
	sed 's/^root:/root:moAdagpFPw1iE/g' $1/etc/shadow > ${TMP_FILE}
	copy_file ${TMP_FILE} $1/etc/shadow 0400 root:sys
	rm ${TMP_FILE}

	# Set root shell to /bin/bash
	sed 's/sbin\/sh/bin\/bash/g' $1/etc/passwd > ${TMP_FILE}
	copy_file ${TMP_FILE} $1/etc/passwd 0644 root:sys
	rm ${TMP_FILE}

	# Allow root login via SSH
	if [ -f $1/etc/ssh/sshd_config ]; then
		sed '/^PermitRootLogin/ s/no/yes/g' \
		    $1/etc/ssh/sshd_config > ${TMP_FILE}
		copy_file ${TMP_FILE} $1/etc/ssh/sshd_config 0644 root:sys
		rm ${TMP_FILE}
	fi

	# Allow root login via telnet
	sed -e 's/^CONSOLE/#CONSOLE/' $1/etc/default/login > ${TMP_FILE}
	copy_file ${TMP_FILE} $1/etc/default/login 0644 root:sys
	rm ${TMP_FILE}

	# Hard-code timezone to US/Pacific
	echo "zone_info=US/Pacific" > $1/etc/rtc_config
	echo "zone_lag=28800" >> $1/etc/rtc_config
	sed -e 's/PST8PDT/US\/Pacific/' < $1/etc/default/init > ${TMP_FILE}
	cp ${TMP_FILE} $1/etc/default/init
	rm ${TMP_FILE}

	#
	# Work around GNU's "uname -S" problem; we use /bin/hostname
	# to set machine's hostname instead.  We should probably fix
	# GNU's uname at some point.
	#
	sed -e 's/sbin\/uname\ -S/bin\/hostname/' < \
	    $1/lib/svc/method/identity-node > ${TMP_FILE}
	cp ${TMP_FILE} $1/lib/svc/method/identity-node
	rm ${TMP_FILE}
	echo "done"

	#
	# Import all manifests at this time so that SMF doesn't
	# re-import them during boot (this saves us some time).
	#
	bootstrap_repository $1

	# clean up NLU environment on target, but keep on source
	rm -rf $1/tmp/*

	cd ${oldpwd}
}

function create_mini
{
	exclude64="-type d -name amd64 -prune -o -type d -name i86xpv -prune -o"

	echo "Creating miniroot (excluding 64-bit):"

	# skip commented, blank lines and excluded entries
	filelist=`egrep -v -e '^[ \t]*#|^[ \t]*$|^[ \t]*!' ${MINI_FILELIST}`

	set -- ${filelist}
        backup=`echo "[$#/$#] ... " | sed 's/.//g'`
        fwidth=`echo "$#\c" | wc -c`

	mkdir -p ${MINIROOT_STAGING} || fatal_error

	# Copy over contents of Debian miniroot into miniroot staging area
	echo "  populating miniroot staging area \c"
 	cd ${FAKEROOT}

	i=1; n=$#
	while [ $# -gt 0 ]; do
		# Do the actual copy (skip SVN directories)
		printf "[%${fwidth}s/%${fwidth}s] ... " $i $n
		find $1 ${exclude64} -print | \
		    ${CPIO} -pdum ${MINIROOT_STAGING} 2> /dev/null
		i=`expr $i + 1`
		shift
		if [ $# -ne 0 ]; then
			echo "${backup}\c"
		fi
	done

	chown -R root:sys ${MINIROOT_STAGING}/etc/init.d

	# now take care of any excluded entries
	egrep -e '^[ \t]*!' ${MINI_FILELIST} | sed -e "s/\!//g" | \
	nawk '{ print $1 }' | while read dirname; do
		if [ -e ${dirname} ]; then
			echo "${dirname}" >> ${MINIROOT_STAGING}/${CD_DIRLIST}
			rm -Rf ${MINIROOT_STAGING}/${dirname} 2> /dev/null
		fi
	done

	# Save this for later use
	mkdir -p ${FAKEROOT}/${MINIROOT_STAGING}
	cp ${MINIROOT_STAGING}/${CD_DIRLIST} \
	    ${FAKEROOT}/${MINIROOT_STAGING}/${CD_DIRLIST}
	echo "done"

	# Customize miniroot
	customize_mini ${MINIROOT_STAGING}

	echo "  preparing ramdisk image ... \c"

	total_size=0
	# Find out the size of miniroot staging area
	for file in ${filelist}
	do
		du -sk ${MINIROOT_STAGING}/${file} | read size name
		((total_size += size))
	done

	# Add some extra space for our root filesystem
	(( total_size += total_size * 15 / 100 ))

	# Create a UFS image file and mount it using lofs
 	mkfile ${total_size}k ${RAMDISK_FILE}
	if [ $? -ne 0 ]; then
		echo "Cannot create UFS image (${total_size} KB)."
		fatal_error
	fi
 	lofidev=`lofiadm -a ${RAMDISK_FILE}`
	if [ $? -ne 0 ]; then
		echo "Cannot mount UFS image via loopback file system."
		fatal_error
	fi
 	newfs ${lofidev} < /dev/null 2> /dev/null
	if [ $? -ne 0 ]; then
		echo "Cannot create UFS file system on ${lofidev}."
		fatal_error
	fi
 	mkdir ${RAMDISK_MNT}
 	mount -o nologging ${lofidev} ${RAMDISK_MNT}

	echo "done (${total_size} KB, uncompressed)"

	# Copy over contents of miniroot staging area into ramdisk image
	echo "  populating ramdisk image using ${MINIROOT_STAGING} ... \c"
 	cd ${MINIROOT_STAGING}

	find . -print | ${CPIO} -pdum ${RAMDISK_MNT} 2> /dev/null

	mkdir -p ${RAMDISK_MNT}/root
	chown root:root ${RAMDISK_MNT}/root

	if test -f ${FAKEROOT}/var/lib/dpkg/alien/nexenta-sunw/ld32; then
		cp ${FAKEROOT}/var/lib/dpkg/alien/nexenta-sunw/ld32 ${RAMDISK_MNT}/lib/ld.so.1
		test -d ${RAMDISK_MNT}/root/lib/amd64 && \
			cp ${FAKEROOT}/var/lib/dpkg/alien/nexenta-sunw/ld64 ${RAMDISK_MNT}/lib/amd64/ld.so.1
	fi

	rm -f ${RAMDISK_MNT}/root/.screenrc
	touch ${RAMDISK_MNT}/.nexenta-first-start

	echo "done"
	cd ${OLDPWD}

	# Blow away temporary resources
 	umount ${RAMDISK_MNT}
 	lofiadm -d ${RAMDISK_FILE}
 	rm -Rf ${RAMDISK_MNT}

	echo "  compressing ramdisk image ... \c"

	# Compress miniroot image
 	gzip -c ${RAMDISK_FILE} > ${OUT_DIR}/x86.miniroot-safe
	du -sk ${OUT_DIR}/x86.miniroot-safe | read size name

	echo "done (${size} KB, compressed)"
}

#
# Certain operations such as lofiadm, svccfg, etc. require root
# privileges.  Therefore bail out if this script is executed by
# non-root user.
#
# NOTE: It may be possible to get around this by setting up
# privileges for non-root users to do those operations, but
# until then we need to be root.  This is risky!
#
if [ `whoami` != "root" ]; then
	echo "Insufficient privileges to run $0"
	exit 1
fi

if [ "x$1" = "x" ]; then
	echo "Error: output directory not specified"
	exit 1
fi
OUT_DIR="$1"

# Make sure that essential directories are present
if [ ! -d ${OUT_DIR} ]; then
	echo "Error: ${OUT_DIR} does not exists"
	exit 1
elif [ ! -d ${CUSTOM_DIR} ]; then
	echo "Error: ${CUSTOM_DIR} does not exist"
	exit 1
elif [ ! -d ${FAKEROOT} ]; then
	echo "Error: ${FAKEROOT} does not exist"
	exit 1
fi

cat << EOF
==============================================================================
Custom:    ${CUSTOM_DIR}
Fakeroot:  ${FAKEROOT}
==============================================================================
EOF

# Step 1: Cleanup
echo "Cleaning up staging directory ... \c"

# Blow away existing staging area
if [ -d ${MINIROOT_STAGING} ]; then
	rm -Rf ${MINIROOT_STAGING}
fi
echo "done"

# Step 2: Create miniroot
create_mini

# Step 3: Cleanup and we're done
cleanup
exit 0
