xref: /netbsd-src/distrib/miniroot/install.sub (revision 2718af68c3efc72c9769069b5c7f9ed36f6b9def)
1#!/bin/sh
2#	$NetBSD: install.sub,v 1.63 2022/03/13 14:20:24 andvar Exp $
3#
4# Copyright (c) 1996 The NetBSD Foundation, Inc.
5# All rights reserved.
6#
7# This code is derived from software contributed to The NetBSD Foundation
8# by Jason R. Thorpe.
9#
10# Redistribution and use in source and binary forms, with or without
11# modification, are permitted provided that the following conditions
12# are met:
13# 1. Redistributions of source code must retain the above copyright
14#    notice, this list of conditions and the following disclaimer.
15# 2. Redistributions in binary form must reproduce the above copyright
16#    notice, this list of conditions and the following disclaimer in the
17#    documentation and/or other materials provided with the distribution.
18#
19# THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22# PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29# POSSIBILITY OF SUCH DAMAGE.
30#
31
32#	NetBSD installation/upgrade script - common subroutines.
33
34ROOTDISK=""				# filled in below
35MACHINE=				# filled by distrib/miniroot/list
36export MACHINE
37VERSION=100				# updated by distrib/miniroot/list
38export VERSION
39RELEASE=10.0				# updated by distrib/miniroot/list
40export RELEASE
41
42ALLSETS="base comp etc games man misc modules rescue text"	# default install sets
43UPGRSETS="base comp games man misc modules rescue text"		# default upgrade sets
44THESETS=						# one of the above
45
46local_sets_dir=""			# Path searched for sets by install_sets
47					# on the local filesystems
48
49# decide upon an editor
50if [ -z "$EDITOR" ]; then
51	if [ -x /usr/bin/vi ]; then
52		EDITOR=vi
53	else
54		EDITOR=ed
55	fi
56fi
57
58getresp() {
59	read resp
60	if [ -z "$resp" ]; then
61		resp=$1
62	fi
63}
64
65isin() {
66# test the first argument against the remaining ones, return succes on a match
67	_a=$1; shift
68	while [ $# != 0 ]; do
69		if [ "$_a" = "$1" ]; then return 0; fi
70		shift
71	done
72	return 1
73}
74
75rmel() {
76# remove first argument from list formed by the remaining arguments
77	local	_a
78
79	_a=$1; shift
80	while [ $# != 0 ]; do
81		if [ "$_a" != "$1" ]; then
82			echo "$1";
83		fi
84		shift
85	done
86}
87
88cutword () {
89# read a line of data, return Nth element.
90	local _a
91	local _n
92	local _oifs
93
94	# optional field separator
95	_oifs="$IFS"
96	case "$1" in
97		-t?*) IFS=${1#-t}; shift;;
98	esac
99
100	_n=$1
101	read _a; set -- $_a
102	IFS="$_oifs"
103	if [ "$1" = "" ]; then return; fi
104	eval echo \$$_n
105}
106
107cutlast () {
108# read a line of data, return last element. Equiv. of awk '{print $NF}'.
109	local _a
110	local _oifs
111
112	# optional field separator
113	_oifs="$IFS"
114	case "$1" in
115		-t?*) IFS=${1#-t}; shift;;
116	esac
117
118	read _a; set -- $_a
119	IFS="$_oifs"
120	if [ "$1" = "" ]; then return; fi
121	eval echo '"${'"$#"'}"'
122}
123
124firstchar () {
125# return first character of argument
126	local _a
127	_a=$1
128	while [ ${#_a} != 1 ]; do
129		_a=${_a%?}
130	done
131	echo $_a
132}
133
134basename () {
135	local _oifs
136	if [ "$1" = "" ]; then return; fi
137	_oifs="$IFS"
138	IFS="/"
139	set -- $1
140	IFS="$_oifs"
141	eval echo '"${'"$#"'}"'
142}
143
144dir_has_sets() {
145	# return true when the directory $1 contains a set for $2...$n
146	local _dir
147	local _file
148
149	_dir=$1; shift
150	for _file in $*
151	do
152		if [ -f $_dir/${_file}.tar.gz ]; then
153			return 0
154		fi
155		# Try for stupid msdos convention
156		if [ -f $_dir/${_file}.tgz ]; then
157			return 0
158		fi
159		# Try for uncompressed files
160		if [ -f $_dir/${_file}.tar ]; then
161			return 0
162		fi
163		# Try for split files
164		if [ -f $_dir/${_file}${VERSION}.aa ]; then
165			return 0
166		fi
167	done
168	return 1
169}
170
171twiddle() {
172# spin the propeller so we don't get bored
173	while : ; do
174		sleep 1; echo -n "/";
175		sleep 1; echo -n "-";
176		sleep 1; echo -n "\\";
177		sleep 1; echo -n "|";
178	done > /dev/tty & echo $!
179}
180
181get_localdir() {
182	# $1 is relative mountpoint
183	local _mp
184	local _dir
185
186	_mp=$1
187	_dir=
188	while : ; do
189	    if [ -n "$_mp" ]; then
190		cat << __get_localdir_1
191Note: your filesystems are mounted under the temporary mount point \"$_mp\".
192The pathname you are requested to enter below should NOT include the \"$_mp\"
193prefix.
194__get_localdir_1
195	    fi
196	    echo -n "Enter the pathname where the sets are stored [$_dir] "
197	    getresp "$_dir"
198	    _dir=$resp
199
200	    # Allow break-out with empty response
201	    if [ -z "$_dir" ]; then
202		echo -n "Are you sure you don't want to set the pathname? [n] "
203		getresp "n"
204		case "$resp" in
205			y*|Y*)
206				break
207				;;
208			*)
209				continue
210				;;
211		esac
212	    fi
213
214	    if dir_has_sets "$_mp/$_dir" $THESETS
215	    then
216		local_sets_dir="$_mp/$_dir"
217		break
218	    else
219		cat << __get_localdir_2
220The directory \"$_mp/$_dir\" does not exist, or does not hold any of the
221upgrade sets.
222__get_localdir_2
223		echo -n "Re-enter pathname? [y] "
224		getresp "y"
225		case "$resp" in
226			y*|Y*)
227				;;
228			*)
229				local_sets_dir=""
230				break
231				;;
232		esac
233	    fi
234	done
235}
236
237getrootdisk() {
238	cat << \__getrootdisk_1
239
240The installation program needs to know which disk to consider
241the root disk.  Note the unit number may be different than
242the unit number you used in the standalone installation
243program.
244
245Available disks are:
246
247__getrootdisk_1
248	_DKDEVS=$(md_get_diskdevs)
249	echo	"$_DKDEVS"
250	echo	""
251	echo -n	"Which disk is the root disk? "
252	getresp ""
253	if isin $resp $_DKDEVS ; then
254		ROOTDISK="$resp"
255	else
256		echo ""
257		echo "The disk $resp does not exist."
258		ROOTDISK=""
259	fi
260}
261
262labelmoredisks() {
263	cat << \__labelmoredisks_1
264
265You may label the following disks:
266
267__labelmoredisks_1
268	echo "$_DKDEVS"
269	echo	""
270	echo -n	"Label which disk? [done] "
271	getresp "done"
272	case "$resp" in
273		"done")
274			;;
275
276		*)
277			if isin $resp $_DKDEVS ; then
278				md_labeldisk $resp
279			else
280				echo ""
281				echo "The disk $resp does not exist."
282			fi
283			;;
284	esac
285}
286
287addhostent() {
288	# $1 - IP address
289	# $2 - symbolic name
290
291	local fqdn
292
293	# Create an entry in the hosts table.  If no host table
294	# exists, create one.  If the IP address already exists,
295	# replace its entry.
296	if [ ! -f /tmp/hosts ]; then
297		echo "127.0.0.1 localhost" > /tmp/hosts
298	fi
299
300	sed "/^$1 /d" < /tmp/hosts > /tmp/hosts.new
301	mv /tmp/hosts.new /tmp/hosts
302
303	if [ -n "${FQDN}" ]; then
304		fqdn=$2.$FQDN
305	fi
306	echo "$1 $2 $fqdn" >> /tmp/hosts
307}
308
309addifconfig() {
310	# $1 - interface name
311	# $2 - interface symbolic name
312	# $3 - interface IP address
313	# $4 - interface netmask
314	# $5 - (optional) interface link-layer medium, preceded by "media ", else ""
315	# $6 - (optional) interface link-layer directives
316	local _m
317
318	# Create a ifconfig.* file for the interface.
319	echo "inet $2 netmask $4 $5 $6" > /tmp/ifconfig.$1
320
321	addhostent $3 $2
322}
323
324configurenetwork() {
325	local _ifsdone
326	local _ifs
327
328#	_IFS=$(md_get_ifdevs)
329	_IFS=$(ifconfig -l | sed '
330		s/lo0//
331		s/ppp[0-9]//g
332		s/sl[0-9]//g
333		s/tun[0-9]//g')
334
335	_ifsdone=""
336	resp=""		# force at least one iteration
337	while [ "${resp}" != "done" ]; do
338	cat << \__configurenetwork_1
339
340You may configure the following network interfaces (the interfaces
341marked with [X] have been successfully configured):
342
343__configurenetwork_1
344
345		for _ifs in $_IFS; do
346			if isin $_ifs $_ifsdone ; then
347				echo -n "[X] "
348			else
349				echo -n "    "
350			fi
351			echo $_ifs
352		done
353		echo	""
354		echo -n	"Configure which interface? [done] "
355		getresp "done"
356		case "$resp" in
357		"done")
358			;;
359		*)
360			_ifs=$resp
361			if isin $_ifs $_IFS ; then
362				if configure_ifs $_ifs ; then
363					_ifsdone="$_ifs $_ifsdone"
364				fi
365			else
366				echo "Invalid response: \"$resp\" is not in list"
367			fi
368			;;
369		esac
370	done
371}
372
373configure_ifs() {
374
375	local _up
376	local _interface_name
377	local _interface_ip
378	local _interface_mask
379	local _interface_symname
380	local _interface_extra
381	local _interface_mediumtype
382	local _interface_supported_media
383	local _m
384	local _t
385
386	_interface_name=$1
387	_up=DOWN
388	if isin $_interface_name $(ifconfig -l -u); then
389		_up=UP
390	fi
391
392	_interface_supported_media=$(ifconfig -m $_interface_name | sed -n '
393		/^[ 	]*media autoselect/d
394		4,$s/[ 	]*media //p')
395
396	# get current "media" "ip" and "netmask" ("broadcast")
397	_t=$(ifconfig $_interface_name | sed -n '
398		s/^[ 	]*media: [^ 	]* \([^ ][^ ]*\).*/\1/p')
399
400	if [ "$_t" != "manual" ] && [ "$_t" != "media:" ] && [ "$_t" != "autoselect" ];
401	then
402		_interface_mediumtype=$1
403	fi
404
405	set -- $(ifconfig $_interface_name | sed -n '
406		/^[ 	]*inet /{
407		s/inet//
408		s,/[0-9]*,,
409		s/--> [0-9.][0-9.]*//
410		s/netmask//
411		s/broadcast//
412		p;}')
413
414	_interface_ip=$1
415	_interface_mask=$2
416
417	# Get IP address
418	resp=""		# force one iteration
419	while [ -z "${resp}" ]; do
420		echo -n "IP address? [$_interface_ip] "
421		getresp "$_interface_ip"
422		_interface_ip=$resp
423	done
424
425	# Get symbolic name
426	resp=""		# force one iteration
427	while [ -z "${resp}" ]; do
428		echo -n "Symbolic (host) name? "
429		getresp ""
430		_interface_symname=$resp
431	done
432
433	# Get netmask
434	resp=""		# force one iteration
435	while [ -z "${resp}" ]; do
436		echo -n "Netmask? [$_interface_mask] "
437		getresp "$_interface_mask"
438		_interface_mask=$resp
439	done
440
441	echo "Your network interface might require explicit selection"
442	echo "of the type of network medium attached. Supported media:"
443	echo "$_interface_supported_media"
444	echo -n "Additional media type arguments (none)? [$_interface_mediumtype] "
445	getresp "$_interface_mediumtype"
446	_m=""
447	if [ "${resp:-none}" != "none" ]; then
448		_interface_mediumtype=$resp
449		_m="media ${resp}"
450	fi
451
452
453	echo "Your network interface might require additional link-layer"
454	echo "directives (like 'link0'). If this is the case you can enter"
455	echo "these at the next prompt."
456	echo ""
457	echo -n "Additional link-layer arguments (none)? [$_interface_extra] "
458	getresp "$_interface_extra"
459	if [ "${resp:-none}" != "none" ]; then
460		_interface_extra=$resp
461	fi
462
463	# Configure the interface.  If it
464	# succeeds, add it to the permanent
465	# network configuration info.
466	if [ $_up != "UP" ]; then
467		ifconfig ${_interface_name} down
468		if ifconfig ${_interface_name} inet \
469		    ${_interface_ip} \
470		    netmask ${_interface_mask} \
471		    ${_interface_extra} ${_m} up ; then
472			addifconfig \
473			    "${_interface_name}" \
474			    "${_interface_symname}" \
475			    "${_interface_ip}" \
476			    "${_interface_mask}" \
477			    "${_m}" \
478			    "${_interface_extra}"
479			return 0
480		fi
481	else
482		echo "Interface ${_interface_name} is already active."
483		echo "Just saving configuration on new root filesystem."
484		addifconfig \
485		    "${_interface_name}" \
486		    "${_interface_symname}" \
487		    "${_interface_ip}" \
488		    "${_interface_mask}" \
489		    "${_m}" \
490		    "${_interface_extra}"
491	fi
492	return 1
493}
494
495# Much of this is gratuitously stolen from /etc/rc.d/network.
496enable_network() {
497
498	# Set up the hostname.
499	if [ -f /mnt/etc/myname ]; then
500		hostname=$(cat /mnt/etc/myname)
501	elif [ -f /mnt/etc/rc.conf ];then
502		hostname=$(sh -c '. /mnt/etc/rc.conf ; echo $hostname')
503	else
504		echo "ERROR: no /etc/myname!"
505		return 1
506	fi
507	if [ -z "$hostname" ];then
508		echo "ERROR: hostname not set in /etc/myname or /etc/rc.conf!"
509		return 1
510	fi
511	hostname $hostname
512
513	# configure all the interfaces which we know about.
514if [ -f /mnt/etc/rc.conf ]; then
515(
516	# assume network interface configuration style 1.2D and up
517	if [ -f /mnt/etc/defaults/rc.conf ]; then
518		. /mnt/etc/defaults/rc.conf
519	fi
520	. /mnt/etc/rc.conf
521
522	if [ "$net_interfaces" != NO ]; then
523		if [ "$auto_ifconfig" = YES ]; then
524			tmp="$(ifconfig -l)"
525		else
526			tmp="$net_interfaces"
527		fi
528		echo -n "configuring network interfaces:"
529		for i in $tmp; do
530			eval $(echo 'args=$ifconfig_'$i)
531			if [ -n "$args" ]; then
532				echo -n " $i"
533				ifconfig $i $args
534			elif [ -f /mnt/etc/ifconfig.$i ]; then
535				echo -n " $i"
536				(while read args; do
537					ifconfig $i $args
538				done) < /mnt/etc/ifconfig.$i
539			elif [ "$auto_ifconfig" != YES ]; then
540				echo
541				echo -n "/mnt/etc/ifconfig.$i missing"
542				echo -n "& ifconfig_$i not set"
543				echo "; interface $i can't be configured"
544			fi
545		done
546		echo "."
547	fi
548)
549else
550(
551	tmp="$IFS"
552	IFS="$IFS."
553	set -- $(echo /mnt/etc/hostname*)
554	IFS=$tmp
555	unset tmp
556
557	while [ $# -ge 2 ] ; do
558		shift		# get rid of "hostname"
559		(
560			read af name mask bcaddr extras
561			read dt dtaddr
562
563			if [ -z "$name" ]; then
564		    echo "/etc/hostname.$1: invalid network configuration file"
565				exit
566			fi
567
568			cmd="ifconfig $1 $af $name "
569			if [ "${dt}" = "dest" ]; then cmd="$cmd $dtaddr"; fi
570			if [ -n "$mask" ]; then cmd="$cmd netmask $mask"; fi
571			if [ "${bcaddr:-NONE}" != "NONE" ]; then
572				cmd="$cmd broadcast $bcaddr";
573			fi
574			cmd="$cmd $extras"
575
576			$cmd
577		) < /mnt/etc/hostname.$1
578		shift
579	done
580)
581fi
582
583	# set the address for the loopback interface
584	ifconfig lo0 inet localhost
585
586	# use loopback, not the wire
587	route add $hostname localhost
588
589	# /etc/mygate, if it exists, contains the name of my gateway host
590	# that name must be in /etc/hosts.
591	if [ -f /mnt/etc/mygate ]; then
592		route delete default > /dev/null 2>&1
593		route add default $(cat /mnt/etc/mygate)
594	fi
595
596	# enable the resolver, if appropriate.
597	if [ -f /mnt/etc/resolv.conf ]; then
598		_resolver_enabled="TRUE"
599		cp /mnt/etc/resolv.conf /tmp/resolv.conf.shadow
600	fi
601
602	# Display results...
603	echo	"Network interface configuration:"
604	ifconfig -a
605
606	echo	""
607
608	if [ "${_resolver_enabled:-FALSE}" = "TRUE" ]; then
609		echo	"Resolver enabled."
610	else
611		echo	"Resolver not enabled."
612	fi
613
614	return 0
615}
616
617install_ftp() {
618	local	_f
619	local	_sets
620	local	_next
621
622	# Build a script to extract valid files from a list
623	# of filenames on stdin.
624	# XXX : Can we use this on more places? Leo.
625
626	echo "#!/bin/sh" > /tmp/fname_filter.sh
627	echo "while read line; do"	>> /tmp/fname_filter.sh
628	echo "    case \$line in"	>> /tmp/fname_filter.sh
629	for _f in $THESETS; do
630		echo "    $_f.tar.gz|$_f.tgz|$_f.tar|$_f.${VERSION}.aa)" \
631					>> /tmp/fname_filter.sh
632		echo '        echo -n "$line ";;' \
633					>> /tmp/fname_filter.sh
634	done
635	echo "        *) ;;"		>> /tmp/fname_filter.sh
636	echo "    esac"			>> /tmp/fname_filter.sh
637	echo "done"			>> /tmp/fname_filter.sh
638
639	# Get several parameters from the user, and create
640	# a shell script that directs the appropriate
641	# commands into ftp.
642	cat << \__install_ftp_1
643
644This is an automated ftp-based installation process.  You will be asked
645several questions.  The correct set of commands will be placed in a script
646that will be fed to ftp(1).
647
648__install_ftp_1
649	# Get server IP address
650	resp=""		# force one iteration
651	while [ -z "${resp}" ]; do
652		echo -n "Server IP? [${_ftp_server_ip}] "
653		getresp "${_ftp_server_ip}"
654		_ftp_server_ip=$resp
655	done
656
657	# Get login name
658	resp=""		# force one iteration
659	while [ -z "${resp}" ]; do
660		echo -n "Login? [${_ftp_server_login}] "
661		getresp "${_ftp_server_login}"
662		_ftp_server_login=$resp
663	done
664
665	# Get password
666	resp=""		# force one iteration
667	while [ -z "${resp}" ]; do
668		echo -n "Password? "
669		stty -echo
670		getresp ""
671		echo ""
672		stty echo
673		_ftp_server_password=$resp
674	done
675
676	cat << \__install_ftp_2
677
678You will be asked to enter the name of the directory that contains the
679installation sets. When you enter a '?' you will see a listing of the
680current directory on the server.
681__install_ftp_2
682	echo ""
683	echo "The default installation directory in the official ftp server is:"
684	echo "/pub/NetBSD/NetBSD-${RELEASE}/${MACHINE}/binary/sets"
685
686	_sets=""
687	while [ -z "$_sets" ]
688	do
689		resp=""		# force one iteration
690		while [ -z "${resp}" ]; do
691			echo -n "Server directory? [${_ftp_server_dir}] "
692		    getresp "${_ftp_server_dir}"
693		    if [ -z "$resp" ] && [ -z "$_ftp_server_dir" ]; then
694			resp=""
695		    fi
696		done
697		if [ $resp != '?' ]; then
698			_ftp_server_dir=$resp
699		fi
700
701		# Build the basics of an ftp-script...
702		echo "#!/bin/sh" > /tmp/ftp-script.sh
703		echo "cd /mnt" >> /tmp/ftp-script.sh
704		echo "ftp -e -i -n $_ftp_server_ip << \__end_commands" >> \
705		    /tmp/ftp-script.sh
706		echo "user $_ftp_server_login $_ftp_server_password" >> \
707		    /tmp/ftp-script.sh
708		echo "bin" >> /tmp/ftp-script.sh
709		echo "cd $_ftp_server_dir" >> /tmp/ftp-script.sh
710
711		# Make a copy of this script that lists the directory
712		# contents, and use that to determine the files to get.
713		cat /tmp/ftp-script.sh	>  /tmp/ftp-dir.sh
714		echo "nlist"		>> /tmp/ftp-dir.sh
715		echo "quit"		>> /tmp/ftp-dir.sh
716		echo "__end_commands"	>> /tmp/ftp-dir.sh
717
718		if [ $resp = '?' ]; then
719			sh /tmp/ftp-dir.sh
720		else
721			_sets=$(sh /tmp/ftp-dir.sh | sort -u | sh /tmp/fname_filter.sh)
722		fi
723	done
724	rm -f /tmp/ftp-dir.sh /tmp/fname_filter.sh
725	rm -f /tmp/ftp-script.sh
726
727	# Prepare ftp-fetch script to fetch binary sets
728	_download_dir=INSTALL
729	_ftp_opts=""
730	_ftp_url="ftp://$_ftp_server_login:$_ftp_server_password@$_ftp_server_ip$_ftp_server_dir/"
731	echo "#!/bin/sh" > /tmp/ftp-fetch.sh
732	echo "cd /mnt" >> /tmp/ftp-fetch.sh
733	echo "mkdir -p $_download_dir" >> /tmp/ftp-fetch.sh
734
735	while : ; do
736		echo "The following sets are available for extraction:"
737		echo "(marked sets are already on the extraction list)"
738		echo ""
739
740		_next=""
741		for _f in $_sets ; do
742			if isin $_f $_setsdone; then
743				echo -n "[X] "
744				_next=""
745			else
746				echo -n "    "
747				if [ -z "$_next" ]; then _next=$_f; fi
748			fi
749			echo $_f
750		done
751		echo ""
752
753		# Get name of the file and add extraction command
754		# to the ftp-fetch script.
755		if [ -z "$_next" ]; then resp=n; else resp=y; fi
756		echo -n "Continue to add filenames [$resp]? "
757		getresp "$resp"
758		if [ "$resp" = "n" ]; then
759			break
760		fi
761
762		echo -n "File name [$_next]? "
763		getresp "$_next"
764		if isin $resp $_sets; then
765			echo "echo Fetching $resp:" >> \
766					/tmp/ftp-fetch.sh
767			echo "ftp ${_ftp_opts} -o $_download_dir/$resp ${_ftp_url}$resp" >> \
768					/tmp/ftp-fetch.sh
769			echo "echo Extracting $resp:" >> \
770					/tmp/ftp-fetch.sh
771			echo "pax -zr${verbose_flag}pe -f $_download_dir/$resp" >> \
772					/tmp/ftp-fetch.sh
773			echo "rm -f $_download_dir/$resp" >> \
774					/tmp/ftp-fetch.sh
775			_setsdone="$resp $_setsdone"
776		else
777			echo "You entered an invalid filename."
778			echo ""
779		fi
780	done
781
782	sh /tmp/ftp-fetch.sh
783	rm -f /tmp/ftp-fetch.sh
784	echo "Extraction complete."
785}
786
787install_from_mounted_fs() {
788	# $1 - directory containing installation sets
789	local _filename
790	local _sets
791	local _next
792	local _all
793	local _f
794	local _dirname
795
796	_dirname=$1
797	_sets=""
798
799	if ! dir_has_sets ${_dirname} $THESETS
800	then
801
802		echo ""
803		echo "The directory at the mount point, \"${_dirname}\", contains: "
804		echo ""
805		ls -F ${_dirname}
806		echo ""
807		echo    "Enter the subdirectory relative to the mountpoint, that"
808		echo -n "contains the savesets: [try this directory] "
809		getresp ""
810		if [ -n "${resp}" ]; then
811			_dirname=${_dirname}/$resp
812		fi
813
814		while ! dir_has_sets ${_dirname} $THESETS; do
815			echo ""
816			echo -n "There are no NetBSD install sets available in "
817			echo "\"${_dirname}\"."
818			echo "\"${_dirname}\" contains: "
819			echo ""
820			ls -F ${_dirname}
821			echo ""
822			echo -n "Enter subdirectory: [try other install media] "
823			getresp ""
824			if [ -z "${resp}" ]; then
825				return
826			fi
827			if [ ! -d ${_dirname}/${resp} ]; then
828				echo "\"${resp}\" is no directory; try again."
829			else
830				_dirname=${_dirname}/$resp
831			fi
832		done
833	fi
834
835	for _f in $THESETS ; do
836		if [ -f ${_dirname}/${_f}.tar.gz ]; then
837			_sets="$_sets ${_f}.tar.gz"
838		elif [ -f ${_dirname}/${_f}.tgz ]; then
839			_sets="$_sets ${_f}.tgz"
840		elif [ -f ${_dirname}/${_f}.tar ]; then
841			_sets="$_sets ${_f}.tar"
842		elif [ -f ${_dirname}/${_f}${VERSION}.aa ]; then
843			_sets="$_sets ${_f}${VERSION}"
844		fi
845	done
846
847	while : ; do
848		echo "The following sets are available for extraction:"
849		echo "(marked sets have already been extracted)"
850		echo ""
851
852		_next=""
853		_all=""
854		for _f in $_sets ; do
855			if isin $_f $_setsdone; then
856				echo -n "[X] "
857				_next=""
858			else
859				echo -n "    "
860				if [ -z "$_next" ]; then
861					_next=$_f;
862				fi
863				_all="$_all $_f"
864			fi
865			echo $_f
866		done
867		echo ""
868
869		# Get the name of the file.
870		if [ -z "$_next" ]; then
871			resp=n
872		else
873			resp=y
874		fi
875		echo -n "Continue extraction [$resp]?"
876		getresp "$resp"
877		if [ "$resp" = "n" ]; then
878			break
879		fi
880
881		echo -n "File name(s) (or "all") [$_next]? "
882		getresp "$_next"
883		if [ "x$resp" = xall ]; then
884			resp="$_all"
885		fi
886
887		for _f in $resp; do
888			_filename="/${_dirname}/$_f"
889
890			# Ensure file exists
891			if [ ! -f $_filename ]; then
892				if [ -f ${_filename}.aa ]; then
893					_filename=${_filename}.\?\?
894				else
895			 echo "File $_filename does not exist.  Check to make"
896			 echo "sure you entered the information properly."
897			 continue 2
898				fi
899			fi
900
901			# Extract file
902			echo "Extracting the $_f set:"
903			case "$_filename" in
904			*.tar)
905				(cd /mnt; pax -r${verbose_flag}pe < $_filename)
906				;;
907			*)
908				cat $_filename | \
909					(cd /mnt; pax -zr${verbose_flag}pe)
910				;;
911			esac
912			echo "Extraction complete."
913			_setsdone="$_f $_setsdone"
914		done
915
916	done
917}
918
919install_cdrom() {
920	local _drive
921	local _partition_range
922	local _partition
923	local _fstype
924	local _directory
925
926	# Get the cdrom device info
927	cat << \__install_cdrom_1
928
929The following CD-ROM devices are installed on your system; please select
930the CD-ROM device containing the partition with the installation sets:
931
932__install_cdrom_1
933	_CDDEVS=$(md_get_cddevs)
934	echo    "$_CDDEVS"
935	echo	""
936	echo -n	"Which is the CD-ROM with the installation media? [abort] "
937	getresp "abort"
938	case "$resp" in
939		abort)
940			echo "Aborting."
941			return
942			;;
943
944		*)
945			if isin $resp $_CDDEVS ; then
946				_drive=$resp
947			else
948				echo ""
949				echo "The CD-ROM $resp does not exist."
950				echo "Aborting."
951				return
952			fi
953			;;
954	esac
955
956	# Get partition
957	_partition_range=$(md_get_partition_range)
958	resp=""		# force one iteration
959	while [ -z "${resp}" ]; do
960		echo -n "Partition? [a] "
961		getresp "a"
962		case "$resp" in
963			$_partition_range)
964				_partition=$resp
965				;;
966
967			*)
968				echo "Invalid response: $resp"
969				resp=""		# force loop to repeat
970				;;
971		esac
972	done
973
974	# Ask for filesystem type
975	cat << \__install_cdrom_2
976
977There are two CD-ROM filesystem types currently supported by this program:
978	1) ISO-9660 (cd9660)
979	2) Berkeley Fast Filesystem (ffs)
980
981__install_cdrom_2
982	resp=""		# force one iteration
983	while [ -z "${resp}" ]; do
984		echo -n "Which filesystem type? [cd9660] "
985		getresp "cd9660"
986		case "$resp" in
987			cd9660|ffs)
988				_fstype=$resp
989				;;
990
991			*)
992				echo "Invalid response: $resp"
993				resp=""		# force loop to repeat
994				;;
995		esac
996	done
997
998	# Mount the CD-ROM
999	if ! mount -t ${_fstype} -o ro \
1000	    /dev/${_drive}${_partition} /mnt2 ; then
1001		echo "Cannot mount CD-ROM drive.  Aborting."
1002		return
1003	fi
1004
1005	install_from_mounted_fs /mnt2
1006	umount -f /mnt2 > /dev/null 2>&1
1007}
1008
1009mount_a_disk() {
1010	# Mount a disk on /mnt2. The set of disk devices to choose from
1011	# is $_DKDEVS.
1012	# returns 0 on failure.
1013
1014	local _drive
1015	local _partition_range
1016	local _partition
1017	local _fstype
1018	local _fsopts
1019	local _directory
1020	local _md_fstype
1021	local _md_fsopts
1022
1023	getresp "abort"
1024	case "$resp" in
1025		abort)
1026			echo "Aborting."
1027			return 0
1028			;;
1029
1030		*)
1031			if isin $resp $_DKDEVS ; then
1032				_drive=$resp
1033			else
1034				echo ""
1035				echo "The disk $resp does not exist."
1036				echo "Aborting."
1037				return 0
1038			fi
1039			;;
1040	esac
1041
1042	# Get partition
1043	_partition_range=$(md_get_partition_range)
1044	resp=""		# force one iteration
1045	while [ -z "${resp}" ]; do
1046		echo -n "Partition? [d] "
1047		getresp "d"
1048		case "$resp" in
1049			$_partition_range)
1050				_partition=$resp
1051				;;
1052
1053			*)
1054				echo "Invalid response: $resp"
1055				resp=""		# force loop to repeat
1056				;;
1057		esac
1058	done
1059
1060	# Ask for filesystem type
1061	cat << \__mount_a_disk_2
1062
1063The following filesystem types are supported:
1064	1) ffs
1065	2) cd9660
1066__mount_a_disk_2
1067	_md_fstype=$(md_native_fstype)
1068	_md_fsopts=$(md_native_fsopts)
1069	if [ -n "$_md_fstype" ]; then
1070		echo "	3) $_md_fstype"
1071	else
1072		_md_fstype="_undefined_"
1073	fi
1074	resp=""		# force one iteration
1075	while [ -z "${resp}" ]; do
1076		echo -n "Which filesystem type? [ffs] "
1077		getresp "ffs"
1078		case "$resp" in
1079			ffs|cd9660)
1080				_fstype=$resp
1081				_fsopts="ro"
1082				;;
1083			$_md_fstype)
1084				_fstype=$resp
1085				_fsopts=$_md_fsopts
1086				;;
1087			*)
1088				echo "Invalid response: $resp"
1089				resp=""		# force loop to repeat
1090				;;
1091		esac
1092	done
1093
1094	# Mount the disk
1095	if ! mount -t ${_fstype} -o $_fsopts \
1096	    /dev/${_drive}${_partition} /mnt2 ; then
1097		echo "Cannot mount disk.  Aborting."
1098		return 0
1099	fi
1100	return 1
1101}
1102
1103install_disk() {
1104	local _directory
1105
1106	cat << \__install_disk_1
1107
1108Ok, lets install from a disk.  The file-system the install sets on may
1109already mounted, or we might have to mount the filesystem to get to it.
1110
1111__install_disk_1
1112
1113	echo -n "Is the file-system with the install sets already mounted? [n] "
1114	getresp "n"
1115	case $resp in
1116	y*|Y*)
1117		echo "What mount point are the sets located in? [] "
1118		getresp ""
1119		if [ -d "$resp" ]; then
1120			install_from_mounted_fs $resp
1121		else
1122			echo "$resp: Not a directory, aborting..."
1123		fi
1124		return
1125		;;
1126	*)
1127		;;
1128	esac
1129
1130	cat << \__install_disk_2
1131
1132The following disk devices are installed on your system; please select
1133the disk device containing the partition with the installation sets:
1134
1135__install_disk_2
1136	_DKDEVS=$(md_get_diskdevs)
1137	echo    "$_DKDEVS"
1138	echo	""
1139	echo -n	"Which is the disk with the installation sets? [abort] "
1140
1141	if mount_a_disk ; then
1142		return
1143	fi
1144
1145	install_from_mounted_fs /mnt2
1146	umount -f /mnt2 > /dev/null 2>&1
1147}
1148
1149install_nfs() {
1150	# Get the IP address of the server
1151	resp=""		# force one iteration
1152	while [ -z "${resp}" ]; do
1153		echo -n "Server IP address? [${_nfs_server_ip}] "
1154		getresp "${_nfs_server_ip}"
1155	done
1156	_nfs_server_ip=$resp
1157
1158	# Get server path to mount
1159	resp=""		# force one iteration
1160	while [ -z "${resp}" ]; do
1161		echo -n "Filesystem on server to mount? [${_nfs_server_path}] "
1162		getresp "${_nfs_server_path}"
1163	done
1164	_nfs_server_path=$resp
1165
1166	# Determine use of TCP
1167	echo -n "Use TCP transport (only works with capable NFS server)? [n] "
1168	getresp "n"
1169	case "$resp" in
1170		y*|Y*)
1171			_nfs_tcp="-T"
1172			;;
1173
1174		*)
1175			echo -n "Use small NFS transfers (needed when server "
1176			echo "or client"
1177			echo -n "has a slow network card)? [n] "
1178			getresp "n"
1179			case "$resp" in
1180			y*|Y*)
1181				_nfs_tcp="-r 1024 -w 1024"
1182				;;
1183
1184			*)
1185				_nfs_tcp=""
1186				;;
1187			esac
1188			;;
1189	esac
1190
1191	# Mount the server
1192	mkdir /mnt2 > /dev/null 2>&1
1193	if ! mount_nfs $_nfs_tcp ${_nfs_server_ip}:${_nfs_server_path} \
1194	    /mnt2 ; then
1195		echo "Cannot mount NFS server.  Aborting."
1196		return
1197	fi
1198
1199	install_from_mounted_fs /mnt2
1200	umount -f /mnt2 > /dev/null 2>&1
1201}
1202
1203install_tape() {
1204	local _xcmd
1205
1206	# Get the name of the tape from the user.
1207	cat << \__install_tape_1
1208
1209The installation program needs to know which tape device to use.  Make
1210sure you use a "no rewind on close" device.
1211
1212__install_tape_1
1213	_tape=$(basename $TAPE)
1214	resp=""		# force one iteration
1215	while [ -z "${resp}" ]; do
1216		echo -n "Name of tape device? [${_tape}]"
1217		getresp "${_tape}"
1218	done
1219	_tape=$(basename $resp)
1220	TAPE="/dev/${_tape}"
1221	if [ ! -c $TAPE ]; then
1222		echo "$TAPE does not exist or is not a character special file."
1223		echo "Aborting."
1224		return
1225	fi
1226	export TAPE
1227
1228	# Rewind the tape device
1229	echo -n "Rewinding tape..."
1230	if ! mt rewind ; then
1231		echo "$TAPE may not be attached to the system or may not be"
1232		echo "a tape device.  Aborting."
1233		return
1234	fi
1235	echo "done."
1236
1237	# Get the file number
1238	resp=""		# force one iteration
1239	while [ -z "${resp}" ]; do
1240		echo -n "File number? "
1241		getresp ""
1242		case "$resp" in
1243			[1-9]*)
1244				_nskip=$(expr $resp - 1)
1245				;;
1246
1247			*)
1248				echo "Invalid file number ${resp}."
1249				resp=""		# fore loop to repeat
1250				;;
1251		esac
1252	done
1253
1254	# Skip to correct file.
1255	echo -n "Skipping to source file..."
1256	if [ "${_nskip}" != "0" ]; then
1257		if ! mt fsf $_nskip ; then
1258			echo "Could not skip $_nskip files.  Aborting."
1259			return
1260		fi
1261	fi
1262	echo "done."
1263
1264	cat << \__install_tape_2
1265
1266There are 2 different ways the file can be stored on tape:
1267
1268	1) an image of a gzipped tar file
1269	2) a standard tar image
1270
1271__install_tape_2
1272	resp=""		# force one iteration
1273	while [ -z "${resp}" ]; do
1274		echo -n "Which way is it? [1] "
1275		getresp "1"
1276		case "$resp" in
1277		1)
1278			_xcmd="pax -zr${verbose_flag}pe"
1279			;;
1280
1281		2)
1282			_xcmd="pax -r${verbose_flag}pe"
1283			;;
1284
1285		*)
1286			echo "Invalid response: $resp."
1287			resp=""		# force loop to repeat
1288			;;
1289		esac
1290		( cd /mnt; dd if=$TAPE | $_xcmd )
1291	done
1292	echo "Extraction complete."
1293}
1294
1295get_timezone() {
1296	local _a
1297	local _zonepath
1298
1299	#
1300	# If the zoneinfo is not on the installation medium or on the
1301	# installed filesystem, set TZ to GMT and return immediately.
1302	#
1303	if [ ! -e /usr/share/zoneinfo ] && [ ! -e /mnt/usr/share/zoneinfo ]; then
1304		TZ=GMT
1305		return
1306	fi
1307	if [ ! -d /usr/share/zoneinfo ]; then
1308		_zonepath=/mnt
1309	else
1310		_zonepath=""
1311	fi
1312
1313cat << \__get_timezone_1
1314
1315Select a time zone for your location. Timezones are represented on the
1316system by a directory structure rooted in "/usr/share/zoneinfo". Most
1317timezones can be selected by entering a token like "MET" or "GMT-6".
1318Other zones are grouped by continent, with detailed zone information
1319separated by a slash ("/"), e.g. "US/Pacific".
1320
1321To get a listing of what's available in /usr/share/zoneinfo, enter "?"
1322at the prompts below.
1323
1324__get_timezone_1
1325	if [ -z "$TZ" ]; then
1326		TZ=$(ls -l /mnt/etc/localtime 2>/dev/null | cutlast)
1327		TZ=${TZ#/usr/share/zoneinfo/}
1328	fi
1329	while :; do
1330		echo -n	"What timezone are you in ['?' for list] [$TZ]? "
1331		getresp "$TZ"
1332		case "$resp" in
1333		"")
1334			echo "Timezone defaults to GMT"
1335			TZ="GMT"
1336			break;
1337			;;
1338		"?")
1339			ls ${_zonepath}/usr/share/zoneinfo
1340			;;
1341		*)
1342			_a=$resp
1343			while [ -d ${_zonepath}/usr/share/zoneinfo/$_a ]; do
1344				echo -n "There are several timezones available"
1345				echo " within zone '$_a'"
1346				echo -n "Select a sub-timezone ['?' for list]: "
1347				getresp ""
1348				case "$resp" in
1349				"?") ls ${_zonepath}/usr/share/zoneinfo/$_a ;;
1350				*)	_a=${_a}/${resp}
1351					if [ -f ${_zonepath}/usr/share/zoneinfo/$_a ]; then
1352						break;
1353					fi
1354					;;
1355				esac
1356			done
1357			if [ -f ${_zonepath}/usr/share/zoneinfo/$_a ]; then
1358				TZ="$_a"
1359				echo "You have selected timezone \"$_a\"".
1360				break 2
1361			fi
1362			echo "'/usr/share/zoneinfo/$_a' is not a valid timezone on this system."
1363			;;
1364		esac
1365	done
1366}
1367
1368install_sets()
1369{
1370	local _yup
1371	_yup="FALSE"
1372
1373	# Ask the user which media to load the distribution from.
1374	# Ask the user if they want verbose extraction.  They might not want
1375	# it on, eg, SPARC frame buffer console.
1376	cat << \__install_sets_1
1377
1378It is now time to extract the installation sets onto the hard disk.
1379Make sure the sets are either on a local device (i.e. tape, CD-ROM) or on a
1380network server.
1381
1382Would you like to see each file listed during extraction (verbose) mode?
1383On some console hardware, such as serial consoles and Sun frame buffers,
1384this can extend the total extraction time.
1385__install_sets_1
1386	echo -n "Use verbose listing for extractions? [y] "
1387	getresp "y"
1388	case "$resp" in
1389	y*|Y*)
1390		verbose_flag=v
1391		;;
1392	*)
1393		echo "Not using verbose listing."
1394		verbose_flag=""
1395		;;
1396	esac
1397
1398	if [ -d ${Default_sets_dir:-/dev/null} ]; then
1399		if dir_has_sets $Default_sets_dir $THESETS; then
1400			local_sets_dir=$Default_sets_dir
1401		fi
1402	fi
1403	if [ -n "${local_sets_dir}" ]; then
1404		install_from_mounted_fs ${local_sets_dir}
1405		if [ -n "$_setsdone" ]; then
1406			_yup="TRUE"
1407		fi
1408	fi
1409
1410	# Go on prodding for alternate locations
1411	resp=""		# force at least one iteration
1412	while [ -z "${resp}" ]; do
1413		# If _yup is not FALSE, it means that we extracted sets above.
1414		# If that's the case, bypass the menu the first time.
1415		if [ "${_yup}" = "FALSE" ]; then
1416			echo -n	"Install from (f)tp, (t)ape, (C)D-ROM, (N)FS"
1417			echo -n " or local (d)isk? "
1418			getresp ""
1419			case "$resp" in
1420			d*|D*)
1421				install_disk
1422				;;
1423			f*|F*)
1424				install_ftp
1425				;;
1426			t*|T*)
1427				install_tape
1428				;;
1429			c*|C*)
1430				install_cdrom
1431				;;
1432			n*|N*)
1433				install_nfs
1434				;;
1435			*)
1436				echo "Invalid response: $resp"
1437				resp=""
1438				;;
1439			esac
1440		else
1441			_yup="FALSE"	# So we'll ask next time
1442		fi
1443
1444		# Give the user the opportunity to extract more sets. They
1445		# don't necessarily have to come from the same media.
1446		echo	""
1447		echo -n	"Extract more sets? [n] "
1448		getresp "n"
1449		case "$resp" in
1450		y*|Y*)
1451			# Force loop to repeat
1452			resp=""
1453			;;
1454
1455		*)
1456			;;
1457		esac
1458	done
1459}
1460
1461munge_fstab()
1462{
1463	local _fstab
1464	local _fstab_shadow
1465	local _dev
1466	local _mp
1467	local _fstype
1468	local _rest
1469
1470	# Now that the 'real' fstab is configured, we munge it into a 'shadow'
1471	# fstab which we'll use for mounting and unmounting all of the target
1472	# filesystems relative to /mnt.  Mount all filesystems.
1473	_fstab=$1
1474	_fstab_shadow=$2
1475	( while read _dev _mp _fstype _rest; do
1476		# Skip comment lines
1477		case "$_dev" in
1478			\#*)	continue;;
1479			*)	;;
1480		esac
1481		# and some filesystem types (like there are swap,kernfs,...)
1482		case "$_fstype" in
1483			ffs|ufs|nfs)	;;
1484			*)	continue;;
1485		esac
1486		if [ "$_mp" = "/" ]; then
1487			echo $_dev /mnt $_fstype $_rest
1488		else
1489			echo $_dev /mnt$_mp $_fstype $_rest
1490		fi
1491	    done ) < $_fstab > $_fstab_shadow
1492}
1493
1494mount_fs()
1495{
1496	# Must mount filesystems manually, one at a time, so we can make
1497	# sure the mount points exist.
1498	# $1 is a file in fstab format
1499	local _fstab
1500
1501	_fstab=$1
1502
1503	( while read line; do
1504		set -- $line
1505		_dev=$1
1506		_mp=$2
1507		_fstype=$3
1508		_opt=$4
1509
1510		# If not the root filesystem, make sure the mount
1511		# point is present.
1512		if [ "$_mp" != "/mnt" ]; then
1513			mkdir -p $_mp
1514		fi
1515
1516		# Mount the filesystem.  If the mount fails, exit
1517		# with an error condition to tell the outer
1518		# later to bail.
1519		if ! mount -v -t $_fstype -o async -o $_opt $_dev $_mp ; then
1520			# error message displayed by mount
1521			exit 1
1522		fi
1523	done ) < $_fstab
1524
1525	if [ "$?" != "0" ]; then
1526		cat << \__mount_filesystems_1
1527
1528FATAL ERROR:  Cannot mount filesystems.  Double-check your configuration
1529and restart the installation process.
1530__mount_filesystems_1
1531		exit
1532	fi
1533}
1534
1535unmount_fs()
1536{
1537	# Unmount all filesystems and check their integrity.
1538	# Usage: [-fast] <fstab file>
1539	local _fast
1540	local _fstab
1541	local _pid
1542
1543	if [ "$1" = "-fast" ]; then
1544		_fast=1
1545		_fstab=$2
1546	else
1547		_fast=0
1548		_fstab=$1
1549	fi
1550
1551	if ! [ -f "${_fstab}" ] || ! [ -s "${_fstab}" ]; then
1552		echo "fstab empty" > /dev/tty
1553		return
1554	fi
1555
1556	if [ $_fast = 0 ]; then
1557		echo -n	"Syncing disks..."
1558		_pid=$(twiddle)
1559		sync; sleep 4; sync; sleep 2; sync; sleep 2
1560		kill $_pid
1561		echo	"done."
1562	fi
1563
1564	(
1565		_devs=""
1566		_mps=""
1567		# maintain reverse order
1568		while read line; do
1569			set -- $line
1570			_devs="$1 ${_devs}"
1571			_mps="$2 ${_mps}"
1572		done
1573		echo -n "Unmounting filesystems... "
1574		for _mp in ${_mps}; do
1575			echo -n "${_mp} "
1576			umount ${_mp}
1577		done
1578		echo "Done."
1579
1580		if [ $_fast = 0 ]; then
1581			exit
1582		fi
1583		echo "Checking filesystem integrity..."
1584		for _dev in ${_devs}; do
1585			echo  "${_dev}"
1586			fsck -f ${_dev}
1587		done
1588		echo "Done."
1589	) < $_fstab
1590}
1591
1592check_fs()
1593{
1594	# Check filesystem integrity.
1595	# $1 is a file in fstab format
1596	local _fstab
1597
1598	_fstab=$1
1599
1600	(
1601		_devs=""
1602		_mps=""
1603		while read line; do
1604			set -- $line
1605			_devs="$1 ${_devs}"
1606			_mps="$2 ${_mps}"
1607		done
1608
1609		echo "Checking filesystem integrity..."
1610		for _dev in ${_devs}; do
1611			echo  "${_dev}"
1612			fsck -f ${_dev}
1613		done
1614		echo "Done."
1615	) < $_fstab
1616}
1617
1618mi_mount_kernfs() {
1619	# Make sure kernfs is mounted.
1620	if [ ! -d /kern ] || [ ! -e /kern/msgbuf ]; then
1621		mkdir /kern > /dev/null 2>&1
1622		/sbin/mount_kernfs /kern /kern
1623	fi
1624}
1625
1626mi_filter_msgbuf() {
1627	# Remove timestemps, sort.
1628	sed -e 's/^\[[0-9. ]*\] //' < /kern/msgbuf | sort -u
1629}
1630
1631mi_filter_dmesg() {
1632	# Remove timestemps, sort.
1633	dmesg | awk '{ h=$0; gsub("^[[0-9. ]*] ", "", h); print h; }' \
1634	    | sort -u
1635}
1636