OpenBSD-4.6/distrib/miniroot/install.sub

#	$OpenBSD: install.sub,v 1.592 2009/07/09 23:45:20 deraadt Exp $
#	$NetBSD: install.sub,v 1.5.2.8 1996/09/02 23:25:02 pk Exp $
#
# Copyright (c) 1997-2009 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.
#
# 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.
#

# OpenBSD 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_installboot()	  - install boot-blocks on disk
#	md_prep_disklabel()	  - put an OpenBSD disklabel on the disk
#	md_consoleinfo()	  - set CDEV, CTTY, CSPEED, CPROM
#
# The following variables can be provided if required:
#	MDSETS	    - list of files to add to THESETS
#	MDTERM      - 'vt220' assumed if not provided
#	MDDKDEVS    - '/^[sw]d[0-9][0-9]* /s/ .*//p' assumed if not provided
#	MDCDDEVS    - '/^cd[0-9][0-9]* /s/ .*//p'    assumed if not provided
#	MDMTDEVS    - '/^[cms]t[0-9][0-9]* /s/ .*//p'
#	MDXAPERTURE - set machdep.allowaperture=value in sysctl.conf
. install.md

set_term() {
	local _layouts

	export TERM=${TERM:-${MDTERM:-vt220}}
	if [[ -n $CONSOLE ]]; then
		ask "Terminal type?" $TERM
		export TERM=$resp
	else
		[[ -x /sbin/kbd ]] || return
		_layouts=$(bsort $(kbd -l | egrep -v "^(user|tables|encoding)"))
		while :; do
			ask "Choose your keyboard layout ('?' or 'L' for list)" "default"
			case $resp in
			"?"|L|l) echo "Available layouts: $_layouts" ;;
			default) return ;;
			*)	kbd $resp && { echo $resp >/tmp/kbdtype ; return ; } ;;
			esac
		done
	fi
}

# Echo the file $1 to standard output, skipping any lines that begin with a
# '#'. i.e. strip comment lines from the file.
stripcom () {
	local _l

	[[ -f $1 ]] || return

	while read _l; do
		[[ -n ${_l%%#*} ]] && echo $_l
	done <$1
}

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

scan_disknames() {
	local _n _oifs=$IFS
	IFS=","
	bsort $(for _n in $(sysctl -n hw.disknames); do echo "$_n "; done | sed -ne "$1")
	IFS=$_oifs
}

get_dkdevs () {
	echo $(scan_disknames "${MDDKDEVS:-/^[sw]d[0-9][0-9]* /s/ .*//p}")
}

get_cddevs () {
	echo $(scan_disknames "${MDCDDEVS:-/^cd[0-9][0-9]* /s/ .*//p}")
}

get_ifdevs() {
	ifconfig \
		| egrep -v '^[[:space:]]|(bridge|enc|gif|gre|lo|pflog|pfsync|ppp|sl|tun)[[:digit:]]+:' \
		| sed -ne 's/^\(.*\):.*/\1/p'
}

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

mount_mnt2() {
	local _dev=$1 _opts _file=/tmp/parts.$1 _parts

	disklabel $_dev 2>/dev/null | grep '^  [a-p]: '	\
		| egrep -v "swap|unused" >$_file

	_parts=$(sed -e 's/^  \(.\): .*/\1/' $_file)
	set -- $_parts
	[[ $# == 0 ]] && { echo "No filesystems found on $_dev." ; return 1 ; }

	if isin "c" $_parts; then
		# Don't ask questions if 'c' contains a filesystem.
		resp=c
	elif [[ $# == 1 ]]; then
		# Don't ask questions if there's only one choice.
		resp=$1
	else
		# Display partitions with filesystems and ask which to use.
		cat /tmp/parts.$_dev
		ask_which "$_dev partition" "has the $MODE sets" \
			'$(disklabel '$_dev' 2>/dev/null | grep "^  [a-p]: " |
			egrep -v "swap|unused" |
			sed '\''s/^  \(.\): .*/\1/'\'')'
			
		[[ $resp == done ]] && return 1
	fi

	# Always mount msdos partitions with -s to get lower case names.
	grep -q "^  $resp: .*MSDOS" $_file && _opts="-s"
	mount -o ro,$_opts /dev/$_dev$resp /mnt2
}

# 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.
#    *Don't* interpret "\" as escape character.
askpass() {
	set -o noglob
	stty -echo
	read -r resp?"$1 "
	stty echo
	set +o noglob
	echo
}

# Make sure lock is initially released
rm -df /tmp/lock

# Acquire lock
lock() {
	while ! mkdir /tmp/lock 2>&- && sleep .1; do done
}

# Release lock
unlock() {
	rm -d /tmp/lock 2>&-
}

# Add trap to kill the listener process
retrap() {
	trap '>&- && kill -KILL $cppid 2>&-; echo; stty echo; exit 0' \
		INT EXIT TERM
}

# The dmesg listener will check for the existance of this file and send a
# signal to the child process if the dmesg output differs from the contents
# of that file
rm -f /tmp/update

# Start listener process looking for dmesg changes
(
	while :; do
		lock
		if test -e /tmp/update && [[ "`dmesg`" != "`cat /tmp/update`" ]]; then
			dmesg >/tmp/update
			kill -TERM 2>&- $$ || exit 1
		fi
		unlock
		sleep .5
	done
) |&
cppid=$!

# Kill the child on exit
retrap

# Issue a read into the global variable $resp. If the dmesg output is
# changed while inside this function, the current read will be aborted
# and the function will return a non-zero value. Normally, the caller
# will then reprint any prompt and call the function again.
_ask() {
	local _int _redo=0 _pid

	trap "_int=1" INT
	trap "_redo=1" TERM
	lock; dmesg >/tmp/update; unlock
	read resp
	lock; rm /tmp/update; unlock
	if (( _redo )); then
		stty raw
		stty -raw
	else
		case $resp in
		!)	echo "Type 'exit' to return to install."
			sh
			_redo=1
			;;
		!*)	eval "${resp#?}"
			_redo=1
			;;
		esac
	fi
	retrap
	(( _int )) && kill -INT $$
	return $_redo
}

# 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() {
	local _question=$1 _default=$2

	while :; do
		echo -n "$_question "
		[[ -z $_default ]] || echo -n "[$_default] "
		_ask && : ${resp:=$_default} && break
	done
}

# Ask for a password twice, saving the input in $_password
askpassword() {
	local _oifs=$IFS
	IFS=
	while :; do
		askpass "Password for $1 account? (will not echo)"
		_password=$resp

		askpass "Password for $1 account? (again)"
		# N.B.: Need quotes around $resp and $_password to preserve leading
		#       or trailing spaces.
		[[ "$resp" == "$_password" ]] && break

		echo "Passwords do not match, try again."
	done
	IFS=$_oifs
}

user_setup() {
	local _q="Setup a user? (enter a lower-case loginname, or 'no')"

	while :; do
		ask "$_q" no
		case $resp in
		n|no)	return ;;
		y|yes)	_q="No really, what is the lower-case loginname, or 'no'?"
			continue ;;
		root|daemon|operator|bin|smmsp|popa3d) ;;
		sshd|uucp|www|named|proxy|nobody|ftp) ;;
		[a-z]*([a-z0-9_]))
			(( ${#resp} <= 31 )) && break ;;
		esac
		echo "$resp is not a useable loginname."
	done
	user=$resp
	while :; do
		ask "Full user name for $user?" $user
		case $resp in
		*[:\&,]*)
			echo "':', '&' or ',' are not allowed." ;;
		*)
			(( ${#resp} <= 100 )) && break
			echo "Too long." ;;
		esac
	done
	username=$resp

	askpassword $user
	userpass=$_password

	if [[ $sshd == y ]]; then
		ask_yn "Since you set up a user, disable sshd(8) logins to root?" yes
		sshd_disableroot=$resp
	fi

}

# 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() {
	local _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
#
# N.B.! $3 and $4 will be "expanded" using eval, so be sure to escape them
#       if they contain spooky stuff
#
# At exit $resp holds selected item, or 'done'
ask_which() {
	local _name=$1 _query=$2 _list=$3 _def=$4 _dynlist _dyndef

	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.
		eval "_dynlist=\"$_list\""
		eval "_dyndef=\"$_def\""

		# Clean away whitespace and determine the default
		set -o noglob
		set -- $_dyndef; _dyndef="$1"
		set -- $_dynlist; _dynlist="$*"
		set +o noglob
		(( $# < 1 )) && resp=done && return

		: ${_dyndef:=$1}
		echo "Available ${_name}s are: $_dynlist."
		echo -n "Which one $_query? (or 'done') "
		[[ -n $_dyndef ]] && echo -n "[$_dyndef] "
		_ask || continue
		[[ -z $resp ]] && resp="$_dyndef"

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

# test the first argument against the remaining ones, return success on a match
isin() {
	local	_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() {
	local	_a=$1

	shift

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

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

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

bsort() {
	local _l _a=$1 _b

	[[ $# -gt 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
}

# show a list (passed via ordered arguments) in column output using ls
showcols() {
	local _l _cdir=/tmp/cdir
	set -A _clist
	mkdir -p $_cdir
	rm -rf -- $_cdir/*
	while read _l; do
		[ "$_l" ] || continue
		mkdir -p /tmp/cdir/"$_l"
		_clist[${#_clist[*]}]="$_l"
	done
	(cd $_cdir; ls -Cdf "${_clist[@]}")
	rm -rf -- $_cdir
}

# Offer to shell out for manual network configuration, and do so if
# the user accepts the offer.
manual_net_cfg() {
	ask_yn "Do you want to do any manual network configuration?"

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

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

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

	(cd /dev ; sh 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() {
	local _addr=$1 _name=$2 _delim="."

	[[ -z $_addr || -z $_name ]] && return

	[[ $_addr == *:* ]] && _delim=":"

	sed "/^[0-9a-fA-F]*[$_delim].*[ 	]$_name\$/d" /tmp/hosts \
		>/tmp/hosts.new 2>/dev/null
	mv /tmp/hosts.new /tmp/hosts

	echo "$_addr $_name" >>/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() {
	local _avail=$1 _selected=$2 _f _action _col=$COLUMNS
	# account for 4 spaces added to the sets list
	let COLUMNS=_col-8

	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
		for _f in $_avail; do
			isin $_f $_selected && echo "[X] $_f" || echo "[ ] $_f"
		done | showcols | sed 's/^/    /'
		ask "Set name(s)? (or 'abort' or 'done')" done

		set -o noglob
		for resp in $resp; do
			case $resp in
			abort)	_selected=; break 2 ;;
			done)	break 2 ;;
			-*)	_action=rmel ;;
			*)	_action=addel ;;
			esac
			resp=${resp#[+-]}
			[[ $resp = all ]] && resp=*

			for _f in $_avail; do
				[[ $_f = $resp ]] && _selected=$($_action $_f $_selected)
			done
		done
	done

	set +o noglob
	COLUMNS=$_col

	resp=$_selected
}

configure_ifs() {
	local _first _ifdevs _ifs _name _hn _vl=0 _vd _vi _p _tags

	# In case of restart, discover last vlan configured.
	while :; do
		_vd=$(ifconfig vlan$_vl 2>&1)
		[[ $_vd == @(*no such interface*) ]] && break
		[[ $_vd == @(vlan$_vl: flags=0<>*) ]] && break
		: $(( _vl++ ))
	done
	_vd=

	while :; do
		# Create new vlan if possible.
		ifconfig vlan$_vl create >/dev/null 2>&1
		ask_which "network interface" "do you wish to configure" \
			'$(get_ifdevs)' \
			${_p:-'$( (ifconfig netboot 2>/dev/null | sed -n '\''1s/:.*//p'\''; get_ifdevs) | sed q )'}
		[[ $resp == done ]] && break

		_ifs=$resp
		_hn=/tmp/hostname.$_ifs
		rm -f $_hn

		# If the offered vlan is chosen, ask the relevant
		# questions and bring it up
		if [[ $_ifs == vlan[0-9]* ]]; then
			# Get existing tag for this vlan.
			_vi=$(ifconfig $_ifs 2>/dev/null | \
				sed -n 's/vlan: \([0-9]*\).*/\1/p')
			# Get list of all in-use tags.
			_tags=$(ifconfig vlan 2>/dev/null | \
				sed -n 's/vlan: \([0-9]*\).*/\1/p')
			# Current tag is a valid tag for this vlan.
			[[ -n $_tags ]] && _tags=$(rmel "$_vi" $_tags)
			if [[ -z $_vi ]]; then
				_vi=0
				while (( (_vi += 1) < 4096 )); do
					! isin "$_vi" $_tags && break	
				done
			fi
			_ifdevs=$(get_ifdevs)
			set -- $_ifdevs
			while [[ $1 == vlan[0-9]* ]]; do
				shift
			done
			ask "Which interface:tag should $_ifs be on?" "${_vd:=$1}:$_vi"
			_vd=${resp%%:*}
			_vi=${resp##*:}

			# Validate that $_vd is a real interface
			if ! (isin "$_vd" $_ifdevs && [[ $_vd != vlan[0-9]* ]]); then
				echo "Invalid interface choice '$_vd'"
				_vd=
				continue
			fi

			# Validate range of $_vi as 1-4095, and $_vi not in use.
			if (( _vi < 1 || _vi > 4095 )) || isin "$_vi" $_tags; then
				echo "Invalid or in-use vlan tag '$_vi'"
				continue
			fi

			# hostname.$_vd must say something, anything, to
			# make sure it is up.
			grep -qs "^up" /tmp/hostname.$_vd || \
				echo "up" >>/tmp/hostname.$_vd
			ifconfig $_vd up

			# Make sure a hostname.$_ifs is created with this info.
			ifconfig $_ifs destroy >/dev/null 2>&1
			ifconfig $_ifs vlan $_vi vlandev $_vd
			echo "vlan $_vi vlandev $_vd" >>$_hn
			# Create a new vlan if we just configured the highest.
			[[ ${_ifs##vlan} == $_vl ]] && (( _vl += 1 ))
		fi

		# First interface configured will use the hostname without
		# asking the user.
		resp=$(hostname -s)
		[[ -n $_first && $_first != $_ifs ]] && \
			ask "Symbolic (host) name for $_ifs?" $resp
		_name=$resp

		v4_config $_ifs $_name $_hn
		v6_config $_ifs $_name $_hn

		if [[ -f $_hn ]]; then
			chmod 640 $_hn
			(( nifs += 1 ))
			: ${_first:=$_ifs}
			_p=done
		fi
	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() {
	local _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

	ifconfig $_ifs group dhcp >/dev/null 2>&1
	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
}

# Convert a hex value to dotted decimal format
hextodec() {
	local _d _b

	for _b in $(echo ${1#0x} | sed 's/\(..\)/\1 /g'); do
		_d=$_d.$((0x$_b))
	done
	echo ${_d#.}
}

v4_config() {
	local _ifs=$1 _name=$2 _hn=$3 _prompt _addr _mask

	if ifconfig $_ifs | grep 'groups:.* dhcp' >/dev/null 2>&1; then
		_addr=dhcp
	else
		set -- $(v4_info $_ifs)
		if [[ -n $2 ]]; then
			_addr=$2; _mask=$(hextodec $3)
			ifconfig $_ifs inet $_addr delete
		fi
	fi

	if [[ -x /sbin/dhclient ]]; then
		_prompt="or 'dhcp' "
		# Don't make 'dhcp' the default if dhcp was already used.
		ifconfig dhcp >/dev/null 2>&1 || _addr=dhcp
	fi
	_prompt="IPv4 address for $_ifs? (${_prompt}or 'none')"

	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
			# Add hosts entry. Overwrites previous entry if any.
			set -- $(v4_info $_ifs)
			addhostent "$2" "$_name"
			echo "dhcp" >>$_hn
			# Create a new bpf in case we start another dhclient
			makedev bpf$(ls /dev | grep -c "^bpf[0-9]")
		fi
		;;
	*)	_addr=$resp
		ask_until "Netmask?" "${_mask:=255.255.255.0}"
		ifconfig $_ifs -group dhcp >/dev/null 2>&1
		if ifconfig $_ifs inet $_addr netmask $resp up ; then
			addhostent "$_addr" "$_name"
			echo "inet $_addr $resp" >>$_hn
		fi
		;;
	esac
}

v6_config() {
	local _ifs=$1 _name=$2 _hn=$3 _addr _prefixlen _prompt

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

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

	[[ -x /sbin/rtsol ]] && _prompt="or 'rtsol' "
	_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 ; }
		ifconfig $_ifs up
		if rtsol -F $_ifs; then
			set -- $(v6_info $_ifs)
			addhostent "$2" "$_name"
			echo "up\nrtsol" >>$_hn
		fi
		return
		;;
	esac

	_addr=$resp
	ask_until "IPv6 prefix length for $_ifs?" "${_prefixlen:=64}"
	ifconfig $_ifs inet6 $_addr prefixlen $resp up || return
	echo "inet6 $_addr $resp" >>$_hn
	addhostent "$_addr" "$_name"

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

v4_defroute() {
	local _dr _prompt=" or 'none'"

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

	# If only one interface, and it is running dhclient, ask nothing
	[[ -f /tmp/dhclient.conf && $nifs == 1 ]] && return

	[[ -x /sbin/dhclient ]] && _prompt=", 'dhcp'$_prompt"
	_prompt="Default IPv4 route? (IPv4 address$_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 delete -inet default >/dev/null 2>&1
		route -n add -inet -host default "$resp" && { echo "$resp" >/tmp/mygate ; break ; }
		# Put the old default route back. The new one did not work.
		route -n add -inet -host default $_dr >/dev/null 2>&1
	done
}

v6_defroute() {
	local _if=$1 _routers _oifs

	if [[ -z $(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() {
	local _f _gw

	# Copy any network configuration files. N.B.: hosts already copied.
	for _f in dhclient.conf resolv.conf resolv.conf.tail; do
		if [ -f /mnt/etc/$_f ]; then
			cp /mnt/etc/$_f /etc/$_f
		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
		(( nifs += 1 ))

		# Strip off /mnt/etc/hostname. prefix
		if=${hn#/mnt/etc/hostname.}

		# Check for ifconfig'able interface.
		(ifconfig $if||ifconfig $if create)> /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 -- $cmd2
				af=$1 name=$2 mask=$3 bcaddr=$4 ext1=$5 cmd2=
				# make sure and get any remaining args in ext2, like the read below
				i=1; while [ i -lt 6 -a -n "$1" ]; do shift; let i=i+1; done
				ext2="$@"
			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
			"#"*|"!"*|"bridge"|"")
				# skip comments, user commands, bridges,
				# and empty lines
				continue
				;;
			"dhcp")	[ "$name" = "NONE" ] && name=
				[ "$mask" = "NONE" ] && mask=
				[ "$bcaddr" = "NONE" ] && bcaddr=
				dhcpif="$dhcpif $if"
				cmd="ifconfig $if $name $mask $bcaddr $ext1 $ext2 down"
				cmd="$cmd; dhclient $if"
				# Create a new bpf in case we start another dhclient
				makedev bpf$(ls /dev | grep -c "^bpf[0-9]")
				;;
			"rtsol")
				rtsolif="$rtsolif $if"
				cmd="ifconfig $if $name $mask $bcaddr $ext1 $ext2 up"
				;;
			"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 [ -z "$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

	[[ -n $rtsolif ]] && /mnt/sbin/rtsol -F $rtsolif

	# /mnt/etc/mygate, if it exists, contains the address(es) of my
	# default gateway(s). Use for ipv4 if no interfaces configured via
	# dhcp. Use for ipv6 if no interfaces configured via rtsol.
	[[ -z $dhcpif ]] && stripcom /mnt/etc/mygate | while read _gw; do
		[[ $_gw == @(*:*) ]] && continue
		route -qn delete default >/dev/null 2>&1
		route -qn add -host default $_gw && break
	done
	[[ -z $rtsolif ]] && stripcom /mnt/etc/mygate | while read _gw; do
		[[ $_gw == !(*:*) ]] && continue
		route -qn delete -inet6 default >/dev/null 2>&1
		route -qn add -host -inet6 default $_gw && break
	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
}

# 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() {
	local _src=$1 _files=$2 _f _sets _get_sets _n _col=$COLUMNS

	# 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 "site$VERSION-$(hostname -s).tgz" && \
			_get_sets=$(addel $_f $_get_sets)
	done

	if [[ -z $_sets ]]; then
		# Show $_src, but delete any ftp password.
		echo -n "Looked at "
		echo $_src | sed -e 's/\(^ftp:\/\/[^/]*\)\(:[^/]*\)\(@.*\)/\1\3/'
		echo "and found no $OBSD sets.  The set names looked for were:"

		let COLUMNS=_col-8
		for _n in $THESETS; do echo $_n; done | showcols | sed 's/^/    /'
		COLUMNS=$_col

		echo
		return
	fi

	resp=y
	isin INSTALL.$ARCH $_files ||
		ask_yn "INSTALL.$ARCH not found. Use sets found here anyway?"
	[[ $resp = n ]] && return

	select_sets "$_sets" "$_get_sets"

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

	[[ $resp = n ]] && return

	shacmd="cat"
	[[ -x /bin/sha256 ]] && shacmd="sha256 /tmp/h"

	for _f in $THESETS ; do
		isin $_f $_get_sets || continue
		echo -n "Getting $_f ..."
		rm -f /tmp/h
		case $_f in
		*.tgz)	ftp $FTPOPTS -o - -m "$_src/$_f" | \
				$shacmd | tar zxphf - -C /mnt
			;;
		*)	ftp $FTPOPTS -o - -m "$_src/$_f" | \
				$shacmd > "/mnt/$_f"
			;;
		esac
		if [ $? -ne 0 ]; then
			echo "'$_f' did not install correctly."
		elif [ -f /tmp/h -a -f "/var/hash/$_f" ]; then
			if [ "$(</tmp/h)" != "$(</var/hash/$_f)" ]; then
				echo "The SHA256 hash $(cat /tmp/h)"
				echo "for $_f did not match what this bsd.rd expected."
				# XXX should mark failure somehow
			fi
			DEFAULTSETS=$(rmel $_f $DEFAULTSETS)
			GOTSETS="$GOTSETS $_f"
		else
			DEFAULTSETS=$(rmel $_f $DEFAULTSETS)
			GOTSETS="$GOTSETS $_f"
		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
#	   embedded blanks in usercodes and passwords.
#	2) substitute '%' FIRST so it doesn't eliminate '%' chars we insert.
encode_for_url() {
	echo "$1" | sed -e "
s/%/%25/g
s/ /%20/g
s/!/%21/g
s/\"/%22/g
s/#/%23/g
s/\\\$/%24/g
s/&/%26/g
s/'/%27/g
s/(/%28/g
s/)/%29/g
s/\*/%2a/g
s/+/%2b/g
s/,/%2c/g
s/-/%2d/g
s/\./%2e/g
s/\//%2f/g
s/:/%3a/g
s/;/%3b/g
s/</%3c/g
s/=/%3d/g
s/>/%3e/g
s/?/%3f/g
s/@/%40/g
s/\[/%5b/g
s/\\\\/%5c/g
s/]/%5d/g
s/\^/%5e/g
s/_/%5f/g
s/\`/%60/g
s/{/%7b/g
s/|/%7c/g
s/}/%7d/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
}

startftplist() {
	# If no networks are configured, we do not need the ftplist file
	(( nifs < 1 )) && return

	# ftp.openbsd.org == 129.128.5.191 and will remain at
	# that address for the foreseeable future.
	ftp $FTPOPTS -a -o - "http://129.128.5.191/cgi-bin/ftplist.cgi?path=$FTPSETDIR" \
		2>/tmp/ftplisterr > $SERVERLISTALL & ftppid=$!

	# If the ftp process takes more than 12 seconds, kill it
	# XXX We are relying on the pid space not randomly biting us --
	# XXX ftp could terminate early, and the pid could be reused
	(sleep 12; kill -INT $ftppid >/dev/null 2>&1) &
}

# Wait for the ftp process to finish, or be killed after the timeout
# XXX contains a bit of debug code for now
waitftplist() {
	local _dot						# XXX

	[[ -z $ftppid ]] && return
	while [[ -n $(jobs $ftppid 2>/dev/null) ]]; do
		echo -n .					# XXX
		_dot=.						# XXX
		sleep 0.2					# XXX
	done
	[[ -n $_dot ]] && echo					# XXX
}

# Get several parameters from the user, and xfer
# files from the server.
# $1 = url type (ftp or http)
# Note:	_ftp_server_ip, _ftp_server_dir, _ftp_server_login,
#	and FTPOPTS must be global.
install_url() {
	local _url_type=$1 _file_list _url_base _oifs _prompt _passwd
	local _server_ip_var=_${_url_type}_server_ip \
		_server_dir_var=_${_url_type}_server_dir

	waitftplist
	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

	if [[ -s $SERVERLISTALL ]]; then
		_prompt="Server? (hostname, list#, 'done' or '?')"
		sed -n "s,^${_url_type}://"'\([[A-Za-z0-9\:_][]A-Za-z0-9:._-]*\),\1,p' \
			$SERVERLISTALL > $SERVERLIST
		set -- $(sed q $SERVERLIST)
		eval $_server_ip_var=\${1%%/*}
	else
		echo "(Was not able to get ftplist from ftp.openbsd.org, but that is OK)"
		_prompt="Server? (hostname or 'done')"
	fi

	# Get server IP address or hostname
	while :; do
		eval resp=\$$_server_ip_var
		ask_until "$_prompt" "$resp"
		case $resp in
		done)	return ;;
		"?")	[[ -s $SERVERLIST ]] || continue
			less -XEN < $SERVERLIST
			;;
		+([0-9]))
			# A numeric hostname is ignored. A number is only used
			# as a line number in $SERVERLIST.
			[[ -s $SERVERLIST ]] || continue
			set -- $(sed -n "${resp}p" $SERVERLIST)
			[[ $# -lt 1 ]] && { echo "There is no line $resp." ; continue ; }
			eval $_server_ip_var=\${1%%/*}
			# Repeat loop to get user to confirm server address.
			;;
		+([A-Za-z0-9\:.\[\]_-]))
			eval $_server_ip_var=\$resp
			break
			;;
		*)	echo "'$resp' is not a valid hostname."
		esac
	done

	# Get directory info from *last* line starting with the server
	# name. This means the last install from a mirror will not keep
	# the specific directory info. But an install from a local
	# server *will* remember the specific directory info.
	set -- $(eval grep -s "^\$$_server_ip_var" $SERVERLIST | sed '$!d') 
	resp=${1#*/}
	# If there is no directory specified, don't use the server name!
	[[ $resp == "$1" ]] && resp=
	if (( $# > 1 )); then
		# It's a mirror, since it has location info.
		resp=$resp/$FTPSETDIR
	fi

	ask_until "Server directory?" "${resp:-pub/OpenBSD/$FTPSETDIR}"
	eval $_server_dir_var=\$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 login in 'anonymous' or 'ftp'
		if [[ $_ftp_server_login == @(anonymous|ftp) ]]; then
			_passwd=root@`hostname`
		else
			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\$$_server_ip_var/\$$_server_dir_var

	# 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 $FTPOPTS "$_url_base/")
		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 $FTPOPTS -o - "$_url_base/index.txt" | \
			sed -e 's/^.* //' | sed -e 's/
//')
	fi

	install_files "$_url_base" "$_file_list"

	# Remember where we installed from
	eval installedfrom=$_url_type://\$$_server_ip_var/\$$_server_dir_var
}

install_mounted_fs() {
	local _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() {
	get_drive "CD-ROM" '$(get_cddevs)' || return
	mount_mnt2 $resp || return

	install_mounted_fs
}

install_disk() {
	ask_yn "Is the disk partition already mounted?"
	if [[ $resp == n ]]; then
		get_drive "disk" '$(bsort $(get_dkdevs))' \
			'$(bsort $(rmel $ROOTDISK $(get_dkdevs)))' || return
		mount_mnt2 $resp || return
	fi

	install_mounted_fs
}

install_nfs() {
	local _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)"
	[[ $resp == y ]] && _tcp=-T

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

	install_mounted_fs
}

install_tape() {
	local _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 ${_z}xvphf - -C /mnt || return
	done
}

set_timezone() {
	local _zonefile=$1 _zonepath _zsed _tz _zoneroot=/usr/share/zoneinfo

	# If the timezone file is not available,
	# return immediately.

	[[ ! -f $_zonefile ]] && return

	# If configured in a previous call, return immediately
	[[ -n $TZ ]] && return

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

	waitftplist
	if [[ -s $SERVERLISTALL ]]; then
		_tz=$(sed -ne '/^TZ=/s/TZ=//p' <$SERVERLISTALL)
		[[ -n $_tz ]] && isin "$_tz" `cat $_zonefile` && TZ=$_tz
	fi

	# If neither the base or SERVERLIST gave a hint, and this is the
	# early question, give up, and ask after the sets are installed
	[[ $_zonefile = /var/tzlist && -z $TZ ]] && return

	while :; do
		_zonepath=""

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

		if [[ $resp = "?" ]]; then
			grep -v /. $_zonefile | showcols
			continue
		fi
		_zonepath=$_zonepath$resp

		while isin $_zonepath/ $(cat $_zonefile); do
			ask "What sub-timezone of '$_zonepath' are you in? ('?' for list)"
			_zsed=$(echo $_zonepath/ | sed 's,/,\\/,g')
			case $resp in
			"")	;;
			"?")	sed -n "/^$_zsed/{s/$_zsed//;/\/./!p;}" $_zonefile | showcols ;;
			*)	_zonepath=$_zonepath/$resp ;;
			esac
		done

		if isin $_zonepath $(cat $_zonefile); then
			TZ=${_zonepath#$_zoneroot}
			return
		fi

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

# Check that missing required sets were deliberately skipped.
sane_install() {
	local _q=$1 _s _m

	for _s in $SANESETS; do
		isin $_s $DEFAULTSETS || continue
		# If sane_install has no argument, harass the user.
		resp=n
		[[ -z $_q ]] && ask_yn "Are 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() {
	local _d _locs="disk ftp http"

	echo

	[[ -s $SERVERLISTALL ]] && \
		_d=$(sed -ne '/^method=/s/method=//p' $SERVERLISTALL)

	ifconfig netboot >/dev/null 2>&1 && : ${_d:=ftp}
	[[ -n $(get_cddevs) ]] && { _locs="cd $_locs" ; : ${_d:=cd} ; }
	[[ -x /sbin/mount_nfs ]] && _locs="$_locs nfs"
	[[ -n $MTDEVS && -x /bin/mt ]] && _locs="$_locs tape"
	: ${_d:=ftp}

	if ! isin "$_d" $_locs; then
		for a in ftp http cd nfs tape disk; do 
			isin $a $_locs && _d=$a && break
		done
	fi

	echo "Let's $MODE the sets!"
	while :; do
		umount -f /mnt2 >/dev/null 2>&1
		[[ -n $method ]] && _d=$method
		sane_install quiet && _d=done

		ask "Location of sets? ($_locs or 'done')" "$_d"
		case $resp in
		done)	sane_install && return ;;
		c*|C*)	isin cd $_locs && install_cdrom && method=cd ;;
		d*|D*)	install_disk && method=disk ;;
		f*|F*)	isin ftp $_locs && install_url ftp && method=ftp ;;
		h*|H*)	isin http $_locs && install_url http && method=http ;;
		n*|N*)	isin nfs $_locs && install_nfs && method=nfs ;;
		t*|T*)	isin tape $_locs && install_tape && method=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 on a svnd device.
#
# In addition,
#
#	1) delete 'softdep' options (no soft updates in ramdisk kernels),
#	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() {
	local _dev _mp _fstype _opt _rest

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

		# Remove any softdep options, as soft updates are not
		# available in the ramdisk kernels.
		_opt=$(echo $_opt | sed -e 's/softdep//')

		# Change read-only ffs to read-write since we'll potentially
		# write to these filesystems.
		[[ $_fstype == ffs ]] && _opt=$(echo $_opt | sed -e 's/ro/rw/')

		# 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() {
	local _async=$1 _dev _mp _fstype _opt _rest _err _msg

	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.
		_msg=$(mount -v -t $_fstype $_async -o $_opt $_dev $_mp)
		_err=$?
		echo $_msg | sed -e 's/, ctime=[^,)]*//'
		if ! $_e ; then
			# 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
		fi
	done </etc/fstab
}

# 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() {
	local _dev _mp _fstype _rest _fail _f

	ask_yn "Force checking of non-root filesystems?" yes
	[[ $resp == y ]] && _f=f

	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 -${_f}p $_dev..."
		if ! fsck -${_f}p $_dev >/dev/null 2>&1; then
			echo "FAILED. You must fsck $_dev manually."
			_fail=y
		else
			echo "OK."
		fi
	done </etc/fstab

	[ "$_fail" ] && exit
}

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

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

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

donetconfig() {
	local _dn _ns _n

	configure_ifs
	v4_defroute

	# 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 & apply fully qualified domain name to hostname.
	resp="${_dn:=$(get_fqdn)}"
	if [[ ! -f /tmp/dhclient.conf || $nifs != 1 ]]; then
		ask "DNS domain name? (e.g. 'bar.com')" "$resp"
	else
		echo "Using DNS domainname $resp"
	fi
	hostname "$(hostname -s).$resp"

	# If only one interface, and it is running dhclient, ask nothing
	# else Get/Confirm nameservers
	resp="${_ns:=none}"
	if [[ ! -f /tmp/dhclient.conf || $nifs != 1 || $resp == none ]]; then
		ask "DNS nameservers? (IP address list or 'none')" "$resp"
	else
		echo "Using DNS nameservers at $resp"
	fi
	# Construct appropriate resolv.conf.
	if [[ $resp != none ]]; then
		echo "lookup file bind" >/tmp/resolv.conf
		for _ns in $resp; do
			echo "nameserver $_ns" >>/tmp/resolv.conf
		done
		cp /tmp/resolv.conf /tmp/resolv.conf.shadow
	fi

	manual_net_cfg
}

populateusrlocal() {
	if [ -f /mnt/etc/mtree/BSD.local.dist ]; then
		/mnt/usr/sbin/chroot /mnt /usr/sbin/mtree -Uedqn -p /usr/local -f /etc/mtree/BSD.local.dist >/dev/null
	fi
}

apply()
{
	if [[ $sshd == n ]]; then
		echo "sshd_flags=NO		# disabled during install" \
			>>/mnt/etc/rc.conf.local
	fi
	if [[ $sshd_disableroot == y ]]; then
		sed -e "/^#\(PermitRootLogin\) yes/s//\1 no/" \
			< /mnt/etc/ssh/sshd_config > /tmp/sshd_config
		cp /tmp/sshd_config /mnt/etc/ssh/sshd_config
	fi
	if [[ $ntpd == y ]]; then
		echo "ntpd_flags=		# enabled during install" \
			>>/mnt/etc/rc.conf.local
		if [[ $ntpd_server != default ]]; then
			# Comment out the default 'servers' line, and add a
			# 'server' line with the first token in $resp as the
			# server.
			set -- $ntpd_server
			sed -e "s/^servers /#&/;/#server /a\\
server $1
" /mnt/etc/ntpd.conf >/tmp/ntpd.conf
			cp /tmp/ntpd.conf /mnt/etc/ntpd.conf
		fi
	fi

	if [[ $x11 == 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

	if [[ $xdm == y && -x /mnt/usr/X11R6/bin/xdm ]]; then
		echo "xdm_flags=		# enabled during install" \
			>>/mnt/etc/rc.conf.local
	fi

	if [[ $defcons == y ]]; then
		cp /mnt/etc/ttys /tmp/ttys
		sed	-e "/^$CTTY/s/std.9600/std.${CSPEED}/" \
			-e "/^$CTTY/s/unknown/vt220	/" \
			-e "/$CTTY/s/off.*/on secure/" /tmp/ttys >/mnt/etc/ttys
		[[ -n $CPROM ]] && \
			echo "stty $CPROM $CSPEED\nset tty $CPROM" >>/mnt/etc/boot.conf
	fi

	ln -sf /usr/share/zoneinfo/$TZ /mnt/etc/localtime
}

questions() {
	local _d _xdmask=y _def

	ask_yn "Start sshd(8) by default?" yes
	sshd=$resp

	ask_yn "Start ntpd(8) by default?"
	ntpd=$resp
	if [[ $resp == y ]]; then
		ask "NTP server? (hostname or 'default')" default
		ntpd_server=$resp
	fi

	def=no
	[[ -n $DISPLAY ]] && def=yes
	if [[ -n $MDXAPERTURE ]]; then
		ask_yn "Do you expect to run the X Window System?" $def
		x11=$resp
		_xdmask=$resp	# if aperture was n, do not ask for xdm
	fi

	if [[ -n $MDXDM && $_xdmask == y ]]; then
		ask_yn "Do you want the X Window System to be started by xdm(1)?"
		xdm=$resp
	fi

	if [[ -n $CDEV ]]; then
		_d=${CPROM:-$CDEV}
		ask_yn "Change the default console to $_d?"
		defcons=$resp
		if [[ $resp == y ]]; then
			ask_which "speed" "should $_d use" \
				"9600 19200 38400 57600 115200" $CSPEED
			case $resp in
			done)	defcons=n ;;
			*)	CSPEED=$resp ;;
			esac
		fi
	fi
}

finish_up() {
	local _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

	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
	sh MAKEDEV all
	# Make sure any devices we found during probe are created in the
	# installed system.
	for _dev in $(get_dkdevs) $(get_cddevs) $MTDEVS ; do
		sh MAKEDEV $_dev
	done
	)
	echo "done."

	(cd /; md_installboot $ROOTDISK)
	cd /

	populateusrlocal

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

	# Pat on the back.
	cat <<__EOT

CONGRATULATIONS! Your OpenBSD $MODE has been successfully completed!
To boot the new system, enter 'reboot' at the command prompt.
__EOT
	[[ $MODE == install ]] && cat <<__EOT
When you login to your new system the first time, please read your mail
using the 'mail' command.
__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.
#
# #######################################################################

ROOTDISK=
ROOTDEV=

SETDIR="$VNAME/$ARCH"
FTPDIR="pub/OpenBSD/$VNAME"
FTPOPTS="-V"
SERVERLISTALL=/tmp/serverlistall
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

# Extract and save one boot's worth of dmesg
dmesg | sed -ne '/^OpenBSD /h;/^OpenBSD /!H;${g;p;}' >/var/run/dmesg.boot

# Are we in a real release, or a snapshot?  If this is a snapshot
# install media, default us to a snapshot directory.
FTPSETDIR=$SETDIR
CURRENT=$(scan_dmesg "/^OpenBSD $VNAME\([^ ]*\).*$/s//\1/p")
[[ -n $CURRENT ]] && FTPSETDIR=snapshots/$ARCH

# Scan /var/run/dmesg.boot for interesting devices.
MTDEVS=$(scan_dmesg "${MDMTDEVS:-/^[cms]t[0-9][0-9]* /s/ .*//p}")
nifs=0
DISPLAY=$(scan_dmesg '/^wsdisplay[0-9]* /s/ .*//p')

CONSOLE=$(scan_dmesg '/^\([^ ]*\).*: console$/s//\1/p')
CONSOLE=${CONSOLE% }
[[ -n $CONSOLE ]] && CSPEED=$(stty speed)

# Look for the serial device matching the console. If we are not installing
# from a serial console, just find the first serial device that could be used
# as a console. If a suitable device is found, set CDEV, CTTY, CSPEED, CPROM.
md_consoleinfo

# Selected sets will be installed in the order they are listed in $THESETS.
# Ensure that siteXX.tgz is the *last* set listed so its contents overwrite
# the contents of the other sets, not the other way around.
THESETS="bsd bsd.rd bsd.mp $MDSETS"
: ${DEFAULTSETS:="bsd bsd.rd"}
for _set in base etc misc comp man game xbase xetc xshare xfont xserv site ; do
	[[ $MODE == upgrade && ( $_set == etc || $_set == xetc ) ]] && continue
	THESETS="$THESETS ${_set}${VERSION}.tgz"
	if [[ -z $DISPLAY ]]; then
		# If we have no displays, skip the X sets
		isin $_set xbase xetc xshare xfont xserv site && continue
	fi
	isin $_set site && continue
	DEFAULTSETS="$DEFAULTSETS ${_set}${VERSION}.tgz"
done
# Since etc${VERSION}.tgz is not in DEFAULTSETS for upgrades, it can always be
# in SANESETS.
SANESETS="${SANESETS:-bsd} base${VERSION}.tgz etc${VERSION}.tgz"

# prepare COLUMNS sanely
COLUMNS=$(stty -a | sed -n '/columns/{s/^.* \([0-9]*\) columns.*$/\1/;p;}')
[ COLUMNS -eq 0 ] && COLUMNS=80

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

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

cat <<__EOT
At any prompt except password prompts you can escape to a shell by
typing '!'. Default answers are shown in []'s and are selected by
pressing RETURN.  You can exit this program at any time by pressing
Control-C, but this can leave your system in an inconsistent state.

__EOT

# Configure the terminal and keyboard.
set_term

if [[ $MODE == install ]]; then
	ask_until "System hostname? (short form, e.g. 'foo')" "$(hostname -s)"
	[[ ${resp%%.*} != $(hostname -s) ]] && hostname $resp
	THESETS="$THESETS site$VERSION-$(hostname -s).tgz"

	echo
	donetconfig

	(( nifs != 0 )) && startftplist

	echo
	while :; do
		askpassword root
		_rootpass="$_password"
		[[ -n "$_password" ]] && break
		echo "The root password must be set."
	done

	questions
	user_setup

	set_timezone /var/tzlist
	echo
fi

# Get ROOTDISK, ROOTDEV and SWAPDEV.
[[ -n $(get_dkdevs) ]] || { echo "No disks found." ; exit ; }

ask_which "disk" "is the root disk" '$(get_dkdevs)'
[[ $resp == done ]] && exit
makedev $resp || exit

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