[BACK]Return to install.sub CVS log [TXT][DIR] Up to [local] / src / distrib / miniroot

File: [local] / src / distrib / miniroot / install.sub (download)

Revision 1.337, Tue Mar 23 02:39:39 2004 UTC (20 years, 2 months ago) by krw
Branch: MAIN
CVS Tags: OPENBSD_3_5_BASE, OPENBSD_3_5
Changes since 1.336: +2 -2 lines

Update copyrights to 2004.

ok deraadt@.

#	$OpenBSD: install.sub,v 1.337 2004/03/23 02:39:39 krw Exp $
#	$NetBSD: install.sub,v 1.5.2.8 1996/09/02 23:25:02 pk Exp $
#
# Copyright (c) 1997-2004 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.
#

# 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_set_term()		  - set up terminal
#
# The following variables can be provided if required:
#	MDSETS	    - list of files to add to THESETS
#	MDTERM      - 'vt220' assumed if not provided
#	MDFSTYPE    - nothing assumed if not provided
#	MDFSOPTS    - nothing assumed if not provided
#	MDDISKDEVS  - '/^[sw]d[0-9][0-9]* /s/ .*//p' assumed if not provided
#	MDCDDEVS    - '/^cd[0-9][0-9]* /s/ .*//p'    assumed if not provided
#	MDXAPERTURE - if not empty, set machdep.allowaperture=value in sysctl.conf
. install.md

set_term() {
	[[ -n $TERM ]] && return
	ask "Terminal type?" ${MDTERM:-vt220}
	TERM=$resp
	export TERM

	md_set_term
}

welcome() {
	local _q

	cat << __EOT

Welcome to the $OBSD $MODE program.

This program will help you $MODE OpenBSD in a simple and rational way. 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 pressing RETURN. At any time you can exit this program by
pressing Control-C and then RETURN, but quitting during an $MODE can leave
your system in an inconsistent state.

__EOT

	# Configure the terminal.
	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: once your system has been upgraded, you must manually merge any changes
to files in the 'etc' set 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' at the prompt to gracefully exit OpenBSD. You can then
power cycle the machine and boot your original OS.
__EOT
		exit
	fi

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

get_dkdevs() {
	bsort `sed -ne "${MDDISKDEVS:-/^[sw]d[0-9][0-9]* /s/ .*//p}" /var/run/dmesg.boot`
}

get_cddevs() {
	bsort `sed -ne "${MDCDDEVS:-/^cd[0-9][0-9]* /s/ .*//p}" /var/run/dmesg.boot`
}

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

# 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() {
	local _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() {
	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 a device from a list generated by scanning
# /var/run/dmesg.boot, and make the device if it doesn't exist.
#
# $1 = device name (disk, cd, etc.)
# $2 = question to ask
# $3 = list of devices from /var/run/dmesg.boot scan
# $4 = default device. If it is not specified, use the first device in $3
# $5 = error message if no devices in $3, defaults to 'No $1s found.'
#
# $resp holds device selected at exit, or 'done'
ask_which() {
	local _name=$1 _query=$2 _devs=$3 _defdev=$4 _err=$5

	set -- $_devs
	if [[ $# -lt 1 ]]; then
		echo "${_err:=No ${_name}s found}."
		resp=done
		return
	fi
	: ${_defdev:=$1}

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

	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: ${_devs}.\nWhich one ${_query}? (or 'done')" "$_defdev"
		[[ $resp == done ]] && break

		# Quote $resp to prevent user from confusing isin() by
		# entering something like 'a a'.
		if isin "$resp" $_devs; then
			makedev $resp && break
		else
			echo "'$resp' is not a valid choice."
		fi
	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 _b _seen=false

	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 "$_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() {
	local _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() {
	local _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() {
	ask_yn "Do you want to do any manual network configuration?"

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

# 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
}

# Check for the existence of the device nodes for the
# supplied device name. If they are missing (as indicated
# by r${1}c not being found) then create them. In either
# case, return true if the nodes exist and false if not.
#
# $1 = name of the device that is about to be used.
makedev() {
	local _dev=$1 _node=/dev/r${1}c

	# Don't need to make network interface devices nodes. If the device
	# nodes exist, don't need to create them.
	if isin $_dev $IFDEVS || [[ -c $_node ]] ; then
		return 0
	fi

	if [[ ! -r /dev/MAKEDEV ]] ; then
		echo "No /dev/MAKEDEV. Can't create device nodes for ${_dev}."
		return 1
	fi

	(cd /dev; sh MAKEDEV $_dev)

	# If the device nodes still do not exist, assume MAKEDEV issued a useful
	# error message and return false.
	[[ -c $_node ]] || return 1

	DEVSMADE=`addel $_dev $DEVSMADE`
}

# Create an entry in the hosts file. If an entry with the
# same symbolic name already exists, delete it.
# $1 - IP address
# $2 - symbolic name
addhostent() {
	sed "/ $2\$/d" /tmp/hosts > /tmp/hosts.new
	mv /tmp/hosts.new /tmp/hosts

	echo "$1 $2" >> /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 _next _f _action

	while : ; do
		_action=
		cat << __EOT

The following sets are available. Enter a filename, 'all' to select
all the sets, or 'done'. You may de-select a set by prepending a '-'
to its name.

__EOT
		_next=
		for _f in $_avail; do
			if isin $_f $_selected; then
				echo "	[X] $_f"
			else
				echo "	[ ] $_f"
				: ${_next:=$_f}
			fi
		done
		: ${_next:=done}

		ask "\nFile 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_all_interfaces() {
	local _IFDEVS=$IFDEVS _ifs

	while : ; do
		_IFDEVS=`rmel "$_ifs" $_IFDEVS`

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

		_ifs=$resp
		configure_ifs $_ifs || _ifs=
	done
}

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

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

	echo "initial-interval 1;" > /etc/dhclient.conf

	if [[ -n $_hostname ]]; then
		echo "send host-name \"$_hostname\";" >> /etc/dhclient.conf
		echo "Issuing hostname-associated DHCP request for $_ifs."
	else
		echo "Issuing free-roaming DHCP request for $_ifs."
	fi

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

	cat >> /etc/resolv.conf.tail << __EOT
lookup file bind
__EOT

	dhclient $_ifs

	set -- $(inet_info $_ifs)

	if [[ $1 == UP && $2 == "0.0.0.0" ]]; then
		ifconfig $_ifs delete down
		rm /etc/dhclient.conf /etc/resolv.conf.tail
		return 1
	fi

	# 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
}

configure_ifs() {
	local _ifs=$1 _addr _mask _name _prompt _media _config

	set -- $(inet_info $_ifs)
	[[ $1 == UP ]] && ifconfig $_ifs delete down
	[[ -n $2 && $2 != "0.0.0.0" ]] && { _addr=$2; _mask=$3; }

	# 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 default media for $_ifs is
$(ifconfig -m $_ifs | sed -n '/supported/D;/media:/p')
__EOT
		ask_yn "Do you want to change the default media?"
		case $resp in
		y)	cat << __EOT
Supported media options for $_ifs are:
$_media
__EOT
			ask "Media options for $_ifs?"
			_media=$resp
			ifconfig $_ifs $_media down || return 1
			;;
		n)	_media=
			;;
		esac
	fi

	# Get address and mask.
	_prompt="IP address for ${_ifs}?"
	[[ -x /sbin/dhclient ]] && _prompt="$_prompt (or 'dhcp')"

	ask_until "$_prompt" "$_addr"
	case $resp in
	dhcp)	if [[ ! -x /sbin/dhclient ]]; then
			echo "DHCP not supported - no /sbin/dhclient found."
			return 1
		fi
		dhcp_request $_ifs "$_name" || dhcp_request $_ifs || return 1
		_config="dhcp NONE NONE"
		# Fake address for the hosts file.
		_addr=127.0.0.1
		;;
	*)	_addr=$resp
		ask_until "Netmask?" "${_mask:=255.255.255.0}"
		_mask=$resp
		ifconfig $_ifs inet $_addr netmask $_mask $_media up || return 1
		_config="inet $_addr $_mask"
		;;
	esac

	# Save configuration information.
	echo "$_config NONE $_media" > /tmp/hostname.$_ifs
	addhostent $_addr $_name
	return 0
 }

# Returns true if $1 contains only alphanumerics
isalphanumeric() {
	local _n=$1
	while [[ ${#_n} -ne 0 ]]; do
		case $_n in
		[A-Za-z0-9]*)	;;
		*)		return 1;;
		esac
		_n=${_n#?}
	done
	return 0
}

# Much of this is gratuitously stolen from /etc/netstart.
enable_network() {
	local _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

	DIDNET=y

	# 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.}

		# Interface names must be alphanumeric only. We check to avoid
		# configuring backup or temp files, and to catch the "*" case.
		if ! isalphanumeric "$if"; then
			continue
		fi
		ifconfig $if > /dev/null 2>&1
		if [ $? -ne 0 ]; then
			continue
		fi

		# 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"|""|"rtsol")
				# skip comments, user commands, bridges,
				# IPv6 rtsol 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"
				;;
			"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)
					# Ignore IPv6 setup
					continue
					;;
				*)	cmd="$cmd $mask $bcaddr"
				esac
				cmd="$cmd $ext1 $ext2$rtcmd" rtcmd=
				;;
			esac
			eval "$cmd"
		done </mnt/etc/hostname.$if
	done

	# /mnt/etc/mygate, if it exists, contains the name of my gateway host
	# that name must be in /etc/hosts.
	if [ -f /mnt/etc/mygate ]; then
		route delete default >/dev/null 2>&1
		route -qn add -host default $(< /mnt/etc/mygate)
	fi

	# 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

	# 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() {
	local _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
		*.tgz)	ftp $_ftp_active -o - -V -m "$_src/$_f" | tar zxphf - -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() {
	echo "$1" | sed -e "
s/%/%25/g
s/ /%20/g
s/!/%21/g
s/\"/"/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
}

# 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 _ftp_active must be global.
install_url() {
	local _url_type=$1 _file_list _url_base _oifs _prompt _passwd

	donetconfig

	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
	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 '?')"
			cat -n $SERVERLIST | less -XE
		else
			echo "FAILED."
			cat /tmp/ftplisterr
		fi
	fi

	# Get server IP address or hostname
	: ${_prompt:="Server? (IP address, hostname or 'done')"}
	while : ; do
		eval resp=\$_${_url_type}_server_ip
		ask_until "$_prompt" "$resp"
		case $resp in
		done)	return ;;
		"?")	[[ -s $SERVERLIST ]] || continue
			cat -n $SERVERLIST | 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)
			[[ $# -lt 1 ]] && { echo "There is no line $resp." ; continue ; }
			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:-pub/OpenBSD/$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

	# 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"
}

# $1 - mount point directory is relative to
# $2 - default directory
install_mounted_fs() {
	local _mp=$1 _dir=$2

	while : ; do
		ask_until "Pathname to the sets? (or 'done')" "$_dir"
		case $resp in
		done)	return
			;;
		*)
			# Accept a valid $_mp relative path.
			[[ -d $_mp/$resp ]] && { _dir=$_mp/$resp ; break ; }
			# Accept a valid absolute path.
			[[ -d /$resp ]] && { _dir=/$resp ; break ; }
			# Otherwise ask again, with original default dir.
			echo "The directory '$resp' does not exist."
			;;
		esac
	done

	install_files "file://$_dir" "`ls -l $_dir`"
}

install_cdrom() {
	local _drive _part _fstype _directory _n

	ask_which "CD-ROM" "contains the ${MODE} media" "$CDDEVS"
	[[ $resp == done ]] && return

	_drive=$resp

	# If it is an ISO9660 CD-ROM, we don't need to ask any other questions
	_n=0
	until disklabel $_drive >/tmp/label.$_drive 2>&1; do
		# Try up to 6 times to access the CD
		if egrep -q '(Input/output error)|(sector size 0)' /tmp/label.$_drive; then
			_n=$(( $_n + 1 ))
			if [ _n -le 5 ]; then
				echo "I/O error accessing $_drive; retrying"
				sleep 10
			else
				echo "Cannot access $_drive."
				return
			fi
		else
			break
		fi
	done

	echo
	if grep -q '^ *c: .*ISO9660' /tmp/label.$_drive; then
		_fstype=cd9660
		_part=c
	else
		# Get partition from user
		resp=
		while [ -z "$resp" ] ; do
			ask "CD-ROM partition to mount? (normally 'c')" c
			case $resp in
			[a-p])
				_part=$resp
				;;
			*)	echo "Invalid response: $resp"
				# force loop to repeat
				resp=
				;;
			esac
		done

		# Ask for filesystem type
		cat << __EOT

Two CD-ROM filesystem types are currently supported by this program:
cd9660		ISO-9660
ffs		Berkeley Fast Filesystem

__EOT
		resp=
		while [ -z "$resp" ] ; do
			ask "Which filesystem type?" cd9660
			case $resp in
			cd9660|ffs)
				_fstype=$resp
				;;
			*)	echo "Invalid response: '$resp'"
				# force loop to repeat
				resp=
				;;
			esac
		done
	fi

	rm -f /tmp/label.$_drive

	# Mount the CD-ROM
	if ! mount -t ${_fstype} -o ro /dev/${_drive}${_part} /mnt2 ; then
		echo "Cannot mount CD-ROM drive."
		return
	fi

	install_mounted_fs /mnt2 "$SETDIR"
	umount -f /mnt2 > /dev/null 2>&1
}

# Mount a disk on /mnt2. The set of disk devices to choose from
# is $DKDEVS.
# returns 0 on success, 1 on failure
mount_a_disk() {
	local _drive _def_partition _partition_range _partition
	local _fstype _fsopts

	ask_which "disk" "contains the ${MODE} sets" "$DKDEVS"
	[[ $resp == done ]] && return 1

	_drive=$resp

	# Get partition
	cat << __EOT

The following partitions have been found on $_drive:

__EOT
	disklabel $_drive 2>/dev/null | grep '^  .:'
	echo
	_likely_partition_range=`disklabel $_drive 2>/dev/null | \
		sed -n	-e '/swap/s/.*//' -e '/unused/s/.*//' \
			-e '/^  .:/{s/^  \(.\).*/\1/;H;}' \
			-e '${g;s/\n//g;s/^/[/;s/$/]/p;}'`
	_partition_range=`disklabel $_drive 2>/dev/null | \
		sed -n	-e '/^  .:/{s/^  \(.\).*/\1/;H;}' \
			-e '${g;s/\n//g;s/^/[/;s/$/]/p;}'`
	_def_partition=`echo $_likely_partition_range | \
		sed -n 's/^\[\(.\).*\]/\1/p'`
	if [ -z "$_def_partition" ]; then
		_def_partition=`echo $_partition_range | \
			sed -n 's/^\[\(.\).*\]/\1/p'`
		if [ -z "$_def_partition" ]; then
			echo "There are no usable partitions on that disk"
			return 1
		fi
	fi

	resp=
	while [ -z "$resp" ]; do
		ask "Partition?" "$_def_partition"
		case $resp in
		$_partition_range)
			_partition=$resp
			;;
		*)	echo "Invalid response: $resp"
			# force loop to repeat
			resp=
			;;
		esac
	done

	# Ask for filesystem type
	cat << __EOT

The following filesystem types are supported:
default		(deduced from the disklabel)
ffs
$MDFSTYPE
__EOT

	resp=
	while [ -z "$resp" ]; do
		ask "Which filesystem type?" default
		case $resp in
		default)
			;;
		ffs)	_fstype="-t ffs"
			_fsopts=async
			;;
		$MDFSTYPE)
			_fstype="-t $resp"
			_fsopts=$MDFSOPTS
			;;
		*)	echo "Invalid response: $resp"
			# force loop to repeat
			resp=
			;;
		esac
	done

	# Mount the disk read-only
	if ! mount $_fstype -o ro,$_fsopts /dev/${_drive}${_partition} /mnt2; then
		echo "Cannot mount disk."
		return 1
	fi

	return 0
}

install_disk() {
	if mount_a_disk; then
		install_mounted_fs /mnt2
		umount -f /mnt2 > /dev/null 2>&1
	fi
}

install_nfs() {
	# Can we actually mount NFS filesystems?
	if [ ! -f /sbin/mount_nfs ]; then
		echo "/sbin/mount_nfs not found. Cannot mount NFS filesystems."
		return
	fi

	donetconfig

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

	# Get server path to mount
	ask_until "Filesystem on server to mount?" "$_nfs_server_path"
	_nfs_server_path=$resp

	# Determine use of TCP
	_nfs_tcp=
	ask_yn "Use TCP transport? (only works with capable NFS server)"
	[[ $resp == y ]] && _nfs_tcp=-T

	# Mount the server
	if ! mount_nfs $_nfs_tcp -o ro ${_nfs_server_ip}:${_nfs_server_path} /mnt2 ; then
		echo "Cannot mount NFS server."
		return
	fi

	install_mounted_fs /mnt2
	umount -f /mnt2 > /dev/null 2>&1
}

install_tape() {
	local _xcmd

	# Get the name of the tape from the user.
	cat << __EOT

The installation program needs to know which tape device to use. Make
sure you use a "no rewind on close" device.

__EOT
	ask_until "Name of tape device?" "${TAPE##*/}"
	TAPE=/dev/${resp##*/}
	if [ ! -c $TAPE ]; then
		echo "$TAPE does not exist or is not a character special file."
		return
	fi
	export TAPE

	# Rewind the tape device
	echo -n "Rewinding ${TAPE} (mt rewind)..."
	if ! mt rewind ; then
		echo "FAILED."
		return
	fi
	echo "done."

	# Get the file number
	resp=
	while [ -z "$resp" ]; do
		ask "File number?"
		case $resp in
		[1-9]*)	_nskip=$(( $resp - 1 ))
			;;
		*)	echo "Invalid file number ${resp}."
			# force loop to repeat
			resp=
			;;
		esac
	done

	# Skip to correct file.
	if [ $_nskip -ne 0 ]; then
		echo -n "Skipping to source file (mt fsf ${_nskip})..."
		if ! mt fsf $_nskip ; then
			echo "FAILED. Could not skip $_nskip files."
			return
		fi
		echo "done."
	fi

cat << __EOT

There are 2 different ways the file can be stored on tape:

1) an image of a gzipped tar file
2) a standard tar image

__EOT

	resp=
	while [ -z "$resp" ]; do
		ask "Which way is it?" 1
		case $resp in
		1)	_xcmd="tar zxvphf -"
			;;
		2)	_xcmd="tar xvphf -"
			;;
		*)	echo "Invalid response: $resp."
			# force loop to repeat
			resp=
			;;
		esac
		( cd /mnt; dd if=$TAPE | $_xcmd )
	done
	echo "Extraction complete."
}

set_timezone() {
	local _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)"
			if [[ $resp == ? ]]; then
				ls -F $_zonepath
			else
				_zonepath=$_zonepath/$resp
			fi
		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 that required sets were successfully installed by checking
# for the presence of a 'random' selection of their contents.
#
# Required sets are:
#	1) bsd
#	2) baseXX
#	3) etcXX
#
# If a 'problem' set is found, add it back to DEFAULTSETS.
sane_install() {
	local _insane

	# Check if bsd is installed and >0 bytes in size.
	if [[ ! -s /mnt/bsd ]]; then
		_insane=y
		DEFAULTSETS=$(addel bsd $DEFAULTSETS)
		cat << __EOT
+*** 'bsd' must be (re)installed: no kernel found.
__EOT
	fi

	# Check if baseXX is installed.
	if [[ ! -d /mnt/sbin || ! -d /mnt/dev ]]; then
		_insane=y
		DEFAULTSETS=$(addel base${VERSION}.tgz $DEFAULTSETS)
		cat << __EOT
+*** 'base${VERSION}.tgz' must be (re)installed: /sbin or /dev is missing.
__EOT
	fi

	# Check if etcXX is installed.
	if [[ ! -s /mnt/etc/rc ]]; then
		_insane=y
		DEFAULTSETS=$(addel etc${VERSION}.tgz $DEFAULTSETS)
		cat << __EOT
+*** 'etc${VERSION}.tgz' must be (re)installed: /etc/rc is missing.
__EOT
	fi

	[[ -n $_insane ]] && 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() {
	cat << __EOT

You will now specify the location and names of the ${MODE} sets you want to
load. You will be able to repeat this step until all of your sets have been
successfully loaded. If you are not sure what sets to ${MODE}, refer to the
installation notes for details on the contents of each.
__EOT

	while : ; do
		cat << __EOT

Sets can be located on a (m)ounted filesystem; a (c)drom, (d)isk or (t)ape
device; or a (f)tp, (n)fs or (h)ttp server.
__EOT
		ask "Where are the ${MODE} sets? (or 'done')"

		case $resp in
		done)	sane_install && return ;;
		c*|C*)	install_cdrom ;;
		d*|D*)	install_disk ;;
		f*|F*)	install_url ftp ;;
		h*|H*)	install_url http ;;
		m*|M*)	install_mounted_fs /mnt ;;
		n*|N*)	install_nfs ;;
		t*|T*)	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).
#
# 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 == \#* || \
		    $_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//')

		# 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

	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.
		if ! mount -v -t $_fstype $_async -o $_opt $_dev $_mp ; 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

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

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

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

donetconfig() {
	local _dn _ns _dr

	[[ -n $DIDNET ]] && return

	DIDNET=y

	configure_all_interfaces

	# 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).
		_ns=$(sed -ne '/^nameserver /s///p' /tmp/resolv.conf)
		# 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.
	ask "DNS domain name? (e.g. 'bar.com')" "${_dn:=$(get_fqdn)}"
	hostname "$(hostname -s).$resp"

	# Get/Confirm nameservers, and construct appropriate resolv.conf.
	ask "DNS nameserver? (IP address or 'none')" "${_ns:=none}"
	if [[ $resp != none ]]; then
		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

	# Get/Confirm the default route.
	_dr=$(route -n show | sed -ne '/^default */{s///; s/ .*//; p;}')
	[[ -f /tmp/dhclient.conf ]] && _dr=dhcp
	while : ; do
		ask_until "Default route? (IP address, 'dhcp' or 'none')" "$_dr"
		case $resp in
		none|dhcp) break ;;
		esac
		route delete default > /dev/null 2>&1
		route -n add -host default "$resp" && { echo "$resp" >/tmp/mygate ; break ; }
		# Put the old default route back. The new one did not work.
		route -n add -host default $_dr >/dev/null 2>&1
	done

	edit_tmp_file hosts
	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
}

questions() {
	ask_yn "Do you wish sshd(8) to be started by default?" yes
	if [[ $resp == n ]]; then
		echo "sshd_flags=NO		# disabled during install" \
		    > /mnt/etc/rc.conf.local
	fi

	[[ -n $MDXAPERTURE ]] || return

	ask_yn "Do you expect to run the X Window System?" yes
	if [[ $resp == y ]]; then
		sed -e "/^#\(machdep\.allowaperture=${MDXAPERTURE}\)/s//\1	/" \
		    /mnt/etc/sysctl.conf > /tmp/sysctl.conf
	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

	echo -n "Making all device nodes..."
	cd /mnt/dev
	sh MAKEDEV all
	# Make sure any devices we added as a result of makedev() calls
	# are recreated in installed system.
	for _dev in $DEVSMADE; do
		sh MAKEDEV $_dev
	done
	echo "done."
	cd /

	md_installboot $ROOTDISK

	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 halt at the command prompt. Once the
system has halted, reset the machine and boot from the disk.
__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=

VERSION=35

VNAME="$(( $VERSION / 10 )).$(( $VERSION % 10 ))"
SETDIR="$VNAME/$ARCH"
FTPDIR="pub/OpenBSD/$VNAME"
OBSD="OpenBSD/$ARCH $VNAME"
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

# Scan /var/run/dmesg.boot for disks and cds
DKDEVS=`get_dkdevs`
CDDEVS=`get_cddevs`
IFDEVS=`get_ifdevs`

# Devices created with makedev().
DEVSMADE=

# 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 $MDSETS"
DEFAULTSETS="bsd"
for _set in base etc misc comp man game xbase xshare xfont xserv site ; do
	[[ $MODE == upgrade && $_set == etc ]] && continue
	THESETS="$THESETS ${_set}${VERSION}.tgz"
	isin $_set xbase xshare xfont xserv site && continue
	DEFAULTSETS="$DEFAULTSETS ${_set}${VERSION}.tgz"
done

# decide upon an editor
if [ -z "$EDITOR" ] ; then
	EDITOR=ed
	[ -x /usr/bin/vi ] && EDITOR=vi
	export EDITOR
fi

# umount all filesystems, just in case we are re-running install or upgrade.
[[ -f /etc/fstab ]] && umount -av 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 initialize the disk(s) that OpenBSD 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
[[ $# -gt 1 ]] && _defdsk=done

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

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