# $MirOS: src/distrib/common/install.sub,v 1.70 2014/03/30 11:03:32 tg Exp $
# $OpenBSD: install.sub,v 1.388 2005/07/02 00:55:48 uwe Exp $
# $NetBSD: install.sub,v 1.5.2.8 1996/09/02 23:25:02 pk Exp $
#
# Copyright (c) 2003, 2004, 2005, 2008, 2009, 2010, 2011, 2013, 2014
#	Thorsten “mirabilos” Glaser <tg@mirbsd.org>
# Copyright (c) 1997-2005 Todd Miller, Theo de Raadt, Ken Westerback
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
# Copyright (c) 1996 The NetBSD Foundation, Inc.
# All rights reserved.
#
# This code is derived from software contributed to The NetBSD Foundation
# by Jason R. Thorpe.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
# 3. All advertising materials mentioning features or use of this software
#    must display the following acknowledgement:
#        This product includes software developed by the NetBSD
#        Foundation, Inc. and its contributors.
# 4. Neither the name of The NetBSD Foundation nor the names of its
#    contributors may be used to endorse or promote products derived
#    from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
# PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#

# MirBSD install/upgrade script common subroutines and initialization code

# Include machine-dependent functions and definitions.
#
# The following functions must be provided:
#	md_congrats()		  - display friendly message
#	md_prep_disklabel()	  - put an OpenBSD disklabel on the disk
#
# The following variables can be provided if required:
#	MDSETS	    - list of files to add to THESETS
#	MDFSTYPE    - nothing assumed if not provided
#	MDFSOPTS    - nothing assumed if not provided
#	MDDKDEVS    - '/^r*a*[swi]d[0-9][0-9]* /s/ .*//p' assumed if not provided
#	MDCDDEVS    - '/^cd[0-9][0-9]* /s/ .*//p'    assumed if not provided
#	MDMTDEVS    - '/^[cmsw]t[0-9][0-9]* /s/ .*//p'
#	MDXAPERTURE - set machdep.allowaperture=value in sysctl.conf
. /etc/functions
. install.md

if _vbox_check; then
	echo Sorry, WirrtualBox is not supported.
	echo To continue on your own risk: ':>/allow-vbox'
	echo But remember that vbox is buggy and often broken!
	test -e /allow-vbox || exit 1
fi

set_term() {
	typeset TERMS=vt100,vt220,wsvtg,wsvt25,dumb
	echo "Possible types: $TERMS"
	ask "Terminal type?" ${TERM:-vt220}
	TERM=$resp
	export TERM

	[[ -x /sbin/kbd ]] || return
	while :; do
		ask "kbd(8) mapping? ('?' for list)" "none"
		[[ $resp = none ]] && return
		[[ $resp = @(\?|+([0-9])) && -z ${layouts[0]} ]] && \
		    set -A layouts -- $(kbd -lq | \
		    egrep '^..\.?(nodead|dvorak)?$' | sort)
		[[ $resp = +([0-9]) ]] && resp=${layouts[$resp]}
		[[ $resp = [a-z]* ]] && if kbd $resp; then
			echo $resp >/tmp/kbdtype
			return
		fi
		[[ $resp = \? ]] || continue
		print Available keyboard mappings:
		typeset -iR3 i=0
		while (( i < ${#layouts[*]} )); do
			print "$i) ${layouts[i]}"
			let i++
		done | rs
	done
}

welcome() {
	typeset _q

	cat <<__EOT

Welcome to the ${OBSD} $MODE program (old, rewrite in progress).

This programme will help you $MODE MirOS. At any prompt except password
prompts you can run a shell command by typing '!foo', or escape to a shell
by typing '!'. Default answers are shown in []s and are selected by just
RETURN, but sometimes there is no default. At any time you can exit this
programme by pressing Control-C and then RETURN, but quitting during an
$MODE can leave your system in an inconsistent state.

__EOT

	# Configure the terminal and keyboard.
	set_term

	cat <<__EOT

IS YOUR DATA BACKED UP? As with anything that modifies disk contents, this
program can cause SIGNIFICANT data loss.

__EOT

	case $MODE in
	upgrade)
		cat <<__EOT
NOTE: before your system has been upgraded, you must manually merge any changes
to files in the 'etc' and 'xetc' sets into the files already on your system.

__EOT
		_q="Proceed with upgrade?"
		;;

	install)
		cat <<__EOT
It is often helpful to have the installation notes handy. For complex disk
configurations, relevant disk hardware manuals and a calculator are useful.

__EOT

		if [ -f /etc/fstab ]; then
			cat <<__EOT
You seem to be trying to restart an interrupted installation! You can skip
the disk preparation steps and continue, or you can reboot and start over.

__EOT
			_q="Skip disk initialization?"
		else
			_q="Proceed with install?"
		fi
		;;
	esac

	ask_yn "$_q"
	if [[ $resp = n ]]; then
		cat <<__EOT

Enter 'halt -p' or 'reboot' at the prompt to gracefully exit MirBSD.
You can then power cycle the machine and boot BSD or your other OSes.
__EOT
		exit
	fi

	echo "Cool! Let's get to it."
}

scan_dmesg() {
	bsort $(sed -ne "$1" /var/run/dmesg.boot)
}

# Get the first (lowest unit #) serial device if any, if MDSERIAL is set.
# NOTE: Only single digit serial devices (<dev>0 -> <dev>9) are looked for.
get_serialdev() {
	typeset _d _bd

	[[ -n $MDSERIAL ]] || exit
	set -- $MDSERIAL
	_d=$1
	_bd=$2
	set -- $(scan_dmesg "/^${_d}\([0-9]\) .*/s//\1/p")
	[[ -z $1 ]] || echo "$_bd$1"
}

get_drive() {
	ask_which "$1" "contains the $MODE media" "$2"
	[[ $resp = done ]] && return 1
	makedev $resp || return 1
	return 0
}

get_partition() {
	typeset _drive=$1 _fstypes=$2 _part _fst

	# Create file /tmp/parts.$_drive where each line is of the
	# form "<partition letter> <fs type>".
	disklabel $_drive 2>/dev/null		\
		| grep '^  [a-p]: '		\
		| egrep -v "swap|unused"	\
		| sed -e 's/^  \(.\):  *[^ ]*  *[^ ]*  *\([^ ]*\) .*/\1 \2/' \
		>/tmp/parts.$_drive

	disklabel $_drive 2>/dev/null | grep '^  .:'

	ask_which "$_drive partition" "has the $MODE sets" \
		 "$(sed -e 's/^\(.\).*/\1/' /tmp/parts.$_drive)"
	[[ $resp = done ]] && return 1

	_part=$resp
	_fst=$(sed -ne "/^$_part /s///p" /tmp/parts.$_drive)

	ask_which "filesystem type" "should be used to mount $_drive$_part" "$_fst $_fstypes ffs"
	case $resp in
	done)	return 1 ;;
	$_fst)	resp="$_part" ;;
	*)	resp="$_part $resp" ;;
	esac

	return 0
}

# Ask for a password, saving the input in $resp.
#    Display $1 as the prompt.
#    *Don't* allow the '!' options that ask does.
#    *Don't* echo input.
askpass() {
	set -o noglob
	stty -echo
	read resp?"$1 "
	stty echo
	set +o noglob
	echo
}

# Ask for user input.
#
#    $1    = the question to ask the user
#    $2    = the default answer
#
# Save the user input (or the default) in $resp.
#
# Allow the user to escape to shells ('!') or execute commands
# ('!foo') before entering the input.
ask() {
	typeset _question=$1 _default=$2

	set -o noglob
	while :; do
		echo -n "$_question "
		[[ -z $_default ]] || echo -n "[$_default] "
		read resp
		case $resp in
		!)	echo "Type 'exit' to return to install."
			sh
			;;
		!*)	eval ${resp#?}
			;;
		*)	: ${resp:=$_default}
			break
			;;
		esac
	done
	set +o noglob
}

# Ask for user input until a non-empty reply is entered.
#
#    $1    = the question to ask the user
#    $2    = the default answer
#
# Save the user input (or the default) in $resp.
ask_until() {
	resp=
	while [[ -z $resp ]]; do
		ask "$1" "$2"
	done
}

# Ask the user for a y or n, and insist on 'y', 'yes', 'n' or 'no'.
#
#    $1    = the question to ask the user
#    $2    = the default answer (assumed to be 'n' if empty).
#
# Return 'y' or 'n' in $resp.
ask_yn() {
	typeset _q=$1 _a=${2:-no} _resp
	typeset -l _resp

	while :; do
		ask "$_q" "$_a"
		_resp=$resp
		case $_resp in
		y|yes)	resp=y; return ;;
		n|no)	resp=n; return ;;
		esac
	done
 }

# Ask for the user to select one value from a list, or 'done'.
#
# $1 = name of the list items (disk, cd, etc.)
# $2 = question to ask
# $3 = list of valid choices
# $4 = default choice, if it is not specified use the first item in $3
# $5 = error message if no items in $3, defaults to 'No $1s found.'
#
# At exit $resp holds selected item, or 'done'
ask_which() {
	typeset _name=$1 _query=$2 _list=$3 _def=$4 _err=$5

	set -- $_list
	if (( $# < 1 )); then
		echo "${_err:=No ${_name}s found}."
		resp=done
		return
	fi
	: ${_def:=$1}

	# Eliminate extraneous (especially trailing) whitespace in _list.
	_list="$*"

	while :; do
		# Put both lines in ask prompt, rather than use a
		# separate 'echo' to ensure the entire question is
		# re-ask'ed after a '!' or '!foo' shell escape.
		ask "Available ${_name}s are: $_list.\nWhich one $_query? (or 'done')" "$_def"

		# Quote $resp to prevent user from confusing isin() by
		# entering something like 'a a'.
		isin "$resp" $_list done && break
		echo "'$resp' is not a valid choice."
	done
}

# test the first argument against the remaining ones, return success on a match
isin() {
	typeset	_a=$1 _b

	shift
	for _b; do
		[[ $_a = $_b ]] && return 0
	done
	return 1
}

# add first argument to list formed by the remaining arguments
# adds to the tail if the element does not already exist
addel() {
	typeset	_a=$1

	shift

	echo -n "$*"
	isin "$_a" $* || echo -n " $_a"
}

# remove all occurrences of first argument from list formed by
# the remaining arguments
rmel() {
	typeset	_a=$1 _b

	shift
	for _b; do
		[[ $_a != $_b ]] && echo -n "$_b "
	done
}

bsort() {
	typeset _l _a=$1 _b

	(( $# > 0 )) || return

	shift
	for _b; do
		if [[ $_a != $_b ]]; then
			if [[ $_a >$_b ]]; then
				_l="$_a $_l"; _a=$_b
			else
				_l="$_b $_l"
			fi
		fi
	done

	# Output the smallest value found.
	echo -n "$_a "

	# Sort remaining values.
	bsort $_l
}

# Add interesting/useful comments from mnt/etc/$1 to /tmp/$1.
#
# $1 == file in /tmp and /mnt/etc directories
save_comments() {
	typeset _file=$1

	if [[ -f /mnt/etc/$_file ]]; then
		grep "^#" /mnt/etc/$_file >/tmp/$_file.new
		[[ -f /tmp/$_file ]] && cat /tmp/$_file >>/tmp/$_file.new
		mv /tmp/$_file.new /tmp/$_file
	fi
}

# Offer to edit a file in /tmp and execute ${EDITOR} to do so if the user
# accepts the offer.
#
# $1 == file in /tmp to edit
edit_tmp_file() {
	typeset _file=$1

	ask_yn "Edit $_file with $EDITOR?"
	[[ $resp = y ]] && $EDITOR /tmp/$_file
}

# Offer to shell out for manual network configuration, and do so if
# the user accepts the offer.
manual_net_cfg() {
	typeset _x

	ask_yn "Do you want to do any manual network configuration?"

	[[ $resp = y ]] && { echo "Type 'exit' to return to $MODE."; sh; }

	# the network is now up…
	pf=https
	ftp -h 2>&1 | fgrep https >/dev/null 2>&1 || pf=http
	_getrnd net $pf
	_ntp
}

# log in via ftp to host $1 as user $2 with password $3
# and return a list of all files in the directory $4 on stdout
ftp_list_files() {
	ftp ${_ftp_active} -V -n "$1" <<__EOT
user "$2" "$3"
cd "$4"
ls
quit
__EOT
}

# Create a device.
#
# $1 = name of the device to create.
makedev() {
	typeset _dev=$1

	if [[ ! -r /dev/MAKEDEV ]]; then
		echo "MAKEDEV not found. Can't create device nodes."
		return 1
	fi

	cd /dev; mksh MAKEDEV $_dev || return 1 ; cd - >/dev/null
}

# Create an entry in the hosts file. If an entry with the
# same symbolic name and address family already exists, delete it.
# $1 - IP address (v6 if it contains ':', else v4)
# $2 - symbolic name
addhostent() {
	typeset _addr=$1 _name=$2 _leader

	if [[ $_addr = *:* ]]; then
		_leader='/^[0-9a-fA-F]*:'
	else
		_leader='/^[0-9]*\.'
	fi
	sed "${_leader}.*[	 ]$_name\$/d" /tmp/hosts >/tmp/hosts.new
	echo "$_addr $_name" >>/tmp/hosts.new
	mv /tmp/hosts.new /tmp/hosts
}

# Show list of available sets and let the user select which sets to install.
#
# $1 = available sets
# $2 = already selected sets
#
# Set $resp to list of selected sets.
select_sets() {
	typeset _avail=$1 _selected=$2 _next _f _action

	cat <<__EOT

Select sets by entering a set name, a file name pattern or 'all'. De-select
sets by prepending a '-' to the set name, file name pattern or 'all'. Selected
sets are labelled '[x]'.
__EOT
	while :; do
		_action=
		_next=
		echo
		for _f in $_avail; do
			if isin $_f $_selected; then
				echo "	[X] $_f"
			else
				echo "	[ ] $_f"
				: ${_next:=$_f}
			fi
		done
		: ${_next:=done}

		ask "Set name? (or 'done')" "$_next"
		case $resp in
		done)	break ;;
		-*)	_action=rmel ;;
		esac

		: ${_action:=addel}
		resp=${resp#+|-}

		case $resp in
		"")	continue ;;
		all)	resp=* ;;
		esac

		# Use @($resp) rather than just $resp to protect
		# against silly user input that might cause syntax
		# errors.
		for _f in $_avail; do
			eval "case $_f in
			@($resp)) _selected=\`$_action $_f \$_selected\` ;;
			esac"
		done
	done

	resp=$_selected
}

configure_ifs() {
	typeset _IFDEVS=$(ifconfig -l) _ifs _name _media _hn

	while :; do
		ask_which "interface" "do you wish to initialise" "$_IFDEVS" \
			"" "No more interfaces to initialise"
		[[ $resp = done ]] && break

		_ifs=$resp
		_hn=/tmp/hostname.$_ifs

		# Get symbolic name - will be used in DHCP requests.
		ask "Symbolic (host) name for $_ifs?" "$(hostname -s)"
		_name=$resp

		# Get and apply media options.
		_media=$(ifconfig -m $_ifs | grep "media ")
		if [[ -n $_media ]]; then
			cat <<__EOT
The media options for $_ifs are currently
$(ifconfig -m $_ifs | sed -n '/supported/D;/media:/p')
__EOT
			ask_yn "Do you want to change the media options?"
			case $resp in
			y)	cat <<__EOT
Supported media options for $_ifs are:
$_media
__EOT
				ask "Media options for $_ifs?"
				_media=$resp
				ifconfig $_ifs $_media || return 1
				;;
			n)	_media=
				;;
			esac
		fi

		rm -f $_hn
		v4_config "$_ifs" "$_media" "$_name" "$_hn"
		v6_config "$_ifs" "$_media" "$_name" "$_hn"

		[[ -f $_hn ]] && _IFDEVS=$(rmel "$_ifs" $_IFDEVS)
	done
}

# Output '<UP | DOWN> [<addr> <netmask> <rest of inet line>]'.
#
# $1 == interface
v4_info() {
	ifconfig $1 inet | sed -n '
		1s/.*<UP,.*/UP/p
		1s/.*<.*/DOWN/p
		/inet/s/netmask//
		/inet/s///p'
}

# Obtain and output the inet6 information related to the given
# interface. Should output '<UP/DOWN> <addr> <prefixlen> <rest of inet line> '.
#
# $1 == interface
v6_info() {
	ifconfig $1 inet6 | sed -n '
		1s/.*<UP,.*/UP/p
		1s/.*<.*/DOWN/p
		/scopeid/d
		/inet6/s///p'
}

# Construct etc/dhclient.conf and issue DHCP request. Return FALSE if
# no IP address assigned to $1.
#
# $1 == interface
# $2 == hostname (optional).
dhcp_request() {
	typeset _ifs=$1 _hn=$2

	echo "lookup file bind" >/etc/resolv.conf.tail

	if [[ -n $_hn ]]; then
		_hn="send host-name \"$_hn\";"
		echo "Issuing hostname-associated DHCP request for $_ifs."
	else
		echo "Issuing free-roaming DHCP request for $_ifs."
	fi

	cat >/etc/dhclient.conf <<__EOT
initial-interval 1;
$_hn
request subnet-mask, broadcast-address, routers, domain-name,
	domain-name-servers, host-name;
__EOT

	dhclient $_ifs

	set -- $(v4_info $_ifs)

	if [[ $1 = UP && -n $2 ]]; then
		# Move configuration files to where they will be copied to the
		# installed system. Overwrites configuration information from
		# last successful dhcp attempt.
		mv /etc/dhclient.conf /tmp/dhclient.conf
		mv /etc/resolv.conf.tail /tmp/resolv.conf.tail
		return 0
	fi
	
	ifconfig $_ifs delete down
	rm /etc/dhclient.conf /etc/resolv.conf.tail
	return 1
}

v4_config() {
	typeset _ifs=$1 _media=$2 _name=$3 _hn=$4 _prompt

	set -- $(v4_info $_ifs)
	if [[ -n $2 ]]; then
		ifconfig $_ifs inet $2 delete
		[[ $2 != "0.0.0.0" ]] && { _addr=$2; _mask=$3; }
	fi

	[[ -x /sbin/dhclient ]] && _prompt=" or 'dhcp'"
	_prompt="IPv4 address for $_ifs? (or 'none'$_prompt)"

	ask_until "$_prompt" "$_addr"
	case $resp in
	none)	;;
	dhcp)	if [[ ! -x /sbin/dhclient ]]; then
			echo "DHCP not possible - no /sbin/dhclient."
		elif dhcp_request $_ifs "$_name" || dhcp_request $_ifs ; then
			addhostent "127.0.0.1" "$_name"
			echo "dhcp NONE NONE NONE $_media" >>$_hn
			dhcp_requested=", 'dhcp'"
		fi
		;;
	*)	_addr=$resp
		ask_until "Netmask?" "${_mask:=255.255.255.0}"
		if ifconfig $_ifs inet $_addr netmask $resp up ; then
			addhostent "$_addr" "$_name"
			echo "inet $_addr $resp NONE $_media" >$_hn
		fi
		;;
	esac
}

v6_config() {
	typeset _ifs=$1 _media=$2 _name=$3 _hn=$4 _addr _prefixlen _prompt _eui

	ifconfig lo0 inet6 >/dev/null 2>&1 || return

	set -- $(v6_info $_ifs)
	[[ -n $2 ]] && { _addr=$2; _prefixlen=$3; }

	[[ -x /sbin/rtsol ]] && _prompt="or 'rtsol' "
	echo To append EUI64 automatically, let the address end with two colons.
	_prompt="IPv6 address for $_ifs? (${_prompt}or 'none')"
	ask_until "$_prompt" "${_addr:-none}"

	case $resp in
	none)	return
		;;
	rtsol)	[[ ! -x /sbin/rtsol ]] && { echo "No /sbin/rtsol." ; return ; }
		sysctl -w net.inet6.ip6.accept_rtadv=1
		ifconfig $_ifs up
		rtsol $_ifs
		addhostent "::1" "$_name"
		echo "up\nrtsol $media" >>$_hn
		return
		;;
	*::)	_eui=eui64
		;;
	esac

	_addr=$resp
	ask_until "IPv6 prefix length for $_ifs?" "${_prefixlen:=64}"
	ifconfig $_ifs inet6 $_addr prefixlen $resp $_eui up || return
	echo inet6 $_addr $resp $_eui $media >>$_hn
	if [[ -n $_eui ]]; then
		set -- $(v6_info $_ifs)
		[[ -n $2 ]] && _addr=$2
	fi
	addhostent "$_addr" "$_name"

	v6_defroute $_ifs
	[[ $resp = none ]] && return
	route -n add -inet6 default "$resp" || return
	echo "route add -inet6 default $resp" >>$_hn
}

v4_defroute() {
	typeset _dr _fls _prompt=" or 'none'"

	_prompt="Default IPv4 route? (IPv4 address$dhcp_requested$_prompt)"

	_dr=$(route -n show -inet | sed -ne '/^default */{s///; s/ .*//; p;}')
	[[ -f /tmp/dhclient.conf ]] && _dr=dhcp

	while :; do
		ask_until "$_prompt" "$_dr"
		[[ $resp = @(none|dhcp) ]] && break
		route -n delete -inet default >/dev/null 2>&1
		route -n add -inet default "$resp" && {
			set -A _fls -- /tmp/hostname.*
			(( ${#_fls[*]} == 1 )) || _fls=/tmp/hostname.local
			echo "route add -inet default $resp" >>$_fls
			break
		}
		# Put the old default route back. The new one did not work.
		route -n add -inet default $_dr >/dev/null 2>&1
	done
}

v6_defroute() {
	typeset _if=$1 _routers _oifs

	if [[ -n $(route -n show -inet6 | sed -ne '/^default */{s///; s/ .*//; p;}') ]]; then
		resp=none
		return
	fi

	if [[ -x /sbin/ping6 ]]; then
		_routers=$(ping6 -n -c 2 ff02::2%$_if 2>&1 | sed -n \
			-e '/bytes from/{s/^.*from //;s/,.*$//;p;}')
	fi

	_oifs=$IFS
	IFS=
	PS3="IPv6 default router? (list #, IPv6 address or 'none'): "
	select i in $_routers; do
		case $i in
		"")	resp=$REPLY
			[[ -n $resp ]] && break
			;;
		*)	resp=$i
			break
			;;
		esac
	done
	IFS=$_oifs
}

# Much of this is gratuitously stolen from /etc/netstart.
enable_network() {
	typeset _netfile

	# Copy any required or optional files found
	for _netfile in hosts dhclient.conf resolv.conf resolv.conf.tail protocols services; do
		if [ -f /mnt/etc/${_netfile} ]; then
			cp /mnt/etc/${_netfile} /etc/${_netfile}
		fi
	done

	# Set the address for the loopback interface. Bringing the
	# interface up, automatically invokes the IPv6 address ::1.
	ifconfig lo0 inet 127.0.0.1

	# configure all of the non-loopback interfaces which we know about.
	# refer to hostname.if(5)
	for hn in /mnt/etc/hostname.*; do
		# Strip off /mnt/etc/hostname. prefix
		if=${hn#/mnt/etc/hostname.}

		# Check for ifconfig'able interface.
		ifconfig $if >/dev/null 2>&1 || continue

		# Now parse the hostname.* file
		while :; do
			if [ "$cmd2" ]; then
				# we are carrying over from the
				# 'read dt dtaddr' last time
				set -A i -- $cmd2
				af=${i[0]}
				name=${i[1]}
				mask=${i[2]}
				bcaddr=${i[3]}
				ext1=${i[4]}
				unset i[0] i[1] i[2] i[3] i[4]
				ext2="${i[*]}"
				cmd2=
			else
				# read the next line or exit the while loop
				read af name mask bcaddr ext1 ext2 || break
			fi
			# $af can be "dhcp", "up", "rtsol", an address family, commands, or
			# a comment.
			case $af in
			"route"|"!route")
				routep="-n $name"
				[ x"$name" = x"+n" ] && routep=
				cmd="/sbin/route ${routep} ${mask} ${bcaddr} ${ext1} ${ext2}"
				;;
			"#"*|"!"*|"bridge"|"")
				# skip comments, user commands, bridges,
				# and empty lines
				continue
				;;
			"dhcp")	[ "$name" = "NONE" ] && name=
				[ "$mask" = "NONE" ] && mask=
				[ "$bcaddr" = "NONE" ] && bcaddr=
				ifconfig $if $name $mask $bcaddr $ext1 $ext2 down
				cmd="dhclient $if"
				;;
			"rtsol")
				ifconfig $if $name $mask $bcaddr $ext1 $ext2 up
				rtsif="$rtsif $if"
				cmd=
				;;
			"up")
				# The only one of these guaranteed to be set is $if
				# the remaining ones exist so that media controls work
				cmd="ifconfig $if $name $mask $bcaddr $ext1 $ext2 up"
				;;
			*)	read dt dtaddr
				if [ "$name" = "alias" ]; then
					# perform a 'shift' of sorts
					alias=$name
					name=$mask
					mask=$bcaddr
					bcaddr=$ext1
					ext1=$ext2
					ext2=
				else
					alias=
				fi
				cmd="ifconfig $if $af $alias $name "
				case $dt in
				dest)	cmd="$cmd $dtaddr"
					;;
				[a-z!]*)
					cmd2="$dt $dtaddr"
					;;
				esac
				if [ ! -n "$name" ]; then
					echo "/mnt/etc/hostname.$if: invalid network configuration file"
					return
				fi
				case $af in
				inet)	[ "$mask" ] && cmd="$cmd netmask $mask"
					if [ "$bcaddr" -a "$bcaddr" != "NONE" ]; then
						cmd="$cmd broadcast $bcaddr"
					fi
					[ "$alias" ] && rtcmd="; route -qn add -host $name 127.0.0.1"
					;;
				inet6)
					[ "$mask" ] && cmd="$cmd prefixlen $mask"
					cmd="$cmd $bcaddr"
					;;
				*)	cmd="$cmd $mask $bcaddr"
				esac
				cmd="$cmd $ext1 $ext2$rtcmd" rtcmd=
				;;
			esac
			eval "$cmd"
		done </mnt/etc/hostname.$if
	done

	# Use loopback, not the wire.
	route -qn add -host $(hostname) 127.0.0.1 >/dev/null
	route -qn add -net 127 127.0.0.1 -reject >/dev/null

	# Grab default route, if existent
	if [ -s /mnt/etc/hostname.local ]; then
		cmd="$(grep ^route /mnt/etc/hostname.local | grep default)"
		[[ -n $cmd ]] && eval "$cmd"
	fi

	# Display results...
	echo "Network interface configuration:"
	ifconfig -am

	# enable the resolver if resolv.conf is available
	route -n show
	if [ -f /etc/resolv.conf ]; then
		echo "\nResolver enabled."
	else
		echo "\nResolver not enabled."
	fi
}

# Install a user-selected subset of the files in $2 from the source
# named in $1. Display an error message for failed installs so the
# user will know to try again.
install_files() {
	typeset _src=$1 _files=$2 _f _sets _get_sets

	# Initialize _sets to the list of sets found in _src, and initialize
	# _get_sets to the intersection of _sets and DEFAULTSETS.
	#
	# Sets will be installed in the order given in THESETS to ensure proper
	# installation. So, to minimize user confusion display the sets in the
	# order in which they will be installed.
	for _f in $THESETS; do
		isin $_f $_files || continue;
		_sets=$(addel $_f $_sets)
		isin $_f $DEFAULTSETS && _get_sets=$(addel $_f $_get_sets)
	done

	if [[ -z $_sets ]]; then
		# Show $_src, but delete any ftp password.
		cat <<__EOT
No $OBSD sets were found at

	$(echo $_src | sed -e 's/\(^ftp:\/\/[^/]*\)\(:[^/]*\)\(@.*\)/\1\3/')

Set names are: $THESETS
__EOT
		return
	fi

	select_sets "$_sets" "$_get_sets"

	[[ -n $resp ]] || return
	_get_sets=$resp

	ask_yn "Ready to $MODE sets?" yes
	[[ $resp = n ]] && return

	for _f in $THESETS; do
		isin $_f $_get_sets || continue
		echo "Getting $_f ..."
		case $_f in
		*.ngz)	ftp $_ftp_active -o - -V -m "$_src/$_f" | \
			    gzip -dcf | tar -M lncp -xphf - -C /mnt
			;;
		*)	ftp $_ftp_active -o "/mnt/$_f" -V -m "$_src/$_f"
			;;
		esac
		if [ $? -ne 0 ]; then
			echo "'$_f' did not install correctly."
		else
			DEFAULTSETS=$(rmel $_f $DEFAULTSETS)
		fi
	done
}

# Encode $1 as specified for usercodes and passwords in RFC 1738
# section 3.1 and section 5.
#
# Escape everything between 0x20 and 0x7e to avoid both illegal url
# characters and characters causing problems during script processing.
#
# *NOTE*
#	1) quotes around $1 are required to preserve trailing or
#	   embeddded blanks in usercodes and passwords.
#	2) substitute '%' FIRST so it doesn't eliminate '%' chars we insert.
encode_for_url() {
	print -nr -- "$1" | sed -e '
s.%.%25.g
s.;.%3B.g
s./.%2F.g
s.?.%3F.g
s.:.%3A.g
s.@.%40.g
s.&.%26.g
s.=.%3D.g
s.+.%2B.g
s.\$.%24.g
s.,.%2C.g
s.	.%09.g
s. .%20.g
s.<.%3C.g
s.>.%3E.g
s.#.%23.g
s.".%22.g
s.{.%7B.g
s.}.%7D.g
s.|.%7C.g
s.\\.%5C.g
s.\^.%5E.g
s.\[.%5B.g
s.].%5D.g
s.`.%60.g
s.'\''.%27.g
s/!/%21/g
s/(/%28/g
s/)/%29/g
s/\*/%2a/g
s/-/%2d/g
s/\./%2e/g
s/_/%5f/g
s/~/%7e/g
'
}

# Check for the presence of an error message in the output of the ftp commands
# used to get the list of files in a directory.
#
# $1 = error message to look for
# $2 = ftp command output
ftp_error() {
	if [[ -n $(echo "$2" | grep "$1") ]]; then
		echo $1
		return 0
	fi
	return 1
}

# Get several parameters from the user, and xfer
# files from the server.
# $1 = url type (ftp, http or https)
# Note:	_ftp_server_ip, _ftp_server_dir, _ftp_server_login,
#	and _ftp_active must be global.
install_url() {
	typeset _url_type=$1 _file_list _url_base _oifs _prompt _passwd

	ask "HTTP/FTP proxy URL? (e.g. 'http://proxy:8080', or 'none')" \
	    "${ftp_proxy:-none}"
	unset ftp_proxy http_proxy
	[[ $resp = none ]] || export ftp_proxy=$resp http_proxy=$resp

	rm -f $SERVERLIST
#	ask_yn "Display the list of known $_url_type servers?" "${_get_server_list:-yes}"
#	_get_server_list=$resp
	_get_server_list=n
#	if [[ $_get_server_list = y ]]; then
#		# ftp.openbsd.org == 129.128.5.191 and will remain at
#		# that address for the forseeable future.
#		echo -n "Getting the list from 129.128.5.191 (ftp.openbsd.org)..."
#		ftp $_ftp_active -V -a -o - \
#			ftp://129.128.5.191/$FTPDIR/ftplist 2>/tmp/ftplisterr \
#			| sed -ne "/^${_url_type}:\/\//s///p" >$SERVERLIST
#		if [[ -s $SERVERLIST ]]; then
#			echo "done."
#			_prompt="Server? (IP address, hostname, list#, 'done' or '?')"
#			sed = $SERVERLIST | sed 'N;s/\n/	/' | less -XE
#		else
#			echo "FAILED."
#			cat /tmp/ftplisterr
#		fi
#	fi

	# Get server IP address or hostname
	: ${_prompt:="Server? (IP address, hostname or 'done')"}
	eval ': ${_'${_url_type}'_server_ip:=www.mirbsd.org}'
	while :; do
		eval resp=\$_${_url_type}_server_ip
		ask_until "$_prompt" "$resp"
		case $resp in
		done)	return ;;
		"?")	[[ -s $SERVERLIST ]] || continue
			sed = $SERVERLIST | sed 'N;s/\n/	/' | less -XE
			;;
		+([0-9]))
			# A numeric hostname is ignored. A number is only used
			# as a line number in $SERVERLIST.
			[[ -s $SERVERLIST ]] || continue
			set -- $(sed -ne "${resp}p" $SERVERLIST)
			if (( $# < 1 )); then
				echo "There is no line $resp."
				continue
			fi
			echo "Using	$*"
			eval _${_url_type}_server_ip=${1%%/*}
			eval _${_url_type}_server_dir=${1#*/}/$SETDIR
			# Repeat loop to get user to confirm server address.
			;;
		*)	eval _${_url_type}_server_ip=$resp
			break
			;;
		esac
	done

	# Some older servers lie about their support for passive mode ftp, so
	# ask the user if it worth trying passive mode to the chosen server.
	# Irrelevant if using a proxy.
	if [[ $_url_type = ftp && -z $ftp_proxy ]]; then
		case $_ftp_active in
		-A)	resp=no ;;
		*)	resp=yes ;;
		esac

		unset _ftp_active
		ask_yn "Does the server support passive mode ftp?" $resp
		[[ $resp = n ]] && _ftp_active=-A
	fi

	# Get server directory
	eval resp=\$_${_url_type}_server_dir
	ask_until "Server directory?" "${resp:-$FTPDIR$SETDIR}"
	eval _${_url_type}_server_dir=$resp

	if [[ $_url_type = ftp ]]; then
		# Get login name, setting IFS to nothing so trailing or
		# embedded blanks are preserved!
		_oifs=$IFS
		IFS=
		ask_until "Login?" "${_ftp_server_login:=anonymous}"
		_ftp_server_login=$resp

		# Get password unless anonymous
		_passwd=root@$(hostname)
		if [[ $_ftp_server_login != anonymous ]]; then
			resp=
			while [[ -z $resp ]]; do
				askpass "Password? (will not echo)"
			done
			_passwd=$resp
		fi
		IFS=$_oifs
	fi

	# Build up the base url since it is so nasty...
	_url_base=$_url_type://
	if [[ $_url_type = ftp && $_ftp_server_login != anonymous ]]; then
		_url_base=$_url_base$(encode_for_url "$_ftp_server_login"):$(encode_for_url "$_passwd")@
	fi
	eval _url_base=$_url_base\$_${_url_type}_server_ip/\$_${_url_type}_server_dir

	# XXX Workaround for problems ftp'ing out from a v6 only host.
	ifconfig lo0 127.0.0.1

	# Get list of files from the server.
	if [[ $_url_type = ftp && -z $ftp_proxy ]]; then
		_file_list=$(ftp_list_files "$_ftp_server_ip" "$_ftp_server_login" "$_passwd" "$_ftp_server_dir")
		ftp_error "Login failed." "$_file_list" && return
		ftp_error "No such file or directory." "$_file_list" && return
	else
		# Assumes index file is "index.txt" for http (or proxy)
		# We can't use index.html since the format is server-dependent
		_file_list=$(ftp -o - -V "$_url_base/index.txt" | sed 's///')
	fi

	install_files "$_url_base" "$_file_list"
}

install_mounted_fs() {
	typeset _dir

	while :; do
		ask_until "Pathname to the sets? (or 'done')" "$SETDIR"
		[[ $resp = done ]] && return
		# Accept a valid /mnt2 or /mnt relative path.
		[[ -d /mnt2/$resp ]] && { _dir=/mnt2/$resp ; break ; }
		[[ -d /mnt/$resp ]] && { _dir=/mnt/$resp ; break ; }
		# Accept a valid absolute path.
		[[ -d /$resp ]] && { _dir=/$resp ; break ; }
		echo "The directory '$resp' does not exist."
	done

	install_files "file://$_dir" "$(ls -l $_dir)"
}

install_cdrom() {
	typeset _drive _part=c _fstype _err=0

	get_drive "CD-ROM" "$CDDEVS" || return
	_drive=$resp

	set -- $(disklabel $_drive 2>&1 | grep '^  c: ') || _err=1
	case $_err:$4 in
	1*|0:ISO9660)
		_fstype=cd9660 ;;
	0:UDF)	_fstype=udf ;;
	*)	get_partition $_drive "cd9660" || return
		set -- $resp
		_part=$1
		[[ -n $2 ]] && _fstype=$2
		;;
	esac

	mount -t $_fstype -o ro /dev/$_drive$_part /mnt2 || return
	install_mounted_fs
}

install_disk() {
	typeset _drive _dev _fstype _fsopts

	ask_yn "Is the disk partition already mounted?"
	if [[ $resp = n ]]; then
		get_drive "disk" "$DKDEVS" || return
		_drive=$resp

		get_partition $_drive "$MDFSTYPE" || return
		set -- $resp
		_dev=/dev/$_drive$1
		[[ -n $2 ]] && _fstype="-t $2"
		[[ $_fstype = $MDFSTYPE ]] && _fsopts=$MDFSOPTS

		if [[ -z $(mount | grep "^$_dev") ]]; then
			mount $_fstype -o ro,$_fsopts $_dev /mnt2 || return
		fi
	fi
	install_mounted_fs
}

install_nfs() {
	typeset _tcp

	# Get the IP address of the server.
	ask_until "Server IP address or hostname?" "$NFS_ADDR"
	NFS_ADDR=$resp

	# Get the server path to mount.
	ask_until "Filesystem on server to mount?" "$NFS_PATH"
	NFS_PATH=$resp

	# Determine use of TCP
	ask_yn "Use TCP transport? (requires TCP-capable NFS server)" yes
	[[ $resp = y ]] && _tcp=-T

	# Mount the server
	mount_nfs $_tcp -o ro $NFS_ADDR:$NFS_PATH /mnt2 || return

	install_mounted_fs
}

install_tape() {
	typeset _z _bs

	# Get the name of the tape device.
	get_drive "tape drive" "$MTDEVS" || return
	export TAPE=/dev/nr$resp
	if [[ ! -c $TAPE ]]; then
		echo "$TAPE is not a character special file."
		return
	fi

	# Rewind the tape device.
	echo -n "Rewinding $TAPE (mt rewind)..."
	mt rewind || return
	echo "done."

	# Extract the desired files.
	while :; do
		ask_until "Skip how many files? (or 'done')" 0
		[[ $resp = done ]] && return
		[[ $resp = +([0-9]) ]] || continue
		(( resp < 0 )) && continue

		if (( resp > 0 )); then
			echo -n "Skipping $resp file(s)..."
			mt fsf $resp || return
			echo "done."
		elif [[ -n $_bs ]]; then
			# Dance to start of next file.
			mt bsf ; mt fsf
		fi

		unset _z
		ask_yn "Is the file gzipped?" yes
		[[ $resp = y ]] && _z=z

		# Get the blocksize to use. If the file isn't gzipped then
		# default to the 20 x 512 = 10,240 byte tar default.
		[[ $_z = z ]] || _bs=10240
		ask_until "Blocksize for this file?" "${_bs:-8k}"
		[[ $resp = done ]] && return
		_bs=$resp

		dd if=$TAPE bs=$_bs | tar -M lncp -${_z}xvphf - -C /mnt || \
		    return
	done
}

set_timezone() {
	typeset _zoneroot=/mnt/usr/share/zoneinfo/ _zonepath

	# If the timezone directory structure is not
	# available, return immediately.

	[[ ! -d $_zoneroot ]] && return

	if [[ -L /mnt/etc/localtime ]]; then
		TZ=$(ls -l /mnt/etc/localtime 2>/dev/null)
		TZ=${TZ#*${_zoneroot#/mnt}}
	fi

	: ${TZ:=GMT}

	while :; do
		_zonepath=$_zoneroot

		ask "What timezone are you in? ('?' for list)" "$TZ"

		if [[ $resp = ? ]]; then
			ls -F ${_zonepath}
			continue;
		fi

		_zonepath=${_zonepath}${resp}

		while [[ -d $_zonepath ]]; do
			ask "What sub-timezone of '${_zonepath#$_zoneroot}' are you in? ('?' for list)"
			case $resp in
			"")	;;
			?)	ls -F $_zonepath ;;
			*)	_zonepath=$_zonepath/$resp ;;
			esac
		done

		if [[ -f $_zonepath ]]; then
			TZ=${_zonepath#$_zoneroot}
			echo -n "Setting local timezone to '$TZ'..."
			ln -sf /usr/share/zoneinfo/$TZ /mnt/etc/localtime
			echo "done."
			return
		fi

		echo -n "'${_zonepath#$_zoneroot}'"
		echo " is not a valid timezone on this system."
	done
}

# Check with the user that missing required sets were deliberately skipped.
sane_install() {
	typeset _s _m

	for _s in $SANESETS; do
		isin $_s $DEFAULTSETS || continue
		ask_yn "'$_s' was not installed.\nAre you *SURE* your $MODE is complete without '$_s'?"
		[[ $resp = n ]] && _m="$_m $_s"
	done

	[[ -n $_m ]] && return 1
	return 0
}

# Ask the user for locations of sets, and then install whatever sets the
# user selects from that location. Repeat as many times as the user
# needs to get all desired sets.
install_sets() {
	typeset _d=disk _locs="disk ftp http shttp"

	[[ -n $CDDEVS ]] && { _locs="cd $_locs" ; _d=cd ; }
	[[ -x /sbin/mount_nfs ]] && _locs="$_locs nfs"
	[[ -n $MTDEVS && -x /bin/mt ]] && _locs="$_locs tape"

	echo "\nLet's $MODE the sets!"
	while :; do
		umount -f /mnt2 >/dev/null 2>&1
		[[ -z $DEFAULTSETS ]] && _d=done

		ask "Location of sets? ($_locs or 'done')" "$_d"
		case $resp in
		done)	sane_install && return ;;
		c*|C*)	isin "cd" $_locs && install_cdrom ;;
		d*|D*)	install_disk ;;
		f*|F*)	isin "ftp" $_locs && install_url ftp ;;
		h*|H*)	isin "http" $_locs && install_url http ;;
		n*|N*)	isin "nfs" $_locs && install_nfs ;;
		s*|S*)	isin "http" $_locs && install_url https ;;
		t*|T*)	isin "tape" $_locs && install_tape ;;
		esac
	done
}

# Create a skeletal but useful /etc/fstab from /tmp/fstab by stripping all
# comment lines and dropping all filesystems which
#
#       1) can't be mounted (no mount_* command is found),
#	2) have 'xx' in the option field (usually /altroot),
#	3) have 'noauto' in the option field,
#	4) are nfs (since name resolution may not be present),
#	5) are mfs (breaks install usually).
#
# In addition,
#
#	2) mount non-ffs filesystems read only,
#	3) prepend '/mnt' to all mount points,
#	4) delete any trailing '/' from the mount point (e.g. root),
#	5) leave out fs_freq and fs_passno fields.
#
# If no /etc/fstab is created, do not proceed with install/upgrade.
munge_fstab() {
	typeset _dev _mp _fstype _opt _rest

	while read _dev _mp _fstype _opt _rest; do
		# Drop irrelevant lines and filesystems.
		[[ $_dev = \#* || \
		    $_fstype = nfs || \
		    $_fstype = mfs || \
		    ! -f /sbin/mount_$_fstype || \
		    $_opt = *noauto* || \
		    $_opt = *xx* ]] && continue

		# Mount non-ffs filesystems read only.
		[[ $_fstype = ffs ]] || _opt=$(echo $_opt | sed -e 's/rw/ro/')

		# Write fs entry in fstab.
		# 1) prepend '/mnt' to the mount point.
		# 2) remove a trailing '/' from the mount point (e.g. root).
		# 3) leave out fs_freq and fs_passno fields (i.e. $_rest).
		echo $_dev /mnt${_mp%/} $_fstype $_opt

	done </tmp/fstab >/etc/fstab

	# If no /etc/fstab was created, we have nowhere to $MODE to.
	if [ ! -s /etc/fstab ]; then
		echo "Unable to create valid /etc/fstab."
		exit
	fi
}

# Must mount filesystems manually, one at a time, so we can make
# sure the mount points exist.
mount_fs() {
	typeset _async=$1 _dev _mp _fstype _opt _rest

	while read _dev _mp _fstype _opt _rest; do
		# If not the root filesystem, make sure the mount
		# point is present.
		[ "$_mp" = "/mnt" ] || mkdir -p $_mp

		# Mount the filesystem. If the mount fails, exit.
		mount -v -t $_fstype $_async -o $_opt $_dev $_mp && continue
		# If it failed, try without async (important for raid) first
		mount -v -t $_fstype         -o $_opt $_dev $_mp && continue
		# In addition to the error message displayed by mount ...
		cat <<__EOT

FATAL ERROR:	Cannot mount filesystems. Double-check your configuration
		and restart the $MODE.

__EOT
		exit
	done </etc/fstab
	[[ -s /mnt/var/db/host.random ]] && \
	    cat /mnt/var/db/host.random >/dev/arandom
}

# Preen all filesystems in /etc/fstab that have a /sbin/fsck_XXX,
# showing individual results, but skipping $ROOTDEV. This was already
# fsck'ed successfully.
#
# Exit if any fsck's fail (but do them all before exiting!).
check_fs() {
	typeset _dev _mp _fstype _rest _fail

	echo "Checking non-root filesystems..."

	while read _dev _mp _fstype _rest; do
		[ "$_dev" != /dev/"$ROOTDEV" ] || continue
		[ -f "/sbin/fsck_$_fstype" ] || continue
		# Make sure device exists before fsck'ing it.
		_rest=${_dev#/dev/}
		makedev ${_rest%[a-p]} || continue
		echo -n "fsck -p ${_dev}..."
		if ! fsck -fp ${_dev} >/dev/null 2>&1; then
			echo "FAILED. You must fsck $_dev manually."
			_fail=y
		else
			echo "OK."
		fi
	done </etc/fstab

	echo "...done."

	[ "$_fail" ] && exit
}

# Extract fully qualified domain name from current hostname. If none is
# currently set, use 'my.domain'.
get_fqdn() {
	typeset _dn

	_dn=$(hostname)
	_dn=${_dn#$(hostname -s)}
	_dn=${_dn#.}

	echo "${_dn:=my.domain}"
}

donetconfig() {
	typeset _dn _ns _n

	configure_ifs

	# As dhclient will populate /etc/resolv.conf, a symbolic link to
	# /tmp/resolv.conf.shadow, mv any such file to /tmp/resolv.conf
	# so it will eventually be copied to /mnt/etc/resolv.conf and will
	# not in the meantime remove the user's ability to choose to use it
	# or not, during the rest of the install.
	if [ -f /tmp/resolv.conf.shadow ]; then
		mv /tmp/resolv.conf.shadow /tmp/resolv.conf
		# Get nameserver address(es). Store as a blank separated list.
		for _n in $(grep '^nameserver ' /tmp/resolv.conf); do
			[[ $_n = nameserver ]] || _ns="$_ns$_n "
		done
		# Zap trailing space in _ns.
		set -- $_ns
		_ns=$*
		# Get default fully qualified domain name from *first* domain
		# given on *last* search or domain statement.
		_dn=$(sed -n \
			-e '/^domain[[:space:]][[:space:]]*/{s///;s/\([^[:space:]]*\).*$/\1/;h;}' \
			-e '/^search[[:space:]][[:space:]]*/{s///;s/\([^[:space:]]*\).*$/\1/;h;}' \
			-e '${g;p;}' /tmp/resolv.conf)
	fi

	# Get/Confirm an IPv4 default route if an IPv4 address was configured.
	[[ -n $(ifconfig -a | sed -ne '/[ 	]inet .* broadcast /p') ]] && v4_defroute

	# Get & apply fully qualified domain name to hostname.
	ask "DNS domain name? (e.g. 'bar.com')" "${_dn:=$(get_fqdn)}"
	hostname "$(hostname -s).$resp"

	# Use a potential IPv4 default route as default nameserver
	[[ -n $_ns ]] || _ns=$(route -n show -inet | \
	    sed -ne '/^default */{s///; s/ .*//; p;}')

	# Get/Confirm nameservers, and construct appropriate resolv.conf.
	ask "DNS nameserver? (IP address or 'none')" "${_ns:=none}"
	if [[ $resp != none ]]; then
		echo '# $MirSecuCron$' >/tmp/resolv.conf
		echo "lookup file bind" >>/tmp/resolv.conf
		for _ns in $resp; do
			echo "nameserver $_ns" >>/tmp/resolv.conf
		done
		ask_yn "Use the nameserver now?" yes
		[[ $resp = y ]] && cp /tmp/resolv.conf /tmp/resolv.conf.shadow
	fi

	edit_tmp_file hosts
	manual_net_cfg
}

questions() {
	[[ -e /mnt/etc/rc.conf.local ]] && mv -f /mnt/etc/rc.conf.local \
	    /mnt/etc/rc.conf.local~

	ask_yn "Start sshd(8) by default?" yes
	if [[ $resp = n ]]; then
		echo "sshd_flags=NO		# disabled during install" \
		    >>/mnt/etc/rc.conf.local~
	fi

	ask_yn "Start ntpd(8) by default?" yes
	if [[ $resp = y ]]; then
		echo "ntpd_flags=		# enabled during install" \
		    >>/mnt/etc/rc.conf.local~
		echo "rdate_flags='-nv ntp.mirbsd.org'" >>/mnt/etc/rc.conf.local~
	fi

	if [[ -e /mnt/etc/rc.conf.local~ ]]; then
		(echo '# $MirSecuCron$'; echo; cat /mnt/etc/rc.conf.local~) \
		    >/mnt/etc/rc.conf.local
		rm -f /mnt/etc/rc.conf.local~
	fi

	if [[ -n $MDXAPERTURE ]]; then
		echo 'This setting affects the machdep.allowaperture sysctl.'
		echo 'If you respond negatively, you must enable it later in'
		echo '/etc/sysctl.conf in order to be able to run XFree86(R).'
		if [[ -e /mnt/usr/X11R6/bin/X ]]; then
			resp=yes
		else
			resp=no
		fi
		ask_yn "Do you expect to run the X Window System?" $resp
		if [[ $resp = y ]]; then
			sed -e "/^#\(machdep\.allowaperture=${MDXAPERTURE}\)/s//\1	/" \
			    /mnt/etc/sysctl.conf >/tmp/sysctl.conf
			cp /tmp/sysctl.conf /mnt/etc/sysctl.conf
		fi
	fi

	echo 'The size for the RSA host key can now be selected here. Larger'
	echo 'key sizes usually mean more security, but always imply much'
	echo 'longer key exchange times (e.g. mail delivery, ssh login), so'
	echo 'they are not recommended for old boxen (say, a SPARCstation)'
	echo 'or if you have to communicate with them very often; choose a'
	echo 'lower size (e.g. 2048) than the default of 4096 then.'
	ask_which "size" "should the RSA host key have" \
	    "2048 3072 4096 6144 8192" 4096
	[[ $resp = done ]] || print "/4096/s//$resp/\nwq" | ed -s /mnt/etc/rc

	[[ -z $SERIALDEV ]] && return
	ask_yn "Change the default console to $SERIALDEV?"
	[[ $resp = n ]] && return
	ask_which "speed" "should $SERIALDEV use" "9600 19200 38400 57600 115200"
	[[ $resp = done ]] && return
	echo '# $MirSecuCron$' >/mnt/boot.cfg
	echo "set tty $SERIALDEV\nstty $SERIALDEV $resp" >>/mnt/boot.cfg
}

finish_up() {
	typeset _dev _mp _fstype _rest

	# Mount all known swap partitions.  This gives systems with little
	# memory a better chance at running 'MAKEDEV all'.
	if [[ -x /mnt/sbin/swapctl ]]; then
		/mnt/sbin/swapctl -a /dev/$SWAPDEV >/dev/null 2>&1
		# Can't do chmod && swapctl -A because devices are not yet
		# created on install'ed systems. On upgrade'ed system there
		# is a small chance the device does not exist on the ramdisk
		# and will thus not get mounted.
		while read _dev _mp _fstype _rest; do
			[[ $_fstype = swap ]] && \
			    /mnt/sbin/swapctl -a $_dev >/dev/null 2>&1
		done </mnt/etc/fstab
	fi

	[[ ! -s /mnt/etc/ttys && -s /mnt/etc/ttys.dist ]] && \
	    cp /mnt/etc/ttys.dist /mnt/etc/ttys

	if grep -qs '^rtsol' /mnt/etc/hostname.*; then
		sed -e "/^#\(net\.inet6\.ip6\.accept_rtadv\)/s//\1/" \
		    /mnt/etc/sysctl.conf >/tmp/sysctl.conf
		cp /tmp/sysctl.conf /mnt/etc/sysctl.conf
	fi

	echo -n "Making all device nodes..."
	cd /mnt/dev
	mksh MAKEDEV all
	# Make sure any devices we found during probe are created in the
	# installed system.
	for _dev in $DKDEVS $CDDEVS $MTDEVS; do
		mksh MAKEDEV $_dev
	done
	echo "done."
	cd /

	if [[ -e $ROOTDISK ]]; then
		_dev=$ROOTDISK
	else
		_dev=/dev/r${ROOTDISK}c
	fi
	# use extracted mdec if it exists (may be newer)
	if [ -e /mnt/usr/mdec/boot ]; then
		_prefix=/mnt/usr/mdec
	elif [ -e /usr/mdec/boot ]; then
		_prefix=/usr/mdec
	else
		_prefix=
	fi
	if [[ ! -e $_dev ]]; then
		print Cannot install bootblocks to "'$ROOTDISK'".
		print You must run installboot manually.
	elif [[ -z $_prefix ]]; then
		print No boot block prototypes found.
		print You must run installboot manually.
	else
		print Installing boot block...
		cat ${_prefix}/boot >/mnt/boot
		chmod 0 /mnt/boot
		sync; sync; sync
		${_prefix}/installboot -v /mnt/boot ${_prefix}/bootxx $_dev
		print done.
	fi

	[ -x /mnt/$MODE.fixes ] && /mnt/usr/sbin/chroot /mnt /$MODE.fixes
	[ -x /mnt/$MODE.site ] && /mnt/usr/sbin/chroot /mnt /$MODE.site

	# Pat on the back.
	cat <<__EOT

CONGRATULATIONS! Your MirBSD $MODE has been successfully completed!
To boot the new system, enter halt at the command prompt. Once the
system has halted, reset the machine and boot from the disk.

Hello there! We from the MirOS project would like to hear from you!
If you installed or updated your existing MirOS installation, which
architecture, maybe your country, a dmesg and a few words about how
you like MirOS. If you don't mind being counted to help us estimate
our userbase, mail to <miros-dev@mirbsd.de> - thanks in advance!
__EOT

	md_congrats
}

# #######################################################################
#
# Initial actions common to both installs and upgrades.
#
# Some may require machine dependent routines, which may
# call functions defined above, so it's safest to put this
# code here rather than at the top of the file.
#
# #######################################################################

ulimit -c 0

ROOTDISK=
ROOTDEV=

VERSION=10

# FTPDIR: prefix for SETDIR for ftp/http/https installs
FTPDIR="MirOS/"
#SETDIR="v${VERSION}/$ARCH"
#OBSD="MirOS BSD #$VERSION/$ARCH"
SETDIR="current/$ARCH"
OBSD="MirOS BSD #$VERSION-current/$ARCH"
SERVERLIST=/tmp/serverlist

# Do not limit ourselves during installs or upgrades.
for _opt in d f l m n p s; do
	ulimit -$_opt unlimited
done

# Scan /var/run/dmesg.boot for interesting devices.
DKDEVS=$(scan_dmesg "${MDDKDEVS:-/^r*a*[swi]d[0-9][0-9]* /s/ .*//p}")
CDDEVS=$(scan_dmesg "${MDCDDEVS:-/^cd[0-9][0-9]* /s/ .*//p}")
MTDEVS=$(scan_dmesg "${MDMTDEVS:-/^[cmsw]t[0-9][0-9]* /s/ .*//p}")
SERIALDEV=$(get_serialdev)

# Selected sets will be installed in the order they are listed in $THESETS.
# Ensure that siteXX.ngz is the *last* set listed so its contents overwrite
# the contents of the other sets, not the other way around. Similarly fixes
# must be second-to-last.
THESETS="bsd bsd.rd $MDSETS"
DEFAULTSETS="bsd"
for _set in base etc gnu dev ada xbase xetc xfont xserv unfree pkgutl ports \
    source xfree pkgsrc psbsk fixes site; do
	[[ $MODE = upgrade && $_set = @(?(x)etc|ports|source|xfree|pkgsrc|psbsk) ]] && continue
	THESETS="$THESETS ${_set}${VERSION}.ngz"
	isin $_set base etc gnu dev fixes && \
	    DEFAULTSETS="$DEFAULTSETS ${_set}${VERSION}.ngz"
done
# Since etc${VERSION}.ngz is not in DEFAULTSETS for upgrades, it can always be
# in SANESETS.
SANESETS="bsd base${VERSION}.ngz etc${VERSION}.ngz"

# decide upon an editor
: ${EDITOR:=ed}
[[ -x /usr/bin/vi ]] && EDITOR=vi
export EDITOR

# umount all filesystems, just in case we are re-running install or upgrade.
[[ -f /etc/fstab ]] && umount -avt nomfs 1>/dev/null 2>&1
umount -v /mnt 1>/dev/null 2>&1

# Introduce ourselves.
welcome

# Get ROOTDISK, ROOTDEV and SWAPDEV.
if [[ $MODE = install && ! -f /etc/fstab ]]; then
	cat <<__EOT

You will now initialise the disk(s) that MirBSD will use. To enable all
available security features you should configure the disk(s) to allow the
creation of separate filesystems for /, /tmp, /var, /usr, and /home.

__EOT
fi

set -- $DKDEVS
(( $# > 1 )) && _defdsk=done

ask_which "disk" "is the root disk" "$DKDEVS" "$_defdsk"
[[ $resp = done ]] && exit
makedev $resp || exit

ROOTDISK=$resp
ROOTDEV=${ROOTDISK}a
SWAPDEV=${ROOTDISK}b
