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