#!/bin/bash
#/
#/ \file
#/
#/ \brief   installation procedure for gomtuu-linux
#/
#/ Installation Procedure Break-down:
#/
#/   1. Read config
#/   2. Check Software Set(s) availability
#/   3. Pre-installation:
#/      - Create disk partitions
#/      - Activate swap
#/      - Mount filesystems
#/   4. Installing Software Sets:
#/      - Retrieve Software Set(s)
#/      - Install Software Set(s)
#/   5. Post-installation:
#/      - <tt>/etc/fstab</tt>
#/      - <tt>/etc/passwd</tt>
#/      - <tt>/etc/if.d/\<\e interface\></tt>
#/      - <tt>/etc/svc.d/\<\e service\></tt>
#/      - ...
#/      - Install bootloader
#/      - Package integration tasks
#/      - Install first-boot script
#/   6. Reboot
#/
#/ \returns
#/ Only returns if the installation failed in which case the exit code is 1.
#/
#/ \author  Arthur Rinkel
#/
#/ \date    May 3, 2014
#/
#/ \todo
#/ Directory structure of package integration tasks should be defined in
#/ file format of Software Set images.
#/

set +o histexpand

shopt -s sourcepath

. paths.sh
. common.sh
. config.sh
. sysinv.sh
. disk_int.sh
. net_int.sh
. swset_int.sh
. sysdef_int.sh

readonly BASENAME=`basename $0`
readonly VERSION="0.1"

readonly HINT_DIR_SUFFIX=".gomtuu-installer" # extra level for "hint directory"

#-------------------------------------------------------------------------------

#/
#/ Output script usage and exit.
#/
#/ \returns
#/ Always exit code 1.
#/
usage()
{
	cat <<_EOF

Usage: $BASENAME [OPTIONS] [CONFIG_FILE]

Perform installation procedure to install Gomtuu Linux on an Alpha system. The
script takes as input a configuration file which determines how and what will
be installed on the system. By default the script will look for a configuration
file by the name of "$CONFIG_FILE.<target_serial_number>" or, as a last resort,
the file "$CONFIG_FILE.default".

OPTIONS

  -t        show commands but don't execute (dry run)
  -D        show debug messages
  -v        be verbose
  -h        show this help and exit
  -V        show version and exit
_EOF

	exit 1
}

#/
#/ Output script version and exit.
#/
#/ \returns
#/ Always exit code 1.
#/ 
version()
{
	cat <<_EOF
$BASENAME $VERSION
_EOF

	exit 1
}

#/
#/ Output intro screen along with a concise system overview.
#/
welcome()
{
	mhdr "GOMTUU INSTALLER"

	cat <<_EOF
Welcome to the Gomtuu installation program. This program will install Linux
on your Alpha system using the configuration from the following file:

  ${CF:-<missing>}

Your Alpha has been identified as follows:

System Model  : ${INV_SYSTEM:-<unknown>}
Serial Number : ${TARGET_SN:-<unknown>}
CPU           : ${INV_PROC:-<unknown>}
Num. of CPUs  : ${INV_NPROC:-<unknown>}
System Memory : ${INV_MEM:-<unknown>} MB

Disk Devices  :

  ${INV_DISKS:-<none detected>}


Installation begins...

_EOF
}

#/
#/ Start installation. This function calls various sub-routines to perform the
#/ complete installation of the target system. Any error that may occur which
#/ endangers a successful installation will cause the Install program to abort
#/ immediately.
#/
#/ \param[in] $1  Configuration file.
#/
installation()
{
	local cf=`trim "$1"`
	local rtdir=`trim "$RUNTIMEDIR"`
	local lst=""

	# these functions may abort the script
	read_config "$cf" || abort
	msg "Using configuration file $cf\n"

	msg "Checking Software Set source..."
	lst=`check_swset_source "$rtdir"`

	pre_install "$NEWSYS_MPNT" "$rtdir"
	system_install "$NEWSYS_MPNT" "$rtdir/$lst"
	post_install "$NEWSYS_MPNT" "$rtdir" "$cf"
}

#/
#/ Verify Software Set source and get List file. If successful, a copy of the
#/ List file is stored on the local filesystem and the name of the List file is
#/ echo'ed to stdout (i.e., only the filename, not the full path). The function
#/ aborts the Install program in case an error occurred (exit code is 1).
#/
#/ \param[in] $1  Location for List file.
#/
check_swset_source()
{
	local dst=`trim "$1"`
	local lst=""		# List file (filename only)

	validate_swset_src "$SWSET_SRC"
	if test $? -eq 0; then
		lst=`get_swlist_file "$SWSET_SRC" "$dst"`
		if test $? -eq 0; then
			echo $lst
		else
			abort "Could not retrieve List file: $lst"
		fi
	else
		abort "Invalid Software Set source: $SWSET_SRC"
	fi
}

#/
#/ Make preparations for the installation. Sub-tasks during this phase of the
#/ installation cannot fail else the Install program will abort immediately.
#/
#/ \param[in] $1  Mountpoint of target system. The directory is created if it
#/                didn't already exist.
#/ \param[in] $2  Runtime directory for Installer.
#/
pre_install()
{
	local mpnt=`trim "$1"`
	local rtdir=`trim "$2"`
	local i_fstab="$rtdir/fstab"	# preliminary fstab file

	mhdr "PRE-INSTALLATION"

	$DRY_RUN mkdirhier "$mpnt" || abort "No mountpoint for target system."

	do_partitioning
	if test $? -ne 0; then
		abort "Disk partitioning failed."
	fi

	create_fstab "$i_fstab"
	if test $? -ne 0; then
		abort "Could not create preliminary fstab file: $i_fstab"
	fi

	activate_swap "$i_fstab"
	if test $? -ne 0; then
		msg "Swap space not available, continuing without it."
	fi

	create_local_filesystems "$i_fstab"
	if test $? -ne 0; then
		abort "Failed creating one or more filesystems."
	fi

	mount_filesystems "$i_fstab" "$mpnt"
	if test $? -ne 0; then
		abort "Failed mounting one or more filesystems."
	fi
}

#/
#/ Install requested Software Sets on target system. Any error preventing a
#/ Software Set from being installed will trigger an abort().
#/
#/ \param[in] $1  Mountpoint of target system.
#/ \param[in] $2  Full pathname to Software Set List file.
#/
system_install()
{
	local mpnt=`trim "$1"`
	local lst=`trim "$2"`
	local swsets=""
	local swset=""
	local swset_dir=""	# Definitive Software Set directory
	local swset_dir_hint=""	# Possible Software Set directory
	local swset_pn=""	# Software Set pathname
	local -i cnt=0		# Software Set count
	local -i total=0	# Total count

	mhdr "INSTALLATION"

	[ -d "$mpnt" ] || abort "Target system mountpoint not found: $mpnt"
	[ -f "$lst"  ] || abort "Software Set List file not found: $lst"

	#
	# Determine "hint directory" for storing a downloaded Software Set.
	# The partition of the target system with the most space is chosen,
	# so we run an elaborate pipeline of commands to figure out which
	# mount point has the most space available. Additionally, we add
	# a sub-directory to this mount point so chances are slim the
	# "hint directory" will collide with a directory from the Software
	# Set when it's extracted.
	#
	swset_dir_hint=`df -lh | grep "$mpnt" | tr -s ' ' '\t' | \
			sort -h -k4 | tail -n1 | cut -f6`
	swset_dir_hint="$swset_dir_hint/$HINT_DIR_SUFFIX"

	swsets=`get_requested_swset_names`
	total=`echo $swsets | wc -w 2>/dev/null`
	if test $total -gt 0; then
		msg "$total Software Set(s) to install:"
		for swset in $swsets; do echo "  $swset"; done

		swset_src=`get_swset_src`
		msg "Using Software Set source at $swset_src"

		cnt=1
		for swset in $swsets; do
			msg "Retrieving Software Set: $swset"

			swset_dir=`fetch_swset_file "$lst" "$swset" "$swset_dir_hint"`
			if test $? -eq 0; then
				swset_pn="$swset_dir/$swset"
				msg "Software Set locally accessible:"
				msg " $swset_pn"
			else
				abort "Software Set not available."
			fi

			msg "Installing $swset ($cnt/$total)"

			install_swset "$swset_pn" "$mpnt"
			if test $? -eq 0; then
				msg "Done"
				let cnt++
			else
				abort "Software Set failed to install."
			fi

			#
			# Usage of the "hint directory" indicates the Software
			# Set was copied and so we can remove the copy when
			# we're done installing it.
			#
			if test "$swset_dir" = "$swset_dir_hint"; then
				$DRY_RUN rm -f "$swset_pn"
			fi
		done
		# remove "hint directory" when we're done installing:
		if test -d "$swset_dir_hint"; then
			$DRY_RUN rm -fr "$swset_dir_hint"
		fi
	else
		abort "No Software Sets to install."
	fi
}

#/
#/ Wrap-up installation of the system. Sub-tasks during this phase of the
#/ installation cannot fail else the Install program will abort immediately.
#/
#/ \param[in] $1  Mountpoint of target system.
#/ \param[in] $2  Runtime directory for Installer.
#/ \param[in] $3  Configuration file.
#/
post_install()
{
	local mpnt=`trim "$1"`
	local rtdir=`trim "$2"`
	local cf=`trim "$3"`

	mhdr "POST-INSTALLATION"

	[ -d "$mpnt" ] || abort "Target system mountpoint not found: $mpnt"

	install_fstab "$rtdir/fstab" "$mpnt"
	if test $? -ne 0; then
		abort "Failed installing fstab file on target system."
	fi

	install_passwd "/etc/passwd" "$mpnt"
	if test $? -ne 0; then
		abort "Failed installing passwd file on target system."
	fi

	install_bootloader "$mpnt"
	if test $? -ne 0; then
		abort "Could not install bootloader for target system."
	fi

	package_integration_tasks "$mpnt" "$cf"
	if test $? -ne 0; then
		abort "One or more package integration tasks failed."
	fi

	install_first_boot_script "$mpnt"
	if test $? -ne 0; then
		abort "Failed installing first-boot procedure."
	fi
}

#/
#/ Layout disk structures on the target disk(s).
#/
#/ \returns
#/ \li Code 0: Partitioning successful
#/ \li Code 1: Invalid configuration or partitioning failed on one or more disks
#/
do_partitioning()
{
	local devs=""
	local dev=""
	local devpath=""

	msg "Partitioning target disk(s)..."

	devs=`get_disk_devices`
	if test -z "$devs"; then
		msg "No devices defined."
		return 1
	fi

	for dev in $devs; do
		devpath=`get_device_path "$dev"`
		if test $? -ne 0; then
			msg "Invalid or unknown device name: $dev"
			return 1
		fi
		msg " device: $dev ($devpath)"

		partition_disk "$devpath"
		if test $? -ne 0; then
			msg "Error partitioning $dev. Check configuration or disk."
			return 1
		fi
	done

	return 0
}

#/
#/ Initialize and enable swap space.
#/
#/ \param[in] $1  Full path to preliminary \c fstab file. This file holds
#/                definitions for swap devices and local filesystems of the
#/                target system.
#/
#/ \returns
#/ \li Code 0: Swap space activated
#/ \li Code 1: No swap space defined or failed to activate
#/
activate_swap()
{
	local fstab=`trim "$1"`
	local devs=""
	local dev=""
	local devpath=""

	msg "Creating swap space..."

	[ -f "$fstab" ] || { msg "fstab file not found: $fstab"; return 1; }

	# get swap device(s) from fstab file:
	devs=`grep -v '^[ \t]*#' <"$fstab" | grep -w swap | tr -s ' ' '\t' | \
		cut -f1`
	if test -z "$devs"; then
		msg "No swap space definitions found."
		return 1
	fi

	for devpath in $devs; do
		dev=${devpath##*/}
		msg " device: $dev ($devpath)"

		$DRY_RUN mkswap -c "$devpath" >/dev/null
		if test $? -eq 0; then
			msg "Activating swap space on device $dev..."
			
			$DRY_RUN swapon "$devpath" >/dev/null
			if test $? -ne 0; then
				msg "Failed activating swap space on $dev."
				return 1
			fi
		else
			msg "Could not create swap space on $dev."
			return 1
		fi
	done

	return 0
}

#/
#/ Initialize filesystem(s) on the target system's disk(s).
#/
#/ \param[in] $1  Full path to a preliminary \c fstab file. This file holds
#/                definitions for swap devices and local filesystems of the
#/                target system.
#/
#/ \returns
#/ \li Code 0: All defined filesystems created
#/ \li Code 1: No filesystems defined or failed to create
#/
create_local_filesystems()
{
	local fstab=`trim "$1"`
	local devs=""
	local dev=""
	local fstype=""

	msg "Creating local filesystems..."

	[ -f "$fstab" ] || { msg "fstab file not found: $fstab"; return 1; }

	#
	# Get local filesystem(s) from fstab file. The list is sorted on the
	# mountpoint field (i.e., deep mountpoints sort after undeep ones).
	#
	devs=`grep -v '^[ \t]*#' <"$fstab" | grep -w 'ext\(2\|3\)' | \
		 tr -s ' ' '\t' | sort -k2 | cut -f1`
	if test -z "$devs"; then
		msg "No ext2 or ext3 filesystems found in $fstab."
		return 1
	fi
	#
	# Create filesystem on each device.
	#
	for dev in $devs; do
		fstype=`grep -w "$dev" <"$fstab" | tr -s ' ' '\t' | cut -f3`

		msg "Creating filesystem ($fstype) on device $dev..."

		$DRY_RUN mkfs -c -q -t "$fstype" "$dev" 2>/dev/null
		if test $? -ne 0; then
			msg "Failed creating filesystem on $dev."
			return 1
		fi
	done

	return 0
}

#/
#/ Make filesystem(s) of target system available.
#/
#/ \param[in] $1  Full path to preliminary \c fstab file. This file holds
#/                definitions for swap devices and local filesystems of the
#/                target system.
#/ \param[in] $2  Mountpoint for mounting filesystem(s) of target system
#/                (e.g., <tt>/mnt</tt>).
#/
#/ \returns
#/ \li Code 0: All defined filesystems mounted
#/ \li Code 1: No filesystems defined or mount failed
#/
mount_filesystems()
{
	local fstab=`trim "$1"`
	local mprefix=`trim "$2"`
	local devs=""
	local dev=""
	local fstype_opt=""
	local mpnt=""
	local mopts_opt=""
	local -i ret=0
	local -a mspec

	msg "Mounting local filesystems..."

	[ -f "$fstab"   ] || { msg "fstab file not found: $fstab"; return 1; }
	[ -d "$mprefix" ] || { msg "Target system mountpoint not found: $mprefix"; return 1; }

	#
	# Get local filesystem(s) from fstab file. The list is sorted on the
	# mount point field (i.e., deep mount points sort after undeep ones).
	#
	devs=`grep -v '^[ \t]*#' <"$fstab" | grep -w 'ext\(2\|3\)' | \
		tr -s ' ' '\t' | sort -k2 | cut -f1`
	if test -z "$devs"; then
		msg "No ext2 or ext3 filesystems found in $fstab."
		return 1
	fi

	#
	# Mount filesystem(s). For mounting we need device path, mount point,
	# filesystem type, and mount options. The latter 3 are extracted from
	# the fstab file and (temporary) stored in an array. The mount order is
	# important (e.g., root must be mounted before other filesystems), but
	# this has already been taken care of above.
	#
	for dev in $devs; do
		mspec=(`grep -v '^[ \t]*#' <"$fstab" | grep -w "$dev" | \
			tr -s ' ' '\t' | cut -f2,3,4`)
		mpnt="${mprefix%%/}/${mspec[0]##/}"
		fstype_opt="${mspec[1]:+-t${mspec[1]}}"
		mopts_opt="${mspec[2]:+-o${mspec[2]}}"
 
		$DRY_RUN mkdirhier "$mpnt" || return 1

		msg "Mounting $dev on $mpnt (${mspec[1]})..."

		$DRY_RUN mount "$mopts_opt" "$fstype_opt" "$dev" "$mpnt" 2>/dev/null
		ret=$?
		if test $ret -ne 0; then
			msg "Mount command reported error ($ret)."
			return 1
		fi
	done

	return 0
}

#/
#/ Generate and copy \c fstab to target system. 
#/
#/ \param[in] $1  fstab source file.
#/ \param[in] $2  Mountpoint of target system.
#/
#/ \returns
#/ \li Code 0: \c fstab file copied to target system
#/ \li Code 1: \c fstab file not found or destination directory not present
#/ \li Code 2: Invalid parameter specified (usage error) 
#/
#/ \todo
#/ - Move \c fstab template to a separate file.
#/ - Get rid of "comment={local|pseudo}" mount options.
#/
install_fstab()
{
	local fstab=`trim "$1"`
	local mpnt=`trim "$2"`

	msg "Preparing fstab file for target system..."

	[ -f "$fstab" ] || { msg "fstab file not found: $fstab"; return 1; }
	[ -n "$mpnt"  ] || { msg "Target system mountpoint not found: $mpnt"; return 2; }

	# Hack "comment" mount option for local filesystems. These filesystems
	# contain mount options like "defaults" or "auto". At this point, fstab
	# only contains local filesystems, so a simple search & replace should
	# do the trick.
	sed -i -e "s@\(defaults\|auto\|noatime\)@&,comment=local@" $fstab
	if test $? -ne 0; then
		msg "Failed inserting \"comment\" mount option for local f/s."
		return 1
	fi

	# add pseudo filesystems to fstab file:
	cat <<_EOF >>"$fstab"
proc      /proc       proc    nodev,noexec,nosuid,comment=pseudo
sys       /sys        sysfs   nodev,noexec,nosuid,comment=pseudo
devpts    /dev/pts    devpts  noexec,nosuid,gid=5,mode=620,comment=pseudo
tmpfs     /dev/shm    tmpfs   nodev,nosuid,comment=pseudo
_EOF

	msg "Installing fstab file on target system..."

	$DRY_RUN cp -fv "$fstab" "$mpnt/etc" 2>/dev/null
	if test $? -ne 0; then
		msg "Couldn't copy fstab file to target system."
		return 1
	fi

	return 0
}

#/
#/ Prepare and copy \c passwd file to target system.
#/
#/ \param[in] $1  \c passwd source file.
#/ \param[in] $2  Mountpoint of target system.
#/
#/ \returns
#/ \li Code 0: \c passwd file copied to target system
#/ \li Code 1: \c passwd file or destination directory not present, or failed
#/             generating file
#/ \li Code 2: Invalid parameter specified (usage error) 
#/
#/ \todo
#/ - Use template for \c passwd file with additional (system) accounts.
#/ - Add login.defs to swset.
#/
install_passwd()
{
	local passwd=`trim "$1"`
	local mpnt=`trim "$2"`
	local newusers="$mpnt/newusers"
	local pass=""

	msg "Preparing passwd file for target system..."

	[ -r "$passwd" ] || { msg "passwd file not readable: $passwd"; return 1; }
	[ -n "$mpnt"   ] || { msg "Target system mountpoint not found: $mpnt"; return 2; }

	pass=`get_password`
	echo "root:$pass:0:0:Privileged operator,,,:/root:/bin/sh" >$newusers

	$DRY_RUN cp -fv "$passwd" "$mpnt/etc" 2>/dev/null

	msg "Installing passwd file on target system..."

	# temporary fix: need login.defs
	if test ! -f "$mpnt/etc/login.defs"; then
		$DRY_RUN cp -v "/etc/login.defs" "$mpnt/etc" 2>/dev/null
	fi

	# initialize passwd file on new system:
	$DRY_RUN chroot "$mpnt" newusers "/newusers"
	if test $? -ne 0; then
		msg "Failed encrypting passwords in passwd file."
		return 1
	fi
	$DRY_RUN chroot "$mpnt" pwconv
	if test $? -ne 0; then
		msg "Failed converting to shadow passwd file."
		return 1
	fi

	# erase unneeded files containing passwords:
	$DRY_RUN rm -f "$newusers"
	$DRY_RUN rm -f "$mpnt/$passwd-"

	return 0
}

#/
#/ Write bootloader on boot disk.
#/
#/ \param[in] $1  Mountpoint of target system.
#/
#/ \returns
#/ \li Code 0: Bootloader installed on target system disk.
#/ \li Code 1: Mountpoint or bootloader for target system not found, or failed
#/             writing bootloader.
#/
install_bootloader()
{
	local mpnt=`trim "$1"`
	local bootloader="$mpnt/boot/bootlx"
	local bootpart=""		# boot partition
	local cpart=""			# whole-disk partition, usually 'c'
	local devpath=""
	local -i bootpart_num=0
	local -i wholedisk_num=0
	local -a bootdev

	msg "Installing bootloader..."

	[ -d "$mpnt"       ] || { msg "Target system mountpoint not found: $mpnt"; return 1; }
	[ -r "$bootloader" ] || { msg "Bootloader binary not found: $bootloader"; return 1; }

	#
	# Check whether <newsys_root>/boot is a separate filesystem where the
	# bootloader is to be installed. If not, <newsys_root>/boot is part of
	# the <newsys_root> filesystem which will in that case be holding the
	# bootloader.
	#
	msg "Checking where /boot is located..."
	mountpoint -q "$mpnt/boot"
	if test $? -eq 0; then
		msg "/boot is a separate filesystem."
		bootdev=(`pinpoint_partition "/boot"`)
	else
		msg "/boot is part of the root filesystem."
		bootdev=(`pinpoint_partition "/"`)
	fi

	devpath=`get_device_path "${bootdev[0]}"`
	if test $? -ne 0; then
		msg "Failed determining system disk (${bootdev[0]})."
		return 1
	fi
	msg "System disk is $devpath"

	# boot partition number (starts at 1 for O/S):
	bootpart_num=`partition_letter_to_number "${bootdev[1]}" "1"`
	if test $? -ne 0; then
		msg "Unable to determine boot partition (${bootdev[0]},${bootdev[1]})."
		return 1
	fi
	bootpart="${bootdev[0]}$bootpart_num"
	msg "Boot partition is '${bootdev[1]}' ($bootpart)."
	
	# whole-disk partition number (starts at 1 for O/S):
	wholedisk_num=`partition_letter_to_number "$PART_WHOLEDISK" "1"`
	if test $? -ne 0; then
		msg "Unable to determine whole-disk partition '$PART_WHOLEDISK'."
		return 1
	fi
	cpart="${bootdev[0]}$wholedisk_num"
	msg "Whole-disk partition '$PART_WHOLEDISK' is: $cpart"

	msg "Writing bootloader: $bootloader"
	$DRY_RUN swriteboot -f "$wholedisk_num" -c "$bootpart_num" \
				"$devpath" "$bootloader" 2>/dev/null
	if test $? -ne 0; then
		msg "Error writing bootloader to system disk ($devpath)."
		return 1
	fi

	return 0
}

#/
#/ Perform operations for packages only possible after installation. We check
#/ a specific directory on the target system for scripts which perform the
#/ final integration of a package on the target system. This could be, for
#/ example, setting the proper IP address in a package's config file. Note
#/ that the Installer has no notion of what a package means, it merely executes
#/ a number of prepared scripts in a certain directory.
#/
#/ The scripts to be executed are located in the directory
#/ <tt>\<target-mpnt\>/\<PKG_INTEGRATION_DIR\></tt> where each package has its
#/ own sub-directory containing a shell script and any additional files needed
#/ for the package's integration. For each package the name of the shell script
#/ is the same, see <tt>PKG_INTEGRATION_SCRIPT</tt>. When the shell script is
#/ invoked, the target's mount point is passed as the first parameter and the
#/ configuration file as the second parameter. The sub-directories are sorted
#/ alphanumerically and hidden directories are ignored. This directly impacts
#/ the execution order of the scripts.
#/ 
#/ \param[in] $1  Mountpoint of target system.
#/ \param[in] $2  Configuration file.
#/
#/ \returns
#/ \li Code 0: All package integrations tasks executed successfully.
#/ \li Code 1: One or more tasks failed to execute.
#/
package_integration_tasks()
{
	local mpnt=`trim "$1"`
	local cf=`trim "$2"`
	local idir="$mpnt/$PKG_INTEGRATION_DIR"
	local pkg_dirs=""
	local pkg=""
	local task=""
	local -i ret=0

	msg "Checking for package integration tasks..."

	pkg_dirs=`find $idir -maxdepth 1 -type d -printf "%P\n" \
		  2>/dev/null | grep '^[[:alnum:]_]\+' | sort`
	if test -n "$pkg_dirs"; then
		for pkg in $pkg_dirs; do
			task="$idir/$pkg/$PKG_INTEGRATION_SCRIPT"
			if test ! -e "$task"; then
				msg "No task script found for \`$pkg'."
				ret=1; continue
			fi
			if test ! -x "$task"; then
				msg "Task script of \`$pkg' not executable."
				ret=1; continue
			fi
			
			msg "Running task(s) for \`$pkg'..."
			$DRY_RUN $task "$mpnt" "$cf"
			if test $? -ne 0; then
				ret=1
			fi
		done
	else
		msg "No integration tasks defined."
	fi

	return $ret
}

#/
#/ Prepare a first-boot script for the target. The first-boot script runs after
#/ the target boots for the first time and performs installation-related
#/ operations that are only possible at that time (e.g., initializing the
#/ system's crontab). The first-boot script nests itself in the normal boot
#/ procedure of the target system, but runs only once to perform the said
#/ operations. This function only \b installs the first-boot script on the
#/ target system (see also <tt>FIRSTBOOT_SH</tt>).
#/
#/ \param[in] $1  Mountpoint of target system.
#/
#/ \returns
#/ \li Code 0: first-boot procedure installed.
#/ \li Code 1: Failed installing first-boot procedure.
#/
install_first_boot_script()
{
	local mpnt=`trim "$1"`
	local inittab="$mpnt/etc/inittab"
	local fb_src=""
	local fb_dst=""
	local fb_dst_dir=""
	local -i ret=0

	msg "Preparing \"first-boot\" procedure..."

	fb_src=`type -p $FIRSTBOOT_SH 2>/dev/null`
	fb_dst="$mpnt/$fb_src"
	fb_dst_dir=`dirname $fb_dst 2>/dev/null`
	
	$DRY_RUN mkdirhier $fb_dst_dir || return 1
	
	$DRY_RUN install -m755 $fb_src $fb_dst_dir || return 1
	
	$DRY_RUN $fb_src -i $inittab
	if test $? -ne 0; then
		msg "Unable to add inittab entry to \`$inittab'."
		ret=1
	fi

	return $ret
}

#/
#/ Unmount filesystems and reboot target system. This function should not return
#/ but if it does, the script is aborted and a message is output to the user.
#/
#/ \todo Set <tt>AUTO_ACTION</tt> or <tt>BOOTDEF_DEV</tt> in SRM.
#/
sign_off()
{
	local mnt=""
	local mpnts=""

	mhdr "FINISH"

	msg ""
	msg "!!  INSTALLATION COMPLETE  !!"
	msg ""
	
	# unmount filesystems of new system in reverse order:
	msg "Unmounting filesystems target system..."
	mpnts=`mount 2>/dev/null | grep "^/dev" | cut -d' ' -f3 | \
		grep "^$NEWSYS_MPNT" | sort | tac`
	for mnt in $mpnts; do
		$DRY_RUN umount -vr "$mnt" 2>/dev/null
		if test $? -ne 0; then
			msg "Failed unmounting $mnt"
		fi 
	done

	# unmount any other filesystems:
	msg "Unmounting remaining filesystems (if any)..."
	$DRY_RUN umount -ar 2>/dev/null

	$DRY_RUN shutdown -r -t3 now

	# wait for system to complete reboot sequence:
	[ $? -eq 0 ] && while true; do sleep 1; done

	#
	# Should not get here, the system should have reboot.
	# Abort Install program and notify user.
	#
	abort "System did not reboot, please reset system manually."
}

#-------------------------------------------------------------------------------

TARGET_SN=`sysinv_get_serial`
CF=""
DEBUG=0
VERBOSE=0
DRY_RUN=""

while getopts :tDvhV OPT
do
	case $OPT in
	t) DRY_RUN="echo"
	   ;;
	D) DEBUG=1
	   ;;
	v) VERBOSE=1
	   ;;
	h) usage
	   ;;
	V) version
	   ;;
	?) echo "$OPTARG -- unknown option" 2>&1
	   usage
	   ;;
	:) echo "$OPTARG -- missing argument" 2>&1
	   usage
	   ;;
	esac

	shift
	OPTIND=1
done

CF=`trim "$1"`
if test -z "$CF"; then
	#
	# Figure out config file to use. Prefer system-specific config file
	# (which is based on the target's serial number) if it exists. Use a
	# default config file as a fallback.
	#
	if test -n "$TARGET_SN"; then
		CF="$CONFIG_DIR/$CONFIG_FILE.$TARGET_SN"
		if test ! -r "$CF"; then
			CF="$CONFIG_DIR/$CONFIG_FILE.default"
		fi
	else
		CF="$CONFIG_DIR/$CONFIG_FILE.default"
	fi
fi

sysinv
welcome
installation "$CF"
sign_off

exit 1
