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