#!/bin/sh

#
# err exitval message
#	Display message to stderr and exit with exitval.
#
err()
{
	exitval=$1
	shift

	$echo 1>&2 "$0: ERROR: $*"
	exit $exitval
}

#
# warn message
#	Display message to stderr.
#
warn()
{
	$echo 1>&2 "$0: WARNING: $*"
}

#
# log message
#	Display informational message to stdout.
#
log()
{
	[ ! -z "$opt_verbose" ] && $echo "$0: LOG: $*"
}

#
# find_tools
#	Find absolute name for all required tools.
#
find_tools()
{
	for _tool in $1; do
		if [ "X${_tool}" = "Xecho" ]; then
			if [ -x /usr/ucb/echo ]; then
				eval ${_tool}=/usr/ucb/${_tool}
				continue;
			fi
		fi
		for _dir in /bin /sbin /usr/bin /usr/sbin /usr/pkg/bin; do
			if [ -x ${_dir}/${_tool} ]; then
				eval ${_tool}=${_dir}/${_tool}
				break;
			fi
		done

		eval '_tvalue=$'${_tool}
		[ -z "$_tvalue" ] && err 255 "'$_tool' is required but not found."
	done
}

#
# do_prepare <setsdir> <sets> <kerndir> <kern> <archdir>
#	Create architecture-specific layout in <archdir> folder,
#	that is, unpack all required binary sets and kernel images,
#	prepare directory layout etc.
#
do_prepare()
{
	_setsdir="$1"; _sets="$2"; _kerndir="$3"; _kern="$4"; _dir="$5"

	log " + Removing old bits in '$_dir'..."
	$rm -rf $_dir || err 255 "Cannot remove '$_dir' directory."
	$mkdir -p $_dir || err 255 "Cannote create '$_dir' directory."

	log " + Unpacking binary sets:"
	for _set in $_sets; do
		[ ! -f "$_setsdir/$_set" ] && err 255 "Set '$_set' not found."
		log "  . $_setsdir/$_set"
		$gzip -cd "$_setsdir/$_set" | ( cd $_dir && $tar xpf - )
	done

	# XXX hack to avoid permission problem
	[ -d $_dir/var/spool/ftp/hidden ] &&
		rmdir $_dir/var/spool/ftp/hidden

	# /etc/motd will be prepared on union mfs at boot
	$mv $_dir/etc/motd $_dir/etc/motd.tmpl

	# Unpack installation kernel image
	[ ! -f "$_kerndir/$_kern" ] &&
		err 255 "Kernel image '$_kerndir/$_kern' not found."

	log " + Unpacking kernel image: $_kerndir/$_kern"
	$gzip -dc "$_kerndir/$_kern" > $_dir/netbsd
	$chmod 755 $_dir/netbsd

	log " + Fix directory layout"
	_fixdirs="altroot home root var tmp"
	cd $_dir && $mv var altvar &&
		for _fixdir in $_fixdirs; do
			[ ! -d $_fixdir ] && $mkdir $_fixdir
		done
}

#
# prepare_nfsroot <client> <cddir>
#	Create NFS boot layout.
#
prepare_nfsroot()
{
	_client="$1" ; _nfsdir="$2/nfsroot"
	_kernels="vmlinux-nfsroot.gz vmlinux.gz vmlinux_RAQ.gz vmlinux_raq-2800.gz"

	_sets="base.tgz etc.tgz"
	_kern="netbsd-GENERIC.gz"
	_kern_sysinst="netbsd-RAMDISK.gz"

	_setsdir="$_client/cobalt/binary/sets"
	_kerndir="$_client/cobalt/binary/kernel"

	log "Running netboot directory setup..."
	do_prepare "$_setsdir" "$_sets" "$_kerndir" "$_kern" "$_nfsdir"

	log " + Unpacking sysinst kernel: $_kerndir/$_kern_sysinst"
	$gzip -dc "$_kerndir/$_kern_sysinst" > $_dir/netbsd.sysinst
	$chmod 755 $_dir/netbsd.sysinst

	log "Copying release binaries onto the disk..."
	( cd $_client && $tar cpf - cobalt ) |
		( cd "$_nfsdir" && tar xpf - )

	[ -d "$_client/shared/mipsel" ] && {
		( cd $_client && $tar cpf - shared/mipsel ) |
			( cd "$_nfsdir" && tar xpf - )
	}

	[ -d "$_client/shared/ALL" ] && {
		( cd $_client && $tar cpf - shared/ALL ) |
			( cd "$_nfsdir" && tar xpf - )
	}

	log "Copying machine dependent bits..."
	( cd $basedir/data/cobalt && tar cpf - * ) |
		( cd "$_nfsdir" && tar xpf - )

	log "Fixing up bootstrap code..."
	cd "$_nfsdir" && ( $mkdir ./boot &&
				$gzip -9c ./usr/mdec/boot > ./boot/boot.gz)

	cd "$_nfsdir/boot" && 
		for _kernel in $_kernels; do
			$ln -s boot.gz $_kernel
		done
}

#
# prepare_cdroot <server> <cddir> <media>
#	Create infrastructure necessary for the host computer to boot.
#
prepare_cdroot()
{
	_server="$1" ; _rootdir="$2" ; _media="$3"

	_sets="base.tgz etc.tgz"
	_kern="netbsd-GENERIC.gz"

	_setsdir="$_server/i386/binary/sets"
	_kerndir="$_server/i386/binary/kernel"

	log "Creating boot host layout..."
	do_prepare "$_setsdir" "$_sets" "$_kerndir" "$_kern" "$_rootdir"

	_bootfsdir="$_rootdir/$_bootfsdir"
	$mkdir -p $_bootfsdir ||
		err 255 "Cannot create '$_bootfsdir' directory."

	log "Copying machine dependent bits..."
	( cd $basedir/data/i386/common && tar cpf - * ) | \
	    ( cd $_rootdir && tar xpf - )
	if [ "$_media" = "cd" ]; then
		( cd $basedir/data/i386/cd && tar cpf - * ) | \
		    ( cd $_rootdir && tar xpf - )
	fi
	if [ "$_media" = "usb" ]; then
		( cd $basedir/data/i386/usb && tar cpf - * ) | \
		    ( cd $_rootdir && tar xpf - )
	fi

	$cp $_cddir/usr/mdec/boot $_cddir || err 255 "Can't copy bootloader."
}

#
# prepare_cddir <server> <client> <cddir> <filename> <source> <tooldir>
#               <tmpdir> <media>
#	Prepares CD directory suitable for later use with makefs(8).
#
prepare_cddir()
{
	_server="$1"; _client="$2"; _cddir="$3"; _filename="$4"; _source="$5"
	_tooldir="$6"; _tmpdir="$7"; _media="$8"

	prepare_cdroot "$_server" "$_cddir" "$_media"
	prepare_nfsroot "$_client" "$_cddir"

	# This one uses _nfsroot_version defined by the above function.
	prepare_cdinstructions "$_server" "$_client" "$_cddir"
}

#
# get_version <setsdir> <rootdir>
#	Get NetBSD version.
#
get_version()
{
	_set="$1/comp.tgz" ; _root="$2"

	[ ! -f "$_set" ] && err 255 "'$1' not found, can't extract sys/param.h."

	$gzip -cd "$_set" |
	( cd $_root && $tar vxpf - ./usr/include/sys/param.h ) > /dev/null 2>&1

	_param="$_root/usr/include/sys/param.h"
	[ ! -f "$_param" ] &&
		err 255 "'$_param' is missing, can't get OS version."

	_version=`${awk} '
	/^#define[ 	]*__NetBSD_Version__/ { printf $6 }' "$_param"`
	$rm -f $_param
}

#
#
# prepare_cdinstructions <server> <client> <rootdir>
#	Fix NetBSD version information in already installed
#	<cdroot>/root/instructions.txt and <cdroot>/boot.cfg files.
prepare_cdinstructions()
{
	_server="$1" ; _client="$2" ; _rootdir="$3"

	log "Fixing instructions.txt and boot.cfg..."

	get_version "$_server/i386/binary/sets" "$_rootdir"
	_root_version=$_version

	get_version "$_client/cobalt/binary/sets" "$_rootdir/nfsroot"
	_nfsroot_version=$_version

	(
		cd "$_rootdir"/root
		$sed "s/__REL__/$_root_version/; s/__HREL__/$_nfsroot_version/"\
			instructions.txt > instructions.txt.1
		$mv instructions.txt.1 instructions.txt
	)
	(
		cd "$_rootdir"
		$sed "s/__REL__/$_root_version/; s/__HREL__/$_nfsroot_version/"\
			boot.cfg > boot.cfg.1
		$mv boot.cfg.1 boot.cfg
	)
}

#
# create_cd <server> <client> <cddir> <filename> <source> <tooldir>
#           <tmpdir> <media>
#	Store all required binaries in <tmpdir> and build ISO/FFS image.
#
create_cd()
{
	prepare_cddir "$@"

	_server="$1"; _client="$2"; _cddir="$3"; _filename="$4"; _source="$5"
	_tooldir="$6"; _tmpdir="$7"; _media="$8"

	[ -z "$_filename" ] && {
		warn "No CD image name supplied, suppress image build."
		exit 0
	}

	[ -f "$_filename" ] && $rm -f "$opt_filename"

	if [ "$_media" = "cd" ]; then
		log "Making ISO image '$_filename'..."
	fi
	if [ "$_media" = "usb" ]; then
		log "Making FFS image '$_filename'..."
	fi

	_imagedir=`$dirname $_filename`
	[ ! -z "$_imagedir" ] && {
		$mkdir -p $_imagedir ||
			err 255 "Cannot create '$_imagedir' directory."
	}

	_specfile=$_tmpdir/spec
	_specroot=$_tmpdir/spec.root
	_specnfsroot=$_tmpdir/spec.nfsroot
	$rm -f $_specfile $_specroot $_specnfsroot
	# /etc/motd will be generated from /etc/motd.tmpl on union mfs
	# /var is mounted as mfs and files will be copied from /altvar
	$cat $_cddir/etc/mtree/set.base \
	     $_cddir/etc/mtree/set.etc | \
	     $sed -e 's,^\./etc/motd,\./etc/motd.tmpl,' \
	          -e 's,^\./var,./altvar,' \
	          -e 's/ size=[0-9]*//' > $_specroot
	$cat $_cddir/nfsroot/etc/mtree/set.base \
	     $_cddir/nfsroot/etc/mtree/set.etc | \
	     $sed -e 's,^\./etc/motd,\./etc/motd.tmpl,' \
	          -e 's,^\./var,./altvar,' \
	          -e 's,^\./,./nfsroot/,' \
	          -e 's/ size=[0-9]*//' > $_specnfsroot
	$cat $_cddir/etc/mtree/set.base | \
	$cat $_specroot $basedir/data/common/spec.root \
	    $_specnfsroot $basedir/data/common/spec.nfsroot > $_specfile

	if [ "$_media" = "cd" ]; then
		_bootxx=$_cddir/usr/mdec/bootxx_cd9660
		"$_tooldir"/bin/nbmakefs -t cd9660 \
		    -F $_specfile -N $_cddir/etc -o \
		    rockridge,label=restorecd,bootimage=i386\;${_bootxx},no-emul-boot,allow-multidot \
		    "$_filename".tmp $_cddir && \
		    mv "$_filename".tmp "$_filename"
		log "Building restorecd complete."
	fi
	if [ "$_media" = "usb" ]; then
		FSSIZE=`expr 512 \* 1000 \* 1000`
		FSSECTORS=`expr ${FSSIZE} / 512`
		HEADS=64
		SECTORS=32
		FSCYLINDERS=`expr ${FSSECTORS} / \( ${HEADS} \* ${SECTORS} \)`
		FRAGSIZE=2048
		BLOCKSIZE=16384
		DENSITY=8192
		_bootxx=$_cddir/usr/mdec/bootxx_ffsv1
		_labelproto=$_tmpdir/label.proto
		"$_tooldir"/bin/nbmakefs -t ffs \
		    -M ${FSSIZE} -m ${FSSIZE} \
		    -F $_specfile -N $_cddir/etc \
		    -o bsize=${BLOCKSIZE},fsize=${FRAGSIZE},density=${DENSITY} \
		    "$_filename".tmp $_cddir
		"$_tooldir"/bin/nbinstallboot -v -m i386 \
		    "$_filename".tmp $_bootxx
		log "Preparing disklabel..."
		cat > $_labelproto <<EOF
type: ESDI
disk: restoreUSB
label: 
flags:
bytes/sector: 512
sectors/track: ${SECTORS}
tracks/cylinder: ${HEADS}
sectors/cylinder: `expr ${HEADS} \* ${SECTORS}`
cylinders: ${FSCYLINDERS}
total sectors: ${FSSECTORS}
rpm: 3600
interleave: 1
trackskew: 0
cylinderskew: 0
headswitch: 0           # microseconds
track-to-track seek: 0  # microseconds
drivedata: 0 

8 partitions:
#        size    offset     fstype [fsize bsize cpg/sgs]
a:    ${FSSECTORS} 0 4.2BSD ${FRAGSIZE} ${BLOCKSIZE} 128
c:    ${FSSECTORS} 0 unused 0 0
d:    ${FSSECTORS} 0 unused 0 0
EOF
		# XXX we have not built tools nbdisklabel-i386 here,
		# XXX but cobalt has a compatible label...
		"$_tooldir"/bin/nbdisklabel-cobalt -R -F "$_filename".tmp $_labelproto
		mv "$_filename".tmp "$_filename"
		log "Building restoreusb complete."

	fi
}

#
# usage
#	Print help screen and quit.
#
usage()
{
	$echo "Usage:"
	$echo "`$basename $0` <parameter>=<value> [<parameter>=<value> ...]"
	$echo ""
	$echo "Obligatory parameters:"
	$echo "    server       Directory containing release(7) layout"
	$echo "                 for boot server"
	$echo "    client       Directory containing release(7) layout"
	$echo "                 for client to netboot"
	$echo "    source       NetBSD sources which were used to build all"
	$echo "                 of the above"
	$echo "    tooldir      Path to tools (including makefs(1) etc.)"
	$echo "                 of the above"
	$echo "    media        Target restoreCD/USB media (\"cd\" or \"usb\")"
	$echo ""
	$echo "Optional parameters:"
	$echo "    tmpdir       Place to keep CD/USB data"
	$echo "    filename     CD/USB image name"
	$echo ""
	exit 1
}

#
# get_opts
#	Analyse script parameters.
#
get_opts()
{
	# Defaults
	opt_verbose=

	# Command line
	while [ $# -gt 0 ]; do
		case $1 in
		*=*)
			eval "opt_$1"; shift ;;
		-v)
			opt_verbose=1; shift ;;
		-h|-help|--help)
			usage ;;
		*)
			shift ;;
		esac
	done
}

#
# bootstrap
#	Sets up script's execution environment.
#
bootstrap()
{
	find_tools "rm cp mv ln tar mkdir echo dirname pwd basename"
	find_tools "gzip chmod awk sed dd cat sh"

	# Define basedir very early to make it available to the rest of the
	# script
	_dirname=`$dirname $0`
	basedir=`cd $_dirname && $pwd`

	get_opts "$@"

	[ -z "$opt_server" ] && usage
	[ -z "$opt_client" ] && usage
	[ -z "$opt_source" ] && usage
	[ -z "$opt_tooldir" ] && usage
	[ "$opt_media" != "cd" -a "$opt_media" != "usb" ]  && usage

	[ -z "$opt_tmpdir" -a "$opt_media" = "cd" ] && \
		opt_tmpdir="$basedir/cd.tmp"
	[ -z "$opt_tmpdir" -a "$opt_media" = "usb" ] && \
		opt_tmpdir="$basedir/usb.tmp"
	[ -z "$opt_indir" -a "$opt_media" = "cd" ] && \
		opt_indir="$opt_tmpdir/restorecd"
	[ -z "$opt_indir" -a "$opt_media" = "usb" ] && \
		opt_indir="$opt_tmpdir/restoreusb"
	[ -z "$opt_outdir" ] && opt_outdir="$opt_tmpdir"

	[ -z "$opt_filename" -a "$opt_media" = "cd" ] && \
		opt_filename="$opt_outdir/restorecd.iso"
	[ -z "$opt_filename" -a "$opt_media" = "usb" ] && \
		opt_filename="$opt_outdir/restoreusb.img"
}

main()
{
	bootstrap "$@"
	create_cd "$opt_server" "$opt_client" "$opt_indir" "$opt_filename" \
		  "$opt_source" "$opt_tooldir" "$opt_tmpdir" "$opt_media"

	exit 0
}

main "$@"
