xref: /openbsd/distrib/miniroot/install.sub (revision 777a7482)
1#!/bin/ksh
2#	$OpenBSD: install.sub,v 1.1266 2024/07/09 14:47:21 krw Exp $
3#
4# Copyright (c) 1997-2015 Todd Miller, Theo de Raadt, Ken Westerback
5# Copyright (c) 2015, Robert Peichaer <rpe@openbsd.org>
6#
7# All rights reserved.
8#
9# Redistribution and use in source and binary forms, with or without
10# modification, are permitted provided that the following conditions
11# are met:
12# 1. Redistributions of source code must retain the above copyright
13#    notice, this list of conditions and the following disclaimer.
14# 2. Redistributions in binary form must reproduce the above copyright
15#    notice, this list of conditions and the following disclaimer in the
16#    documentation and/or other materials provided with the distribution.
17#
18# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28#
29# Copyright (c) 1996 The NetBSD Foundation, Inc.
30# All rights reserved.
31#
32# This code is derived from software contributed to The NetBSD Foundation
33# by Jason R. Thorpe.
34#
35# Redistribution and use in source and binary forms, with or without
36# modification, are permitted provided that the following conditions
37# are met:
38# 1. Redistributions of source code must retain the above copyright
39#    notice, this list of conditions and the following disclaimer.
40# 2. Redistributions in binary form must reproduce the above copyright
41#    notice, this list of conditions and the following disclaimer in the
42#    documentation and/or other materials provided with the distribution.
43#
44# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
45# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
46# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
47# PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
48# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
49# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
50# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
51# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
52# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
53# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
54# POSSIBILITY OF SUCH DAMAGE.
55#
56
57# OpenBSD install/upgrade script common subroutines and initialization code
58
59# ------------------------------------------------------------------------------
60# Misc functions
61# ------------------------------------------------------------------------------
62
63# Print error message to stderr and exit the script.
64err_exit() {
65	print -u2 -- "$*"
66	exit 1
67}
68
69# Show usage of the installer script and exit.
70usage() {
71	err_exit "usage: ${0##*/} [-ax] [-f filename] [-m install | upgrade]"
72}
73
74# Wait for the ftp(1) process started in start_cgiinfo() to end and extract
75# various informations from the ftplist.cgi output.
76wait_cgiinfo() {
77	local _l _s _key _val
78
79	if [ -f /tmp/cgipid ]; then
80		wait "$(</tmp/cgipid)" 2>/dev/null
81		rm -f /tmp/cgipid
82	fi
83
84	# Ensure, there is actual data to extract info from.
85	[[ -s $CGI_INFO ]] || return
86
87	# Extract the list of mirror servers.
88	sed -En 's,^https?://([[A-Za-z0-9:_][]A-Za-z0-9:._-]*),\1,p' \
89		$CGI_INFO >$HTTP_LIST 2>/dev/null
90
91	# Extract the previously selected mirror server (first entry in the
92	# ftplist.cgi output, if that has no location info).
93	read -r -- _s _l <$HTTP_LIST
94	[[ -z $_l ]] && : ${HTTP_SERVER:=${_s%%/*}}
95
96	# Extract the previously used install method, timezone information
97	# and a reference timestamp.
98	while IFS='=' read -r -- _key _val; do
99		case $_key=$_val in
100		method=+([a-z])*([0-9]))	CGI_METHOD=$_val;;
101		TIME=+([0-9]))			CGI_TIME=$_val;;
102		TZ=+([-_/+[:alnum:]]))		CGI_TZ=$_val;;
103		esac
104	done <$CGI_INFO
105}
106
107
108# ------------------------------------------------------------------------------
109# Utils functions
110# ------------------------------------------------------------------------------
111
112# Test the first argument against the remaining ones, return success on a match.
113isin() {
114	local _a=$1 _b
115
116	shift
117	for _b; do
118		[[ $_a == "$_b" ]] && return 0
119	done
120	return 1
121}
122
123# Add first argument to list formed by the remaining arguments.
124# Adds to the tail if the element does not already exist.
125addel() {
126	local _a=$1
127
128	shift
129	isin "$_a" $* && echo "$*" || echo "${*:+$* }$_a"
130}
131
132# Remove all occurrences of first argument from list formed by the remaining
133# arguments.
134rmel() {
135	local _a=$1 _b _c
136
137	shift
138	for _b; do
139		[[ $_a != "$_b" ]] && _c="${_c:+$_c }$_b"
140	done
141	echo "$_c"
142}
143
144# Sort and print unique list of provided arguments.
145bsort() {
146	local _a _l
147
148	set -s -- $@
149	for _a; do
150		_l=$(addel $_a $_l)
151	done
152	echo $_l
153}
154
155# If possible, print the timestamp received from the ftplist.cgi output,
156# adjusted with the time elapsed since it was received.
157http_time() {
158	local _sec=$(cat $HTTP_SEC 2>/dev/null)
159
160	[[ -n $_sec && -n $CGI_TIME ]] &&
161		echo $((CGI_TIME + SECONDS - _sec))
162}
163
164# Prints the supplied parameters properly escaped for future sh/ksh parsing.
165# Quotes are added if needed, so you should not do that yourself.
166quote() (
167	# Since this is a subshell we won't pollute the calling namespace.
168	for _a; do
169		alias Q=$_a; _a=$(alias Q); print -rn -- " ${_a#Q=}"
170	done | sed '1s/ //'
171	echo
172)
173
174# Show a list of ordered arguments (read line by line from stdin) in column
175# output using ls.
176show_cols() {
177	local _l _cdir=/tmp/i/cdir _clist
178
179	mkdir -p $_cdir
180	rm -rf -- $_cdir/*
181	while read _l; do
182		[[ -n $_l ]] || continue
183		mkdir -p /tmp/i/cdir/"$_l"
184		_clist[${#_clist[*]}]="$_l"
185	done
186	(cd $_cdir; ls -Cdf "${_clist[@]}")
187	rm -rf -- $_cdir
188}
189
190# Echo file $1 to stdout. Skip comment lines. Strip leading and trailing
191# whitespace if IFS is set.
192stripcom() {
193	local _file=$1 _line
194
195	[[ -f $_file ]] || return
196
197	set -o noglob
198	while read _line; do
199		[[ -n ${_line%%#*} ]] && echo $_line
200	done <$_file
201	set +o noglob
202}
203
204# Create a temporary directory based on the supplied directory name prefix.
205tmpdir() {
206	local _i=1 _dir
207
208	until _dir="${1?}.$_i.$RANDOM" && mkdir -- "$_dir" 2>/dev/null; do
209		((++_i < 10000)) || return 1
210	done
211	echo "$_dir"
212}
213
214# Generate unique filename based on the supplied filename $1.
215unique_filename() {
216	local _fn=$1 _ufn
217
218	while _ufn=${_fn}.$RANDOM && [[ -e $_ufn ]]; do :; done
219	print -- "$_ufn"
220}
221
222# Let rc.firsttime feed file $1 using $2 as subject to whatever mail system we
223# have at hand by then.
224prep_root_mail() {
225	local _fn=$1 _subject=$2 _ufn
226
227	[[ -s $_fn ]] || return
228
229	_ufn=$(unique_filename /mnt/var/log/${_fn##*/})
230	cp $_fn $_ufn
231	chmod 600 $_ufn
232	_ufn=${_ufn#/mnt}
233
234	cat <<__EOT >>/mnt/etc/rc.firsttime
235( /usr/bin/mail -s '$_subject' root <$_ufn && rm $_ufn ) >/dev/null 2>&1 &
236__EOT
237}
238
239# Examine the contents of the dhcpleased lease file $1 for a line containing the
240# field(s) provided as parameters and return the value of the first field found.
241#
242# Note that value strings are VIS_SAFE'd.
243lease_value() {
244	local _lf=$1 _o _opt _val
245
246	[[ -s $_lf ]] || return
247	shift
248
249	for _o; do
250		while read -r _opt _val; do
251			[[ $_opt == ${_o}: ]] && echo "$_val" && return
252		done < "$_lf"
253	done
254}
255
256# Extract one boot's worth of dmesg.
257dmesgtail() {
258	dmesg | sed -n 'H;/^OpenBSD/h;${g;p;}'
259}
260
261# ------------------------------------------------------------------------------
262# Device related functions
263# ------------------------------------------------------------------------------
264
265# Show device name, info, NAA and size for the provided list of disk devices.
266# Create device nodes as needed and cleanup afterwards.
267diskinfo() {
268	local _d _i _n _s
269
270	for _d; do
271		# Extract disk information enclosed in <> from dmesg.
272		_i=$(dmesg | sed -n '/^'$_d' at /h;${g;s/^.*<\(.*\)>.*$/\1/p;}')
273		_i=${_i##+([[:space:],])}
274		_i=${_i%%+([[:space:],])}
275
276		# Extract Network Address Authority information from dmesg.
277		_n=$(dmesg | sed -En '/^'$_d' at /h;${g;s/^.* ([a-z0-9]+\.[a-zA-Z0-9_]+)$/\1/p;}')
278
279		# Extract disk size from disklabel output.
280		make_dev $_d
281		_s=$(disklabel -dpg $_d 2>/dev/null | sed -n '/.*# total bytes: \(.*\)/{s//(\1)/p;}')
282		rm -f /dev/{r,}$_d?
283
284		echo "    $_d: $_i $_n $_s"
285	done
286}
287
288# Create devices passed as arguments.
289make_dev() {
290	[[ -z $(cd /dev && sh MAKEDEV "$@" 2>&1) ]]
291}
292
293# Sort and print information from dmesg.boot using sed expression $1.
294scan_dmesg() {
295	bsort $(sed -n "$1" /var/run/dmesg.boot)
296}
297
298# Extract device names from hw.disknames matching sed expression $1.
299scan_disknames() {
300	bsort $(IFS=,
301        	for _d in $(sysctl -n hw.disknames); do
302			echo "${_d%%:*} "
303        	done | sed -n "$1")
304}
305
306# Return list of disks with softraid chunks, optionally limited to the volume $1.
307get_softraid_chunks() {
308	local _device=${1:-softraid0}
309
310	[[ -x /sbin/bioctl ]] || return
311	bioctl $_device 2>/dev/null | sed -n 's/.*<\(.*\).>$/\1/p'
312}
313
314# Return list of softraid volumes.
315get_softraid_volumes() {
316	bioctl softraid0 | sed -n 's/^softraid0.*\(sd[0-9]*\).*/\1/p'
317}
318
319# Return disk devices found in hw.disknames.
320get_dkdevs() {
321	scan_disknames "${MDDKDEVS:-/^[sw]d[0-9][0-9]* /s/ .*//p}"
322}
323
324# Return CDROM devices found in hw.disknames.
325get_cddevs() {
326	scan_disknames "${MDCDDEVS:-/^cd[0-9][0-9]* /s/ .*//p}"
327}
328
329# Return sorted list of disks not in DISKS_DONE which contains disks already
330# initialized during installation.
331# Ignore softraid chunks, they either auto-assembled at boot or were created
332# manually in an installer shell.
333get_dkdevs_uninitialized() {
334	local _disks=$(get_dkdevs) _d
335
336	for _d in $DISKS_DONE $(get_softraid_chunks); do
337		_disks=$(rmel "$_d" $_disks)
338	done
339	bsort $_disks
340}
341
342# Return list of valid root disks
343get_dkdevs_root() {
344	local _disks=$(get_dkdevs) _d
345
346	if [[ $MODE == upgrade ]]; then
347		for _d in $_disks; do
348			is_rootdisk "$_d" || _disks=$(rmel "$_d" $_disks)
349		done
350	fi
351	echo $_disks
352}
353
354# Return list of all network devices, optionally limited by parameters to
355# ifconfig. Filter out dynamically created network pseudo-devices except vlan.
356get_ifs() {
357	local _if _if_list=$(rmel vlan $(ifconfig -C))
358
359	for _if in $(ifconfig "$@" 2>/dev/null | sed '/^[a-z]/!d;s/:.*//'); do
360		isin "${_if%%+([0-9])}" $_if_list || echo $_if
361	done
362}
363
364# Map an interface to its MAC address if it is unique.
365if_name_to_lladdr() {
366	local _lladdr
367
368	_lladdr=$(ifconfig $1 2>/dev/null |
369	    sed -n 's/^[[:space:]]*lladdr[[:space:]]//p')
370	[[ -n $_lladdr && -n $(ifconfig -M "$_lladdr") ]] && echo $_lladdr
371}
372
373# Print a list of interface names and unique lladdr.
374get_ifs_and_lladdrs() {
375	local _if
376
377	for _if in $(get_ifs); do
378		echo $_if
379		if_name_to_lladdr $_if
380	done
381}
382
383# Return the device name of the disk device $1, which may be a disklabel UID.
384get_dkdev_name() {
385	local _dev=${1#/dev/} _d
386
387	_dev=${_dev%.[a-p]}
388	((${#_dev} < 16)) && _dev=${_dev%[a-p]}
389	local IFS=,
390	for _d in $(sysctl -n hw.disknames); do
391		[[ $_dev == @(${_d%:*}|${_d#*:}) ]] && echo ${_d%:*} && break
392	done
393}
394
395# Inspect disk $1 if it has a partition-table of type $2 and optionally
396# if it has a partition of type $3.
397disk_has() {
398	local _disk=$1 _pttype=$2 _part=$3 _cmd _p_pttype _p_part
399
400	[[ -n $_disk && -n $_pttype ]] || exit
401
402	# Commands to inspect disk. Default: "fdisk $_disk"
403	local _c_hfs="pdisk -l $_disk"
404
405	# Patterns for partition-table-types and partition-types.
406	local _p_gpt='Usable LBA:'
407	local _p_gpt_openbsd='^[ *]...: OpenBSD '
408	local _p_gpt_apfsisc='^[ *]...: APFS ISC '
409	local _p_gpt_biosboot='^[ *]...: BIOS Boot '
410	local _p_gpt_efisys='^[ *]...: EFI Sys '
411	local _p_hfs='^Partition map '
412	local _p_hfs_openbsd=' OpenBSD OpenBSD '
413	local _p_mbr='Signature: 0xAA55'
414	local _p_mbr_openbsd='^..: A6 '
415	local _p_mbr_dos='^..: 06 '
416	local _p_mbr_dos_active='^\*.: 06 '
417	local _p_mbr_linux='^..: 83 '
418
419	# Compose command and patterns based on the parameters.
420	eval "_cmd=\"\$_c_${_pttype}\""
421	eval "_p_pttype=\"\$_p_${_pttype}\""
422	eval "_p_part=\"\$_p_${_pttype}_${_part}\""
423
424	# Set the default command if none was defined before.
425	_cmd=${_cmd:-fdisk $_disk}
426
427	# Abort in case of undefined patterns.
428	[[ -z $_p_pttype ]] && exit
429	[[ -n $_part && -z $_p_part ]] && exit
430
431	if [[ -z $_p_part ]]; then
432		$_cmd 2>/dev/null | grep -Eq "$_p_pttype"
433	else
434		$_cmd 2>/dev/null | grep -Eq "$_p_pttype" &&
435			$_cmd 2>/dev/null | grep -Eq "$_p_part"
436	fi
437}
438
439# Handle disklabel auto-layout for the root disk $1 during interactive install
440# and autopartitioning during unattended install by asking for and downloading
441# autopartitioning template. Write the resulting fstab to $2. Abort unattended
442# installation if autopartitioning fails.
443disklabel_autolayout() {
444	local _disk=$1 _f=$2 _dl=/tmp/i/disklabel.auto _op _qst
445
446	# Skip disklabel auto-layout for any disk except the root disk.
447	[[ $_disk != $ROOTDISK ]] && return
448
449	while $AI; do
450		ask "URL to autopartitioning template for disklabel?" none
451		[[ $resp == none ]] && break
452		if ! $FTP_TLS && [[ $resp == https://* ]]; then
453			err_exit "https not supported on this platform."
454		fi
455		echo "Fetching $resp"
456		if unpriv ftp -Vo - "$resp" >$_dl && [[ -s $_dl ]]; then
457			disklabel -T $_dl -F $_f -w -A $_disk && return
458			err_exit "Autopartitioning failed."
459		else
460			err_exit "No autopartitioning template found."
461		fi
462	done
463
464	_qst="Use (A)uto layout, (E)dit auto layout, or create (C)ustom layout?"
465	while :; do
466		echo "The auto-allocated layout for $_disk is:"
467		disklabel -h -A $_disk | egrep "^#  |^  [a-p]:"
468		ask "$_qst" a
469		case $resp in
470		[aA]*)	_op=-w;;
471		[eE]*)	_op=-E;;
472		[cC]*)	return 0;;
473		*)	continue;;
474		esac
475		disklabel -F $_f $_op -A $_disk
476		return
477	done
478}
479
480# Create a partition table and configure the partition layout for disk $1.
481configure_disk() {
482	local _disk=$1 _fstab=/tmp/i/fstab.$1 _opt
483
484	make_dev $_disk || return
485
486	# Deal with disklabels, including editing the root disklabel
487	# and labeling additional disks. This is machine-dependent since
488	# some platforms may not be able to provide this functionality.
489	# /tmp/i/fstab.$_disk is created here with 'disklabel -F'.
490	rm -f /tmp/i/*.$_disk
491	md_prep_disklabel $_disk || return
492
493	# Make sure a '/' mount point exists on the root disk.
494	if ! grep -qs ' / ffs ' /tmp/i/fstab.$ROOTDISK; then
495		echo "'/' must be configured!"
496		$AI && exit 1 || return 1
497	fi
498
499	if [[ -f $_fstab ]]; then
500		# Avoid duplicate mount points on different disks.
501		while read _pp _mp _rest; do
502			# Multiple swap partitions are ok.
503			if [[ $_mp == none ]]; then
504				echo "$_pp $_mp $_rest" >>/tmp/i/fstab
505				continue
506			fi
507			# Non-swap mountpoints must be in only one file.
508			if [[ $_fstab != $(grep -l " $_mp " /tmp/i/fstab.*) ]]; then
509				_rest=$_disk
510				_disk=
511				break
512			fi
513		done <$_fstab
514
515		# Duplicate mountpoint.
516		if [[ -z $_disk ]]; then
517			# Allow disklabel(8) to read back mountpoint info
518			# if it is immediately run against the same disk.
519			cat /tmp/i/fstab.$_rest >/etc/fstab
520			rm /tmp/i/fstab.$_rest
521
522			set -- $(grep -h " $_mp " /tmp/i/fstab.*[0-9])
523			echo "$_pp and $1 can't both be mounted at $_mp."
524			$AI && exit 1 || return 1
525		fi
526
527		# Add ffs filesystems to list after newfs'ing them. Ignore
528		# other filesystems.
529		while read _pp _mp _fstype _rest; do
530			[[ $_fstype == ffs ]] || continue
531
532			# Default to FFS2 unless otherwise directed.
533			if [[ $_mp == / ]]; then
534				_opt=${MDROOTFSOPT:--O2}
535			else
536				_opt=${MDFSOPT:--O2}
537			fi
538			newfs -q $_opt ${_pp##/dev/}
539
540			# N.B.: '!' is lexically < '/'.
541			# That is required for correct sorting of mount points.
542			FSENT="$FSENT $_mp!$_pp"
543		done <$_fstab
544	fi
545
546	return 0
547}
548
549# ------------------------------------------------------------------------------
550# Functions for the dmesg listener
551# ------------------------------------------------------------------------------
552
553# Acquire lock.
554lock() {
555	while ! mkdir /tmp/i/lock 2>/dev/null && sleep .1; do :; done
556}
557
558# Release lock.
559unlock() {
560	rm -df /tmp/i/lock 2>/dev/null
561}
562
563# Add a trap to kill the dmesg listener co-process on exit of the installer.
564retrap() {
565	trap '
566		if [[ -f /tmp/cppid ]]; then
567			kill -KILL -"$(</tmp/cppid)" 2>/dev/null
568			rm -f /tmp/cppid
569		fi
570		echo
571		stty echo
572		exit 0
573	' INT EXIT TERM
574}
575
576# Start a listener process looking for dmesg changes which indicates a possible
577# plug-in/-out of devices (e.g. usb disks, cdroms, etc.). This is used to abort
578# and redraw question prompts, especially in ask_which().
579start_dmesg_listener() {
580	local _update=/tmp/i/update
581
582	# Ensure the lock is initially released and that no update files exists.
583	unlock
584	rm -f $_update
585
586	# Do not start the listener if in non-interactive mode.
587	$AI && return
588
589	# To ensure that only one dmesg listener instance can be active, run it
590	# in a co-process subshell of which there can always only be one active.
591	set -m
592	(
593	while :; do
594		lock
595		# The dmesg listener will continuously check for the existence of
596		# the update file and sends a signal to the parent process (that
597		# is the installer script) if the dmesg output differs from the
598		# contents of that file.
599		if [[ -e $_update && "$(dmesgtail)" != "$(<$_update)" ]]; then
600			dmesgtail >$_update
601			rm -f /tmp/cppid
602			kill -TERM 2>/dev/null $$ || exit 1
603		fi
604		unlock
605		sleep .5
606	done
607	) |&
608	# Save the co-process pid so it can be used in the retrap() function which
609	# adds a trap to kill the co-process on exit of the installer script.
610	echo $! > /tmp/cppid
611	set +m
612	retrap
613}
614
615# ------------------------------------------------------------------------------
616# Functions to ask (or auto-answer) questions
617# ------------------------------------------------------------------------------
618
619# Log installer questions and answers so that the resulting file can be used as
620# response file for an unattended install/upgrade.
621log_answers() {
622	if [[ -n $1 && -n $2 ]]; then
623		print -r -- "${1%%'?'*} = $2" >>/tmp/i/$MODE.resp
624	fi
625}
626
627# Fetch response file for autoinstall.
628get_responsefile() {
629	local _rf _if _lf _path _aifile
630	export AI_HOSTNAME= AI_MAC= AI_MODE= AI_SERVER=
631
632	[[ -f /auto_upgrade.conf ]] && _rf=/auto_upgrade.conf AI_MODE=upgrade
633	[[ -f /auto_install.conf ]] && _rf=/auto_install.conf AI_MODE=install
634	[[ -f $_rf ]] && cp $_rf /tmp/ai/ai.$AI_MODE.conf && return
635
636	for _if in ''; do
637		[[ -x /sbin/dhcpleased ]] || break
638
639		# Select a network interface for initial dhcp request.
640		# Prefer the interface the system netbooted from.
641		set -- $(get_ifs netboot)
642		(($# == 0)) && set -- $(get_ifs)
643		(($# == 1)) && _if=$1
644
645		# Ask if multiple were found and system was not netbooted.
646		while (($# > 1)); do
647			ask_which "network interface" \
648				"should be used for the initial DHCP request" \
649				"$*"
650			isin "$resp" $* && _if=$resp && break
651		done
652
653		# Issue initial dhcp request via the found interface.
654		[[ -n $_if ]] && ifconfig $_if inet autoconf || break
655		_lf=/var/db/dhcpleased/$_if
656
657		if ! wait_for_dhcp_info $_if 30; then
658			echo "No dhcp address on interface $_if in 30 seconds."
659			continue
660		fi
661
662		# Extract installer mode and response file path from lease file.
663		_aifile=$(lease_value $_lf filename)
664		[[ $_aifile == ?(*/)auto_@(install|upgrade) ]] || _aifile=
665		_path=${_aifile%auto_@(install|upgrade)}
666		AI_MODE=${_aifile##*?(/)auto_}
667
668		# Extract install server ip address from lease file.
669		AI_SERVER=$(lease_value $_lf next-server)
670
671		# Prime hostname with host-name option from lease file.
672		AI_HOSTNAME=$(lease_value $_lf host-name)
673		hostname "$AI_HOSTNAME"
674	done
675
676	# Try to fetch mac-mode.conf, then hostname-mode.conf, and finally
677	# mode.conf if install server and mode are known, otherwise tell which
678	# one was missing.
679	if [[ -n $AI_SERVER && -n $AI_MODE ]]; then
680		AI_MAC=$(ifconfig $_if | sed 's/.*lladdr \(.*\)/\1/p;d')
681		for _rf in {$AI_MAC-,${AI_HOSTNAME:+$AI_HOSTNAME-,}}$AI_MODE; do
682			# Append HTTP_SETDIR as parameter to _url which can be
683			# used by the webserver to return dynamically created
684			# response files.
685			_url="http://$AI_SERVER/$_path$_rf.conf?path=$HTTP_SETDIR"
686			echo "Fetching $_url"
687			if unpriv ftp -Vo - "$_url" \
688				>"/tmp/ai/ai.$AI_MODE.conf" 2>/dev/null; then
689				ifconfig $_if inet -autoconf delete down \
690				    2>/dev/null
691				rm /var/db/dhcpleased/$_if
692				return 0
693			fi
694		done
695	else
696		[[ -z $AI_SERVER ]] && echo "Could not determine auto server."
697		[[ -z $AI_MODE ]] && echo "Could not determine auto mode."
698	fi
699
700	# Ask for url or local path to response file. Provide a default url if
701	# server was found in lease file.
702	while :; do
703		ask "Response file location?" \
704			"${AI_SERVER:+http://$AI_SERVER/install.conf}"
705		[[ -n $resp ]] && _rf=$resp && break
706	done
707
708	# Ask for the installer mode only if auto-detection failed.
709	AI_MODE=$(echo "$_rf" | sed -En 's/^.*(install|upgrade).conf$/\1/p')
710	while [[ -z $AI_MODE ]]; do
711		ask "(I)nstall or (U)pgrade?"
712		[[ $resp == [iI]* ]] && AI_MODE=install
713		[[ $resp == [uU]* ]] && AI_MODE=upgrade
714	done
715
716	echo "Fetching $_rf"
717	[[ -f $_rf ]] && _rf="file://$_rf"
718	if unpriv ftp -Vo - "$_rf" >"/tmp/ai/ai.$AI_MODE.conf" 2>/dev/null; then
719		ifconfig $_if inet -autoconf delete down 2>/dev/null
720		return 0
721	fi
722	return 1
723}
724
725# Find a response to question $1 in $AI_RESPFILE and return it via $resp.
726# Return default answer $2 if provided and none is found in the file.
727#
728# Move the existing ai.conf file to a tmp file, read from it line by line
729# and write a new ai.conf file skipping the line containing the response.
730#
731# 1) skip empty and comment lines and lines without =
732# 2) split question (_key) and answer (_val) at leftmost =
733# 3) strip leading/trailing blanks
734# 4) compare questions case insensitive (typeset -l)
735#
736_autorespond() {
737	typeset -l _q=$1 _key
738	local _def=$2 _l _val
739
740	[[ -f $AI_RESPFILE && -n $_q ]] || return
741
742	mv /tmp/ai/ai.conf /tmp/ai/ai.conf.tmp
743	while IFS=' 	' read -r _l; do
744		[[ $_l == [!#=]*=?* ]] || continue
745		_key=${_l%%*([[:blank:]])=*}
746		_val=${_l##*([!=])=*([[:blank:]])}
747		[[ $_q == @(|*[[:blank:]])"$_key"@([[:blank:]?]*|) ]] &&
748			resp=$_val && cat && return
749		print -r " $_l"
750	done </tmp/ai/ai.conf.tmp >/tmp/ai/ai.conf
751	[[ -n $_def ]] && resp=$_def && return
752	err_exit "\nQuestion has no answer in response file: \"$_q\""
753}
754
755# Capture user response either by issuing an interactive read or by searching
756# the response file and store the response in the global variable $resp.
757#
758# Optionally present a question $1 and a default answer $2 shown in [].
759#
760# If the dmesg output is changed while waiting for the interactive response,
761# the current read will be aborted and the function will return a non-zero
762# value. Normally, the caller function will then reprint any prompt and call
763# the function again.
764_ask() {
765	local _q=$1 _def=$2 _int _redo=0 _pid
766
767	lock; dmesgtail >/tmp/i/update; unlock
768	echo -n "${_q:+$_q }${_def:+[$_def] }"
769	_autorespond "$_q" "$_def" && echo "$resp" && return
770	trap "_int=1" INT
771	trap "_redo=1" TERM
772	read resp
773	lock; rm /tmp/i/update; unlock
774	if ((_redo)); then
775		stty raw
776		stty -raw
777	else
778		case $resp in
779		!)	echo "Type 'exit' to return to install."
780			ksh
781			_redo=1
782			;;
783		!*)	eval "${resp#?}"
784			_redo=1
785			;;
786		esac
787	fi
788	retrap
789	((_int)) && kill -INT $$
790	: ${resp:=$_def}
791	return $_redo
792}
793
794# Ask for user response to question $1 with an optional default answer $2.
795# Write the question and the answer to a logfile.
796ask() {
797
798	# Prompt again in case the dmesg listener detected a change.
799	while ! _ask "$1" "$2"; do :; done
800	log_answers "$1" "$resp"
801}
802
803# Ask the user a yes/no question $1 with 'no' as default answer unless $2 is
804# set to 'yes' and insist on 'y', 'yes', 'n' or 'no' as response.
805# Return response via $resp as 'y' with exit code 0 or 'n' with exit code 1.
806ask_yn() {
807	local _q=$1 _a=${2:-no}
808	typeset -l _resp
809
810	while :; do
811		ask "$_q" "$_a"
812		_resp=$resp
813		case $_resp in
814		y|yes)	resp=y; return 0;;
815		n|no)	resp=n; return 1;;
816		esac
817		echo "'$resp' is not a valid choice."
818		$AI && exit 1
819	done
820}
821
822# Ask for the user to select one value from a list, or 'done'.
823# At exit $resp holds selected item, or 'done'.
824#
825# Parameters:
826#
827# $1 = name of the list items (disk, cd, etc.)
828# $2 = question to ask
829# $3 = list of valid choices
830# $4 = default choice, if it is not specified use the first item in $3
831#
832# N.B.! $3 and $4 will be "expanded" using eval, so be sure to escape them
833#       if they contain spooky stuff
834ask_which() {
835	local _name=$1 _query=$2 _list=$3 _def=$4 _dynlist _dyndef _key _q
836	_key=$(echo "$_name" | sed 's/[^[:alnum:]]/_/g')
837
838	while :; do
839		eval "_dynlist=\"$_list\""
840		eval "_dyndef=\"$_def\""
841
842		# Clean away whitespace and determine the default.
843		set -o noglob
844		set -- $_dyndef; _dyndef="$1"
845		set -- $_dynlist; _dynlist="$*"
846		set +o noglob
847		(($# < 1)) && resp=done && return
848		: ${_dyndef:=$1}
849
850		echo "Available ${_name}s are: $_dynlist."
851		_q="Which $_name $_query?"
852		echo -n "$_q (or 'done') ${_dyndef:+[$_dyndef] }"
853		_autorespond "$_q" "${_dyndef-done}" && echo "$resp" \
854			|| _ask || continue
855		[[ -z $resp ]] && resp="$_dyndef"
856
857		# Quote $resp to prevent user from confusing isin() by
858		# entering something like 'a a'.
859		if isin "$resp" $_dynlist done; then
860			log_answers "$_q" "$resp"
861			break
862		fi
863		echo "'$resp' is not a valid choice."
864		$AI && [[ -n $AI_RESPFILE ]] && exit 1
865	done
866}
867
868# Ask for user response to question $1 with an optional default answer $2
869# until a non-empty reply is entered.
870ask_until() {
871	resp=
872
873	while :; do
874		ask "$1" "$2"
875		[[ -n $resp ]] && break
876		echo "A response is required."
877		$AI && exit 1
878	done
879}
880
881# Capture a user password and save it in $resp, optionally showing prompt $1.
882#
883# 1) *Don't* allow the '!' options that ask does.
884# 2) *Don't* echo input.
885# 3) *Don't* interpret "\" as escape character.
886# 4) Preserve whitespace in input
887#
888ask_pass() {
889	stty -echo
890	IFS= read -r resp?"$1 "
891	stty echo
892	echo
893}
894
895# Ask for a password twice showing prompt $1. Ensure both inputs are identical
896# and save it in $_password.
897ask_password() {
898	local _q=$1
899
900	if $AI; then
901		echo -n "$_q "
902		_autorespond "$_q"
903		echo '<provided>'
904		_password=$resp
905		return
906	fi
907
908	while :; do
909		ask_pass "$_q (will not echo)"
910		_password=$resp
911
912		ask_pass "$_q (again)"
913		[[ $resp == "$_password" ]] && break
914
915		echo "Passwords do not match, try again."
916	done
917}
918
919# Ask for a passphrase once showing prompt $1. Ensure input is not empty
920# and save it in $_passphrase.
921ask_passphrase() {
922	local _q=$1
923
924	if $AI; then
925		echo -n "$_q "
926		_autorespond "$_q"
927		echo '<provided>'
928		_passphrase=$resp
929		return
930	fi
931
932	while :; do
933		IFS= read -r _passphrase?"$_q (will echo) "
934
935		[[ -n $_passphrase ]] && break
936
937		echo "Empty passphrase, try again."
938	done
939}
940
941# ------------------------------------------------------------------------------
942# Support functions for donetconfig()
943# ------------------------------------------------------------------------------
944
945# Issue a DHCP request to configure interface $1 and add it to group 'dhcp' to
946# later be able to identify DHCP configured interfaces.
947dhcp_request() {
948	local _if=$1
949
950	echo "lookup file bind" >>/etc/resolv.conf
951
952	ifconfig $_if group dhcp >/dev/null 2>&1
953
954	if [[ -x /sbin/dhcpleased ]]; then
955		ifconfig $_if inet autoconf
956		if ! wait_for_dhcp_info $_if 30; then
957			echo "No dhcp address on interface $_if in 30 seconds."
958		fi
959
960	else
961		echo "DHCP leases not available during install."
962	fi
963}
964
965# Obtain and output the inet information related to interface $1.
966# Outputs:
967# <flags>\n<addr> <netmask> <rest of inet line>[\n<more inet lines>]
968v4_info() {
969	ifconfig $1 inet | sed -n '
970		1s/.*flags=.*<\(.*\)>.*/\1/p
971		/inet/s/netmask //
972		/.inet /s///p'
973}
974
975# Wait until dhcp has configured interface $1 within timeout $2 seconds.
976wait_for_dhcp_info() {
977	local _if=$1 _secs=$2
978
979	# Wait until $_if has a V4 address.
980	while (( _secs > 0 )); do
981		set -- $(v4_info $_if)
982		[[ -n $2 ]] && break
983		sleep 1
984		(( _secs-- ))
985	done
986
987	# Wait until there is a leases file to parse.
988	while (( _secs > 0 )); do
989		[[ -s /var/db/dhcpleased/$_if ]] && break
990		sleep 1
991		(( _secs-- ))
992	done
993
994	return $(( _secs <= 0 ))
995}
996
997# Convert a netmask in hex format ($1) to dotted decimal format.
998hextodec() {
999	set -- $(echo ${1#0x} | sed 's/\(..\)/0x\1 /g')
1000	echo $(($1)).$(($2)).$(($3)).$(($4))
1001}
1002
1003# Create an entry in the hosts file using IP address $1 and symbolic name $2.
1004# Treat $1 as IPv6 address if it contains ':', otherwise as IPv4. If an entry
1005# with the same name and address family already exists, delete it first.
1006add_hostent() {
1007	local _addr=$1 _name=$2 _delim="."
1008
1009	[[ -z $_addr || -z $_name ]] && return
1010
1011	[[ $_addr == *:* ]] && _delim=":"
1012	sed -i "/^[0-9a-fA-F]*[$_delim].*[ 	]$_name\$/d" \
1013		/tmp/i/hosts 2>/dev/null
1014
1015	echo "$_addr $_name" >>/tmp/i/hosts
1016}
1017
1018# Configure VLAN interface $1 and create the corresponding hostname.if(5) file.
1019# Ask the user what parent network interface and vnetid to use.
1020vlan_config() {
1021	local _if=$1 _hn=/tmp/i/hostname.$1 _hn_vd _vd _vdvi _vdvi_used _vi
1022	local _sed_vdvi='s/.encap: vnetid ([[:alnum:]]+) parent ([[:alnum:]]+)/\2:\1/p'
1023
1024	# Use existing parent device and vnetid for this interface as default in
1025	# case of a restart.
1026	_vdvi=$(ifconfig $_if 2>/dev/null | sed -En "$_sed_vdvi")
1027	_vd=${_vdvi%%:*}
1028	_vi=${_vdvi##*:}
1029
1030	# Use the vlan interface minor as the default vnetid. If it's 0, set it
1031	# to 'none' which equals to the default vlan.
1032	if [[ $_vi == @(|none) ]]; then
1033		((${_if##vlan} == 0)) && _vi=none || _vi=${_if##vlan}
1034	fi
1035
1036	# Use the first non vlan interface as the default parent.
1037	if [[ $_vd == @(|none) ]]; then
1038		_vd=$(get_ifs | sed '/^vlan/d' | sed q)
1039	fi
1040
1041	ask "Which interface:tag should $_if be on?" "$_vd:$_vi"
1042	_vd=${resp%%:*}
1043	_vi=${resp##*:}
1044
1045	# Ensure that the given parent is an existing (non vlan) interface.
1046	if ! isin "$_vd" $(get_ifs | sed '/^vlan/d'); then
1047		echo "Invalid parent interface choice '$_vd'."
1048		return 1
1049	fi
1050
1051	# Get a list of parent:vnetid tuples of all configured vlan interfaces.
1052	_vdvi_used=$(ifconfig vlan 2>/dev/null | sed -En "$_sed_vdvi")
1053
1054	# Ensure that the given vnetid is not already configured on the given
1055	# parent interface.
1056	for _vdvi in $_vdvi_used; do
1057		if [[ $_vdvi == $_vd:* && ${_vdvi##*:} == $_vi ]]; then
1058			echo "vlan tag '$_vi' already used on parent '$_vd'"
1059			return 1
1060		fi
1061	done
1062
1063	# Further ensure that the given vnetid is 'none', or within 1-4095.
1064	if [[ $_vi == none ]]; then
1065		_vi="-vnetid"
1066	elif (($_vi > 0 && $_vi < 4096)); then
1067		_vi="vnetid $_vi"
1068	else
1069		echo "Invalid vlan tag '$_vi'."
1070		return 1
1071	fi
1072
1073	# Write the config to the hostname.if files and set proper permissions.
1074	_hn_vd=/tmp/i/hostname.$_vd
1075	grep -qs "^up" $_hn_vd || echo up >>$_hn_vd
1076	echo "$_vi parent $_vd" >>$_hn
1077	chmod 640 $_hn_vd $_hn
1078
1079	# Bring up the parent interface and configure the vlan interface.
1080	ifconfig $_vd up
1081	ifconfig $_if destroy >/dev/null 2>&1
1082	ifconfig $_if create >/dev/null 2>&1
1083	ifconfig $_if $_vi parent $_vd
1084}
1085
1086# Configure IPv4 on interface $1.
1087v4_config() {
1088	local _if=$1 _name=$2 _hn=$3 _addr _mask _newaddr
1089
1090	# Set default answers based on any existing configuration.
1091	set -- $(v4_info $_if)
1092	if [[ -n $2 ]] && ! isin $_if $(get_ifs dhcp); then
1093		_addr=$2;
1094		_mask=$(hextodec $3)
1095	fi
1096
1097	# Nuke existing inet configuration.
1098	ifconfig $_if -inet
1099	ifconfig $_if -group dhcp >/dev/null 2>&1
1100
1101	while :; do
1102		ask_until "IPv4 address for $_if? (or 'autoconf' or 'none')" \
1103			  "${_addr:-autoconf}"
1104		case $resp in
1105		none)	return
1106			;;
1107		a|autoconf|dhcp)
1108			dhcp_request $_if
1109			echo "inet autoconf" >>$_hn
1110			return
1111			;;
1112		esac
1113
1114		_newaddr=$resp
1115
1116		# Ask for the netmask if the user did not use CIDR notation.
1117		if [[ $_newaddr == */* ]]; then
1118			ifconfig $_if $_newaddr up
1119		else
1120			ask_until "Netmask for $_if?" "${_mask:-255.255.255.0}"
1121			ifconfig $_if $_newaddr netmask $resp
1122		fi
1123
1124		set -- $(v4_info $_if)
1125		if [[ -n $2 ]]; then
1126			echo "inet $2 $3" >>$_hn
1127			add_hostent "$2" "$_name"
1128			return
1129		fi
1130
1131		$AI && exit 1
1132	done
1133}
1134
1135# Obtain and output the inet6 information related to interface $1.
1136# <flags>\n<addr> <prefixlen> <rest of inet6 line>[\n<more inet6 lines>]
1137v6_info() {
1138	ifconfig $1 inet6 | sed -n '
1139		1s/.*flags=.*<\(.*\)>.*/\1/p
1140		/scopeid/d
1141		/inet6/s/prefixlen //
1142		/.inet6 /s///p'
1143}
1144
1145# Configure an IPv6 default route on interface $1 and preserve that information
1146# in the /etc/mygate file. Ask the user to either select from a list of default
1147# router candidates or to enter a router IPv6 address.
1148v6_defroute() {
1149	local _if _v6ifs _prompt _resp _routers _dr PS3
1150
1151	# Only configure a default route if an IPv6 address was manually configured.
1152	for _if in $(get_ifs); do
1153		set -- $(v6_info $_if)
1154		[[ -z $2 || $1 == *AUTOCONF6* ]] || _v6ifs="$_v6ifs $_if"
1155	done
1156	[[ -n $_v6ifs ]] || return
1157
1158	# Start with any existing default routes.
1159	_routers=$(route -n show -inet6 |
1160		sed -En 's/^default[[:space:]]+([^[:space:]]+).*/\1 /p')
1161
1162	# Add more default router candidates by ping6'ing
1163	# the All-Routers multicast address.
1164	for _if in $_v6ifs; do
1165		_resp=$(ping6 -n -c 2 ff02::2%$_if 2>/dev/null |
1166			sed -En '/^[0-9]+ bytes from /{s///;s/: .*$//p;}')
1167		for _dr in $_resp; do
1168			_routers=$(addel $_dr $_routers)
1169		done
1170	done
1171
1172	[[ -n $_routers ]] && _routers=$(bsort $_routers)
1173	_prompt="IPv6 default router?"
1174
1175	if $AI; then
1176		_autorespond "$_prompt (IPv6 address or 'none')" none &&
1177			echo "$_prompt $resp"
1178		[[ $resp != none ]] &&
1179			route -n add -inet6 -host default $resp &&
1180			echo $resp >>/tmp/i/mygate
1181	else
1182		PS3="$_prompt (list #, IPv6 address or 'none') "
1183		select _resp in none $_routers; do
1184			[[ $REPLY == none || $_resp == none ]] && break
1185			[[ -z $_resp ]] && _resp=$REPLY
1186			# Avoid possible "file exists" errors
1187			route -n -q delete -inet6 -host default $_resp
1188			if route -n add -inet6 -host default $_resp; then
1189				echo $_resp >>/tmp/i/mygate
1190				break
1191			fi
1192		done
1193	fi
1194}
1195
1196# Configure IPv6 interface $1, add hostname $2 to the hosts file,
1197# create the hostname.if file $3. Ask the user for the IPv6 address
1198# and prefix length if the address was not specified in CIDR notation,
1199# unless he chooses 'autoconf'.
1200v6_config() {
1201	local _if=$1 _name=$2 _hn=$3 _addr _newaddr _prefixlen
1202
1203	ifconfig lo0 inet6 >/dev/null 2>&1 || return
1204
1205	# Preset the default answers by preserving possibly existing
1206	# configuration from previous runs.
1207	set -- $(v6_info $_if)
1208	if [[ $1 == *AUTOCONF6* ]]; then
1209		_addr=autoconf
1210	elif [[ -n $2 ]]; then
1211		_addr=$2
1212		_prefixlen=$3
1213	fi
1214
1215	# Nuke existing inet6 configuration.
1216	ifconfig $_if -inet6
1217
1218	while :; do
1219		ask_until "IPv6 address for $_if? (or 'autoconf' or 'none')" \
1220			  "${_addr:-none}"
1221		case $resp in
1222		none)	return
1223			;;
1224		a|autoconf)
1225			ifconfig $_if inet6 autoconf up
1226			echo "inet6 autoconf" >>$_hn
1227			return
1228			;;
1229		esac
1230
1231		_newaddr=$resp
1232		if [[ $_newaddr == */* ]]; then
1233			ifconfig $_if inet6 $_newaddr up
1234		else
1235			ask_until "IPv6 prefix length for $_if?" \
1236				  "${_prefixlen:-64}"
1237			ifconfig $_if inet6 $_newaddr/$resp up
1238		fi
1239
1240		set -- $(v6_info $_if)
1241		if [[ -n $2 ]]; then
1242			echo "inet6 $2 $3" >>$_hn
1243			add_hostent "$2" "$_name"
1244			return
1245		fi
1246
1247		$AI && exit 1
1248	done
1249}
1250
1251# Perform an 802.11 network scan on interface $1 and cache the result a file.
1252ieee80211_scan() {
1253	[[ -f $WLANLIST ]] ||
1254		ifconfig $1 scan |
1255			sed -n 's/^[[:space:]]*nwid \(.*\) chan [0-9]* bssid \([[:xdigit:]:]*\).*/\1 (\2)/p' >$WLANLIST
1256	cat $WLANLIST
1257}
1258
1259# Configure 802.11 interface $1 and append ifconfig options to hostname.if $2.
1260# Ask the user for the access point ESSID, the security protocol and a secret.
1261ieee80211_config() {
1262	local _if=$1 _hn=$2 _prompt _nwid _haswep=0 _haswpa=0 _err
1263
1264	# Reset 802.11 settings and determine WEP and WPA capabilities.
1265	ifconfig $_if -nwid
1266	ifconfig $_if -nwkey 2>/dev/null && _haswep=1
1267	ifconfig $_if -wpa 2>/dev/null && _haswpa=1
1268
1269	# Empty scan cache.
1270	rm -f $WLANLIST
1271
1272	while [[ -z $_nwid ]]; do
1273		ask_until "Access point? (ESSID, 'any', list# or '?')" "any"
1274		case "$resp" in
1275		+([0-9]))
1276			_nwid=$(ieee80211_scan $_if |
1277			    sed -n ${resp}'{s/ ([[:xdigit:]:]*)$//p;q;}')
1278			[[ -z $_nwid ]] && echo "There is no line $resp."
1279			[[ $_nwid = \"*\" ]] && _nwid=${_nwid#\"} _nwid=${_nwid%\"}
1280			;;
1281		\?)	ieee80211_scan $_if | cat -n | more -c
1282			;;
1283		*)	_nwid=$resp
1284			;;
1285		esac
1286	done
1287
1288	# 'any' implies that only open access points are considered.
1289	if [[ $_nwid != any ]]; then
1290
1291		_prompt="Security protocol? (O)pen"
1292		((_haswep == 1)) && _prompt="$_prompt, (W)EP"
1293		((_haswpa == 1)) && _prompt="$_prompt, WPA-(P)SK"
1294		while :; do
1295			ask_until "$_prompt" "O"
1296			case "${_haswep}${_haswpa}-${resp}" in
1297			??-[Oo]) # No further questions
1298				ifconfig $_if join "$_nwid"
1299				quote join "$_nwid" >>$_hn
1300				break
1301				;;
1302			1?-[Ww])	ask_passphrase "WEP key?"
1303				# Make sure ifconfig accepts the key.
1304				if _err=$(ifconfig $_if join "$_nwid" nwkey "$_passphrase" 2>&1) &&
1305					[[ -z $_err ]]; then
1306					quote join "$_nwid" nwkey "$_passphrase" >>$_hn
1307					break
1308				fi
1309				echo "$_err"
1310				;;
1311			?1-[Pp])	ask_passphrase "WPA passphrase?"
1312				# Make sure ifconfig accepts the key.
1313				if ifconfig $_if join "$_nwid" wpakey "$_passphrase"; then
1314					quote join "$_nwid" wpakey "$_passphrase" >>$_hn
1315					break
1316				fi
1317				;;
1318			*)	echo "'$resp' is not a valid choice."
1319				;;
1320			esac
1321		done
1322	fi
1323}
1324
1325# Set up IPv4 and IPv6 interface configuration.
1326configure_ifs() {
1327	local _first _hn _if _ifs _lladdr _name _p _q _vi _vn
1328	resp=
1329
1330	# Always need lo0 configured.
1331	ifconfig lo0 inet 127.0.0.1/8
1332
1333	# In case of restart, delete previous default gateway config.
1334	rm -f /tmp/i/mygate
1335
1336	while :; do
1337		set -sA _ifs -- $(get_ifs)
1338
1339                # Skip all interface configuration questions if there is no
1340                # physical interface to begin with.
1341                ((${#_ifs[*]} == 0)) && break
1342
1343		# Discover last configured vlan interface and increment its
1344		# minor for the next offered vlan interface.
1345		_vi=
1346		for _if in "${_ifs[@]}"; do
1347			[[ $_if = vlan+([[:digit:]]) ]] && _vi=${_if#vlan}
1348		done
1349		[[ -n $_vi ]] && ((_vi++))
1350		[[ -n ${_ifs[*]} ]] && _vn="vlan${_vi:-0}"
1351
1352		echo "Available network interfaces are: ${_ifs[*]} $_vn."
1353		if [[ $resp == '?' ]]; then
1354			for _if in "${_ifs[@]}"; do
1355				_lladdr=$(if_name_to_lladdr $_if)
1356				[[ -n $_lladdr ]] && echo "    $_if: lladdr $_lladdr"
1357			done
1358		fi
1359
1360		_q="Network interface to configure?"
1361		ask_until "$_q (name, lladdr, '?', or 'done')" \
1362		    ${_p:-$( (get_ifs netboot; get_ifs) | sed q )}
1363
1364		[[ $resp == done ]] && break
1365		[[ $resp == '?'  ]] && continue
1366
1367		# Quote $resp to prevent user from confusing isin() by
1368		# entering something like 'a a'.
1369		if ! isin "$resp" $(get_ifs_and_lladdrs) $_vn done; then
1370			echo "'$resp' is not a valid choice."
1371			$AI && [[ -n $AI_RESPFILE ]] && exit 1
1372			continue
1373		fi
1374
1375		_if=$resp
1376		_hn=/tmp/i/hostname.$_if
1377		rm -f $_hn
1378
1379		# Map lladdr to interface name if needed
1380		# and remove duplicate configuration.
1381		if [[ $_if == ??:??:??:??:??:?? ]]; then
1382			_lladdr=$_if
1383			_if=$(ifconfig -M $_lladdr)
1384			[[ -z $_if ]] && continue # should not be possible
1385			rm -f /tmp/i/hostname.$_if
1386		else
1387			_lladdr=$(if_name_to_lladdr $_if)
1388			[[ -n $_lladdr ]] && rm -f /tmp/i/hostname.$_lladdr
1389		fi
1390
1391		# If the offered vlan is chosen, ask the relevant
1392		# questions and bring it up.
1393		if [[ $_if == vlan+([0-9]) ]]; then
1394			vlan_config $_if || continue
1395		fi
1396
1397		# Test if it is an 802.11 interface.
1398		ifconfig $_if 2>/dev/null | grep -q "^[[:space:]]*ieee80211:" &&
1399			ieee80211_config $_if $_hn
1400
1401		# First interface configured will use the hostname without
1402		# asking the user.
1403		resp=$(hostname -s)
1404		[[ -n $_first && $_first != $_if ]] &&
1405			ask "Symbolic (host) name for $_if?" "$resp"
1406		_name=$resp
1407
1408		v4_config $_if $_name $_hn
1409		v6_config $_if $_name $_hn
1410
1411		if [[ -f $_hn ]]; then
1412			chmod 640 $_hn
1413			: ${_first:=$_if}
1414		fi
1415
1416		NIFS=$(ls -1 /tmp/i/hostname.* 2>/dev/null | grep -c ^)
1417		_p=done
1418	done
1419}
1420
1421# Set up IPv4 default route by asking the user for an IPv4 address and preserve
1422# that information in /etc/mygate. If setting the default route fails, try to
1423# revert to a possibly existing previous one.
1424v4_defroute() {
1425	local _dr _dr_if
1426
1427	# Only configure a default route if an IPv4 address was configured.
1428	grep -q '^inet ' /tmp/i/hostname.* 2>/dev/null || return
1429
1430	# Check routing table to see if a default route ($1) already exists
1431	# and what interface it is connected to ($2).
1432	set -- $(route -n show -inet |
1433		sed -En 's/^default +([0-9.]+) .* ([a-z0-9]+) *$/\1 \2/p')
1434	[[ -n $1 ]] && _dr=$1 _dr_if=$2
1435
1436	# Don't ask if a default route exits and is handled by dhcp.
1437	[[ -n $_dr ]] && isin "$_dr_if" $(get_ifs dhcp) && return
1438
1439	while :; do
1440		ask_until "Default IPv4 route? (IPv4 address or 'none')" "$_dr"
1441		[[ $resp == none ]] && break
1442		route delete -inet default >/dev/null 2>&1
1443		if route -n add -inet -host default "$resp"; then
1444			echo "$resp" >>/tmp/i/mygate
1445			break
1446		else
1447			route -n add -inet -host default $_dr >/dev/null 2>&1
1448		fi
1449	done
1450}
1451
1452# Extract the domain part from currently configured fully qualified domain name.
1453# If none is set, use 'my.domain'.
1454get_fqdn() {
1455	local _dn
1456
1457	_dn=$(hostname)
1458	_dn=${_dn#$(hostname -s)}
1459	_dn=${_dn#.}
1460
1461	echo "${_dn:=my.domain}"
1462}
1463
1464
1465# ------------------------------------------------------------------------------
1466# Support functions for install_sets()
1467# ------------------------------------------------------------------------------
1468
1469# SANESETS defines the required list of set files for a sane install or upgrade.
1470# During install_files(), each successfully installed set file is removed from
1471# DEFAULTSETS. Check if there are SANESETS still in DEFAULTSETS and if they were
1472# deliberately skipped. If $1 is not defined, ask the user about each skipped
1473# set file. Care is taken to make sure the return value is correct.
1474sane_install() {
1475	local _q=$1 _s
1476
1477	for _s in $SANESETS; do
1478		isin "$_s" $DEFAULTSETS || continue
1479		[[ -n $_q ]] && return 1
1480		if ! ask_yn "Are you *SURE* your $MODE is complete without '$_s'?"; then
1481			$AI && exit 1 || return 1
1482		fi
1483	done
1484}
1485
1486# Show list of available sets $1 and let the user select which sets to install.
1487# Preselect sets listed in $2 and store the list of selected sets in $resp.
1488#
1489# If the list of available sets only contains kernels during an upgrade, assume
1490# that the user booted into the installer using the currently installed bsd.rd
1491# and specified a set location pointing to a new release. In this case, only
1492# show and preselect bsd.rd. By setting UPGRADE_BSDRD the signify key for the
1493# next release is used to verify the downloaded bsd.rd, the current bsd.rd is
1494# preserved and no questions about missing sets are asked.
1495select_sets() {
1496	local _avail=$1 _selected=$2 _f _action _col=$COLUMNS
1497	local _bsd_rd _no_sets=true
1498
1499	if [[ $MODE == upgrade ]]; then
1500		for _f in $_avail; do
1501			[[ $_f != bsd* ]] && _no_sets=false
1502			[[ $_f == bsd.rd* ]] && _bsd_rd=$_f
1503		done
1504		$_no_sets && UPGRADE_BSDRD=true _avail=$_bsd_rd _selected=$_bsd_rd
1505	fi
1506
1507	# account for 4 spaces added to the sets list
1508	let COLUMNS=_col-8
1509
1510	cat <<'__EOT'
1511
1512Select sets by entering a set name, a file name pattern or 'all'. De-select
1513sets by prepending a '-', e.g.: '-game*'. Selected sets are labelled '[X]'.
1514__EOT
1515	while :; do
1516		for _f in $_avail; do
1517			isin "$_f" $_selected && echo "[X] $_f" || echo "[ ] $_f"
1518		done | show_cols | sed 's/^/    /'
1519		ask "Set name(s)? (or 'abort' or 'done')" done
1520
1521		set -o noglob
1522		for resp in $resp; do
1523			case $resp in
1524			abort)	_selected=; break 2;;
1525			done)	break 2;;
1526			-*)	_action=rmel;;
1527			*)	_action=addel;;
1528			esac
1529			resp=${resp#[+-]}
1530			[[ $resp == all ]] && resp=*
1531
1532			for _f in $_avail; do
1533				[[ $_f == $resp ]] &&
1534					_selected=$($_action $_f $_selected)
1535			done
1536		done
1537	done
1538
1539	set +o noglob
1540	COLUMNS=$_col
1541
1542	resp=$_selected
1543}
1544
1545# Run a command ($2+) as unprivileged user ($1).
1546# Take extra care that after "cmd" no "user" processes exist.
1547#
1548# Optionally:
1549#	- create "file" and chown it to "user"
1550#	- after "cmd", chown "file" back to root
1551#
1552# Usage: do_as user [-f file] cmd
1553do_as() {
1554	(( $# >= 2 )) || return
1555
1556	local _file _rc _user=$1
1557	shift
1558
1559	if [[ $1 == -f ]]; then
1560		_file=$2
1561		shift 2
1562	fi
1563
1564	if [[ -n $_file ]]; then
1565		>$_file
1566		chown "$_user" "$_file"
1567	fi
1568
1569	doas -u "$_user" "$@"
1570	_rc=$?
1571
1572	while doas -u "$_user" kill -9 -1 2>/dev/null; do
1573		echo "Processes still running for user $_user after: $@"
1574		sleep 1
1575	done
1576
1577	[[ -n $_file ]] && chown root "$_file"
1578
1579	return $_rc
1580}
1581
1582unpriv() {
1583	do_as _sndio "$@"
1584}
1585
1586unpriv2() {
1587	do_as _file "$@"
1588}
1589
1590# Find and list filesystems to store the prefetched sets. Prefer filesystems
1591# which are not used during extraction with 512M free space. Otherwise search
1592# any other filesystem that has 2 GB free space to prevent overflow during
1593# extraction.
1594prefetcharea_fs_list() {
1595	local _fs_list
1596
1597	_fs_list=$( (
1598		for fs in /mnt/{tmp,home,usr{/local,}}; do
1599			df -k $fs 2>/dev/null | grep " $fs\$"
1600		done
1601		df -k
1602	) | (
1603		while read a a a a m m; do
1604			[[ $m == /mnt/@(tmp|home|usr/@(src,obj,xobj))@(|/*) ]] &&
1605				((a > 524288)) && echo $m && continue
1606			[[ $m == /mnt@(|/*) ]] &&
1607				((a > 524288 * 4)) && echo $m
1608		done
1609	) | (
1610		while read fs; do
1611			isin "$fs" $list || list="$list${list:+ }$fs"
1612		done
1613		echo $list
1614	) )
1615
1616	[[ -n $_fs_list ]] && echo $_fs_list || return 1
1617}
1618
1619# Install a user-selected subset of the files listed in $2 from the source $1.
1620# Display an error message for each failed install and ask the user whether to
1621# continue or not.
1622install_files() {
1623	local _src=$1 _files=$2 _f _sets _get_sets _n _col=$COLUMNS _tmpfs \
1624		_tmpfs_list _tmpsrc _cfile=/tmp/SHA256 _fsrc _unver _t _issue
1625	local _srclocal=false _unpriv=unpriv
1626
1627	# Fetch sets from local sources (disk, cdrom, nfs) as root.
1628	[[ $_src == file://* ]] && _srclocal=true _unpriv=
1629
1630	# Based on the file list in $_files, create two lists for select_sets().
1631	# _sets:     the list of files the user can select from
1632	# _get_sets: the list of files that are shown as pre-selected
1633	#
1634	# Sets will be installed in the order given in ALLSETS to ensure proper
1635	# installation.  So, to minimize user confusion display the sets in the
1636	# order in which they will be installed.
1637	for _f in $ALLSETS; do
1638		isin "$_f" $_files || continue
1639		_sets=$(addel $_f $_sets)
1640		isin "$_f" $DEFAULTSETS "site$VERSION-$(hostname -s).tgz" &&
1641			_get_sets=$(addel $_f $_get_sets)
1642	done
1643
1644	if [[ -z $_sets ]]; then
1645		echo -n "Looked at $_src "
1646		echo "and found no $OBSD sets.  The set names looked for were:"
1647
1648		let COLUMNS=_col-8
1649		for _n in $ALLSETS; do echo $_n; done | show_cols | sed 's/^/    /'
1650		COLUMNS=$_col
1651
1652		$AI && exit 1
1653		echo
1654		return
1655	fi
1656
1657	isin "INSTALL.$ARCH" $_files ||
1658		ask_yn "INSTALL.$ARCH not found. Use sets found here anyway?" ||
1659		return
1660
1661	select_sets "$_sets" "$_get_sets"
1662
1663	[[ -n $resp ]] || return
1664	_get_sets=$resp
1665
1666	# Reorder $_get_sets.
1667	_get_sets=$(for s in $ALLSETS; do isin "$s" $_get_sets && echo $s; done;
1668	    isin "BUILDINFO" $_files && echo "BUILDINFO")
1669
1670	# Note which sets didn't verify ok.
1671	_unver=$_get_sets
1672
1673	# Try to prefetch and control checksum of the set files.
1674	# Use dummy for loop as combined assignment and do { ... } while(0).
1675	for _issue in ''; do
1676		! isin SHA256.sig $_files &&
1677			_issue="Directory does not contain SHA256.sig" && break
1678
1679		if ! $_srclocal; then
1680			! _tmpfs_list=$(prefetcharea_fs_list) &&
1681				_issue="Cannot determine prefetch area" && break
1682
1683			for _tmpfs in $_tmpfs_list; do
1684				# Try to clean up from previous runs, assuming
1685				# the _tmpfs selection yields the same mount
1686				# point.
1687				for _tmpsrc in $_tmpfs/sets.+([0-9]).+([0-9]); do
1688					[[ -d $_tmpsrc ]] && rm -r $_tmpsrc
1689				done
1690
1691				# Create a download directory for the sets and
1692				# check that the _sndio user can read files from
1693				# it. Otherwise cleanup and skip the filesystem.
1694				if _tmpsrc=$(tmpdir "$_tmpfs/sets"); then
1695					(
1696					>$_tmpsrc/t &&
1697					$_unpriv cat $_tmpsrc/t
1698					) >/dev/null 2>&1 && break ||
1699						rm -r $_tmpsrc
1700				fi
1701			done
1702
1703			[[ ! -d $_tmpsrc ]] &&
1704				_issue="Cannot create prefetch area" && break
1705		fi
1706
1707		# Cleanup from previous runs.
1708		rm -f $_cfile $_cfile.sig
1709
1710		_t=Get/Verify
1711		$_srclocal && _t='Verifying '
1712
1713		# Fetch signature file.
1714		! $_unpriv ftp -D "$_t" -Vmo - "$_src/SHA256.sig" >"$_cfile.sig" &&
1715			_issue="Cannot fetch SHA256.sig" && break
1716
1717		# The bsd.rd only download/verify/install assumes the sets
1718		# location of the next release. So use the right signature file.
1719		$UPGRADE_BSDRD &&
1720			PUB_KEY=/mnt/etc/signify/openbsd-$((VERSION + 1))-base.pub
1721
1722		# Verify signature file with public keys.
1723		! unpriv -f "$_cfile" \
1724			signify -Vep $PUB_KEY -x "$_cfile.sig" -m "$_cfile" &&
1725			_issue="Signature check of SHA256.sig failed" && break
1726
1727		# Fetch and verify the set files.
1728		for _f in $_get_sets; do
1729			reset_watchdog
1730
1731			rm -f /tmp/h /tmp/fail
1732
1733			# Fetch set file and create a checksum by piping through
1734			# sha256. Create a flag file in case ftp failed. Sets
1735			# from net are written to the prefetch area, the output
1736			# of local sets is discarded.
1737			( $_unpriv ftp -D "$_t" -Vmo - "$_src/$_f" || >/tmp/fail ) |
1738			( $_srclocal && unpriv2 sha256 >/tmp/h ||
1739				unpriv2 -f /tmp/h sha256 -ph /tmp/h >"$_tmpsrc/$_f" )
1740
1741			# Handle failed transfer.
1742			if [[ -f /tmp/fail ]]; then
1743				rm -f "$_tmpsrc/$_f"
1744				if ! ask_yn "Fetching of $_f failed. Continue anyway?"; then
1745					[[ -d $_tmpsrc ]] && rm -rf "$_tmpsrc"
1746					$AI && exit 1
1747					return
1748				fi
1749				_unver=$(rmel $_f $_unver)
1750				_get_sets=$(rmel $_f $_get_sets)
1751				continue
1752			fi
1753
1754			# Verify sets by comparing its checksum with SHA256.
1755			if fgrep -qx "SHA256 ($_f) = $(</tmp/h)" "$_cfile"; then
1756				_unver=$(rmel $_f $_unver)
1757			else
1758				if ! ask_yn "Checksum test for $_f failed. Continue anyway?"; then
1759					[[ -d $_tmpsrc ]] && rm -rf "$_tmpsrc"
1760					$AI && exit 1
1761					return
1762				fi
1763			fi
1764		done
1765	done
1766
1767	[[ -n $_unver ]] && : ${_issue:="Unverified sets:" ${_unver% }}
1768	if [[ -n $_issue ]] &&
1769		! ask_yn "$_issue. Continue without verification?"; then
1770		[[ -d $_tmpsrc ]] && rm -rf "$_tmpsrc"
1771		$AI && exit 1
1772		return
1773	fi
1774
1775	# We are committed to installing new files.  Attempt to cope with
1776	# potential space shortage in /usr by deleting a few versioned
1777	# areas which will be replaced from the new sets
1778	if [[ $MODE == upgrade ]]; then
1779		if isin base$VERSION.tgz $_get_sets; then
1780			rm -f /mnt/usr/share/relink/usr/lib/*
1781			rm -rf /mnt/usr/lib/libLLVM.so.[0-7].0
1782			rm -rf /mnt/usr/libdata/perl5
1783		fi
1784		if isin comp$VERSION.tgz $_get_sets; then
1785			rm -rf /mnt/usr/lib/{gcc-lib,clang}
1786			rm -rf /mnt/usr/bin/{gcc,g++}
1787			rm -rf /mnt/usr/include/g++
1788			# file changed to dir in libc++
1789			rm -rf /mnt/usr/include/c++/v1
1790		fi
1791		rm -rf /mnt/var/syspatch/*
1792	fi
1793
1794	# Install the set files.
1795	for _f in $_get_sets; do
1796		reset_watchdog
1797		_fsrc="$_src/$_f"
1798
1799		# Take the set file from the prefetch area if possible.
1800		[[ -f $_tmpsrc/$_f ]] && _fsrc="file://$_tmpsrc/$_f"
1801
1802		# Extract the set files and put the kernel files in place.
1803		case $_fsrc in
1804		*.tgz)	$_unpriv ftp -D Installing -Vmo - "$_fsrc" |
1805				tar -zxphf - -C /mnt &&
1806			if [[ $_f == ?(x)base*.tgz && $MODE == install ]]; then
1807				ftp -D Extracting -Vmo - \
1808				file:///mnt/var/sysmerge/${_f%%base*}etc.tgz |
1809				tar -zxphf - -C /mnt
1810			fi
1811			;;
1812		*BUILDINFO) $_unpriv ftp -D Installing -Vmo - "$_fsrc" \
1813			    > "/mnt/var/db/installed.$_f"
1814			;;
1815		*)	# Make a backup of the existing ramdisk kernel in the
1816			# bsd.rd only download/verify/install case.
1817			$UPGRADE_BSDRD && [[ $_f == bsd.rd* ]] &&
1818				cp /mnt/$_f /mnt/$_f.old.$VERSION
1819			$_unpriv ftp -D Installing -Vmo - "$_fsrc" >"/mnt/$_f"
1820			;;
1821		esac
1822		if (($?)); then
1823			if ! ask_yn "Installation of $_f failed. Continue anyway?"; then
1824				[[ -d $_tmpsrc ]] && rm -rf "$_tmpsrc"
1825				$AI && exit 1
1826				return
1827			fi
1828		else
1829			# Remove each successfully installed set file from
1830			# DEFAULTSETS which is checked by sane_sets().
1831			DEFAULTSETS=$(rmel $_f $DEFAULTSETS)
1832			# Reset DEFAULTSETS to make sure that sane_sets() does
1833			# not complain about missing set files in the bsd.rd
1834			# only download/verify/install case,
1835			$UPGRADE_BSDRD && DEFAULTSETS=
1836		fi
1837		[[ -d $_tmpsrc ]] && rm -f "$_tmpsrc/$_f"
1838	done
1839	[[ -d $_tmpsrc ]] && rm -rf "$_tmpsrc" || true
1840
1841	# Keep SHA256 from installed sets for sysupgrade(8).
1842	if [[ -f $_cfile ]]; then
1843		cp $_cfile /mnt/var/db/installed.SHA256
1844	elif $_srclocal && [[ -f ${_src#file://}/SHA256 ]]; then
1845		cp ${_src#file://}/SHA256 /mnt/var/db/installed.SHA256
1846	fi
1847
1848	reset_watchdog
1849}
1850
1851# Fetch install sets from an HTTP server possibly using a proxy.
1852install_http() {
1853	local _d _f _flist _file_list _prompt _tls _http_proto _url_base
1854	local _idx=/tmp/i/index.txt _sha=/tmp/i/SHA256 _sig=/tmp/i/SHA256.sig
1855	local _iu_url _iu_srv _iu_dir _mirror_url _mirror_srv _mirror_dir
1856	local _ftp_stdout=/tmp/i/ftpstdout _rurl_base
1857
1858	# N.B.: Don't make INSTALL_MIRROR a local variable! It preserves the
1859	# mirror information if install_http() is called multiple times with
1860	# mirror and local servers. That ensures that the mirror server ends
1861	# up in /etc/installurl file if one of the servers is not a mirror.
1862
1863	# N.B.: 'http_proxy' is an environment variable used by ftp(1).
1864	# DON'T change the name or case!
1865	ask "HTTP proxy URL? (e.g. 'http://proxy:8080', or 'none')" \
1866		"${http_proxy:-none}"
1867	unset http_proxy
1868	[[ $resp == none ]] || export http_proxy=$resp
1869
1870	# If the mirror server listfile download failed, inform the user and
1871	# show a reduced prompt.
1872	if [[ -s $HTTP_LIST ]]; then
1873		_prompt="HTTP Server? (hostname, list#, 'done' or '?')"
1874	else
1875		echo "(Unable to get list from openbsd.org, but that is OK)"
1876		_prompt="HTTP Server? (hostname or 'done')"
1877	fi
1878
1879	# Use information from /etc/installurl as defaults for upgrades.
1880	# Format of installurl: _http_proto://_iu_srv/_iu_dir
1881	#                       ^--------- _iu_url ---------^
1882	if [[ $MODE == upgrade ]] &&
1883		_iu_url=$(stripcom /mnt/etc/installurl); then
1884		_iu_srv=${_iu_url#*://}
1885		_iu_srv=${_iu_srv%%/*}
1886		_iu_dir=${_iu_url##*$_iu_srv*(/)}
1887		[[ -n $_iu_srv ]] && HTTP_SERVER=$_iu_srv
1888	fi
1889
1890	# Get server IP address or hostname and optionally the http protocol.
1891	while :; do
1892		ask_until "$_prompt" "$HTTP_SERVER"
1893		case $resp in
1894		done)	return
1895			;;
1896		"?")	[[ -s $HTTP_LIST ]] || continue
1897			# Show a numbered list of mirror servers.
1898			cat -n < $HTTP_LIST | more -c
1899			;;
1900		+([0-9]))
1901			# A number is only used as a line number in $HTTP_LIST.
1902			[[ -s $HTTP_LIST ]] || continue
1903			# Extract the URL from the mirror server listfile.
1904			set -- $(sed -n "${resp}p" $HTTP_LIST)
1905			if (($# < 1)); then
1906				echo "There is no line $resp."
1907				continue
1908			fi
1909			HTTP_SERVER=${1%%/*}
1910			# Repeat loop to get user to confirm server address.
1911			;;
1912		?(http?(s)://)+([A-Za-z0-9:.\[\]_-]))
1913			case $resp in
1914			https://*)	_tls=force _http_proto=https;;
1915			http://*)	_tls=no    _http_proto=http;;
1916			*)		_tls=try   _http_proto=$HTTP_PROTO;;
1917			esac
1918			if ! $FTP_TLS && [[ $_tls == force ]]; then
1919				echo "https not supported on this platform."
1920				$AI && exit 1 || continue
1921			fi
1922			HTTP_SERVER=${resp#*://}
1923			break
1924			;;
1925		*)	echo "'$resp' is not a valid hostname."
1926			;;
1927		esac
1928	done
1929
1930	# Get directory info from *last* line starting with the server
1931	# name. This means the last install from a mirror will not keep
1932	# the specific directory info. But an install from a local
1933	# server *will* remember the specific directory info.
1934	# Format: _mirror_srv/_mirror_dir location_info
1935	#         ^---- _mirror_url ----^
1936	set -- $(grep -i "^$HTTP_SERVER" $HTTP_LIST 2>/dev/null | sed '$!d')
1937	_mirror_url=${1%%*(/)}
1938	_mirror_srv=${_mirror_url%%/*}
1939	_mirror_dir=${_mirror_url##*$_mirror_srv*(/)}
1940
1941	# Decide on the default for the "Server directory" question.
1942	if [[ -n $_mirror_url ]]; then
1943		# Use directory information from cgi server if HTTP_SERVER was
1944		# found in HTTP_LIST. That is either an official mirror or the
1945		# server used in a previous installation or upgrade.
1946		_d=$_mirror_dir/$HTTP_SETDIR
1947
1948		# Preserve the information that it is an official mirror if
1949		# location is present in $2.
1950		(($# > 1)) && INSTALL_MIRROR=$_mirror_url
1951	elif [[ -n $_iu_url ]]; then
1952		# Otherwise, if it exists, use directory information from
1953		# installurl(5) during upgrade.
1954		_d=$_iu_dir/$HTTP_SETDIR
1955	else
1956		_d=pub/OpenBSD/$HTTP_SETDIR
1957	fi
1958
1959	ask_until "Server directory?" "$_d"
1960	HTTP_DIR=${resp##+(/)}
1961	_url_base="$_http_proto://$HTTP_SERVER/$HTTP_DIR"
1962
1963	# Fetch SHA256.sig to create the list of files to select from.
1964	rm -f $_idx $_sha $_sig $_ftp_stdout
1965	if ! unpriv -f $_sig \
1966		ftp -w 15 -vMo $_sig "$_url_base/SHA256.sig" \
1967			>$_ftp_stdout 2>/dev/null; then
1968		case $_tls in
1969		force)	$AI && exit 1 || return
1970			;;
1971		try)	echo "Unable to connect using HTTPS; using HTTP instead."
1972			_http_proto=http
1973			_url_base="http://$HTTP_SERVER/$HTTP_DIR"
1974			unpriv -f $_sig ftp -vMo $_sig "$_url_base/SHA256.sig" \
1975				>$_ftp_stdout 2>/dev/null
1976			;;
1977		esac
1978	fi
1979
1980	# In case of URL redirection, use the final location to retrieve the
1981	# rest of the files from. Redirection does not change INSTALL_MIRROR.
1982	_rurl_base=$(sed -n 's/^Requesting //p' $_ftp_stdout | sed '$!d')
1983	_rurl_base=${_rurl_base%/SHA256.sig*}
1984
1985	# Verify SHA256.sig, write SHA256 and extract the list of files.
1986	if unpriv -f $_sha \
1987		signify -Vep $PUB_KEY -x $_sig -m $_sha >/dev/null 2>&1; then
1988		_file_list="$(sed -n 's/^SHA256 (\(.*\)).*$/\1/p' $_sha)"
1989		_file_list="SHA256.sig $_file_list"
1990	else
1991		echo "Unable to get a verified list of distribution sets."
1992		# Deny this server, if it's a mirror without a valid SHA256.sig.
1993		if [[ ${_rurl_base%/$HTTP_SETDIR} == "$_http_proto://$INSTALL_MIRROR" ]]; then
1994			$AI && exit 1 || return
1995		fi
1996	fi
1997
1998	# Fetch index.txt, extract file list but add only entries that are not
1999	# already in _file_list. This allows for a verified list of distribution
2000	# sets from SHA256.sig, siteXX sets or the whole set list from index.txt
2001	# if SHA256.sig was not found (e.g. self compiled sets).
2002	if unpriv -f $_idx \
2003		ftp -VMo $_idx "$_rurl_base/index.txt" 2>/dev/null; then
2004		_flist=$(sed -En 's/^.* ([a-zA-Z][a-zA-Z0-9._-]+)$/\1/p' $_idx)
2005		for _f in $_flist; do
2006			! isin "$_f" $_file_list && _file_list="$_file_list $_f"
2007		done
2008	fi
2009	rm -f $_idx $_sha $_sig $_ftp_stdout
2010
2011	install_files "$_rurl_base" "$_file_list"
2012
2013	# Remember the sets location which is used later for creating the
2014	# installurl(5) file and to tell the cgi server.
2015	if [[ -n $INSTALL_MIRROR ]]; then
2016		INSTALL_URL=$_http_proto://$INSTALL_MIRROR
2017	else
2018		# Remove the architecture and snapshots or version part.
2019		INSTALL_URL=${_url_base%/$ARCH}
2020		INSTALL_URL=${INSTALL_URL%@(/$VNAME|/snapshots)}
2021	fi
2022}
2023
2024# Ask for the path to the set files on an already mounted filesystem and start
2025# the set installation.
2026install_mounted_fs() {
2027	local _dir
2028
2029	while :; do
2030		ask_until "Pathname to the sets? (or 'done')" "$SETDIR"
2031		[[ $resp == done ]] && return
2032		# Accept a valid /mnt2 or /mnt relative path.
2033		[[ -d /mnt2/$resp ]] && { _dir=/mnt2/$resp; break; }
2034		[[ -d /mnt/$resp ]] && { _dir=/mnt/$resp; break; }
2035		# Accept a valid absolute path.
2036		[[ -d /$resp ]] && { _dir=/$resp; break; }
2037		echo "The directory '$resp' does not exist."
2038		$AI && exit 1
2039	done
2040
2041	install_files "file://$_dir" "$(ls $_dir/)"
2042}
2043
2044# Install sets from CD-ROM drive $1.
2045install_cdrom() {
2046	local _drive=$1
2047
2048	make_dev $_drive && mount_mnt2 $_drive || return
2049
2050	install_mounted_fs
2051}
2052
2053# Install sets from disk.
2054# Ask for the disk device containing the set files.
2055install_disk() {
2056	local _ismounted=yes
2057
2058	# No partitions are mounted prior to regular installation.
2059	[[ $MODE == install ]] && _ismounted=no
2060
2061	if ! ask_yn "Is the disk partition already mounted?" $_ismounted; then
2062		ask_which "disk" "contains the $MODE media" \
2063			'$(bsort $(get_dkdevs))' \
2064			'$(get_dkdevs_uninitialized)'
2065		[[ $resp == done ]] && return 1
2066
2067		# Ensure the device file exists and mount the fs on /mnt2.
2068		make_dev $resp && mount_mnt2 $resp || return
2069	fi
2070
2071	install_mounted_fs
2072}
2073
2074# Ask for the nfs share details, mount it and start the set installation.
2075install_nfs() {
2076	local _tcp
2077
2078	# Get the IP address of the server.
2079	ask_until "Server IP address or hostname?" "$NFS_ADDR"
2080	NFS_ADDR=$resp
2081
2082	# Get the server path to mount.
2083	ask_until "Filesystem on server to mount?" "$NFS_PATH"
2084	NFS_PATH=$resp
2085
2086	# Determine use of TCP.
2087	ask_yn "Use TCP transport? (requires TCP-capable NFS server)" && _tcp=-T
2088
2089	# Mount the server.
2090	mount_nfs $_tcp -o ro -R 5 $NFS_ADDR:$NFS_PATH /mnt2 || return
2091
2092	install_mounted_fs
2093}
2094
2095# Mount filesystem containing the set files on device $1, optionally ask the
2096# user for the device name.
2097mount_mnt2() {
2098	local _dev=$1 _opts _file=/tmp/i/parts.$1 _parts
2099
2100	disklabel $_dev 2>/dev/null |
2101		sed -En '/swap|unused/d;/^  [a-p]: /p' >$_file
2102	_parts=$(sed 's/^  \(.\): .*/\1/' $_file)
2103	set -- $_parts
2104	(($# == 0)) && { echo "No filesystems found on $_dev."; return 1; }
2105
2106	if isin "c" $_parts; then
2107		# Don't ask questions if 'c' contains a filesystem.
2108		resp=c
2109	elif (($# == 1)); then
2110		# Don't ask questions if there's only one choice.
2111		resp=$1
2112	else
2113		# Display partitions with filesystems and ask which to use.
2114		cat $_file
2115		ask_which "$_dev partition" "has the $MODE sets" \
2116			'$(disklabel '$_dev' 2>/dev/null |
2117			sed -En '\''/swap|unused/d;/^  ([a-p]): .*/s//\1/p'\'')'
2118		[[ $resp == done ]] && return 1
2119	fi
2120
2121	# Always mount msdos partitions with -s to get lower case names.
2122	grep -q "^  $resp: .*MSDOS" $_file && _opts="-s"
2123	mount -o ro,$_opts /dev/$_dev$resp /mnt2
2124}
2125
2126
2127# ------------------------------------------------------------------------------
2128# Functions used in install.sh/upgrade.sh and its associates
2129# ------------------------------------------------------------------------------
2130
2131# Ask for terminal type if on console, otherwise ask for/set keyboard layout.
2132set_term() {
2133	local _layouts
2134	export TERM=${TERM:-${MDTERM:-vt220}}
2135
2136	if [[ -n $CONSOLE ]]; then
2137		ask "Terminal type?" "$TERM"
2138		TERM=$resp
2139	else
2140		[[ -x /sbin/kbd ]] || return
2141		_layouts=$(bsort $(kbd -l | egrep -v "^(user|tables|encoding)"))
2142		# Ensure all connected keyboards get the same encoding
2143		make_dev $(scan_dmesg '/^wskbd[0-9]* /s/ .*//p')
2144		while :; do
2145			ask "Choose your keyboard layout ('?' or 'L' for list)" default
2146			case $resp in
2147			[lL\?])		echo "Available layouts: $_layouts"
2148					;;
2149			default)	break
2150					;;
2151			*)		if kbd -q "$resp"; then
2152						echo $resp >/tmp/i/kbdtype
2153						break
2154					fi
2155					;;
2156			esac
2157		done
2158	fi
2159}
2160
2161# Configure the network.
2162donetconfig() {
2163	local _dn _ns _f1 _f2 _f3 _autoconf_ns=false
2164
2165	configure_ifs
2166	v4_defroute
2167	v6_defroute
2168
2169	# Check for nameserver proposals resolvd found.
2170	if [[ -f /etc/resolv.conf ]]; then
2171		# Get/store nameserver address(es) as a blank separated list
2172		# and the default fully qualified domain name from *first*
2173		# domain given on *last* search or domain statement.
2174		while read -r -- _f1 _f2 _f3; do
2175			[[ $_f1 == nameserver ]] && _ns="${_ns:+$_ns }$_f2"
2176			[[ $_f3 == '# resolvd: '* ]] && _autoconf_ns=true
2177			[[ $_f1 == @(domain|search) ]] && _dn=$_f2
2178		done </etc/resolv.conf
2179	fi
2180
2181	# Get & apply fqdn to hostname. Don't ask if there's only one configured
2182	# interface and if it's managed by dhcp and if the domain name is
2183	# configured via dhcp too.
2184	resp="${_dn:-$(get_fqdn)}"
2185	if ifconfig dhcp >/dev/null 2>&1 && [[ $NIFS == 1 && -z $_dn ]]; then
2186		# If we have a 'domain-name' option in the lease file use that.
2187		# It might *NOT* not be the same as the first domain in any
2188		# 'domain-search' option.
2189		set -- $(get_ifs dhcp)
2190		set -- $(lease_value /var/db/dhcpleased/$1 domain-name)
2191		[[ -n $1 ]] && resp=$1
2192		echo "Using DNS domainname $resp"
2193	else
2194		ask "DNS domain name? (e.g. 'example.com')" "$resp"
2195	fi
2196	hostname "$(hostname -s).$resp"
2197
2198	if $_autoconf_ns && [[ -n $_ns ]]; then
2199		echo "Using DNS nameservers at $_ns"
2200		return
2201	fi
2202
2203	# Get & add nameservers to /tmp/resolv.conf. Don't ask if there's only
2204	# one configured interface and if it's managed by dhcp and if the
2205	# nameserver is configured via dhcp too.
2206	resp="${_ns:-none}"
2207	if ifconfig dhcp >/dev/null 2>&1 && [[ $NIFS == 1 && -n $_ns ]]; then
2208		echo "Using DNS nameservers at $resp"
2209	else
2210		ask "DNS nameservers? (IP address list or 'none')" "$resp"
2211	fi
2212
2213	# Construct appropriate resolv.conf.
2214	if [[ $resp != none ]]; then
2215		echo "lookup file bind" >/tmp/resolv.conf
2216		for _ns in $resp; do
2217			echo "nameserver $_ns" >>/tmp/resolv.conf
2218		done
2219		# replace it, and resolvd will repair
2220		cp /tmp/resolv.conf /etc/resolv.conf
2221	fi
2222}
2223
2224# Ask user about daemon startup on boot, X Window usage and console setup.
2225# The actual configuration is done later in apply().
2226questions() {
2227	local _d _cdef=no
2228
2229	ask_yn "Start sshd(8) by default?" yes
2230	START_SSHD=$resp
2231
2232	APERTURE=
2233	resp=
2234	START_XDM=
2235	if [[ -n $(scan_dmesg '/^wsdisplay[0-9]* /s/ .*//p') ]]; then
2236		if [[ -n $(scan_dmesg '/^[a-z]*[01]: aperture needed/p') ]]; then
2237			ask_yn "Do you expect to run the X Window System?" yes &&
2238				APERTURE=$MDXAPERTURE
2239		fi
2240		if [[ -n $MDXDM && $resp != n ]]; then
2241			ask_yn "Do you want the X Window System to be started by xenodm(1)?"
2242			START_XDM=$resp
2243		fi
2244	fi
2245
2246	if [[ -n $CDEV ]]; then
2247		_d=${CPROM:-$CDEV}
2248		[[ -n $CONSOLE ]] && _cdef=yes
2249		ask_yn "Change the default console to $_d?" $_cdef
2250		DEFCONS=$resp
2251		if [[ $resp == y ]]; then
2252			ask_which "speed" "should $_d use" \
2253				"9600 19200 38400 57600 115200" $CSPEED
2254			case $resp in
2255			done)	DEFCONS=n;;
2256			*)	CSPEED=$resp;;
2257			esac
2258		fi
2259	fi
2260}
2261
2262# Gather information for setting up the user later in do_install().
2263user_setup() {
2264	local _q="Setup a user? (enter a lower-case loginname, or 'no')"
2265
2266	while :; do
2267		ask "$_q" no
2268		case $resp in
2269		n|no)	return
2270			;;
2271		y|yes)	_q="No really, what is the lower-case loginname, or 'no'?"
2272			continue
2273			;;
2274		root|daemon|operator|bin|build|sshd|www|nobody|ftp)
2275			;;
2276		[a-z]*([-a-z0-9_]))
2277			((${#resp} <= 31)) && break
2278			;;
2279		esac
2280		echo "$resp is not a usable loginname."
2281	done
2282	ADMIN=$resp
2283	while :; do
2284		ask "Full name for user $ADMIN?" "$ADMIN"
2285		case $resp in
2286		*[:\&,]*)
2287			echo "':', '&' or ',' are not allowed."
2288			;;
2289		*)
2290			((${#resp} <= 100)) && break
2291			echo "Too long."
2292			;;
2293		esac
2294	done
2295	ADMIN_NAME=$resp
2296
2297	ask_password "Password for user $ADMIN?"
2298	ADMIN_PASS=$_password
2299
2300	ADMIN_KEY=
2301	$AI && ask "Public ssh key for user $ADMIN" none &&
2302		[[ $resp != none ]] && ADMIN_KEY=$resp
2303}
2304
2305# Ask user whether or not to allow logins to root in case sshd(8) is enabled.
2306# If no user is setup, show a hint to enable root logins, but warn about risks
2307# of doing so.
2308ask_root_sshd() {
2309	typeset -l _resp
2310
2311	[[ $START_SSHD == y ]] || return
2312
2313	if [[ -z $ADMIN ]]; then
2314		echo "Since no user was setup, root logins via sshd(8) might be useful."
2315	fi
2316	echo "WARNING: root is targeted by password guessing attacks, pubkeys are safer."
2317	while :; do
2318		ask "Allow root ssh login? (yes, no, prohibit-password)" no
2319		_resp=$resp
2320		case $_resp in
2321		y|yes)	SSHD_ENABLEROOT=yes
2322			;;
2323		n|no)	SSHD_ENABLEROOT=no
2324			;;
2325		w|p|without-password|prohibit-password)
2326			SSHD_ENABLEROOT=prohibit-password
2327			;;
2328		*)	echo "'$resp' is not a valid choice."
2329			$AI && exit 1
2330			continue
2331			;;
2332		esac
2333		break
2334	done
2335}
2336
2337# Set TZ variable based on zonefile $1 and user selection.
2338set_timezone() {
2339	local _zonefile=$1 _zonepath _zsed _zoneroot=/usr/share/zoneinfo
2340
2341	# If the timezone file is not available,
2342	# return immediately.
2343	[[ ! -f $_zonefile ]] && return
2344
2345	# If configured in a previous call, return immediately.
2346	[[ -n $TZ ]] && return
2347
2348	if [[ -h /mnt/etc/localtime ]]; then
2349		TZ=$(ls -l /mnt/etc/localtime 2>/dev/null)
2350		TZ=${TZ#*${_zoneroot#/mnt}/}
2351	fi
2352
2353	wait_cgiinfo
2354	isin "$CGI_TZ" $(<$_zonefile) && TZ=$CGI_TZ
2355
2356	# If neither the base or HTTP_LIST gave a hint, and this is the
2357	# early question, give up, and ask after the sets are installed.
2358	[[ $_zonefile == /var/tzlist && -z $TZ ]] && return
2359
2360	while :; do
2361		ask "What timezone are you in? ('?' for list)" "$TZ"
2362		_zonepath=${resp%%*(/)}
2363		case $_zonepath in
2364		"")	continue
2365			;;
2366		"?")	grep -v /. $_zonefile | show_cols
2367			continue
2368			;;
2369		esac
2370
2371		while isin "$_zonepath/" $(<$_zonefile); do
2372			ask "What sub-timezone of '$_zonepath' are you in? ('?' for list)"
2373			_zsed=$(echo $_zonepath/ | sed 's,/,\\/,g')
2374			resp=${resp%%*(/)}
2375			case $resp in
2376			"")	;;
2377			"?")	sed -n "/^$_zsed/{s/$_zsed//;/\/./!p;}" $_zonefile | show_cols;;
2378			*)	_zonepath=$_zonepath/$resp;;
2379			esac
2380		done
2381
2382		if isin "$_zonepath" $(<$_zonefile); then
2383			TZ=${_zonepath#$_zoneroot}
2384			return
2385		fi
2386
2387		echo -n "'${_zonepath}'"
2388		echo " is not a valid timezone on this system."
2389	done
2390}
2391
2392# Determine if the supplied disk is a potential root disk, by:
2393# - Check the disklabel if there is an 'a' partition of type 4.2BSD
2394# - Mount the partition (read-only) and look for typical root filesystem layout
2395is_rootdisk() {
2396	local _d=$1 _rc=1
2397
2398	make_dev $_d
2399	if disklabel $_d | grep -q '^  a: .*4\.2BSD ' &&
2400		mount -t ffs -r /dev/${_d}a /mnt; then
2401		if $UU; then
2402			ls -d /mnt/{auto_upgrade.conf,bin,dev,etc,home,sbin,tmp,usr,var}
2403		else
2404			ls -d /mnt/{bin,dev,etc,home,sbin,tmp,usr,var}
2405		fi
2406		_rc=$?
2407		umount /mnt
2408	fi >/dev/null 2>&1
2409	rm -f /dev/{r,}$_d?
2410
2411	return $_rc
2412}
2413
2414# Get global root information. ie. ROOTDISK, ROOTDEV and SWAPDEV.
2415get_rootinfo() {
2416	local _default=$(get_dkdevs_root) _dkdev
2417	local _q="Which disk is the root disk? ('?' for details)"
2418
2419	while :; do
2420		echo "Available disks are: $(get_dkdevs_root | sed 's/^$/none/')."
2421		_ask "$_q" $_default || continue
2422		case $resp in
2423		"?")	diskinfo $(get_dkdevs);;
2424		'')	;;
2425		*)	# Translate $resp to disk dev name in case it is a DUID.
2426			# get_dkdev_name bounces back the disk dev name if not.
2427			_dkdev=$(get_dkdev_name "$resp")
2428			if isin "$_dkdev" $(get_dkdevs); then
2429				[[ $MODE == install ]] && break
2430				is_rootdisk "$_dkdev" && break
2431				echo "$resp is not a valid root disk."
2432				_default="$(rmel "$_dkdev" $_default) $_dkdev"
2433			else
2434				echo "no such disk"
2435			fi
2436			;;
2437		esac
2438		$AI && exit 1
2439	done
2440	log_answers "$_q" "$resp"
2441
2442	make_dev $_dkdev || exit
2443
2444	ROOTDISK=$_dkdev
2445	ROOTDEV=${ROOTDISK}a
2446	SWAPDEV=${ROOTDISK}b
2447}
2448
2449# Parse and "unpack" a hostname.if(5) line given as positional parameters.
2450# Fill the _cmds array with the resulting interface configuration commands.
2451parse_hn_line() {
2452	local _af=0 _name=1 _mask=2 _bc=3 _prefix=2 _c _cmd _prev _daddr _dhcp _i
2453	local _has_dhcp=false _has_inet6=false
2454	set -A _c -- "$@"
2455	set -o noglob
2456
2457	ifconfig $_if inet6 >/dev/null 2>&1 && _has_inet6=true
2458	[[ -x /sbin/dhcpleased ]] && _has_dhcp=true
2459
2460	case ${_c[_af]} in
2461	''|*([[:blank:]])'#'*)
2462		return
2463		;;
2464	inet)	((${#_c[*]} > 1)) || return
2465		if [[ ${_c[_name]} == autoconf ]]; then
2466			_cmds[${#_cmds[*]}]="ifconfig $_if ${_c[@]}"
2467			V4_AUTOCONF=true
2468			return
2469		fi
2470		[[ ${_c[_name]} == alias ]] && _mask=3 _bc=4
2471		[[ -n ${_c[_mask]} ]] && _c[_mask]="netmask ${_c[_mask]}"
2472		if [[ -n ${_c[_bc]} ]]; then
2473			_c[_bc]="broadcast ${_c[_bc]}"
2474			[[ ${_c[_bc]} == *NONE ]] && _c[_bc]=
2475		fi
2476		_cmds[${#_cmds[*]}]="ifconfig $_if ${_c[@]}"
2477		;;
2478	inet6)	! $_has_inet6 && return
2479		((${#_c[*]} > 1)) || return
2480		if [[ ${_c[_name]} == autoconf ]]; then
2481			_cmds[${#_cmds[*]}]="ifconfig $_if ${_c[@]}"
2482			V6_AUTOCONF=true
2483			return
2484		fi
2485		[[ ${_c[_name]} == alias ]] && _prefix=3
2486		[[ -n ${_c[_prefix]} ]] && _c[_prefix]="prefixlen ${_c[_prefix]}"
2487		_cmds[${#_cmds[*]}]="ifconfig $_if ${_c[@]}"
2488		;;
2489	dest)	((${#_c[*]} == 2)) && _daddr=${_c[1]} || return
2490		! $_has_inet6 && [[ $_daddr == @(*:*) ]] && return
2491		_prev=$((${#_cmds[*]} - 1))
2492		((_prev >= 0)) || return
2493		set -A _c -- ${_cmds[_prev]}
2494		_name=3
2495		[[ ${_c[_name]} == alias ]] && _name=4
2496		_c[_name]="${_c[_name]} $_daddr"
2497		_cmds[$_prev]="${_c[@]}"
2498		;;
2499	dhcp)	! $_has_dhcp && return
2500		_cmds[${#_cmds[*]}]="ifconfig $_if inet autoconf"
2501		V4_AUTOCONF=true
2502		;;
2503	'!'*)
2504		# Skip shell commands in the installer.
2505		return
2506		;;
2507	*)	_cmds[${#_cmds[*]}]="ifconfig $_if ${_c[@]}"
2508		;;
2509	esac
2510	unset _c
2511	set +o noglob
2512}
2513
2514# Start interface using the on-disk hostname.if file passed as argument $1.
2515# Much of this is gratuitously stolen from /etc/netstart.
2516ifstart() {
2517	local _if=$1 _lladdr _hn=/mnt/etc/hostname.$1 _cmds _i=0 _line
2518	set -A _cmds
2519
2520	if [[ $_if == +([[:alpha:]])+([[:digit:]]) ]]; then
2521		_lladdr=$(if_name_to_lladdr $_if)
2522		[[ -n $_lladdr && -f /mnt/etc/hostname.$_lladdr ]] && return
2523	elif [[ $_if == ??:??:??:??:??:?? ]]; then
2524		_lladdr=$_if
2525		_if=$(ifconfig -M $_lladdr)
2526		[[ -z $_if ]] && return
2527	else
2528		return
2529	fi
2530
2531	# Create interface if it does not yet exist.
2532	{ ifconfig $_if || ifconfig $_if create; } >/dev/null 2>&1 || return
2533
2534	((NIFS++))
2535
2536	# Parse the hostname.if(5) file and fill _cmds array with interface
2537	# configuration commands.
2538	set -o noglob
2539	while IFS= read -- _line; do
2540		parse_hn_line $_line
2541	done <$_hn
2542
2543	# Apply the interface configuration commands stored in _cmds array.
2544	while ((_i < ${#_cmds[*]})); do
2545		eval "${_cmds[_i]}"
2546		((_i++))
2547	done
2548	unset _cmds
2549	set +o noglob
2550}
2551
2552# Configure the network during upgrade based on the on-disk configuration.
2553enable_ifs() {
2554	local _gw _v4set=false _v6set=false _hn _if _trunks _svlans _vlans
2555
2556	# Set the address for the loopback interface. Bringing the
2557	# interface up, automatically invokes the IPv6 address ::1.
2558	ifconfig lo0 inet 127.0.0.1/8
2559
2560	# Configure all of the non-loopback interfaces which we know about.
2561	# Refer to hostname.if(5)
2562	for _hn in /mnt/etc/hostname.*; do
2563		# Strip off prefix to get interface name.
2564		_if=${_hn#/mnt/etc/hostname.}
2565		if isin "${_if%%+([0-9])}" $(ifconfig -C); then
2566			# Dynamic interfaces must be done later.
2567			case ${_if%%+([0-9])} in
2568			trunk)	_trunks="$_trunks $_if";;
2569			svlan)	_svlans="$_svlans $_if";;
2570			vlan)	_vlans="$_vlans $_if";;
2571			esac
2572		elif [[ $_if == ??:??:??:??:??:?? ]]; then
2573			# start by lladdr
2574			ifstart $_if
2575		else
2576			# 'Real' interfaces (if available) are done now.
2577			ifconfig $_if >/dev/null 2>&1 && ifstart $_if
2578		fi
2579	done
2580	# Configure any dynamic interfaces now that 'real' ones are up.
2581	# ORDER IS IMPORTANT! (see /etc/netstart).
2582	for _if in $_trunks $_svlans $_vlans; do
2583		ifstart $_if
2584	done
2585
2586	# /mnt/etc/mygate, if it exists, contains the address(es) of my
2587	# default gateway(s). Use for ipv4 if no interfaces configured via
2588	# autoconf. Use for ipv6 if no interfaces configured via autoconf.
2589	stripcom /mnt/etc/mygate |
2590	while read _gw; do
2591		case $_gw in
2592		'!'*)
2593			# Skip shell commands in the installer.
2594			continue
2595			;;
2596		!(*:*))
2597			($_v4set || $V4_AUTOCONF) && continue
2598			route -qn add -host default $_gw
2599			_v4set=true
2600			;;
2601		*)
2602			($_v6set || $V6_AUTOCONF) && continue
2603			route -qn add -host -inet6 default $_gw
2604			_v6set=true
2605			;;
2606		esac
2607	done
2608
2609	route -qn add -net 127 127.0.0.1 -reject >/dev/null
2610}
2611
2612enable_network() {
2613	local _f
2614
2615	# Use installed network configuration files during upgrade.
2616	for _f in resolv.conf; do
2617		if [[ -f /mnt/etc/$_f ]]; then
2618			cp /mnt/etc/$_f /etc/$_f
2619		fi
2620	done
2621
2622	# Create a minimal hosts file.
2623	echo "127.0.0.1\tlocalhost" >/tmp/i/hosts
2624	echo "::1\t\tlocalhost" >>/tmp/i/hosts
2625
2626	_f=/mnt/etc/soii.key
2627	[[ -f $_f ]] && sysctl "net.inet6.ip6.soiikey=$(<$_f)"
2628
2629	enable_ifs
2630}
2631
2632# Fetch the list of mirror servers and installer choices from previous runs if
2633# available from ftplist.cgi. Start the ftp process in the background, but kill
2634# it if it takes longer than 12 seconds.
2635start_cgiinfo() {
2636	# If no networks are configured, we do not need the httplist file.
2637	((NIFS < 1)) && return
2638
2639	# Ensure proper name resolution in case there's no dns yet.
2640	add_hostent 199.185.178.80 ftplist1.openbsd.org
2641	add_hostent 2620:3d:c000:178::80 ftplist1.openbsd.org
2642
2643	# Make sure the ftp subshell gets its own process group.
2644	set -m
2645	(
2646		unpriv2 ftp -w 15 -Vao - \
2647			"$HTTP_PROTO://ftplist1.openbsd.org/cgi-bin/ftplist.cgi?dbversion=1" \
2648			2>/dev/null >$CGI_INFO
2649
2650		# Remember finish time for adjusting the received timestamp.
2651		echo -n $SECONDS >$HTTP_SEC
2652		feed_random
2653	) &
2654	echo $! > /tmp/cgipid
2655	set +m
2656
2657	# If the ftp process takes more than 12 seconds, kill it.
2658	(
2659		sleep 12;
2660		if [ -f /tmp/cgipid ]; then
2661			kill -INT -"$(</tmp/cgipid)" >/dev/null 2>&1
2662			# wait will be done by wait_cgiinfo
2663		fi
2664	) &
2665}
2666
2667# Create a skeletal but useful /etc/fstab from /tmp/i/fstab by stripping all
2668# comment lines and dropping all filesystems which
2669#
2670# 1) can't be mounted (no mount_* command is found),
2671# 2) have 'xx' in the option field (usually /altroot),
2672# 3) have 'noauto' in the option field,
2673# 4) are nfs (since name resolution may not be present),
2674# 5) are on a vnd device.
2675#
2676# In addition,
2677#
2678# 1) mount non-ffs filesystems read only,
2679# 2) prepend '/mnt' to all mount points,
2680# 3) delete any trailing '/' from the mount point (e.g. root),
2681#
2682# If no /etc/fstab is created, do not proceed with install/upgrade.
2683munge_fstab() {
2684	local _dev _mp _fstype _opt _rest
2685
2686	while read _dev _mp _fstype _opt _rest; do
2687		# Drop irrelevant lines and filesystems.
2688		[[ $_dev == @(/dev/vnd*|\#*) ||
2689			$_fstype == nfs ||
2690			! -f /sbin/mount_$_fstype ||
2691			$_opt == *noauto* ||
2692			$_opt == *xx* ]] && continue
2693
2694		# Change read-only ffs to read-write since we'll potentially
2695		# write to these filesystems.
2696		# Mount non-ffs filesystems read only.
2697		if [[ $_fstype == ffs ]]; then
2698			_opt=$(echo $_opt | sed 's/[[:<:]]ro[[:>:]]/rw/')
2699		else
2700			_opt=$(echo $_opt | sed 's/[[:<:]]rw[[:>:]]/ro/')
2701		fi
2702
2703		# Write fs entry in fstab.
2704		# 1) prepend '/mnt' to the mount point.
2705		# 2) remove a trailing '/' from the mount point (e.g. root).
2706		echo $_dev /mnt${_mp%/} $_fstype $_opt $_rest
2707
2708	done </tmp/i/fstab >/etc/fstab
2709
2710	# If no /etc/fstab was created, we have nowhere to $MODE to.
2711	if [[ ! -s /etc/fstab ]]; then
2712		echo "Unable to create valid /etc/fstab."
2713		exit
2714	fi
2715}
2716
2717# Preen all filesystems in /etc/fstab that have a /sbin/fsck_XXX and a
2718# fs_passno > 0, showing individual results, but skipping $ROOTDEV. This was
2719# already fsck'ed successfully.
2720#
2721# Exit if any fsck's fail (but do them all before exiting!).
2722check_fs() {
2723	local _dev _dn _mp _fstype _rest _fail _f _passno
2724
2725	ask_yn "Force checking of clean non-root filesystems?" && _f=f
2726
2727	while read _dev _mp _fstype _rest _rest _passno _rest; do
2728		_dn=$(get_dkdev_name "$_dev")
2729		[[ $ROOTDEV == @(${_dev#/dev/}|$_dn${_dev##*.}) ]] && continue
2730		[[ -f /sbin/fsck_$_fstype ]] || continue
2731		# Make sure device exists before fsck'ing it.
2732		make_dev "$_dn" || continue
2733		((_passno > 0)) || continue
2734		echo -n "fsck -${_f}p $_dev..."
2735		if ! fsck -${_f}p $_dev >/dev/null 2>&1; then
2736			echo " FAILED. You must fsck $_dev manually."
2737			_fail=y
2738		else
2739			echo " OK."
2740		fi
2741	done </etc/fstab
2742
2743	[[ -n $_fail ]] && exit
2744}
2745
2746# Must mount filesystems manually, one at a time, so we can make sure the mount
2747# points exist.
2748mount_fs() {
2749	local _async=$1 _dev _mp _fstype _opt _rest _msg _fail
2750
2751	while read _dev _mp _fstype _opt _rest; do
2752		# If not the root filesystem, make sure the mount
2753		# point is present.
2754		[[ $_mp == /mnt ]] || mkdir -p $_mp
2755
2756		# Mount the filesystem. Remember any failure.
2757		_msg=$(mount -v -t $_fstype $_async -o $_opt $_dev $_mp) ||
2758			_fail="$_fail\n$_mp ($_dev)"
2759		echo $_msg | sed 's/, ctime=[^,)]*//'
2760	done </etc/fstab
2761
2762	if [[ -n $_fail ]]; then
2763		# One or more mounts failed. Continue or abort?
2764		echo "\nWARNING! The following filesystems were not properly mounted:$_fail"
2765		ask_yn "Continue anyway?" || exit
2766	fi
2767}
2768
2769# Feed the random pool some entropy before we read from it.
2770feed_random() {
2771	(dmesg; cat $CGI_INFO /*.conf; sysctl; route -n show; df;
2772		ifconfig -A; hostname) >/dev/random 2>&1
2773	if [[ -e /mnt/var/db/host.random ]]; then
2774		dd if=/mnt/var/db/host.random of=/dev/random bs=65536 count=1 \
2775			status=none
2776	fi
2777}
2778
2779# Ask the user for locations of sets, and then install whatever sets the user
2780# selects from that location. Repeat as many times as the user needs to get all
2781# desired sets.
2782install_sets() {
2783	local _cddevs=$(get_cddevs) _d _im _locs="disk http" _src
2784
2785	echo
2786
2787	# Set default location to method recorded last time.
2788	_d=$CGI_METHOD
2789
2790	# Set default location to HTTP in case we netbooted.
2791	ifconfig netboot >/dev/null 2>&1 && : ${_d:=http}
2792
2793	# Set default location to HTTP if installurl(5) exists.
2794	[[ -s /mnt/etc/installurl ]] && _d=http
2795
2796	# Set default location to the first cdrom device if any are found.
2797	[[ -n $_cddevs ]] && : ${_d:=cd0}
2798
2799	# Add NFS to set locations if the boot kernel supports it.
2800	[[ -x /sbin/mount_nfs ]] && _locs="$_locs nfs"
2801
2802	# In case none of the above applied, set HTTP as default location.
2803	: ${_d:=http}
2804
2805	# If the default location set so far is not one of the cdrom devices or
2806	# is not in the list of valid locations, set a sane default.
2807	if ! isin "$_d" $_cddevs $_locs; then
2808		for _src in http $_cddevs nfs disk; do
2809			isin "$_src" $_cddevs $_locs && _d=$_src && break
2810		done
2811	fi
2812
2813	echo "Let's $MODE the sets!"
2814	while :; do
2815		# Get list of cdroms again in case one just got plugged in.
2816		_cddevs=$(get_cddevs)
2817		umount -f /mnt2 >/dev/null 2>&1
2818
2819		ask "Location of sets? (${_cddevs:+$_cddevs }$_locs or 'done')" "$_d"
2820		case $resp in
2821		done)	sane_install && return
2822			;;
2823		[cC]*)	if [[ -n $_cddevs ]]; then
2824				set -- $_cddevs
2825				[[ $resp == [cC]?([dD]) ]] && resp=$1
2826				_im=$resp
2827				install_cdrom $resp && INSTALL_METHOD=$_im
2828			fi
2829			;;
2830		[dD]*)	install_disk && INSTALL_METHOD=disk
2831			;;
2832		[hH]*)	isin http $_locs && install_http && INSTALL_METHOD=http
2833			;;
2834		[nN]*)	isin nfs $_locs && install_nfs && INSTALL_METHOD=nfs
2835			;;
2836		*)	$AI && err_exit "'$resp' is not a valid choice."
2837			;;
2838		esac
2839
2840		# Preserve the selected install source selection.
2841		[[ -n $INSTALL_METHOD ]] && _d=$INSTALL_METHOD
2842
2843		if [ -x /mnt/usr/sbin/fw_update -a \
2844		    "$(echo /mnt2/*firmware*tgz)" != "/mnt2/*firmware*tgz" ]; then
2845			DESTDIR=/mnt /mnt/usr/sbin/fw_update /mnt2/*firmware*tgz
2846			enable_ifs	# try again with firmwares
2847		fi
2848
2849		# Set default to 'done' to leave the while-loop.
2850		sane_install quiet || $AI && _d=done
2851	done
2852}
2853
2854# Apply configuration settings based on the previously gathered information.
2855apply() {
2856	if [[ $START_SSHD == n ]]; then
2857		echo "sshd_flags=NO" >>/mnt/etc/rc.conf.local
2858	elif [[ -n $SSHD_ENABLEROOT ]]; then
2859		# Only change sshd_config if the user choice is not the default.
2860		if ! grep -q "^#PermitRootLogin $SSHD_ENABLEROOT\$" \
2861				/mnt/etc/ssh/sshd_config; then
2862			sed -i "s/^#\(PermitRootLogin\) .*/\1 $SSHD_ENABLEROOT/" \
2863				/mnt/etc/ssh/sshd_config
2864		fi
2865	fi
2866
2867	[[ -n $APERTURE ]] &&
2868		echo "machdep.allowaperture=$APERTURE # See xf86(4)" \
2869			>>/mnt/etc/sysctl.conf
2870
2871	[[ $START_XDM == y && -x /mnt/usr/X11R6/bin/xenodm ]] &&
2872		echo "xenodm_flags=" >>/mnt/etc/rc.conf.local
2873
2874	if [[ $DEFCONS == y ]]; then
2875		cp /mnt/etc/ttys /tmp/i/ttys
2876		sed	-e "/^console/s/on  secure/off secure/" \
2877			-e "/^$CTTY/s/std.9600/std.${CSPEED}/" \
2878			-e "/^$CTTY/s/std.115200/std.${CSPEED}/" \
2879			-e "/^$CTTY/s/unknown/vt220	/" \
2880			-e "/$CTTY/s/off.*/on secure/" /tmp/i/ttys >/mnt/etc/ttys
2881		[[ -n $CPROM ]] &&
2882			echo "stty $CPROM $CSPEED\nset tty $CPROM" \
2883				>>/mnt/etc/boot.conf
2884	fi
2885
2886	ln -sf /usr/share/zoneinfo/$TZ /mnt/etc/localtime
2887}
2888
2889# Return string suitable for the encrypted password field in master.passwd.
2890#
2891# 1) Without argument, return a single '*'.
2892# 2) Return argument unchanged if it looks like a encrypted password string
2893#    or if it consists of just 13 asterisks.
2894# 3) Otherwise return encrypted password string.
2895#
2896encr_pwd() {
2897	local _p=$1
2898
2899	if [[ -z $_p ]]; then
2900		echo '*'
2901	elif [[ $_p == \$2?\$[0-9][0-9]\$* && ${#_p} > 40 ||
2902		$_p == '*************' ]]; then
2903		echo "$_p"
2904	else
2905		encrypt -b a -- "$_p"
2906	fi
2907}
2908
2909# Store entropy for the next boot.
2910store_random() {
2911	dd if=/dev/random of=/mnt/var/db/host.random bs=65536 count=1 \
2912		status=none
2913	dd if=/dev/random of=/mnt/etc/random.seed bs=512 count=1 status=none
2914	chmod 600 /mnt/var/db/host.random /mnt/etc/random.seed
2915}
2916
2917# Final steps common for installs and upgrades.
2918finish_up() {
2919	local _dev _mp _fstype _rest _d
2920	local _kernel_dir=/mnt/usr/share/relink/kernel
2921	local _kernel=${MDKERNEL:-GENERIC} _syspatch_archs="amd64 arm64 i386"
2922
2923	# Mount all known swap partitions.  This gives systems with little
2924	# memory a better chance at running 'MAKEDEV all'.
2925	if [[ -x /mnt/sbin/swapctl ]]; then
2926		/mnt/sbin/swapctl -a /dev/$SWAPDEV >/dev/null 2>&1
2927		# Can't do chmod && swapctl -A because devices are not yet
2928		# created on install'ed systems. On upgrade'ed system there
2929		# is a small chance the device does not exist on the ramdisk
2930		# and will thus not get mounted.
2931		while read _dev _mp _fstype _rest; do
2932			[[ $_fstype == swap ]] &&
2933				/mnt/sbin/swapctl -a $_dev >/dev/null 2>&1
2934		done </mnt/etc/fstab
2935	fi
2936
2937	# Create /etc/installurl if it does not yet exist.
2938	if [[ ! -f /mnt/etc/installurl ]]; then
2939		echo "${INSTALL_URL:-https://cdn.openbsd.org/pub/OpenBSD}" \
2940			>/mnt/etc/installurl
2941	fi
2942
2943	echo -n "Making all device nodes..."
2944	(cd /mnt/dev; sh MAKEDEV all
2945		# Make sure any devices we found during probe are created in the
2946		# installed system.
2947		for _dev in $(get_dkdevs) $(get_cddevs); do
2948			sh MAKEDEV $_dev
2949		done
2950	)
2951	echo " done."
2952
2953	# We may run some programs in chroot, and some of them might be
2954	# dynamic.  That is highly discouraged, but let us play it safe.
2955	rm -f /mnt/var/run/ld.so.hints
2956
2957	# Conditionally create /usr/{src,obj,xobj} directories and set
2958	# proper ownership and permissions during install.
2959	if [[ $MODE == install ]]; then
2960		mkdir -p /mnt/usr/{src,{,x}obj} && (
2961			cd /mnt/usr
2962			chmod 770 {,x}obj
2963			chown build:wobj {,x}obj
2964			chmod 775 src
2965			chown root:wsrc src
2966		)
2967	fi
2968
2969	# In case root is on a softraid volume, make sure all underlying
2970	# device nodes exist before installing boot-blocks on disk.
2971	make_dev $(get_softraid_chunks $ROOTDISK)
2972	md_installboot $ROOTDISK
2973
2974	chmod og-rwx /mnt/bsd{,.mp,.rd} 2>/dev/null
2975	if [[ -f /mnt/bsd.mp ]] && ((NCPU > 1)); then
2976		_kernel=$_kernel.MP
2977		echo "Multiprocessor machine; using bsd.mp instead of bsd."
2978		mv /mnt/bsd /mnt/bsd.sp 2>/dev/null
2979		mv /mnt/bsd.mp /mnt/bsd
2980	fi
2981
2982	# Write kernel.SHA256 matching the just installed kernel and fix path to
2983	# ensure it references the kernel as /bsd.
2984	sha256 /mnt/bsd | (umask 077; sed 's,/mnt,,' >/mnt/var/db/kernel.SHA256)
2985
2986	# Ensure that sysmerge in batch mode is run on reboot.
2987	[[ $MODE == upgrade ]] &&
2988		echo "/usr/sbin/sysmerge -b" >>/mnt/etc/rc.sysmerge
2989
2990	# If a proxy was needed to fetch the sets, use it for fw_update and syspatch
2991	[[ -n $http_proxy ]] &&
2992		quote export "http_proxy=$http_proxy" >>/mnt/etc/rc.firsttime
2993
2994	# Ensure that fw_update is run on reboot.
2995	echo "/usr/sbin/fw_update" >>/mnt/etc/rc.firsttime
2996
2997	# Run syspatch -c on reboot if the arch is supported and if it is a
2998	# release system (not -stable or -current). List uninstalled syspatches
2999	# on the console and in the rc.firsttime output mail.
3000	isin "$ARCH" $_syspatch_archs && cat <<'__EOT' >>/mnt/etc/rc.firsttime
3001set -A _KERNV -- $(sysctl -n kern.version |
3002	sed 's/^OpenBSD \([1-9][0-9]*\.[0-9]\)\([^ ]*\).*/\1 \2/;q')
3003if ((${#_KERNV[*]} == 1)); then
3004	echo "Checking for available binary patches..."
3005	_CKPATCH=$(syspatch -c)
3006	if [[ -n $_CKPATCH ]]; then
3007		echo "Run syspatch(8) to install:"
3008		echo "$_CKPATCH" | column -xc 80
3009	fi
3010fi
3011__EOT
3012
3013	if [[ -x /mnt/usr/sbin/fw_update ]]; then
3014		DESTDIR=/mnt /mnt/usr/sbin/fw_update
3015		# Rerun installboot(8) to pick up just fetched boot firmware.
3016		typeset -f md_fw >/dev/null && md_fw $ROOTDISK apple-boot
3017	fi
3018
3019	if [[ -f $_kernel_dir.tgz ]]; then
3020		echo -n "Relinking to create unique kernel..."
3021		(
3022		set -e
3023		rm -rf $_kernel_dir
3024		mkdir -m 700 -p $_kernel_dir
3025		tar -C $_kernel_dir -xzf $_kernel_dir.tgz $_kernel
3026		rm -f $_kernel_dir.tgz
3027		chroot /mnt /bin/ksh -e -c "cd ${_kernel_dir#/mnt}/$_kernel
3028			make newbsd
3029			[ -f /etc/bsd.re-config ] &&
3030				config -e -c /etc/bsd.re-config -f bsd
3031			make newinstall"
3032		) >/dev/null 2>&1 && echo " done." || echo " failed."
3033	fi
3034
3035	# Email installer questions and their answers to root on next boot.
3036	prep_root_mail /tmp/i/$MODE.resp "$(hostname) $MODE response file"
3037
3038	if [[ -x /mnt/$MODE.site ]]; then
3039		if ! chroot /mnt /$MODE.site; then
3040			store_random
3041			err_exit "$MODE.site failed"
3042		fi
3043	fi
3044
3045	# Store entropy for the next boot.
3046	store_random
3047
3048	# Pat on the back.
3049	cat <<__EOT
3050
3051CONGRATULATIONS! Your OpenBSD $MODE has been successfully completed!
3052
3053__EOT
3054	if [[ $MODE == install ]]; then
3055		cat <<'__EOT'
3056When you login to your new system the first time, please read your mail
3057using the 'mail' command.
3058
3059__EOT
3060
3061		md_congrats
3062	fi
3063
3064	$AI && >/tmp/ai/ai.done
3065}
3066
3067do_autoinstall() {
3068	rm -f /tmp/ai/ai.done
3069
3070	echo "Performing non-interactive $AI_MODE..."
3071	/$AI_MODE -af /tmp/ai/ai.$AI_MODE.conf 2>&1 </dev/null |
3072		tee /dev/stderr | sed "s/^.*$(echo '\r')//" >/tmp/ai/ai.log
3073
3074	$UU || [[ -f /tmp/ai/ai.done ]] ||
3075		err_exit "failed; check /tmp/ai/ai.log"
3076
3077	# Email autoinstall protocol to root on next boot.
3078	prep_root_mail /tmp/ai/ai.log "$(hostname) $AI_MODE log"
3079
3080	exec reboot
3081}
3082
3083# Chose an existing partition as key disk and set global $KEYDISK on success,
3084# otherwise return non-zero.
3085pick_keydisk() {
3086	KEYDISK=
3087	local _disk _label
3088
3089	ask_which disk 'contains the key disk' '$(rmel $ROOTDISK $(get_dkdevs))'
3090	[[ $resp == done ]] && return 1
3091	_disk=$resp
3092
3093	make_dev $_disk
3094	if disklabel $_disk 2>/dev/null | ! grep -qw RAID; then
3095		echo "$_disk must contain a RAID partition."
3096		return 1
3097	fi
3098
3099	ask_which "$_disk partition" 'is the key disk' \
3100		"\$(disklabel $_disk 2>/dev/null |
3101		    sed -En 's/^  ([a-p]):.*RAID.*$/\1/p')"
3102	[[ $resp == done ]] && return 1
3103	_label=$resp
3104	KEYDISK=$_disk$_label
3105}
3106
3107encrypt_root() {
3108	local _args _chunk=$ROOTDISK
3109
3110	[[ $MDBOOTSR == y ]] || return
3111
3112	[[ -x /sbin/bioctl ]] || return
3113
3114	# Do not even try if softraid is in use already,
3115	# e.g. auto-assembled at boot or done in (S)hell.
3116	[[ -z $(get_softraid_volumes) ]] || return
3117
3118	while :; do
3119		ask 'Encrypt the root disk with a (p)assphrase or (k)eydisk?' no
3120		case $resp in
3121		# Retry on failure to allow passphrase or skip.
3122		[kK]*)
3123			pick_keydisk || continue
3124			_args=-k$KEYDISK
3125			break
3126			;;
3127		[pP]*)  $AI || break
3128			ask_passphrase 'New passphrase?'
3129			_args=-s
3130			break
3131			;;
3132		[nN]*)	return
3133			;;
3134		*)	echo "'$resp' is not a valid choice."
3135			;;
3136		esac
3137	done
3138
3139	echo "\nConfiguring the crypto chunk $_chunk...\n"
3140	md_prep_fdisk $_chunk
3141	echo 'RAID *' | disklabel -w -A -T- $_chunk
3142
3143	# Standard input is ignored in interactive mode.
3144	print -r -- "$_passphrase" |
3145		bioctl -Cforce -cC -l${_chunk}a $_args softraid0 >/dev/null
3146	unset _passphrase
3147
3148	# No volumes existed before asking, but we just created one.
3149	ROOTDISK=$(get_softraid_volumes)
3150	ROOTDEV=${ROOTDISK}a
3151	SWAPDEV=${ROOTDISK}b
3152	echo "\nConfiguring the root disk $ROOTDISK...\n"
3153}
3154
3155do_install() {
3156	local _rootkey _rootpass
3157
3158	# Ask for and set the system hostname and add the hostname specific
3159	# siteXX set.
3160	while :; do
3161		ask_until "System hostname? (short form, e.g. 'foo')" \
3162			"$(hostname -s)"
3163		[[ $resp != *+([[:cntrl:]]|[[:space:]])* ]] && break
3164		echo "Invalid hostname."
3165		$AI && exit 1
3166	done
3167	[[ ${resp%%.*} != $(hostname -s) ]] && hostname "$resp"
3168	ALLSETS="$ALLSETS site$VERSION-$(hostname -s).tgz"
3169	export PS1='\h# '
3170
3171	echo
3172
3173	# Configure the network.
3174	donetconfig
3175
3176	# Fetch list of mirror servers and installer choices from previous runs.
3177	start_cgiinfo
3178
3179	echo
3180
3181	while :; do
3182		ask_password "Password for root account?"
3183		_rootpass="$_password"
3184		[[ -n "$_password" ]] && break
3185		echo "The root password must be set."
3186	done
3187
3188	# Ask for the root user public ssh key during autoinstall.
3189	_rootkey=
3190	if $AI; then
3191		ask "Public ssh key for root account?" none
3192		[[ $resp != none ]] && _rootkey=$resp
3193	fi
3194
3195	# Ask user about daemon startup on boot, X Window usage and console
3196	# setup.
3197	questions
3198
3199	# Gather information for setting up the initial user account.
3200	user_setup
3201	ask_root_sshd
3202
3203	# Set TZ variable based on zonefile and user selection.
3204	set_timezone /var/tzlist
3205
3206	echo
3207
3208	# Get information about ROOTDISK, etc.
3209	get_rootinfo
3210
3211	encrypt_root
3212
3213	DISKS_DONE=
3214	FSENT=
3215
3216	# Remove traces of previous install attempt.
3217	rm -f /tmp/i/fstab*
3218
3219	# Configure the disk(s).
3220	while :; do
3221		# Always do ROOTDISK first, and repeat until it is configured.
3222		if ! isin "$ROOTDISK" $DISKS_DONE; then
3223			resp=$ROOTDISK
3224			rm -f /tmp/i/fstab
3225		else
3226			# Force the user to think and type in a disk name by
3227			# making 'done' the default choice.
3228			ask_which "disk" "do you wish to initialize" \
3229				'$(get_dkdevs_uninitialized)' done
3230			[[ $resp == done ]] && break
3231		fi
3232		_disk=$resp
3233		configure_disk $_disk || continue
3234		DISKS_DONE=$(addel $_disk $DISKS_DONE)
3235	done
3236
3237	# Write fstab entries to fstab in mount point alphabetic order
3238	# to enforce a rational mount order.
3239	for _mp in $(bsort $FSENT); do
3240		_pp=${_mp##*!}
3241		_mp=${_mp%!*}
3242		echo -n "$_pp $_mp ffs rw"
3243
3244		# Only '/' is neither nodev nor nosuid. i.e. it can obviously
3245		# *always* contain devices or setuid programs.
3246		[[ $_mp == / ]] && { echo " 1 1"; continue; }
3247
3248		# Every other mounted filesystem is nodev. If the user chooses
3249		# to mount /dev as a separate filesystem, then on the user's
3250		# head be it.
3251		echo -n ",nodev"
3252
3253		# The only directories that the install puts suid binaries into
3254		# (as of 3.2) are:
3255		#
3256		# /sbin
3257		# /usr/bin
3258		# /usr/sbin
3259		# /usr/libexec
3260		# /usr/libexec/auth
3261		# /usr/X11R6/bin
3262		#
3263		# and ports and users can do who knows what to /usr/local and
3264		# sub directories thereof.
3265		#
3266		# So try to ensure that only filesystems that are mounted at
3267		# or above these directories can contain suid programs. In the
3268		# case of /usr/libexec, give blanket permission for
3269		# subdirectories.
3270		case $_mp in
3271		/sbin|/usr)			;;
3272		/usr/bin|/usr/sbin)		;;
3273		/usr/libexec|/usr/libexec/*)	;;
3274		/usr/local|/usr/local/*)	;;
3275		/usr/X11R6|/usr/X11R6/bin)	;;
3276		*)	echo -n ",nosuid"	;;
3277		esac
3278		echo " 1 2"
3279	done >>/tmp/i/fstab
3280
3281	# Create a skeletal /etc/fstab which is usable for the installation
3282	# process.
3283	munge_fstab
3284
3285	# Use async options for faster mounts of the filesystems.
3286	mount_fs "-o async"
3287
3288	# Feed the random pool some entropy before we read from it.
3289	feed_random
3290
3291	# Ask the user for locations, and install whatever sets the user
3292	# selected.
3293	install_sets
3294
3295	# Set 'wxallowed' mount option for the filesystem /usr/local resides on.
3296	_mp=$(df /mnt/usr/local | sed '$!d')
3297	_mp=${_mp##*/mnt}
3298	sed -i "s#\(${_mp:-/} ffs rw\)#\1,wxallowed#" /tmp/i/fstab
3299
3300	# If we did not succeed at setting TZ yet, we try again
3301	# using the timezone names extracted from the base set.
3302	if [[ -z $TZ ]]; then
3303		(cd /mnt/usr/share/zoneinfo
3304			ls -1dF $(tar cvf /dev/null [A-Za-y]*) >/mnt/tmp/tzlist )
3305		echo
3306		set_timezone /mnt/tmp/tzlist
3307		rm -f /mnt/tmp/tzlist
3308	fi
3309
3310	# If we got a timestamp from the cgi server, and that time diffs by more
3311	# than 120 seconds, ask if the user wants to adjust the time.
3312	if _time=$(http_time) && _now=$(date +%s) &&
3313		(( _now - _time > 120 || _time - _now > 120 )); then
3314		ln -sf /mnt/usr/share/zoneinfo/$TZ /etc/localtime
3315		if ask_yn "Time appears wrong.  Set to '$(date -r "$(http_time)")'?" yes; then
3316			date $(date -r "$(http_time)" "+%Y%m%d%H%M.%S") >/dev/null
3317			# N.B. This will screw up SECONDS.
3318		fi
3319		rm -f /etc/localtime
3320	fi
3321
3322	# If we managed to talk to the cgi server before, tell it what
3323	# location we used... so it can perform magic next time.
3324	if [[ -s $HTTP_LIST ]]; then
3325		_i=${INSTALL_URL:+install=$INSTALL_URL&}
3326		_i=$_i${TZ:+TZ=$TZ&}
3327		_i=$_i${INSTALL_METHOD:+method=$INSTALL_METHOD}
3328		_i=${_i%&}
3329		[[ -n $_i ]] && unpriv2 ftp -w 15 -Vao - \
3330			"$HTTP_PROTO://ftplist1.openbsd.org/cgi-bin/ftpinstall.cgi?dbversion=1&$_i" \
3331			 >/dev/null 2>&1 &
3332	fi
3333
3334	# Ensure an enabled console has the correct speed in /etc/ttys.
3335	sed "/^console.*on.*secure.*$/s/std\.[0-9]*/std.$(stty speed </dev/console)/" \
3336		/mnt/etc/ttys >/tmp/i/ttys
3337	mv /tmp/i/ttys /mnt/etc/ttys
3338
3339	echo -n "Saving configuration files..."
3340
3341	# Save any leases obtained during install.
3342	(cd /var/db/dhcpleased; for _f in *; do
3343		[[ -f $_f ]] && mv $_f /mnt/var/db/dhcpleased/.
3344	done)
3345
3346	# Move configuration files from /tmp/i/ to /mnt/etc.
3347	hostname >/tmp/i/myname
3348
3349	# Append entries to installed hosts file, changing '1.2.3.4 hostname'
3350	# to '1.2.3.4 hostname.$FQDN hostname'. Leave untouched lines containing
3351	# domain information or aliases. These are lines the user added/changed
3352	# manually.
3353
3354	# Add common entries.
3355	echo "127.0.0.1\tlocalhost" >/mnt/etc/hosts
3356	echo "::1\t\tlocalhost" >>/mnt/etc/hosts
3357
3358	# Note we may have no hosts file if no interfaces were configured.
3359	if [[ -f /tmp/i/hosts ]]; then
3360		_dn=$(get_fqdn)
3361		while read _addr _hn _aliases; do
3362			[[ $_hn == ftplist[0-9].openbsd.org ]] && continue
3363			if [[ -n $_aliases || $_hn != ${_hn%%.*} || -z $_dn ]]; then
3364				echo "$_addr\t$_hn $_aliases"
3365			else
3366				echo "$_addr\t$_hn.$_dn $_hn"
3367			fi
3368		done </tmp/i/hosts >>/mnt/etc/hosts
3369		rm /tmp/i/hosts
3370	fi
3371
3372	# Possible files to copy from /tmp/i/: fstab hostname.* kbdtype mygate
3373	#     myname ttys boot.conf resolv.conf sysctl.conf
3374	# Save only non-empty (-s) regular (-f) files.
3375	(cd /tmp/i; for _f in fstab hostname* kbdtype my* ttys *.conf; do
3376		[[ -f $_f && -s $_f ]] && mv $_f /mnt/etc/.
3377	done)
3378	[[ -s /etc/resolv.conf ]] && cp /etc/resolv.conf /mnt/etc/resolv.conf
3379
3380	echo " done."
3381
3382	# Apply configuration settings based on information from questions().
3383	apply
3384
3385	# Create user account based on information from user_setup().
3386	if [[ -n $ADMIN ]]; then
3387		_encr=$(encr_pwd "$ADMIN_PASS")
3388		_home=/home/$ADMIN
3389		uline="${ADMIN}:${_encr}:1000:1000:staff:0:0:${ADMIN_NAME}:$_home:/bin/ksh"
3390		echo "$uline" >>/mnt/etc/master.passwd
3391		echo "${ADMIN}:*:1000:" >>/mnt/etc/group
3392		echo $ADMIN >/mnt/root/.forward
3393
3394		_home=/mnt$_home
3395		mkdir -p $_home
3396		(cd /mnt/etc/skel; pax -rw -k -pe . $_home)
3397		(umask 077 && sed "s,^To: root\$,To: ${ADMIN_NAME} <${ADMIN}>," \
3398			/mnt/var/mail/root >/mnt/var/mail/$ADMIN )
3399		chown -R 1000:1000 $_home /mnt/var/mail/$ADMIN
3400		sed -i -e "s@^wheel:.:0:root\$@wheel:\*:0:root,${ADMIN}@" \
3401			/mnt/etc/group 2>/dev/null
3402
3403		# During autoinstall, add public ssh key to authorized_keys.
3404		[[ -n "$ADMIN_KEY" ]] &&
3405			print -r -- "$ADMIN_KEY" >>$_home/.ssh/authorized_keys
3406	fi
3407
3408	# Store root password and rebuild password database.
3409	if [[ -n "$_rootpass" ]]; then
3410		_encr=$(encr_pwd "$_rootpass")
3411		sed -i -e "s@^root::@root:${_encr}:@" /mnt/etc/master.passwd \
3412			2>/dev/null
3413	fi
3414	pwd_mkdb -p -d /mnt/etc /etc/master.passwd
3415
3416	# During autoinstall, add root user's public ssh key to authorized_keys.
3417	[[ -n "$_rootkey" ]] && (
3418		umask 077
3419		print -r -- "$_rootkey" >>/mnt/root/.ssh/authorized_keys
3420	)
3421
3422	# Perform final steps common to both an install and an upgrade.
3423	finish_up
3424}
3425
3426do_upgrade() {
3427	local _f
3428
3429	# Get $ROOTDISK and $ROOTDEV
3430	get_rootinfo
3431
3432	echo -n "Checking root filesystem (fsck -fp /dev/$ROOTDEV)..."
3433	fsck -fp /dev/$ROOTDEV >/dev/null 2>&1 || { echo "FAILED."; exit; }
3434	echo " OK."
3435
3436	echo -n "Mounting root filesystem (mount -o ro /dev/$ROOTDEV /mnt)..."
3437	mount -o ro /dev/$ROOTDEV /mnt || { echo "FAILED."; exit; }
3438	echo " OK."
3439
3440	# The fstab and myname files are required.
3441	for _f in /mnt/etc/{fstab,myname}; do
3442		[[ -f $_f ]] || { echo "No $_f!"; exit; }
3443		cp $_f /tmp/i/${_f##*/}
3444	done
3445
3446	# Set system hostname and register hostname specific site set.
3447	hostname $(stripcom /tmp/i/myname)
3448	ALLSETS="$ALLSETS site$VERSION-$(hostname -s).tgz"
3449	export PS1='\h# '
3450
3451	# Configure the network.
3452	enable_network
3453
3454	# Create a skeletal /etc/fstab which is usable for the upgrade process.
3455	munge_fstab
3456
3457	# Do not need to look in /mnt anymore
3458	umount /mnt || { echo "Can't umount $ROOTDEV!"; exit; }
3459
3460	# Fetch list of mirror servers and installer choices from previous runs.
3461	start_cgiinfo
3462
3463	# fsck -p non-root filesystems in /etc/fstab.
3464	check_fs
3465
3466	# Mount filesystems in /etc/fstab.
3467	mount_fs
3468
3469	rm -f /mnt/bsd.upgrade /mnt/auto_upgrade.conf
3470
3471	# Feed the random pool some entropy before we read from it.
3472	feed_random
3473
3474	# Ensure that previous installer choices (e.g. method) are available.
3475	wait_cgiinfo
3476
3477	# Ask the user for locations, and install whatever sets the user
3478	# selected.
3479	install_sets
3480
3481	# Perform final steps common to both an install and an upgrade.
3482	finish_up
3483	if [ -f /tmp/wdpid ]; then
3484		kill -KILL "$(</tmp/wdpid)" 2>/dev/null
3485		# do not bother waiting
3486		rm -f /tmp/wdpid
3487	fi
3488}
3489
3490check_unattendedupgrade() {
3491	local _d=$(get_dkdevs_root) _rc=1
3492
3493	_d=${_d%% *}
3494	if [[ -n $_d ]]; then
3495		make_dev $_d
3496		if mount -t ffs -r /dev/${_d}a /mnt 2>/dev/null; then
3497			[[ -f /mnt/bsd.upgrade && -f /mnt/auto_upgrade.conf ]]
3498			_rc=$?
3499			((_rc == 0)) && cp /mnt/auto_upgrade.conf /
3500			echo "Which disk is the root disk = ${_d}" >> /auto_upgrade.conf
3501			umount /mnt
3502		fi
3503		rm -f /dev/{r,}$_d?
3504	fi
3505
3506	return $_rc
3507}
3508
3509WATCHDOG_PERIOD_SEC=$((30 * 60))
3510
3511# Restart the background timer.
3512reset_watchdog() {
3513	local _pid
3514	if [ -f /tmp/wdpid ]; then
3515		_pid=$(</tmp/wdpid)
3516		kill -KILL -$_pid 2>/dev/null
3517		wait $_pid 2>/dev/null
3518		rm -f /tmp/wdpid
3519		start_watchdog
3520	fi
3521}
3522
3523# Start a process to reboot a stalled sysupgrade.
3524# This mechanism is only used during non-interactive sysupgrade.
3525start_watchdog() {
3526	set -m
3527	(
3528		sleep $WATCHDOG_PERIOD_SEC && echo WATCHDOG > /dev/tty && reboot
3529	) >/dev/null 2>&1 &
3530	echo $! > /tmp/wdpid
3531	set +m
3532}
3533
3534# return if we only want internal functions
3535[[ -n $FUNCS_ONLY ]] && return
3536
3537# ------------------------------------------------------------------------------
3538# Initial actions common to both installs and upgrades.
3539#
3540# Some may require machine dependent routines, which may call functions defined
3541# above, so it's safest to put this code here rather than at the top.
3542# ------------------------------------------------------------------------------
3543
3544# Parse parameters.
3545AI=false
3546UU=false
3547MODE=
3548PROGNAME=${0##*/}
3549AI_RESPFILE=
3550while getopts "af:m:x" opt; do
3551	case $opt in
3552	a)	AI=true;;
3553	f)	AI_RESPFILE=$OPTARG;;
3554	m)	MODE=$OPTARG;;
3555	x)	UU=true;;
3556	*)	usage;;
3557	esac
3558done
3559shift $((OPTIND-1))
3560(($# == 0)) || usage
3561
3562# The installer can be started by using the symbolic links 'install', 'upgrade'
3563# and 'autoinstall' pointing to this script. Set MODE and AI based on that.
3564if [[ -z $MODE ]]; then
3565	case $PROGNAME in
3566	autoinstall)		AI=true;;
3567	install|upgrade)	MODE=$PROGNAME;;
3568	*)			exit 1;;
3569	esac
3570fi
3571
3572# Do not limit ourselves during installs or upgrades.
3573for _opt in d f l m n p s; do
3574	ulimit -$_opt unlimited
3575done
3576
3577# umount all filesystems, just in case we are re-running install or upgrade.
3578cd /
3579umount -af >/dev/null 2>&1
3580
3581# Include machine-dependent functions and definitions.
3582#
3583# The following functions must be provided:
3584#	md_congrats()		  - display friendly message
3585#	md_installboot()	  - install boot-blocks on disk
3586#	md_prep_disklabel()	  - put an OpenBSD disklabel on the disk
3587#	md_consoleinfo()	  - set CDEV, CTTY, CSPEED, CPROM
3588#
3589# The following functions can be provided if required:
3590#	md_fw()                   - device specific firmware quirks
3591#	md_prep_fdisk()           - put a partition table on the disk
3592#
3593# The following variables can be provided if required:
3594#	MDEFI       - set to 'y' on archs that support GPT partitioning
3595#	MDBOOTSR    - set to 'y' on archs that support boot from softraid volumes
3596#	MDFSOPT     - newfs options for non-root partitions, '-O2' assumed if not provided
3597#	MDROOTFSOPT - newfs options for the root partition, '-O2' assumed if not provided
3598#	MDSETS	    - list of files to add to DEFAULT and ALLSETS
3599#	MDSANESETS  - list of files to add to SANESETS
3600#	MDTERM      - 'vt220' assumed if not provided
3601#	MDDKDEVS    - '/^[sw]d[0-9][0-9]* /s/ .*//p' assumed if not provided
3602#	MDCDDEVS    - '/^cd[0-9][0-9]* /s/ .*//p'    assumed if not provided
3603#	MDXAPERTURE - set machdep.allowaperture=value in sysctl.conf
3604#	MDXDM       - ask if xdm should be started if set to 'y'
3605#	NCPU	    - the number of cpus for mp capable arches
3606#	MDKERNEL    - the name of the boot kernel
3607#	MDHALT      - default to 'halt' at the end of installs if set to 'y'
3608. install.md
3609
3610# Start listener process looking for dmesg changes.
3611start_dmesg_listener
3612
3613CGI_INFO=/tmp/i/cgiinfo
3614CGI_METHOD=
3615CGI_TIME=
3616CGI_TZ=
3617export EDITOR=ed
3618HTTP_DIR=
3619HTTP_LIST=/tmp/i/httplist
3620HTTP_SEC=/tmp/i/httpsec
3621INSTALL_METHOD=
3622NIFS=0
3623export PS1="$MODE# "
3624PUB_KEY=/etc/signify/openbsd-${VERSION}-base.pub
3625ROOTDEV=
3626ROOTDISK=
3627SETDIR="$VNAME/$ARCH"
3628UPGRADE_BSDRD=false
3629V4_AUTOCONF=false
3630V6_AUTOCONF=false
3631WLANLIST=/tmp/i/wlanlist
3632
3633# Save one boot's worth of dmesg.
3634dmesgtail >/var/run/dmesg.boot
3635
3636# Are we in a real release, or a snapshot?  If this is a snapshot
3637# install media, default us to a snapshot directory.
3638HTTP_SETDIR=$SETDIR
3639set -- $(scan_dmesg "/^OpenBSD $VNAME\([^ ]*\).*$/s//\1/p")
3640[[ $1 == -!(stable) ]] && HTTP_SETDIR=snapshots/$ARCH
3641
3642# Detect if ftp(1) has tls support and set defaults based on that.
3643if [[ -e /etc/ssl/cert.pem ]]; then
3644	FTP_TLS=true
3645	HTTP_PROTO=https
3646else
3647	FTP_TLS=false
3648	HTTP_PROTO=http
3649fi
3650
3651# Scan /var/run/dmesg.boot for console device.
3652CONSOLE=$(scan_dmesg '/^\([^ ]*\).*: console$/s//\1/p')
3653[[ -n $CONSOLE ]] && CSPEED=$(stty speed </dev/console)
3654
3655# Look for the serial device matching the console. If we are not installing
3656# from a serial console, just find the first serial device that could be used
3657# as a console. If a suitable device is found, set CDEV, CTTY, CSPEED, CPROM.
3658md_consoleinfo
3659
3660# Selected sets will be installed in the order they are listed in $ALLSETS.
3661# Ensure that siteXX.tgz is the *last* set listed so its contents overwrite
3662# the contents of the other sets, not the other way around.
3663SETS=$(echo {base,comp,man,game,xbase,xshare,xfont,xserv}$VERSION.tgz)
3664DEFAULTSETS="${MDSETS:-bsd bsd.rd} $SETS"
3665ALLSETS="${MDSETS:-bsd bsd.rd} $SETS site$VERSION.tgz"
3666SANESETS="${MDSANESETS:-bsd} base${VERSION}.tgz"
3667if ((NCPU > 1)); then
3668	DEFAULTSETS="${MDSETS:-bsd bsd.mp bsd.rd} $SETS"
3669	ALLSETS="${MDSETS:-bsd bsd.mp bsd.rd} $SETS site$VERSION.tgz"
3670	SANESETS="${MDSANESETS:-bsd bsd.mp} base${VERSION}.tgz"
3671fi
3672
3673# Prepare COLUMNS sanely.
3674export COLUMNS=$(stty -a </dev/console |
3675	sed -n '/columns/{s/^.* \([0-9]*\) columns.*$/\1/;p;}')
3676((COLUMNS == 0)) && COLUMNS=80
3677
3678# Interactive or automatic installation?
3679if ! $AI; then
3680	cat <<'__EOT'
3681At any prompt except password prompts you can escape to a shell by
3682typing '!'. Default answers are shown in []'s and are selected by
3683pressing RETURN.  You can exit this program at any time by pressing
3684Control-C, but this can leave your system in an inconsistent state.
3685
3686__EOT
3687elif $UU; then
3688	MODE=upgrade
3689	check_unattendedupgrade || exit 1
3690
3691	start_watchdog
3692
3693	get_responsefile
3694	do_autoinstall
3695elif [[ -z $AI_RESPFILE ]]; then
3696	get_responsefile ||
3697		err_exit "No response file found; non-interactive mode aborted."
3698
3699	do_autoinstall
3700else
3701	cp $AI_RESPFILE /tmp/ai/ai.conf || exit
3702fi
3703
3704# Configure the terminal and keyboard.
3705set_term
3706
3707# In case of restart, delete previously logged answers.
3708rm -f /tmp/i/$MODE.resp
3709
3710case $MODE in
3711install)	do_install;;
3712upgrade)	do_upgrade;;
3713esac
3714
3715# In case of autoinstall, this is a second process of install.sub.
3716# Exiting here returns to the original process, which handles the
3717# automatic reboot in do_autoinstall().
3718$AI && exit
3719
3720_d=reboot
3721[[ $MODE == install && $MDHALT == y ]] && _d=halt
3722
3723while :; do
3724	ask "Exit to (S)hell, (H)alt or (R)eboot?" "$_d"
3725	case $resp in
3726	[hH]*)	exec halt;;
3727	[rR]*)	exec reboot;;
3728	[sS]*)	break;;
3729	esac
3730done
3731
3732# Fall through to .profile which leaves us at the command prompt.
3733echo "To boot the new system, enter 'reboot' at the command prompt."
3734