freebsd-update.sh revision 284937
1#!/bin/sh
2
3#-
4# Copyright 2004-2007 Colin Percival
5# All rights reserved
6#
7# Redistribution and use in source and binary forms, with or without
8# modification, are permitted providing that the following conditions 
9# are met:
10# 1. Redistributions of source code must retain the above copyright
11#    notice, this list of conditions and the following disclaimer.
12# 2. Redistributions in binary form must reproduce the above copyright
13#    notice, this list of conditions and the following disclaimer in the
14#    documentation and/or other materials provided with the distribution.
15#
16# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
20# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
24# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
25# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26# POSSIBILITY OF SUCH DAMAGE.
27
28# $FreeBSD: stable/10/usr.sbin/freebsd-update/freebsd-update.sh 284937 2015-06-29 18:51:08Z delphij $
29
30#### Usage function -- called from command-line handling code.
31
32# Usage instructions.  Options not listed:
33# --debug	-- don't filter output from utilities
34# --no-stats	-- don't show progress statistics while fetching files
35usage () {
36	cat <<EOF
37usage: `basename $0` [options] command ... [path]
38
39Options:
40  -b basedir   -- Operate on a system mounted at basedir
41                  (default: /)
42  -d workdir   -- Store working files in workdir
43                  (default: /var/db/freebsd-update/)
44  -f conffile  -- Read configuration options from conffile
45                  (default: /etc/freebsd-update.conf)
46  -F           -- Force a fetch operation to proceed
47  -k KEY       -- Trust an RSA key with SHA256 hash of KEY
48  -r release   -- Target for upgrade (e.g., 6.2-RELEASE)
49  -s server    -- Server from which to fetch updates
50                  (default: update.FreeBSD.org)
51  -t address   -- Mail output of cron command, if any, to address
52                  (default: root)
53  --not-running-from-cron
54               -- Run without a tty, for use by automated tools
55Commands:
56  fetch        -- Fetch updates from server
57  cron         -- Sleep rand(3600) seconds, fetch updates, and send an
58                  email if updates were found
59  upgrade      -- Fetch upgrades to FreeBSD version specified via -r option
60  install      -- Install downloaded updates or upgrades
61  rollback     -- Uninstall most recently installed updates
62  IDS          -- Compare the system against an index of "known good" files.
63EOF
64	exit 0
65}
66
67#### Configuration processing functions
68
69#-
70# Configuration options are set in the following order of priority:
71# 1. Command line options
72# 2. Configuration file options
73# 3. Default options
74# In addition, certain options (e.g., IgnorePaths) can be specified multiple
75# times and (as long as these are all in the same place, e.g., inside the
76# configuration file) they will accumulate.  Finally, because the path to the
77# configuration file can be specified at the command line, the entire command
78# line must be processed before we start reading the configuration file.
79#
80# Sound like a mess?  It is.  Here's how we handle this:
81# 1. Initialize CONFFILE and all the options to "".
82# 2. Process the command line.  Throw an error if a non-accumulating option
83#    is specified twice.
84# 3. If CONFFILE is "", set CONFFILE to /etc/freebsd-update.conf .
85# 4. For all the configuration options X, set X_saved to X.
86# 5. Initialize all the options to "".
87# 6. Read CONFFILE line by line, parsing options.
88# 7. For each configuration option X, set X to X_saved iff X_saved is not "".
89# 8. Repeat steps 4-7, except setting options to their default values at (6).
90
91CONFIGOPTIONS="KEYPRINT WORKDIR SERVERNAME MAILTO ALLOWADD ALLOWDELETE
92    KEEPMODIFIEDMETADATA COMPONENTS IGNOREPATHS UPDATEIFUNMODIFIED
93    BASEDIR VERBOSELEVEL TARGETRELEASE STRICTCOMPONENTS MERGECHANGES
94    IDSIGNOREPATHS BACKUPKERNEL BACKUPKERNELDIR BACKUPKERNELSYMBOLFILES"
95
96# Set all the configuration options to "".
97nullconfig () {
98	for X in ${CONFIGOPTIONS}; do
99		eval ${X}=""
100	done
101}
102
103# For each configuration option X, set X_saved to X.
104saveconfig () {
105	for X in ${CONFIGOPTIONS}; do
106		eval ${X}_saved=\$${X}
107	done
108}
109
110# For each configuration option X, set X to X_saved if X_saved is not "".
111mergeconfig () {
112	for X in ${CONFIGOPTIONS}; do
113		eval _=\$${X}_saved
114		if ! [ -z "${_}" ]; then
115			eval ${X}=\$${X}_saved
116		fi
117	done
118}
119
120# Set the trusted keyprint.
121config_KeyPrint () {
122	if [ -z ${KEYPRINT} ]; then
123		KEYPRINT=$1
124	else
125		return 1
126	fi
127}
128
129# Set the working directory.
130config_WorkDir () {
131	if [ -z ${WORKDIR} ]; then
132		WORKDIR=$1
133	else
134		return 1
135	fi
136}
137
138# Set the name of the server (pool) from which to fetch updates
139config_ServerName () {
140	if [ -z ${SERVERNAME} ]; then
141		SERVERNAME=$1
142	else
143		return 1
144	fi
145}
146
147# Set the address to which 'cron' output will be mailed.
148config_MailTo () {
149	if [ -z ${MAILTO} ]; then
150		MAILTO=$1
151	else
152		return 1
153	fi
154}
155
156# Set whether FreeBSD Update is allowed to add files (or directories, or
157# symlinks) which did not previously exist.
158config_AllowAdd () {
159	if [ -z ${ALLOWADD} ]; then
160		case $1 in
161		[Yy][Ee][Ss])
162			ALLOWADD=yes
163			;;
164		[Nn][Oo])
165			ALLOWADD=no
166			;;
167		*)
168			return 1
169			;;
170		esac
171	else
172		return 1
173	fi
174}
175
176# Set whether FreeBSD Update is allowed to remove files/directories/symlinks.
177config_AllowDelete () {
178	if [ -z ${ALLOWDELETE} ]; then
179		case $1 in
180		[Yy][Ee][Ss])
181			ALLOWDELETE=yes
182			;;
183		[Nn][Oo])
184			ALLOWDELETE=no
185			;;
186		*)
187			return 1
188			;;
189		esac
190	else
191		return 1
192	fi
193}
194
195# Set whether FreeBSD Update should keep existing inode ownership,
196# permissions, and flags, in the event that they have been modified locally
197# after the release.
198config_KeepModifiedMetadata () {
199	if [ -z ${KEEPMODIFIEDMETADATA} ]; then
200		case $1 in
201		[Yy][Ee][Ss])
202			KEEPMODIFIEDMETADATA=yes
203			;;
204		[Nn][Oo])
205			KEEPMODIFIEDMETADATA=no
206			;;
207		*)
208			return 1
209			;;
210		esac
211	else
212		return 1
213	fi
214}
215
216# Add to the list of components which should be kept updated.
217config_Components () {
218	for C in $@; do
219		if [ "$C" = "src" ]; then
220			if [ -e /usr/src/COPYRIGHT ]; then
221				COMPONENTS="${COMPONENTS} ${C}"
222			else
223				echo "src component not installed, skipped"
224			fi
225		else
226			COMPONENTS="${COMPONENTS} ${C}"
227		fi
228	done
229}
230
231# Add to the list of paths under which updates will be ignored.
232config_IgnorePaths () {
233	for C in $@; do
234		IGNOREPATHS="${IGNOREPATHS} ${C}"
235	done
236}
237
238# Add to the list of paths which IDS should ignore.
239config_IDSIgnorePaths () {
240	for C in $@; do
241		IDSIGNOREPATHS="${IDSIGNOREPATHS} ${C}"
242	done
243}
244
245# Add to the list of paths within which updates will be performed only if the
246# file on disk has not been modified locally.
247config_UpdateIfUnmodified () {
248	for C in $@; do
249		UPDATEIFUNMODIFIED="${UPDATEIFUNMODIFIED} ${C}"
250	done
251}
252
253# Add to the list of paths within which updates to text files will be merged
254# instead of overwritten.
255config_MergeChanges () {
256	for C in $@; do
257		MERGECHANGES="${MERGECHANGES} ${C}"
258	done
259}
260
261# Work on a FreeBSD installation mounted under $1
262config_BaseDir () {
263	if [ -z ${BASEDIR} ]; then
264		BASEDIR=$1
265	else
266		return 1
267	fi
268}
269
270# When fetching upgrades, should we assume the user wants exactly the
271# components listed in COMPONENTS, rather than trying to guess based on
272# what's currently installed?
273config_StrictComponents () {
274	if [ -z ${STRICTCOMPONENTS} ]; then
275		case $1 in
276		[Yy][Ee][Ss])
277			STRICTCOMPONENTS=yes
278			;;
279		[Nn][Oo])
280			STRICTCOMPONENTS=no
281			;;
282		*)
283			return 1
284			;;
285		esac
286	else
287		return 1
288	fi
289}
290
291# Upgrade to FreeBSD $1
292config_TargetRelease () {
293	if [ -z ${TARGETRELEASE} ]; then
294		TARGETRELEASE=$1
295	else
296		return 1
297	fi
298	if echo ${TARGETRELEASE} | grep -qE '^[0-9.]+$'; then
299		TARGETRELEASE="${TARGETRELEASE}-RELEASE"
300	fi
301}
302
303# Define what happens to output of utilities
304config_VerboseLevel () {
305	if [ -z ${VERBOSELEVEL} ]; then
306		case $1 in
307		[Dd][Ee][Bb][Uu][Gg])
308			VERBOSELEVEL=debug
309			;;
310		[Nn][Oo][Ss][Tt][Aa][Tt][Ss])
311			VERBOSELEVEL=nostats
312			;;
313		[Ss][Tt][Aa][Tt][Ss])
314			VERBOSELEVEL=stats
315			;;
316		*)
317			return 1
318			;;
319		esac
320	else
321		return 1
322	fi
323}
324
325config_BackupKernel () {
326	if [ -z ${BACKUPKERNEL} ]; then
327		case $1 in
328		[Yy][Ee][Ss])
329			BACKUPKERNEL=yes
330			;;
331		[Nn][Oo])
332			BACKUPKERNEL=no
333			;;
334		*)
335			return 1
336			;;
337		esac
338	else
339		return 1
340	fi
341}
342
343config_BackupKernelDir () {
344	if [ -z ${BACKUPKERNELDIR} ]; then
345		if [ -z "$1" ]; then
346			echo "BackupKernelDir set to empty dir"
347			return 1
348		fi
349
350		# We check for some paths which would be extremely odd
351		# to use, but which could cause a lot of problems if
352		# used.
353		case $1 in
354		/|/bin|/boot|/etc|/lib|/libexec|/sbin|/usr|/var)
355			echo "BackupKernelDir set to invalid path $1"
356			return 1
357			;;
358		/*)
359			BACKUPKERNELDIR=$1
360			;;
361		*)
362			echo "BackupKernelDir ($1) is not an absolute path"
363			return 1
364			;;
365		esac
366	else
367		return 1
368	fi
369}
370
371config_BackupKernelSymbolFiles () {
372	if [ -z ${BACKUPKERNELSYMBOLFILES} ]; then
373		case $1 in
374		[Yy][Ee][Ss])
375			BACKUPKERNELSYMBOLFILES=yes
376			;;
377		[Nn][Oo])
378			BACKUPKERNELSYMBOLFILES=no
379			;;
380		*)
381			return 1
382			;;
383		esac
384	else
385		return 1
386	fi
387}
388
389# Handle one line of configuration
390configline () {
391	if [ $# -eq 0 ]; then
392		return
393	fi
394
395	OPT=$1
396	shift
397	config_${OPT} $@
398}
399
400#### Parameter handling functions.
401
402# Initialize parameters to null, just in case they're
403# set in the environment.
404init_params () {
405	# Configration settings
406	nullconfig
407
408	# No configuration file set yet
409	CONFFILE=""
410
411	# No commands specified yet
412	COMMANDS=""
413
414	# Force fetch to proceed
415	FORCEFETCH=0
416
417	# Run without a TTY
418	NOTTYOK=0
419}
420
421# Parse the command line
422parse_cmdline () {
423	while [ $# -gt 0 ]; do
424		case "$1" in
425		# Location of configuration file
426		-f)
427			if [ $# -eq 1 ]; then usage; fi
428			if [ ! -z "${CONFFILE}" ]; then usage; fi
429			shift; CONFFILE="$1"
430			;;
431		-F)
432			FORCEFETCH=1
433			;;
434		--not-running-from-cron)
435			NOTTYOK=1
436			;;
437
438		# Configuration file equivalents
439		-b)
440			if [ $# -eq 1 ]; then usage; fi; shift
441			config_BaseDir $1 || usage
442			;;
443		-d)
444			if [ $# -eq 1 ]; then usage; fi; shift
445			config_WorkDir $1 || usage
446			;;
447		-k)
448			if [ $# -eq 1 ]; then usage; fi; shift
449			config_KeyPrint $1 || usage
450			;;
451		-s)
452			if [ $# -eq 1 ]; then usage; fi; shift
453			config_ServerName $1 || usage
454			;;
455		-r)
456			if [ $# -eq 1 ]; then usage; fi; shift
457			config_TargetRelease $1 || usage
458			;;
459		-t)
460			if [ $# -eq 1 ]; then usage; fi; shift
461			config_MailTo $1 || usage
462			;;
463		-v)
464			if [ $# -eq 1 ]; then usage; fi; shift
465			config_VerboseLevel $1 || usage
466			;;
467
468		# Aliases for "-v debug" and "-v nostats"
469		--debug)
470			config_VerboseLevel debug || usage
471			;;
472		--no-stats)
473			config_VerboseLevel nostats || usage
474			;;
475
476		# Commands
477		cron | fetch | upgrade | install | rollback | IDS)
478			COMMANDS="${COMMANDS} $1"
479			;;
480
481		# Anything else is an error
482		*)
483			usage
484			;;
485		esac
486		shift
487	done
488
489	# Make sure we have at least one command
490	if [ -z "${COMMANDS}" ]; then
491		usage
492	fi
493}
494
495# Parse the configuration file
496parse_conffile () {
497	# If a configuration file was specified on the command line, check
498	# that it exists and is readable.
499	if [ ! -z "${CONFFILE}" ] && [ ! -r "${CONFFILE}" ]; then
500		echo -n "File does not exist "
501		echo -n "or is not readable: "
502		echo ${CONFFILE}
503		exit 1
504	fi
505
506	# If a configuration file was not specified on the command line,
507	# use the default configuration file path.  If that default does
508	# not exist, give up looking for any configuration.
509	if [ -z "${CONFFILE}" ]; then
510		CONFFILE="/etc/freebsd-update.conf"
511		if [ ! -r "${CONFFILE}" ]; then
512			return
513		fi
514	fi
515
516	# Save the configuration options specified on the command line, and
517	# clear all the options in preparation for reading the config file.
518	saveconfig
519	nullconfig
520
521	# Read the configuration file.  Anything after the first '#' is
522	# ignored, and any blank lines are ignored.
523	L=0
524	while read LINE; do
525		L=$(($L + 1))
526		LINEX=`echo "${LINE}" | cut -f 1 -d '#'`
527		if ! configline ${LINEX}; then
528			echo "Error processing configuration file, line $L:"
529			echo "==> ${LINE}"
530			exit 1
531		fi
532	done < ${CONFFILE}
533
534	# Merge the settings read from the configuration file with those
535	# provided at the command line.
536	mergeconfig
537}
538
539# Provide some default parameters
540default_params () {
541	# Save any parameters already configured, and clear the slate
542	saveconfig
543	nullconfig
544
545	# Default configurations
546	config_WorkDir /var/db/freebsd-update
547	config_MailTo root
548	config_AllowAdd yes
549	config_AllowDelete yes
550	config_KeepModifiedMetadata yes
551	config_BaseDir /
552	config_VerboseLevel stats
553	config_StrictComponents no
554	config_BackupKernel yes
555	config_BackupKernelDir /boot/kernel.old
556	config_BackupKernelSymbolFiles no
557
558	# Merge these defaults into the earlier-configured settings
559	mergeconfig
560}
561
562# Set utility output filtering options, based on ${VERBOSELEVEL}
563fetch_setup_verboselevel () {
564	case ${VERBOSELEVEL} in
565	debug)
566		QUIETREDIR="/dev/stderr"
567		QUIETFLAG=" "
568		STATSREDIR="/dev/stderr"
569		DDSTATS=".."
570		XARGST="-t"
571		NDEBUG=" "
572		;;
573	nostats)
574		QUIETREDIR=""
575		QUIETFLAG=""
576		STATSREDIR="/dev/null"
577		DDSTATS=".."
578		XARGST=""
579		NDEBUG=""
580		;;
581	stats)
582		QUIETREDIR="/dev/null"
583		QUIETFLAG="-q"
584		STATSREDIR="/dev/stdout"
585		DDSTATS=""
586		XARGST=""
587		NDEBUG="-n"
588		;;
589	esac
590}
591
592# Perform sanity checks and set some final parameters
593# in preparation for fetching files.  Figure out which
594# set of updates should be downloaded: If the user is
595# running *-p[0-9]+, strip off the last part; if the
596# user is running -SECURITY, call it -RELEASE.  Chdir
597# into the working directory.
598fetchupgrade_check_params () {
599	export HTTP_USER_AGENT="freebsd-update (${COMMAND}, `uname -r`)"
600
601	_SERVERNAME_z=\
602"SERVERNAME must be given via command line or configuration file."
603	_KEYPRINT_z="Key must be given via -k option or configuration file."
604	_KEYPRINT_bad="Invalid key fingerprint: "
605	_WORKDIR_bad="Directory does not exist or is not writable: "
606
607	if [ -z "${SERVERNAME}" ]; then
608		echo -n "`basename $0`: "
609		echo "${_SERVERNAME_z}"
610		exit 1
611	fi
612	if [ -z "${KEYPRINT}" ]; then
613		echo -n "`basename $0`: "
614		echo "${_KEYPRINT_z}"
615		exit 1
616	fi
617	if ! echo "${KEYPRINT}" | grep -qE "^[0-9a-f]{64}$"; then
618		echo -n "`basename $0`: "
619		echo -n "${_KEYPRINT_bad}"
620		echo ${KEYPRINT}
621		exit 1
622	fi
623	if ! [ -d "${WORKDIR}" -a -w "${WORKDIR}" ]; then
624		echo -n "`basename $0`: "
625		echo -n "${_WORKDIR_bad}"
626		echo ${WORKDIR}
627		exit 1
628	fi
629	chmod 700 ${WORKDIR}
630	cd ${WORKDIR} || exit 1
631
632	# Generate release number.  The s/SECURITY/RELEASE/ bit exists
633	# to provide an upgrade path for FreeBSD Update 1.x users, since
634	# the kernels provided by FreeBSD Update 1.x are always labelled
635	# as X.Y-SECURITY.
636	RELNUM=`uname -r |
637	    sed -E 's,-p[0-9]+,,' |
638	    sed -E 's,-SECURITY,-RELEASE,'`
639	ARCH=`uname -m`
640	FETCHDIR=${RELNUM}/${ARCH}
641	PATCHDIR=${RELNUM}/${ARCH}/bp
642
643	# Figure out what directory contains the running kernel
644	BOOTFILE=`sysctl -n kern.bootfile`
645	KERNELDIR=${BOOTFILE%/kernel}
646	if ! [ -d ${KERNELDIR} ]; then
647		echo "Cannot identify running kernel"
648		exit 1
649	fi
650
651	# Figure out what kernel configuration is running.  We start with
652	# the output of `uname -i`, and then make the following adjustments:
653	# 1. Replace "SMP-GENERIC" with "SMP".  Why the SMP kernel config
654	# file says "ident SMP-GENERIC", I don't know...
655	# 2. If the kernel claims to be GENERIC _and_ ${ARCH} is "amd64"
656	# _and_ `sysctl kern.version` contains a line which ends "/SMP", then
657	# we're running an SMP kernel.  This mis-identification is a bug
658	# which was fixed in 6.2-STABLE.
659	KERNCONF=`uname -i`
660	if [ ${KERNCONF} = "SMP-GENERIC" ]; then
661		KERNCONF=SMP
662	fi
663	if [ ${KERNCONF} = "GENERIC" ] && [ ${ARCH} = "amd64" ]; then
664		if sysctl kern.version | grep -qE '/SMP$'; then
665			KERNCONF=SMP
666		fi
667	fi
668
669	# Define some paths
670	BSPATCH=/usr/bin/bspatch
671	SHA256=/sbin/sha256
672	PHTTPGET=/usr/libexec/phttpget
673
674	# Set up variables relating to VERBOSELEVEL
675	fetch_setup_verboselevel
676
677	# Construct a unique name from ${BASEDIR}
678	BDHASH=`echo ${BASEDIR} | sha256 -q`
679}
680
681# Perform sanity checks etc. before fetching updates.
682fetch_check_params () {
683	fetchupgrade_check_params
684
685	if ! [ -z "${TARGETRELEASE}" ]; then
686		echo -n "`basename $0`: "
687		echo -n "-r option is meaningless with 'fetch' command.  "
688		echo "(Did you mean 'upgrade' instead?)"
689		exit 1
690	fi
691
692	# Check that we have updates ready to install
693	if [ -f ${BDHASH}-install/kerneldone -a $FORCEFETCH -eq 0 ]; then
694		echo "You have a partially completed upgrade pending"
695		echo "Run '$0 install' first."
696		echo "Run '$0 fetch -F' to proceed anyway."
697		exit 1
698	fi
699}
700
701# Perform sanity checks etc. before fetching upgrades.
702upgrade_check_params () {
703	fetchupgrade_check_params
704
705	# Unless set otherwise, we're upgrading to the same kernel config.
706	NKERNCONF=${KERNCONF}
707
708	# We need TARGETRELEASE set
709	_TARGETRELEASE_z="Release target must be specified via -r option."
710	if [ -z "${TARGETRELEASE}" ]; then
711		echo -n "`basename $0`: "
712		echo "${_TARGETRELEASE_z}"
713		exit 1
714	fi
715
716	# The target release should be != the current release.
717	if [ "${TARGETRELEASE}" = "${RELNUM}" ]; then
718		echo -n "`basename $0`: "
719		echo "Cannot upgrade from ${RELNUM} to itself"
720		exit 1
721	fi
722
723	# Turning off AllowAdd or AllowDelete is a bad idea for upgrades.
724	if [ "${ALLOWADD}" = "no" ]; then
725		echo -n "`basename $0`: "
726		echo -n "WARNING: \"AllowAdd no\" is a bad idea "
727		echo "when upgrading between releases."
728		echo
729	fi
730	if [ "${ALLOWDELETE}" = "no" ]; then
731		echo -n "`basename $0`: "
732		echo -n "WARNING: \"AllowDelete no\" is a bad idea "
733		echo "when upgrading between releases."
734		echo
735	fi
736
737	# Set EDITOR to /usr/bin/vi if it isn't already set
738	: ${EDITOR:='/usr/bin/vi'}
739}
740
741# Perform sanity checks and set some final parameters in
742# preparation for installing updates.
743install_check_params () {
744	# Check that we are root.  All sorts of things won't work otherwise.
745	if [ `id -u` != 0 ]; then
746		echo "You must be root to run this."
747		exit 1
748	fi
749
750	# Check that securelevel <= 0.  Otherwise we can't update schg files.
751	if [ `sysctl -n kern.securelevel` -gt 0 ]; then
752		echo "Updates cannot be installed when the system securelevel"
753		echo "is greater than zero."
754		exit 1
755	fi
756
757	# Check that we have a working directory
758	_WORKDIR_bad="Directory does not exist or is not writable: "
759	if ! [ -d "${WORKDIR}" -a -w "${WORKDIR}" ]; then
760		echo -n "`basename $0`: "
761		echo -n "${_WORKDIR_bad}"
762		echo ${WORKDIR}
763		exit 1
764	fi
765	cd ${WORKDIR} || exit 1
766
767	# Construct a unique name from ${BASEDIR}
768	BDHASH=`echo ${BASEDIR} | sha256 -q`
769
770	# Check that we have updates ready to install
771	if ! [ -L ${BDHASH}-install ]; then
772		echo "No updates are available to install."
773		echo "Run '$0 fetch' first."
774		exit 1
775	fi
776	if ! [ -f ${BDHASH}-install/INDEX-OLD ] ||
777	    ! [ -f ${BDHASH}-install/INDEX-NEW ]; then
778		echo "Update manifest is corrupt -- this should never happen."
779		echo "Re-run '$0 fetch'."
780		exit 1
781	fi
782
783	# Figure out what directory contains the running kernel
784	BOOTFILE=`sysctl -n kern.bootfile`
785	KERNELDIR=${BOOTFILE%/kernel}
786	if ! [ -d ${KERNELDIR} ]; then
787		echo "Cannot identify running kernel"
788		exit 1
789	fi
790}
791
792# Perform sanity checks and set some final parameters in
793# preparation for UNinstalling updates.
794rollback_check_params () {
795	# Check that we are root.  All sorts of things won't work otherwise.
796	if [ `id -u` != 0 ]; then
797		echo "You must be root to run this."
798		exit 1
799	fi
800
801	# Check that we have a working directory
802	_WORKDIR_bad="Directory does not exist or is not writable: "
803	if ! [ -d "${WORKDIR}" -a -w "${WORKDIR}" ]; then
804		echo -n "`basename $0`: "
805		echo -n "${_WORKDIR_bad}"
806		echo ${WORKDIR}
807		exit 1
808	fi
809	cd ${WORKDIR} || exit 1
810
811	# Construct a unique name from ${BASEDIR}
812	BDHASH=`echo ${BASEDIR} | sha256 -q`
813
814	# Check that we have updates ready to rollback
815	if ! [ -L ${BDHASH}-rollback ]; then
816		echo "No rollback directory found."
817		exit 1
818	fi
819	if ! [ -f ${BDHASH}-rollback/INDEX-OLD ] ||
820	    ! [ -f ${BDHASH}-rollback/INDEX-NEW ]; then
821		echo "Update manifest is corrupt -- this should never happen."
822		exit 1
823	fi
824}
825
826# Perform sanity checks and set some final parameters
827# in preparation for comparing the system against the
828# published index.  Figure out which index we should
829# compare against: If the user is running *-p[0-9]+,
830# strip off the last part; if the user is running
831# -SECURITY, call it -RELEASE.  Chdir into the working
832# directory.
833IDS_check_params () {
834	export HTTP_USER_AGENT="freebsd-update (${COMMAND}, `uname -r`)"
835
836	_SERVERNAME_z=\
837"SERVERNAME must be given via command line or configuration file."
838	_KEYPRINT_z="Key must be given via -k option or configuration file."
839	_KEYPRINT_bad="Invalid key fingerprint: "
840	_WORKDIR_bad="Directory does not exist or is not writable: "
841
842	if [ -z "${SERVERNAME}" ]; then
843		echo -n "`basename $0`: "
844		echo "${_SERVERNAME_z}"
845		exit 1
846	fi
847	if [ -z "${KEYPRINT}" ]; then
848		echo -n "`basename $0`: "
849		echo "${_KEYPRINT_z}"
850		exit 1
851	fi
852	if ! echo "${KEYPRINT}" | grep -qE "^[0-9a-f]{64}$"; then
853		echo -n "`basename $0`: "
854		echo -n "${_KEYPRINT_bad}"
855		echo ${KEYPRINT}
856		exit 1
857	fi
858	if ! [ -d "${WORKDIR}" -a -w "${WORKDIR}" ]; then
859		echo -n "`basename $0`: "
860		echo -n "${_WORKDIR_bad}"
861		echo ${WORKDIR}
862		exit 1
863	fi
864	cd ${WORKDIR} || exit 1
865
866	# Generate release number.  The s/SECURITY/RELEASE/ bit exists
867	# to provide an upgrade path for FreeBSD Update 1.x users, since
868	# the kernels provided by FreeBSD Update 1.x are always labelled
869	# as X.Y-SECURITY.
870	RELNUM=`uname -r |
871	    sed -E 's,-p[0-9]+,,' |
872	    sed -E 's,-SECURITY,-RELEASE,'`
873	ARCH=`uname -m`
874	FETCHDIR=${RELNUM}/${ARCH}
875	PATCHDIR=${RELNUM}/${ARCH}/bp
876
877	# Figure out what directory contains the running kernel
878	BOOTFILE=`sysctl -n kern.bootfile`
879	KERNELDIR=${BOOTFILE%/kernel}
880	if ! [ -d ${KERNELDIR} ]; then
881		echo "Cannot identify running kernel"
882		exit 1
883	fi
884
885	# Figure out what kernel configuration is running.  We start with
886	# the output of `uname -i`, and then make the following adjustments:
887	# 1. Replace "SMP-GENERIC" with "SMP".  Why the SMP kernel config
888	# file says "ident SMP-GENERIC", I don't know...
889	# 2. If the kernel claims to be GENERIC _and_ ${ARCH} is "amd64"
890	# _and_ `sysctl kern.version` contains a line which ends "/SMP", then
891	# we're running an SMP kernel.  This mis-identification is a bug
892	# which was fixed in 6.2-STABLE.
893	KERNCONF=`uname -i`
894	if [ ${KERNCONF} = "SMP-GENERIC" ]; then
895		KERNCONF=SMP
896	fi
897	if [ ${KERNCONF} = "GENERIC" ] && [ ${ARCH} = "amd64" ]; then
898		if sysctl kern.version | grep -qE '/SMP$'; then
899			KERNCONF=SMP
900		fi
901	fi
902
903	# Define some paths
904	SHA256=/sbin/sha256
905	PHTTPGET=/usr/libexec/phttpget
906
907	# Set up variables relating to VERBOSELEVEL
908	fetch_setup_verboselevel
909}
910
911#### Core functionality -- the actual work gets done here
912
913# Use an SRV query to pick a server.  If the SRV query doesn't provide
914# a useful answer, use the server name specified by the user.
915# Put another way... look up _http._tcp.${SERVERNAME} and pick a server
916# from that; or if no servers are returned, use ${SERVERNAME}.
917# This allows a user to specify "portsnap.freebsd.org" (in which case
918# portsnap will select one of the mirrors) or "portsnap5.tld.freebsd.org"
919# (in which case portsnap will use that particular server, since there
920# won't be an SRV entry for that name).
921#
922# We ignore the Port field, since we are always going to use port 80.
923
924# Fetch the mirror list, but do not pick a mirror yet.  Returns 1 if
925# no mirrors are available for any reason.
926fetch_pick_server_init () {
927	: > serverlist_tried
928
929# Check that host(1) exists (i.e., that the system wasn't built with the
930# WITHOUT_BIND set) and don't try to find a mirror if it doesn't exist.
931	if ! which -s host; then
932		: > serverlist_full
933		return 1
934	fi
935
936	echo -n "Looking up ${SERVERNAME} mirrors... "
937
938# Issue the SRV query and pull out the Priority, Weight, and Target fields.
939# BIND 9 prints "$name has SRV record ..." while BIND 8 prints
940# "$name server selection ..."; we allow either format.
941	MLIST="_http._tcp.${SERVERNAME}"
942	host -t srv "${MLIST}" |
943	    sed -nE "s/${MLIST} (has SRV record|server selection) //p" |
944	    cut -f 1,2,4 -d ' ' |
945	    sed -e 's/\.$//' |
946	    sort > serverlist_full
947
948# If no records, give up -- we'll just use the server name we were given.
949	if [ `wc -l < serverlist_full` -eq 0 ]; then
950		echo "none found."
951		return 1
952	fi
953
954# Report how many mirrors we found.
955	echo `wc -l < serverlist_full` "mirrors found."
956
957# Generate a random seed for use in picking mirrors.  If HTTP_PROXY
958# is set, this will be used to generate the seed; otherwise, the seed
959# will be random.
960	if [ -n "${HTTP_PROXY}${http_proxy}" ]; then
961		RANDVALUE=`sha256 -qs "${HTTP_PROXY}${http_proxy}" |
962		    tr -d 'a-f' |
963		    cut -c 1-9`
964	else
965		RANDVALUE=`jot -r 1 0 999999999`
966	fi
967}
968
969# Pick a mirror.  Returns 1 if we have run out of mirrors to try.
970fetch_pick_server () {
971# Generate a list of not-yet-tried mirrors
972	sort serverlist_tried |
973	    comm -23 serverlist_full - > serverlist
974
975# Have we run out of mirrors?
976	if [ `wc -l < serverlist` -eq 0 ]; then
977		echo "No mirrors remaining, giving up."
978		return 1
979	fi
980
981# Find the highest priority level (lowest numeric value).
982	SRV_PRIORITY=`cut -f 1 -d ' ' serverlist | sort -n | head -1`
983
984# Add up the weights of the response lines at that priority level.
985	SRV_WSUM=0;
986	while read X; do
987		case "$X" in
988		${SRV_PRIORITY}\ *)
989			SRV_W=`echo $X | cut -f 2 -d ' '`
990			SRV_WSUM=$(($SRV_WSUM + $SRV_W))
991			;;
992		esac
993	done < serverlist
994
995# If all the weights are 0, pretend that they are all 1 instead.
996	if [ ${SRV_WSUM} -eq 0 ]; then
997		SRV_WSUM=`grep -E "^${SRV_PRIORITY} " serverlist | wc -l`
998		SRV_W_ADD=1
999	else
1000		SRV_W_ADD=0
1001	fi
1002
1003# Pick a value between 0 and the sum of the weights - 1
1004	SRV_RND=`expr ${RANDVALUE} % ${SRV_WSUM}`
1005
1006# Read through the list of mirrors and set SERVERNAME.  Write the line
1007# corresponding to the mirror we selected into serverlist_tried so that
1008# we won't try it again.
1009	while read X; do
1010		case "$X" in
1011		${SRV_PRIORITY}\ *)
1012			SRV_W=`echo $X | cut -f 2 -d ' '`
1013			SRV_W=$(($SRV_W + $SRV_W_ADD))
1014			if [ $SRV_RND -lt $SRV_W ]; then
1015				SERVERNAME=`echo $X | cut -f 3 -d ' '`
1016				echo "$X" >> serverlist_tried
1017				break
1018			else
1019				SRV_RND=$(($SRV_RND - $SRV_W))
1020			fi
1021			;;
1022		esac
1023	done < serverlist
1024}
1025
1026# Take a list of ${oldhash}|${newhash} and output a list of needed patches,
1027# i.e., those for which we have ${oldhash} and don't have ${newhash}.
1028fetch_make_patchlist () {
1029	grep -vE "^([0-9a-f]{64})\|\1$" |
1030	    tr '|' ' ' |
1031		while read X Y; do
1032			if [ -f "files/${Y}.gz" ] ||
1033			    [ ! -f "files/${X}.gz" ]; then
1034				continue
1035			fi
1036			echo "${X}|${Y}"
1037		done | uniq
1038}
1039
1040# Print user-friendly progress statistics
1041fetch_progress () {
1042	LNC=0
1043	while read x; do
1044		LNC=$(($LNC + 1))
1045		if [ $(($LNC % 10)) = 0 ]; then
1046			echo -n $LNC
1047		elif [ $(($LNC % 2)) = 0 ]; then
1048			echo -n .
1049		fi
1050	done
1051	echo -n " "
1052}
1053
1054# Function for asking the user if everything is ok
1055continuep () {
1056	while read -p "Does this look reasonable (y/n)? " CONTINUE; do
1057		case "${CONTINUE}" in
1058		y*)
1059			return 0
1060			;;
1061		n*)
1062			return 1
1063			;;
1064		esac
1065	done
1066}
1067
1068# Initialize the working directory
1069workdir_init () {
1070	mkdir -p files
1071	touch tINDEX.present
1072}
1073
1074# Check that we have a public key with an appropriate hash, or
1075# fetch the key if it doesn't exist.  Returns 1 if the key has
1076# not yet been fetched.
1077fetch_key () {
1078	if [ -r pub.ssl ] && [ `${SHA256} -q pub.ssl` = ${KEYPRINT} ]; then
1079		return 0
1080	fi
1081
1082	echo -n "Fetching public key from ${SERVERNAME}... "
1083	rm -f pub.ssl
1084	fetch ${QUIETFLAG} http://${SERVERNAME}/${FETCHDIR}/pub.ssl \
1085	    2>${QUIETREDIR} || true
1086	if ! [ -r pub.ssl ]; then
1087		echo "failed."
1088		return 1
1089	fi
1090	if ! [ `${SHA256} -q pub.ssl` = ${KEYPRINT} ]; then
1091		echo "key has incorrect hash."
1092		rm -f pub.ssl
1093		return 1
1094	fi
1095	echo "done."
1096}
1097
1098# Fetch metadata signature, aka "tag".
1099fetch_tag () {
1100	echo -n "Fetching metadata signature "
1101	echo ${NDEBUG} "for ${RELNUM} from ${SERVERNAME}... "
1102	rm -f latest.ssl
1103	fetch ${QUIETFLAG} http://${SERVERNAME}/${FETCHDIR}/latest.ssl	\
1104	    2>${QUIETREDIR} || true
1105	if ! [ -r latest.ssl ]; then
1106		echo "failed."
1107		return 1
1108	fi
1109
1110	openssl rsautl -pubin -inkey pub.ssl -verify		\
1111	    < latest.ssl > tag.new 2>${QUIETREDIR} || true
1112	rm latest.ssl
1113
1114	if ! [ `wc -l < tag.new` = 1 ] ||
1115	    ! grep -qE	\
1116    "^freebsd-update\|${ARCH}\|${RELNUM}\|[0-9]+\|[0-9a-f]{64}\|[0-9]{10}" \
1117		tag.new; then
1118		echo "invalid signature."
1119		return 1
1120	fi
1121
1122	echo "done."
1123
1124	RELPATCHNUM=`cut -f 4 -d '|' < tag.new`
1125	TINDEXHASH=`cut -f 5 -d '|' < tag.new`
1126	EOLTIME=`cut -f 6 -d '|' < tag.new`
1127}
1128
1129# Sanity-check the patch number in a tag, to make sure that we're not
1130# going to "update" backwards and to prevent replay attacks.
1131fetch_tagsanity () {
1132	# Check that we're not going to move from -pX to -pY with Y < X.
1133	RELPX=`uname -r | sed -E 's,.*-,,'`
1134	if echo ${RELPX} | grep -qE '^p[0-9]+$'; then
1135		RELPX=`echo ${RELPX} | cut -c 2-`
1136	else
1137		RELPX=0
1138	fi
1139	if [ "${RELPATCHNUM}" -lt "${RELPX}" ]; then
1140		echo
1141		echo -n "Files on mirror (${RELNUM}-p${RELPATCHNUM})"
1142		echo " appear older than what"
1143		echo "we are currently running (`uname -r`)!"
1144		echo "Cowardly refusing to proceed any further."
1145		return 1
1146	fi
1147
1148	# If "tag" exists and corresponds to ${RELNUM}, make sure that
1149	# it contains a patch number <= RELPATCHNUM, in order to protect
1150	# against rollback (replay) attacks.
1151	if [ -f tag ] &&
1152	    grep -qE	\
1153    "^freebsd-update\|${ARCH}\|${RELNUM}\|[0-9]+\|[0-9a-f]{64}\|[0-9]{10}" \
1154		tag; then
1155		LASTRELPATCHNUM=`cut -f 4 -d '|' < tag`
1156
1157		if [ "${RELPATCHNUM}" -lt "${LASTRELPATCHNUM}" ]; then
1158			echo
1159			echo -n "Files on mirror (${RELNUM}-p${RELPATCHNUM})"
1160			echo " are older than the"
1161			echo -n "most recently seen updates"
1162			echo " (${RELNUM}-p${LASTRELPATCHNUM})."
1163			echo "Cowardly refusing to proceed any further."
1164			return 1
1165		fi
1166	fi
1167}
1168
1169# Fetch metadata index file
1170fetch_metadata_index () {
1171	echo ${NDEBUG} "Fetching metadata index... "
1172	rm -f ${TINDEXHASH}
1173	fetch ${QUIETFLAG} http://${SERVERNAME}/${FETCHDIR}/t/${TINDEXHASH}
1174	    2>${QUIETREDIR}
1175	if ! [ -f ${TINDEXHASH} ]; then
1176		echo "failed."
1177		return 1
1178	fi
1179	if [ `${SHA256} -q ${TINDEXHASH}` != ${TINDEXHASH} ]; then
1180		echo "update metadata index corrupt."
1181		return 1
1182	fi
1183	echo "done."
1184}
1185
1186# Print an error message about signed metadata being bogus.
1187fetch_metadata_bogus () {
1188	echo
1189	echo "The update metadata$1 is correctly signed, but"
1190	echo "failed an integrity check."
1191	echo "Cowardly refusing to proceed any further."
1192	return 1
1193}
1194
1195# Construct tINDEX.new by merging the lines named in $1 from ${TINDEXHASH}
1196# with the lines not named in $@ from tINDEX.present (if that file exists).
1197fetch_metadata_index_merge () {
1198	for METAFILE in $@; do
1199		if [ `grep -E "^${METAFILE}\|" ${TINDEXHASH} | wc -l`	\
1200		    -ne 1 ]; then
1201			fetch_metadata_bogus " index"
1202			return 1
1203		fi
1204
1205		grep -E "${METAFILE}\|" ${TINDEXHASH}
1206	done |
1207	    sort > tINDEX.wanted
1208
1209	if [ -f tINDEX.present ]; then
1210		join -t '|' -v 2 tINDEX.wanted tINDEX.present |
1211		    sort -m - tINDEX.wanted > tINDEX.new
1212		rm tINDEX.wanted
1213	else
1214		mv tINDEX.wanted tINDEX.new
1215	fi
1216}
1217
1218# Sanity check all the lines of tINDEX.new.  Even if more metadata lines
1219# are added by future versions of the server, this won't cause problems,
1220# since the only lines which appear in tINDEX.new are the ones which we
1221# specifically grepped out of ${TINDEXHASH}.
1222fetch_metadata_index_sanity () {
1223	if grep -qvE '^[0-9A-Z.-]+\|[0-9a-f]{64}$' tINDEX.new; then
1224		fetch_metadata_bogus " index"
1225		return 1
1226	fi
1227}
1228
1229# Sanity check the metadata file $1.
1230fetch_metadata_sanity () {
1231	# Some aliases to save space later: ${P} is a character which can
1232	# appear in a path; ${M} is the four numeric metadata fields; and
1233	# ${H} is a sha256 hash.
1234	P="[-+./:=%@_[~[:alnum:]]"
1235	M="[0-9]+\|[0-9]+\|[0-9]+\|[0-9]+"
1236	H="[0-9a-f]{64}"
1237
1238	# Check that the first four fields make sense.
1239	if gunzip -c < files/$1.gz |
1240	    grep -qvE "^[a-z]+\|[0-9a-z]+\|${P}+\|[fdL-]\|"; then
1241		fetch_metadata_bogus ""
1242		return 1
1243	fi
1244
1245	# Remove the first three fields.
1246	gunzip -c < files/$1.gz |
1247	    cut -f 4- -d '|' > sanitycheck.tmp
1248
1249	# Sanity check entries with type 'f'
1250	if grep -E '^f' sanitycheck.tmp |
1251	    grep -qvE "^f\|${M}\|${H}\|${P}*\$"; then
1252		fetch_metadata_bogus ""
1253		return 1
1254	fi
1255
1256	# Sanity check entries with type 'd'
1257	if grep -E '^d' sanitycheck.tmp |
1258	    grep -qvE "^d\|${M}\|\|\$"; then
1259		fetch_metadata_bogus ""
1260		return 1
1261	fi
1262
1263	# Sanity check entries with type 'L'
1264	if grep -E '^L' sanitycheck.tmp |
1265	    grep -qvE "^L\|${M}\|${P}*\|\$"; then
1266		fetch_metadata_bogus ""
1267		return 1
1268	fi
1269
1270	# Sanity check entries with type '-'
1271	if grep -E '^-' sanitycheck.tmp |
1272	    grep -qvE "^-\|\|\|\|\|\|"; then
1273		fetch_metadata_bogus ""
1274		return 1
1275	fi
1276
1277	# Clean up
1278	rm sanitycheck.tmp
1279}
1280
1281# Fetch the metadata index and metadata files listed in $@,
1282# taking advantage of metadata patches where possible.
1283fetch_metadata () {
1284	fetch_metadata_index || return 1
1285	fetch_metadata_index_merge $@ || return 1
1286	fetch_metadata_index_sanity || return 1
1287
1288	# Generate a list of wanted metadata patches
1289	join -t '|' -o 1.2,2.2 tINDEX.present tINDEX.new |
1290	    fetch_make_patchlist > patchlist
1291
1292	if [ -s patchlist ]; then
1293		# Attempt to fetch metadata patches
1294		echo -n "Fetching `wc -l < patchlist | tr -d ' '` "
1295		echo ${NDEBUG} "metadata patches.${DDSTATS}"
1296		tr '|' '-' < patchlist |
1297		    lam -s "${FETCHDIR}/tp/" - -s ".gz" |
1298		    xargs ${XARGST} ${PHTTPGET} ${SERVERNAME}	\
1299			2>${STATSREDIR} | fetch_progress
1300		echo "done."
1301
1302		# Attempt to apply metadata patches
1303		echo -n "Applying metadata patches... "
1304		tr '|' ' ' < patchlist |
1305		    while read X Y; do
1306			if [ ! -f "${X}-${Y}.gz" ]; then continue; fi
1307			gunzip -c < ${X}-${Y}.gz > diff
1308			gunzip -c < files/${X}.gz > diff-OLD
1309
1310			# Figure out which lines are being added and removed
1311			grep -E '^-' diff |
1312			    cut -c 2- |
1313			    while read PREFIX; do
1314				look "${PREFIX}" diff-OLD
1315			    done |
1316			    sort > diff-rm
1317			grep -E '^\+' diff |
1318			    cut -c 2- > diff-add
1319
1320			# Generate the new file
1321			comm -23 diff-OLD diff-rm |
1322			    sort - diff-add > diff-NEW
1323
1324			if [ `${SHA256} -q diff-NEW` = ${Y} ]; then
1325				mv diff-NEW files/${Y}
1326				gzip -n files/${Y}
1327			else
1328				mv diff-NEW ${Y}.bad
1329			fi
1330			rm -f ${X}-${Y}.gz diff
1331			rm -f diff-OLD diff-NEW diff-add diff-rm
1332		done 2>${QUIETREDIR}
1333		echo "done."
1334	fi
1335
1336	# Update metadata without patches
1337	cut -f 2 -d '|' < tINDEX.new |
1338	    while read Y; do
1339		if [ ! -f "files/${Y}.gz" ]; then
1340			echo ${Y};
1341		fi
1342	    done |
1343	    sort -u > filelist
1344
1345	if [ -s filelist ]; then
1346		echo -n "Fetching `wc -l < filelist | tr -d ' '` "
1347		echo ${NDEBUG} "metadata files... "
1348		lam -s "${FETCHDIR}/m/" - -s ".gz" < filelist |
1349		    xargs ${XARGST} ${PHTTPGET} ${SERVERNAME}	\
1350		    2>${QUIETREDIR}
1351
1352		while read Y; do
1353			if ! [ -f ${Y}.gz ]; then
1354				echo "failed."
1355				return 1
1356			fi
1357			if [ `gunzip -c < ${Y}.gz |
1358			    ${SHA256} -q` = ${Y} ]; then
1359				mv ${Y}.gz files/${Y}.gz
1360			else
1361				echo "metadata is corrupt."
1362				return 1
1363			fi
1364		done < filelist
1365		echo "done."
1366	fi
1367
1368# Sanity-check the metadata files.
1369	cut -f 2 -d '|' tINDEX.new > filelist
1370	while read X; do
1371		fetch_metadata_sanity ${X} || return 1
1372	done < filelist
1373
1374# Remove files which are no longer needed
1375	cut -f 2 -d '|' tINDEX.present |
1376	    sort > oldfiles
1377	cut -f 2 -d '|' tINDEX.new |
1378	    sort |
1379	    comm -13 - oldfiles |
1380	    lam -s "files/" - -s ".gz" |
1381	    xargs rm -f
1382	rm patchlist filelist oldfiles
1383	rm ${TINDEXHASH}
1384
1385# We're done!
1386	mv tINDEX.new tINDEX.present
1387	mv tag.new tag
1388
1389	return 0
1390}
1391
1392# Extract a subset of a downloaded metadata file containing only the parts
1393# which are listed in COMPONENTS.
1394fetch_filter_metadata_components () {
1395	METAHASH=`look "$1|" tINDEX.present | cut -f 2 -d '|'`
1396	gunzip -c < files/${METAHASH}.gz > $1.all
1397
1398	# Fish out the lines belonging to components we care about.
1399	for C in ${COMPONENTS}; do
1400		look "`echo ${C} | tr '/' '|'`|" $1.all
1401	done > $1
1402
1403	# Remove temporary file.
1404	rm $1.all
1405}
1406
1407# Generate a filtered version of the metadata file $1 from the downloaded
1408# file, by fishing out the lines corresponding to components we're trying
1409# to keep updated, and then removing lines corresponding to paths we want
1410# to ignore.
1411fetch_filter_metadata () {
1412	# Fish out the lines belonging to components we care about.
1413	fetch_filter_metadata_components $1
1414
1415	# Canonicalize directory names by removing any trailing / in
1416	# order to avoid listing directories multiple times if they
1417	# belong to multiple components.  Turning "/" into "" doesn't
1418	# matter, since we add a leading "/" when we use paths later.
1419	cut -f 3- -d '|' $1 |
1420	    sed -e 's,/|d|,|d|,' |
1421	    sed -e 's,/|-|,|-|,' |
1422	    sort -u > $1.tmp
1423
1424	# Figure out which lines to ignore and remove them.
1425	for X in ${IGNOREPATHS}; do
1426		grep -E "^${X}" $1.tmp
1427	done |
1428	    sort -u |
1429	    comm -13 - $1.tmp > $1
1430
1431	# Remove temporary files.
1432	rm $1.tmp
1433}
1434
1435# Filter the metadata file $1 by adding lines with "/boot/$2"
1436# replaced by ${KERNELDIR} (which is `sysctl -n kern.bootfile` minus the
1437# trailing "/kernel"); and if "/boot/$2" does not exist, remove
1438# the original lines which start with that.
1439# Put another way: Deal with the fact that the FOO kernel is sometimes
1440# installed in /boot/FOO/ and is sometimes installed elsewhere.
1441fetch_filter_kernel_names () {
1442	grep ^/boot/$2 $1 |
1443	    sed -e "s,/boot/$2,${KERNELDIR},g" |
1444	    sort - $1 > $1.tmp
1445	mv $1.tmp $1
1446
1447	if ! [ -d /boot/$2 ]; then
1448		grep -v ^/boot/$2 $1 > $1.tmp
1449		mv $1.tmp $1
1450	fi
1451}
1452
1453# For all paths appearing in $1 or $3, inspect the system
1454# and generate $2 describing what is currently installed.
1455fetch_inspect_system () {
1456	# No errors yet...
1457	rm -f .err
1458
1459	# Tell the user why his disk is suddenly making lots of noise
1460	echo -n "Inspecting system... "
1461
1462	# Generate list of files to inspect
1463	cat $1 $3 |
1464	    cut -f 1 -d '|' |
1465	    sort -u > filelist
1466
1467	# Examine each file and output lines of the form
1468	# /path/to/file|type|device-inum|user|group|perm|flags|value
1469	# sorted by device and inode number.
1470	while read F; do
1471		# If the symlink/file/directory does not exist, record this.
1472		if ! [ -e ${BASEDIR}/${F} ]; then
1473			echo "${F}|-||||||"
1474			continue
1475		fi
1476		if ! [ -r ${BASEDIR}/${F} ]; then
1477			echo "Cannot read file: ${BASEDIR}/${F}"	\
1478			    >/dev/stderr
1479			touch .err
1480			return 1
1481		fi
1482
1483		# Otherwise, output an index line.
1484		if [ -L ${BASEDIR}/${F} ]; then
1485			echo -n "${F}|L|"
1486			stat -n -f '%d-%i|%u|%g|%Mp%Lp|%Of|' ${BASEDIR}/${F};
1487			readlink ${BASEDIR}/${F};
1488		elif [ -f ${BASEDIR}/${F} ]; then
1489			echo -n "${F}|f|"
1490			stat -n -f '%d-%i|%u|%g|%Mp%Lp|%Of|' ${BASEDIR}/${F};
1491			sha256 -q ${BASEDIR}/${F};
1492		elif [ -d ${BASEDIR}/${F} ]; then
1493			echo -n "${F}|d|"
1494			stat -f '%d-%i|%u|%g|%Mp%Lp|%Of|' ${BASEDIR}/${F};
1495		else
1496			echo "Unknown file type: ${BASEDIR}/${F}"	\
1497			    >/dev/stderr
1498			touch .err
1499			return 1
1500		fi
1501	done < filelist |
1502	    sort -k 3,3 -t '|' > $2.tmp
1503	rm filelist
1504
1505	# Check if an error occurred during system inspection
1506	if [ -f .err ]; then
1507		return 1
1508	fi
1509
1510	# Convert to the form
1511	# /path/to/file|type|user|group|perm|flags|value|hlink
1512	# by resolving identical device and inode numbers into hard links.
1513	cut -f 1,3 -d '|' $2.tmp |
1514	    sort -k 1,1 -t '|' |
1515	    sort -s -u -k 2,2 -t '|' |
1516	    join -1 2 -2 3 -t '|' - $2.tmp |
1517	    awk -F \| -v OFS=\|		\
1518		'{
1519		    if (($2 == $3) || ($4 == "-"))
1520			print $3,$4,$5,$6,$7,$8,$9,""
1521		    else
1522			print $3,$4,$5,$6,$7,$8,$9,$2
1523		}' |
1524	    sort > $2
1525	rm $2.tmp
1526
1527	# We're finished looking around
1528	echo "done."
1529}
1530
1531# For any paths matching ${MERGECHANGES}, compare $1 and $2 and find any
1532# files which differ; generate $3 containing these paths and the old hashes.
1533fetch_filter_mergechanges () {
1534	# Pull out the paths and hashes of the files matching ${MERGECHANGES}.
1535	for F in $1 $2; do
1536		for X in ${MERGECHANGES}; do
1537			grep -E "^${X}" ${F}
1538		done |
1539		    cut -f 1,2,7 -d '|' |
1540		    sort > ${F}-values
1541	done
1542
1543	# Any line in $2-values which doesn't appear in $1-values and is a
1544	# file means that we should list the path in $3.
1545	comm -13 $1-values $2-values |
1546	    fgrep '|f|' |
1547	    cut -f 1 -d '|' > $2-paths
1548
1549	# For each path, pull out one (and only one!) entry from $1-values.
1550	# Note that we cannot distinguish which "old" version the user made
1551	# changes to; but hopefully any changes which occur due to security
1552	# updates will exist in both the "new" version and the version which
1553	# the user has installed, so the merging will still work.
1554	while read X; do
1555		look "${X}|" $1-values |
1556		    head -1
1557	done < $2-paths > $3
1558
1559	# Clean up
1560	rm $1-values $2-values $2-paths
1561}
1562
1563# For any paths matching ${UPDATEIFUNMODIFIED}, remove lines from $[123]
1564# which correspond to lines in $2 with hashes not matching $1 or $3, unless
1565# the paths are listed in $4.  For entries in $2 marked "not present"
1566# (aka. type -), remove lines from $[123] unless there is a corresponding
1567# entry in $1.
1568fetch_filter_unmodified_notpresent () {
1569	# Figure out which lines of $1 and $3 correspond to bits which
1570	# should only be updated if they haven't changed, and fish out
1571	# the (path, type, value) tuples.
1572	# NOTE: We don't consider a file to be "modified" if it matches
1573	# the hash from $3.
1574	for X in ${UPDATEIFUNMODIFIED}; do
1575		grep -E "^${X}" $1
1576		grep -E "^${X}" $3
1577	done |
1578	    cut -f 1,2,7 -d '|' |
1579	    sort > $1-values
1580
1581	# Do the same for $2.
1582	for X in ${UPDATEIFUNMODIFIED}; do
1583		grep -E "^${X}" $2
1584	done |
1585	    cut -f 1,2,7 -d '|' |
1586	    sort > $2-values
1587
1588	# Any entry in $2-values which is not in $1-values corresponds to
1589	# a path which we need to remove from $1, $2, and $3, unless it
1590	# that path appears in $4.
1591	comm -13 $1-values $2-values |
1592	    sort -t '|' -k 1,1 > mlines.tmp
1593	cut -f 1 -d '|' $4 |
1594	    sort |
1595	    join -v 2 -t '|' - mlines.tmp |
1596	    sort > mlines
1597	rm $1-values $2-values mlines.tmp
1598
1599	# Any lines in $2 which are not in $1 AND are "not present" lines
1600	# also belong in mlines.
1601	comm -13 $1 $2 |
1602	    cut -f 1,2,7 -d '|' |
1603	    fgrep '|-|' >> mlines
1604
1605	# Remove lines from $1, $2, and $3
1606	for X in $1 $2 $3; do
1607		sort -t '|' -k 1,1 ${X} > ${X}.tmp
1608		cut -f 1 -d '|' < mlines |
1609		    sort |
1610		    join -v 2 -t '|' - ${X}.tmp |
1611		    sort > ${X}
1612		rm ${X}.tmp
1613	done
1614
1615	# Store a list of the modified files, for future reference
1616	fgrep -v '|-|' mlines |
1617	    cut -f 1 -d '|' > modifiedfiles
1618	rm mlines
1619}
1620
1621# For each entry in $1 of type -, remove any corresponding
1622# entry from $2 if ${ALLOWADD} != "yes".  Remove all entries
1623# of type - from $1.
1624fetch_filter_allowadd () {
1625	cut -f 1,2 -d '|' < $1 |
1626	    fgrep '|-' |
1627	    cut -f 1 -d '|' > filesnotpresent
1628
1629	if [ ${ALLOWADD} != "yes" ]; then
1630		sort < $2 |
1631		    join -v 1 -t '|' - filesnotpresent |
1632		    sort > $2.tmp
1633		mv $2.tmp $2
1634	fi
1635
1636	sort < $1 |
1637	    join -v 1 -t '|' - filesnotpresent |
1638	    sort > $1.tmp
1639	mv $1.tmp $1
1640	rm filesnotpresent
1641}
1642
1643# If ${ALLOWDELETE} != "yes", then remove any entries from $1
1644# which don't correspond to entries in $2.
1645fetch_filter_allowdelete () {
1646	# Produce a lists ${PATH}|${TYPE}
1647	for X in $1 $2; do
1648		cut -f 1-2 -d '|' < ${X} |
1649		    sort -u > ${X}.nodes
1650	done
1651
1652	# Figure out which lines need to be removed from $1.
1653	if [ ${ALLOWDELETE} != "yes" ]; then
1654		comm -23 $1.nodes $2.nodes > $1.badnodes
1655	else
1656		: > $1.badnodes
1657	fi
1658
1659	# Remove the relevant lines from $1
1660	while read X; do
1661		look "${X}|" $1
1662	done < $1.badnodes |
1663	    comm -13 - $1 > $1.tmp
1664	mv $1.tmp $1
1665
1666	rm $1.badnodes $1.nodes $2.nodes
1667}
1668
1669# If ${KEEPMODIFIEDMETADATA} == "yes", then for each entry in $2
1670# with metadata not matching any entry in $1, replace the corresponding
1671# line of $3 with one having the same metadata as the entry in $2.
1672fetch_filter_modified_metadata () {
1673	# Fish out the metadata from $1 and $2
1674	for X in $1 $2; do
1675		cut -f 1-6 -d '|' < ${X} > ${X}.metadata
1676	done
1677
1678	# Find the metadata we need to keep
1679	if [ ${KEEPMODIFIEDMETADATA} = "yes" ]; then
1680		comm -13 $1.metadata $2.metadata > keepmeta
1681	else
1682		: > keepmeta
1683	fi
1684
1685	# Extract the lines which we need to remove from $3, and
1686	# construct the lines which we need to add to $3.
1687	: > $3.remove
1688	: > $3.add
1689	while read LINE; do
1690		NODE=`echo "${LINE}" | cut -f 1-2 -d '|'`
1691		look "${NODE}|" $3 >> $3.remove
1692		look "${NODE}|" $3 |
1693		    cut -f 7- -d '|' |
1694		    lam -s "${LINE}|" - >> $3.add
1695	done < keepmeta
1696
1697	# Remove the specified lines and add the new lines.
1698	sort $3.remove |
1699	    comm -13 - $3 |
1700	    sort -u - $3.add > $3.tmp
1701	mv $3.tmp $3
1702
1703	rm keepmeta $1.metadata $2.metadata $3.add $3.remove
1704}
1705
1706# Remove lines from $1 and $2 which are identical;
1707# no need to update a file if it isn't changing.
1708fetch_filter_uptodate () {
1709	comm -23 $1 $2 > $1.tmp
1710	comm -13 $1 $2 > $2.tmp
1711
1712	mv $1.tmp $1
1713	mv $2.tmp $2
1714}
1715
1716# Fetch any "clean" old versions of files we need for merging changes.
1717fetch_files_premerge () {
1718	# We only need to do anything if $1 is non-empty.
1719	if [ -s $1 ]; then
1720		# Tell the user what we're doing
1721		echo -n "Fetching files from ${OLDRELNUM} for merging... "
1722
1723		# List of files wanted
1724		fgrep '|f|' < $1 |
1725		    cut -f 3 -d '|' |
1726		    sort -u > files.wanted
1727
1728		# Only fetch the files we don't already have
1729		while read Y; do
1730			if [ ! -f "files/${Y}.gz" ]; then
1731				echo ${Y};
1732			fi
1733		done < files.wanted > filelist
1734
1735		# Actually fetch them
1736		lam -s "${OLDFETCHDIR}/f/" - -s ".gz" < filelist |
1737		    xargs ${XARGST} ${PHTTPGET} ${SERVERNAME}	\
1738		    2>${QUIETREDIR}
1739
1740		# Make sure we got them all, and move them into /files/
1741		while read Y; do
1742			if ! [ -f ${Y}.gz ]; then
1743				echo "failed."
1744				return 1
1745			fi
1746			if [ `gunzip -c < ${Y}.gz |
1747			    ${SHA256} -q` = ${Y} ]; then
1748				mv ${Y}.gz files/${Y}.gz
1749			else
1750				echo "${Y} has incorrect hash."
1751				return 1
1752			fi
1753		done < filelist
1754		echo "done."
1755
1756		# Clean up
1757		rm filelist files.wanted
1758	fi
1759}
1760
1761# Prepare to fetch files: Generate a list of the files we need,
1762# copy the unmodified files we have into /files/, and generate
1763# a list of patches to download.
1764fetch_files_prepare () {
1765	# Tell the user why his disk is suddenly making lots of noise
1766	echo -n "Preparing to download files... "
1767
1768	# Reduce indices to ${PATH}|${HASH} pairs
1769	for X in $1 $2 $3; do
1770		cut -f 1,2,7 -d '|' < ${X} |
1771		    fgrep '|f|' |
1772		    cut -f 1,3 -d '|' |
1773		    sort > ${X}.hashes
1774	done
1775
1776	# List of files wanted
1777	cut -f 2 -d '|' < $3.hashes |
1778	    sort -u |
1779	    while read HASH; do
1780		if ! [ -f files/${HASH}.gz ]; then
1781			echo ${HASH}
1782		fi
1783	done > files.wanted
1784
1785	# Generate a list of unmodified files
1786	comm -12 $1.hashes $2.hashes |
1787	    sort -k 1,1 -t '|' > unmodified.files
1788
1789	# Copy all files into /files/.  We only need the unmodified files
1790	# for use in patching; but we'll want all of them if the user asks
1791	# to rollback the updates later.
1792	while read LINE; do
1793		F=`echo "${LINE}" | cut -f 1 -d '|'`
1794		HASH=`echo "${LINE}" | cut -f 2 -d '|'`
1795
1796		# Skip files we already have.
1797		if [ -f files/${HASH}.gz ]; then
1798			continue
1799		fi
1800
1801		# Make sure the file hasn't changed.
1802		cp "${BASEDIR}/${F}" tmpfile
1803		if [ `sha256 -q tmpfile` != ${HASH} ]; then
1804			echo
1805			echo "File changed while FreeBSD Update running: ${F}"
1806			return 1
1807		fi
1808
1809		# Place the file into storage.
1810		gzip -c < tmpfile > files/${HASH}.gz
1811		rm tmpfile
1812	done < $2.hashes
1813
1814	# Produce a list of patches to download
1815	sort -k 1,1 -t '|' $3.hashes |
1816	    join -t '|' -o 2.2,1.2 - unmodified.files |
1817	    fetch_make_patchlist > patchlist
1818
1819	# Garbage collect
1820	rm unmodified.files $1.hashes $2.hashes $3.hashes
1821
1822	# We don't need the list of possible old files any more.
1823	rm $1
1824
1825	# We're finished making noise
1826	echo "done."
1827}
1828
1829# Fetch files.
1830fetch_files () {
1831	# Attempt to fetch patches
1832	if [ -s patchlist ]; then
1833		echo -n "Fetching `wc -l < patchlist | tr -d ' '` "
1834		echo ${NDEBUG} "patches.${DDSTATS}"
1835		tr '|' '-' < patchlist |
1836		    lam -s "${PATCHDIR}/" - |
1837		    xargs ${XARGST} ${PHTTPGET} ${SERVERNAME}	\
1838			2>${STATSREDIR} | fetch_progress
1839		echo "done."
1840
1841		# Attempt to apply patches
1842		echo -n "Applying patches... "
1843		tr '|' ' ' < patchlist |
1844		    while read X Y; do
1845			if [ ! -f "${X}-${Y}" ]; then continue; fi
1846			gunzip -c < files/${X}.gz > OLD
1847
1848			bspatch OLD NEW ${X}-${Y}
1849
1850			if [ `${SHA256} -q NEW` = ${Y} ]; then
1851				mv NEW files/${Y}
1852				gzip -n files/${Y}
1853			fi
1854			rm -f diff OLD NEW ${X}-${Y}
1855		done 2>${QUIETREDIR}
1856		echo "done."
1857	fi
1858
1859	# Download files which couldn't be generate via patching
1860	while read Y; do
1861		if [ ! -f "files/${Y}.gz" ]; then
1862			echo ${Y};
1863		fi
1864	done < files.wanted > filelist
1865
1866	if [ -s filelist ]; then
1867		echo -n "Fetching `wc -l < filelist | tr -d ' '` "
1868		echo ${NDEBUG} "files... "
1869		lam -s "${FETCHDIR}/f/" - -s ".gz" < filelist |
1870		    xargs ${XARGST} ${PHTTPGET} ${SERVERNAME}	\
1871		    2>${QUIETREDIR}
1872
1873		while read Y; do
1874			if ! [ -f ${Y}.gz ]; then
1875				echo "failed."
1876				return 1
1877			fi
1878			if [ `gunzip -c < ${Y}.gz |
1879			    ${SHA256} -q` = ${Y} ]; then
1880				mv ${Y}.gz files/${Y}.gz
1881			else
1882				echo "${Y} has incorrect hash."
1883				return 1
1884			fi
1885		done < filelist
1886		echo "done."
1887	fi
1888
1889	# Clean up
1890	rm files.wanted filelist patchlist
1891}
1892
1893# Create and populate install manifest directory; and report what updates
1894# are available.
1895fetch_create_manifest () {
1896	# If we have an existing install manifest, nuke it.
1897	if [ -L "${BDHASH}-install" ]; then
1898		rm -r ${BDHASH}-install/
1899		rm ${BDHASH}-install
1900	fi
1901
1902	# Report to the user if any updates were avoided due to local changes
1903	if [ -s modifiedfiles ]; then
1904		echo
1905		echo -n "The following files are affected by updates, "
1906		echo "but no changes have"
1907		echo -n "been downloaded because the files have been "
1908		echo "modified locally:"
1909		cat modifiedfiles
1910	fi | $PAGER
1911	rm modifiedfiles
1912
1913	# If no files will be updated, tell the user and exit
1914	if ! [ -s INDEX-PRESENT ] &&
1915	    ! [ -s INDEX-NEW ]; then
1916		rm INDEX-PRESENT INDEX-NEW
1917		echo
1918		echo -n "No updates needed to update system to "
1919		echo "${RELNUM}-p${RELPATCHNUM}."
1920		return
1921	fi
1922
1923	# Divide files into (a) removed files, (b) added files, and
1924	# (c) updated files.
1925	cut -f 1 -d '|' < INDEX-PRESENT |
1926	    sort > INDEX-PRESENT.flist
1927	cut -f 1 -d '|' < INDEX-NEW |
1928	    sort > INDEX-NEW.flist
1929	comm -23 INDEX-PRESENT.flist INDEX-NEW.flist > files.removed
1930	comm -13 INDEX-PRESENT.flist INDEX-NEW.flist > files.added
1931	comm -12 INDEX-PRESENT.flist INDEX-NEW.flist > files.updated
1932	rm INDEX-PRESENT.flist INDEX-NEW.flist
1933
1934	# Report removed files, if any
1935	if [ -s files.removed ]; then
1936		echo
1937		echo -n "The following files will be removed "
1938		echo "as part of updating to ${RELNUM}-p${RELPATCHNUM}:"
1939		cat files.removed
1940	fi | $PAGER
1941	rm files.removed
1942
1943	# Report added files, if any
1944	if [ -s files.added ]; then
1945		echo
1946		echo -n "The following files will be added "
1947		echo "as part of updating to ${RELNUM}-p${RELPATCHNUM}:"
1948		cat files.added
1949	fi | $PAGER
1950	rm files.added
1951
1952	# Report updated files, if any
1953	if [ -s files.updated ]; then
1954		echo
1955		echo -n "The following files will be updated "
1956		echo "as part of updating to ${RELNUM}-p${RELPATCHNUM}:"
1957
1958		cat files.updated
1959	fi | $PAGER
1960	rm files.updated
1961
1962	# Create a directory for the install manifest.
1963	MDIR=`mktemp -d install.XXXXXX` || return 1
1964
1965	# Populate it
1966	mv INDEX-PRESENT ${MDIR}/INDEX-OLD
1967	mv INDEX-NEW ${MDIR}/INDEX-NEW
1968
1969	# Link it into place
1970	ln -s ${MDIR} ${BDHASH}-install
1971}
1972
1973# Warn about any upcoming EoL
1974fetch_warn_eol () {
1975	# What's the current time?
1976	NOWTIME=`date "+%s"`
1977
1978	# When did we last warn about the EoL date?
1979	if [ -f lasteolwarn ]; then
1980		LASTWARN=`cat lasteolwarn`
1981	else
1982		LASTWARN=`expr ${NOWTIME} - 63072000`
1983	fi
1984
1985	# If the EoL time is past, warn.
1986	if [ ${EOLTIME} -lt ${NOWTIME} ]; then
1987		echo
1988		cat <<-EOF
1989		WARNING: `uname -sr` HAS PASSED ITS END-OF-LIFE DATE.
1990		Any security issues discovered after `date -r ${EOLTIME}`
1991		will not have been corrected.
1992		EOF
1993		return 1
1994	fi
1995
1996	# Figure out how long it has been since we last warned about the
1997	# upcoming EoL, and how much longer we have left.
1998	SINCEWARN=`expr ${NOWTIME} - ${LASTWARN}`
1999	TIMELEFT=`expr ${EOLTIME} - ${NOWTIME}`
2000
2001	# Don't warn if the EoL is more than 3 months away
2002	if [ ${TIMELEFT} -gt 7884000 ]; then
2003		return 0
2004	fi
2005
2006	# Don't warn if the time remaining is more than 3 times the time
2007	# since the last warning.
2008	if [ ${TIMELEFT} -gt `expr ${SINCEWARN} \* 3` ]; then
2009		return 0
2010	fi
2011
2012	# Figure out what time units to use.
2013	if [ ${TIMELEFT} -lt 604800 ]; then
2014		UNIT="day"
2015		SIZE=86400
2016	elif [ ${TIMELEFT} -lt 2678400 ]; then
2017		UNIT="week"
2018		SIZE=604800
2019	else
2020		UNIT="month"
2021		SIZE=2678400
2022	fi
2023
2024	# Compute the right number of units
2025	NUM=`expr ${TIMELEFT} / ${SIZE}`
2026	if [ ${NUM} != 1 ]; then
2027		UNIT="${UNIT}s"
2028	fi
2029
2030	# Print the warning
2031	echo
2032	cat <<-EOF
2033		WARNING: `uname -sr` is approaching its End-of-Life date.
2034		It is strongly recommended that you upgrade to a newer
2035		release within the next ${NUM} ${UNIT}.
2036	EOF
2037
2038	# Update the stored time of last warning
2039	echo ${NOWTIME} > lasteolwarn
2040}
2041
2042# Do the actual work involved in "fetch" / "cron".
2043fetch_run () {
2044	workdir_init || return 1
2045
2046	# Prepare the mirror list.
2047	fetch_pick_server_init && fetch_pick_server
2048
2049	# Try to fetch the public key until we run out of servers.
2050	while ! fetch_key; do
2051		fetch_pick_server || return 1
2052	done
2053
2054	# Try to fetch the metadata index signature ("tag") until we run
2055	# out of available servers; and sanity check the downloaded tag.
2056	while ! fetch_tag; do
2057		fetch_pick_server || return 1
2058	done
2059	fetch_tagsanity || return 1
2060
2061	# Fetch the latest INDEX-NEW and INDEX-OLD files.
2062	fetch_metadata INDEX-NEW INDEX-OLD || return 1
2063
2064	# Generate filtered INDEX-NEW and INDEX-OLD files containing only
2065	# the lines which (a) belong to components we care about, and (b)
2066	# don't correspond to paths we're explicitly ignoring.
2067	fetch_filter_metadata INDEX-NEW || return 1
2068	fetch_filter_metadata INDEX-OLD || return 1
2069
2070	# Translate /boot/${KERNCONF} into ${KERNELDIR}
2071	fetch_filter_kernel_names INDEX-NEW ${KERNCONF}
2072	fetch_filter_kernel_names INDEX-OLD ${KERNCONF}
2073
2074	# For all paths appearing in INDEX-OLD or INDEX-NEW, inspect the
2075	# system and generate an INDEX-PRESENT file.
2076	fetch_inspect_system INDEX-OLD INDEX-PRESENT INDEX-NEW || return 1
2077
2078	# Based on ${UPDATEIFUNMODIFIED}, remove lines from INDEX-* which
2079	# correspond to lines in INDEX-PRESENT with hashes not appearing
2080	# in INDEX-OLD or INDEX-NEW.  Also remove lines where the entry in
2081	# INDEX-PRESENT has type - and there isn't a corresponding entry in
2082	# INDEX-OLD with type -.
2083	fetch_filter_unmodified_notpresent	\
2084	    INDEX-OLD INDEX-PRESENT INDEX-NEW /dev/null
2085
2086	# For each entry in INDEX-PRESENT of type -, remove any corresponding
2087	# entry from INDEX-NEW if ${ALLOWADD} != "yes".  Remove all entries
2088	# of type - from INDEX-PRESENT.
2089	fetch_filter_allowadd INDEX-PRESENT INDEX-NEW
2090
2091	# If ${ALLOWDELETE} != "yes", then remove any entries from
2092	# INDEX-PRESENT which don't correspond to entries in INDEX-NEW.
2093	fetch_filter_allowdelete INDEX-PRESENT INDEX-NEW
2094
2095	# If ${KEEPMODIFIEDMETADATA} == "yes", then for each entry in
2096	# INDEX-PRESENT with metadata not matching any entry in INDEX-OLD,
2097	# replace the corresponding line of INDEX-NEW with one having the
2098	# same metadata as the entry in INDEX-PRESENT.
2099	fetch_filter_modified_metadata INDEX-OLD INDEX-PRESENT INDEX-NEW
2100
2101	# Remove lines from INDEX-PRESENT and INDEX-NEW which are identical;
2102	# no need to update a file if it isn't changing.
2103	fetch_filter_uptodate INDEX-PRESENT INDEX-NEW
2104
2105	# Prepare to fetch files: Generate a list of the files we need,
2106	# copy the unmodified files we have into /files/, and generate
2107	# a list of patches to download.
2108	fetch_files_prepare INDEX-OLD INDEX-PRESENT INDEX-NEW || return 1
2109
2110	# Fetch files.
2111	fetch_files || return 1
2112
2113	# Create and populate install manifest directory; and report what
2114	# updates are available.
2115	fetch_create_manifest || return 1
2116
2117	# Warn about any upcoming EoL
2118	fetch_warn_eol || return 1
2119}
2120
2121# If StrictComponents is not "yes", generate a new components list
2122# with only the components which appear to be installed.
2123upgrade_guess_components () {
2124	if [ "${STRICTCOMPONENTS}" = "no" ]; then
2125		# Generate filtered INDEX-ALL with only the components listed
2126		# in COMPONENTS.
2127		fetch_filter_metadata_components $1 || return 1
2128
2129		# Tell the user why his disk is suddenly making lots of noise
2130		echo -n "Inspecting system... "
2131
2132		# Look at the files on disk, and assume that a component is
2133		# supposed to be present if it is more than half-present.
2134		cut -f 1-3 -d '|' < INDEX-ALL |
2135		    tr '|' ' ' |
2136		    while read C S F; do
2137			if [ -e ${BASEDIR}/${F} ]; then
2138				echo "+ ${C}|${S}"
2139			fi
2140			echo "= ${C}|${S}"
2141		    done |
2142		    sort |
2143		    uniq -c |
2144		    sed -E 's,^ +,,' > compfreq
2145		grep ' = ' compfreq |
2146		    cut -f 1,3 -d ' ' |
2147		    sort -k 2,2 -t ' ' > compfreq.total
2148		grep ' + ' compfreq |
2149		    cut -f 1,3 -d ' ' |
2150		    sort -k 2,2 -t ' ' > compfreq.present
2151		join -t ' ' -1 2 -2 2 compfreq.present compfreq.total |
2152		    while read S P T; do
2153			if [ ${P} -gt `expr ${T} / 2` ]; then
2154				echo ${S}
2155			fi
2156		    done > comp.present
2157		cut -f 2 -d ' ' < compfreq.total > comp.total
2158		rm INDEX-ALL compfreq compfreq.total compfreq.present
2159
2160		# We're done making noise.
2161		echo "done."
2162
2163		# Sometimes the kernel isn't installed where INDEX-ALL
2164		# thinks that it should be: In particular, it is often in
2165		# /boot/kernel instead of /boot/GENERIC or /boot/SMP.  To
2166		# deal with this, if "kernel|X" is listed in comp.total
2167		# (i.e., is a component which would be upgraded if it is
2168		# found to be present) we will add it to comp.present.
2169		# If "kernel|<anything>" is in comp.total but "kernel|X" is
2170		# not, we print a warning -- the user is running a kernel
2171		# which isn't part of the release.
2172		KCOMP=`echo ${KERNCONF} | tr 'A-Z' 'a-z'`
2173		grep -E "^kernel\|${KCOMP}\$" comp.total >> comp.present
2174
2175		if grep -qE "^kernel\|" comp.total &&
2176		    ! grep -qE "^kernel\|${KCOMP}\$" comp.total; then
2177			cat <<-EOF
2178
2179WARNING: This system is running a "${KCOMP}" kernel, which is not a
2180kernel configuration distributed as part of FreeBSD ${RELNUM}.
2181This kernel will not be updated: you MUST update the kernel manually
2182before running "$0 install".
2183			EOF
2184		fi
2185
2186		# Re-sort the list of installed components and generate
2187		# the list of non-installed components.
2188		sort -u < comp.present > comp.present.tmp
2189		mv comp.present.tmp comp.present
2190		comm -13 comp.present comp.total > comp.absent
2191
2192		# Ask the user to confirm that what we have is correct.  To
2193		# reduce user confusion, translate "X|Y" back to "X/Y" (as
2194		# subcomponents must be listed in the configuration file).
2195		echo
2196		echo -n "The following components of FreeBSD "
2197		echo "seem to be installed:"
2198		tr '|' '/' < comp.present |
2199		    fmt -72
2200		echo
2201		echo -n "The following components of FreeBSD "
2202		echo "do not seem to be installed:"
2203		tr '|' '/' < comp.absent |
2204		    fmt -72
2205		echo
2206		continuep || return 1
2207		echo
2208
2209		# Suck the generated list of components into ${COMPONENTS}.
2210		# Note that comp.present.tmp is used due to issues with
2211		# pipelines and setting variables.
2212		COMPONENTS=""
2213		tr '|' '/' < comp.present > comp.present.tmp
2214		while read C; do
2215			COMPONENTS="${COMPONENTS} ${C}"
2216		done < comp.present.tmp
2217
2218		# Delete temporary files
2219		rm comp.present comp.present.tmp comp.absent comp.total
2220	fi
2221}
2222
2223# If StrictComponents is not "yes", COMPONENTS contains an entry
2224# corresponding to the currently running kernel, and said kernel
2225# does not exist in the new release, add "kernel/generic" to the
2226# list of components.
2227upgrade_guess_new_kernel () {
2228	if [ "${STRICTCOMPONENTS}" = "no" ]; then
2229		# Grab the unfiltered metadata file.
2230		METAHASH=`look "$1|" tINDEX.present | cut -f 2 -d '|'`
2231		gunzip -c < files/${METAHASH}.gz > $1.all
2232
2233		# If "kernel/${KCOMP}" is in ${COMPONENTS} and that component
2234		# isn't in $1.all, we need to add kernel/generic.
2235		for C in ${COMPONENTS}; do
2236			if [ ${C} = "kernel/${KCOMP}" ] &&
2237			    ! grep -qE "^kernel\|${KCOMP}\|" $1.all; then
2238				COMPONENTS="${COMPONENTS} kernel/generic"
2239				NKERNCONF="GENERIC"
2240				cat <<-EOF
2241
2242WARNING: This system is running a "${KCOMP}" kernel, which is not a
2243kernel configuration distributed as part of FreeBSD ${RELNUM}.
2244As part of upgrading to FreeBSD ${RELNUM}, this kernel will be
2245replaced with a "generic" kernel.
2246				EOF
2247				continuep || return 1
2248			fi
2249		done
2250
2251		# Don't need this any more...
2252		rm $1.all
2253	fi
2254}
2255
2256# Convert INDEX-OLD (last release) and INDEX-ALL (new release) into
2257# INDEX-OLD and INDEX-NEW files (in the sense of normal upgrades).
2258upgrade_oldall_to_oldnew () {
2259	# For each ${F}|... which appears in INDEX-ALL but does not appear
2260	# in INDEX-OLD, add ${F}|-|||||| to INDEX-OLD.
2261	cut -f 1 -d '|' < $1 |
2262	    sort -u > $1.paths
2263	cut -f 1 -d '|' < $2 |
2264	    sort -u |
2265	    comm -13 $1.paths - |
2266	    lam - -s "|-||||||" |
2267	    sort - $1 > $1.tmp
2268	mv $1.tmp $1
2269
2270	# Remove lines from INDEX-OLD which also appear in INDEX-ALL
2271	comm -23 $1 $2 > $1.tmp
2272	mv $1.tmp $1
2273
2274	# Remove lines from INDEX-ALL which have a file name not appearing
2275	# anywhere in INDEX-OLD (since these must be files which haven't
2276	# changed -- if they were new, there would be an entry of type "-").
2277	cut -f 1 -d '|' < $1 |
2278	    sort -u > $1.paths
2279	sort -k 1,1 -t '|' < $2 |
2280	    join -t '|' - $1.paths |
2281	    sort > $2.tmp
2282	rm $1.paths
2283	mv $2.tmp $2
2284
2285	# Rename INDEX-ALL to INDEX-NEW.
2286	mv $2 $3
2287}
2288
2289# Helper for upgrade_merge: Return zero true iff the two files differ only
2290# in the contents of their RCS tags.
2291samef () {
2292	X=`sed -E 's/\\$FreeBSD.*\\$/\$FreeBSD\$/' < $1 | ${SHA256}`
2293	Y=`sed -E 's/\\$FreeBSD.*\\$/\$FreeBSD\$/' < $2 | ${SHA256}`
2294
2295	if [ $X = $Y ]; then
2296		return 0;
2297	else
2298		return 1;
2299	fi
2300}
2301
2302# From the list of "old" files in $1, merge changes in $2 with those in $3,
2303# and update $3 to reflect the hashes of merged files.
2304upgrade_merge () {
2305	# We only need to do anything if $1 is non-empty.
2306	if [ -s $1 ]; then
2307		cut -f 1 -d '|' $1 |
2308		    sort > $1-paths
2309
2310		# Create staging area for merging files
2311		rm -rf merge/
2312		while read F; do
2313			D=`dirname ${F}`
2314			mkdir -p merge/old/${D}
2315			mkdir -p merge/${OLDRELNUM}/${D}
2316			mkdir -p merge/${RELNUM}/${D}
2317			mkdir -p merge/new/${D}
2318		done < $1-paths
2319
2320		# Copy in files
2321		while read F; do
2322			# Currently installed file
2323			V=`look "${F}|" $2 | cut -f 7 -d '|'`
2324			gunzip < files/${V}.gz > merge/old/${F}
2325
2326			# Old release
2327			if look "${F}|" $1 | fgrep -q "|f|"; then
2328				V=`look "${F}|" $1 | cut -f 3 -d '|'`
2329				gunzip < files/${V}.gz		\
2330				    > merge/${OLDRELNUM}/${F}
2331			fi
2332
2333			# New release
2334			if look "${F}|" $3 | cut -f 1,2,7 -d '|' |
2335			    fgrep -q "|f|"; then
2336				V=`look "${F}|" $3 | cut -f 7 -d '|'`
2337				gunzip < files/${V}.gz		\
2338				    > merge/${RELNUM}/${F}
2339			fi
2340		done < $1-paths
2341
2342		# Attempt to automatically merge changes
2343		echo -n "Attempting to automatically merge "
2344		echo -n "changes in files..."
2345		: > failed.merges
2346		while read F; do
2347			# If the file doesn't exist in the new release,
2348			# the result of "merging changes" is having the file
2349			# not exist.
2350			if ! [ -f merge/${RELNUM}/${F} ]; then
2351				continue
2352			fi
2353
2354			# If the file didn't exist in the old release, we're
2355			# going to throw away the existing file and hope that
2356			# the version from the new release is what we want.
2357			if ! [ -f merge/${OLDRELNUM}/${F} ]; then
2358				cp merge/${RELNUM}/${F} merge/new/${F}
2359				continue
2360			fi
2361
2362			# Some files need special treatment.
2363			case ${F} in
2364			/etc/spwd.db | /etc/pwd.db | /etc/login.conf.db)
2365				# Don't merge these -- we're rebuild them
2366				# after updates are installed.
2367				cp merge/old/${F} merge/new/${F}
2368				;;
2369			*)
2370				if ! merge -p -L "current version"	\
2371				    -L "${OLDRELNUM}" -L "${RELNUM}"	\
2372				    merge/old/${F}			\
2373				    merge/${OLDRELNUM}/${F}		\
2374				    merge/${RELNUM}/${F}		\
2375				    > merge/new/${F} 2>/dev/null; then
2376					echo ${F} >> failed.merges
2377				fi
2378				;;
2379			esac
2380		done < $1-paths
2381		echo " done."
2382
2383		# Ask the user to handle any files which didn't merge.
2384		while read F; do
2385			# If the installed file differs from the version in
2386			# the old release only due to RCS tag expansion
2387			# then just use the version in the new release.
2388			if samef merge/old/${F} merge/${OLDRELNUM}/${F}; then
2389				cp merge/${RELNUM}/${F} merge/new/${F}
2390				continue
2391			fi
2392
2393			cat <<-EOF
2394
2395The following file could not be merged automatically: ${F}
2396Press Enter to edit this file in ${EDITOR} and resolve the conflicts
2397manually...
2398			EOF
2399			read dummy </dev/tty
2400			${EDITOR} `pwd`/merge/new/${F} < /dev/tty
2401		done < failed.merges
2402		rm failed.merges
2403
2404		# Ask the user to confirm that he likes how the result
2405		# of merging files.
2406		while read F; do
2407			# Skip files which haven't changed except possibly
2408			# in their RCS tags.
2409			if [ -f merge/old/${F} ] && [ -f merge/new/${F} ] &&
2410			    samef merge/old/${F} merge/new/${F}; then
2411				continue
2412			fi
2413
2414			# Skip files where the installed file differs from
2415			# the old file only due to RCS tags.
2416			if [ -f merge/old/${F} ] &&
2417			    [ -f merge/${OLDRELNUM}/${F} ] &&
2418			    samef merge/old/${F} merge/${OLDRELNUM}/${F}; then
2419				continue
2420			fi
2421
2422			# Warn about files which are ceasing to exist.
2423			if ! [ -f merge/new/${F} ]; then
2424				cat <<-EOF
2425
2426The following file will be removed, as it no longer exists in
2427FreeBSD ${RELNUM}: ${F}
2428				EOF
2429				continuep < /dev/tty || return 1
2430				continue
2431			fi
2432
2433			# Print changes for the user's approval.
2434			cat <<-EOF
2435
2436The following changes, which occurred between FreeBSD ${OLDRELNUM} and
2437FreeBSD ${RELNUM} have been merged into ${F}:
2438EOF
2439			diff -U 5 -L "current version" -L "new version"	\
2440			    merge/old/${F} merge/new/${F} || true
2441			continuep < /dev/tty || return 1
2442		done < $1-paths
2443
2444		# Store merged files.
2445		while read F; do
2446			if [ -f merge/new/${F} ]; then
2447				V=`${SHA256} -q merge/new/${F}`
2448
2449				gzip -c < merge/new/${F} > files/${V}.gz
2450				echo "${F}|${V}"
2451			fi
2452		done < $1-paths > newhashes
2453
2454		# Pull lines out from $3 which need to be updated to
2455		# reflect merged files.
2456		while read F; do
2457			look "${F}|" $3
2458		done < $1-paths > $3-oldlines
2459
2460		# Update lines to reflect merged files
2461		join -t '|' -o 1.1,1.2,1.3,1.4,1.5,1.6,2.2,1.8		\
2462		    $3-oldlines newhashes > $3-newlines
2463
2464		# Remove old lines from $3 and add new lines.
2465		sort $3-oldlines |
2466		    comm -13 - $3 |
2467		    sort - $3-newlines > $3.tmp
2468		mv $3.tmp $3
2469
2470		# Clean up
2471		rm $1-paths newhashes $3-oldlines $3-newlines
2472		rm -rf merge/
2473	fi
2474
2475	# We're done with merging files.
2476	rm $1
2477}
2478
2479# Do the work involved in fetching upgrades to a new release
2480upgrade_run () {
2481	workdir_init || return 1
2482
2483	# Prepare the mirror list.
2484	fetch_pick_server_init && fetch_pick_server
2485
2486	# Try to fetch the public key until we run out of servers.
2487	while ! fetch_key; do
2488		fetch_pick_server || return 1
2489	done
2490 
2491	# Try to fetch the metadata index signature ("tag") until we run
2492	# out of available servers; and sanity check the downloaded tag.
2493	while ! fetch_tag; do
2494		fetch_pick_server || return 1
2495	done
2496	fetch_tagsanity || return 1
2497
2498	# Fetch the INDEX-OLD and INDEX-ALL.
2499	fetch_metadata INDEX-OLD INDEX-ALL || return 1
2500
2501	# If StrictComponents is not "yes", generate a new components list
2502	# with only the components which appear to be installed.
2503	upgrade_guess_components INDEX-ALL || return 1
2504
2505	# Generate filtered INDEX-OLD and INDEX-ALL files containing only
2506	# the components we want and without anything marked as "Ignore".
2507	fetch_filter_metadata INDEX-OLD || return 1
2508	fetch_filter_metadata INDEX-ALL || return 1
2509
2510	# Merge the INDEX-OLD and INDEX-ALL files into INDEX-OLD.
2511	sort INDEX-OLD INDEX-ALL > INDEX-OLD.tmp
2512	mv INDEX-OLD.tmp INDEX-OLD
2513	rm INDEX-ALL
2514
2515	# Adjust variables for fetching files from the new release.
2516	OLDRELNUM=${RELNUM}
2517	RELNUM=${TARGETRELEASE}
2518	OLDFETCHDIR=${FETCHDIR}
2519	FETCHDIR=${RELNUM}/${ARCH}
2520
2521	# Try to fetch the NEW metadata index signature ("tag") until we run
2522	# out of available servers; and sanity check the downloaded tag.
2523	while ! fetch_tag; do
2524		fetch_pick_server || return 1
2525	done
2526
2527	# Fetch the new INDEX-ALL.
2528	fetch_metadata INDEX-ALL || return 1
2529
2530	# If StrictComponents is not "yes", COMPONENTS contains an entry
2531	# corresponding to the currently running kernel, and said kernel
2532	# does not exist in the new release, add "kernel/generic" to the
2533	# list of components.
2534	upgrade_guess_new_kernel INDEX-ALL || return 1
2535
2536	# Filter INDEX-ALL to contain only the components we want and without
2537	# anything marked as "Ignore".
2538	fetch_filter_metadata INDEX-ALL || return 1
2539
2540	# Convert INDEX-OLD (last release) and INDEX-ALL (new release) into
2541	# INDEX-OLD and INDEX-NEW files (in the sense of normal upgrades).
2542	upgrade_oldall_to_oldnew INDEX-OLD INDEX-ALL INDEX-NEW
2543
2544	# Translate /boot/${KERNCONF} or /boot/${NKERNCONF} into ${KERNELDIR}
2545	fetch_filter_kernel_names INDEX-NEW ${NKERNCONF}
2546	fetch_filter_kernel_names INDEX-OLD ${KERNCONF}
2547
2548	# For all paths appearing in INDEX-OLD or INDEX-NEW, inspect the
2549	# system and generate an INDEX-PRESENT file.
2550	fetch_inspect_system INDEX-OLD INDEX-PRESENT INDEX-NEW || return 1
2551
2552	# Based on ${MERGECHANGES}, generate a file tomerge-old with the
2553	# paths and hashes of old versions of files to merge.
2554	fetch_filter_mergechanges INDEX-OLD INDEX-PRESENT tomerge-old
2555
2556	# Based on ${UPDATEIFUNMODIFIED}, remove lines from INDEX-* which
2557	# correspond to lines in INDEX-PRESENT with hashes not appearing
2558	# in INDEX-OLD or INDEX-NEW.  Also remove lines where the entry in
2559	# INDEX-PRESENT has type - and there isn't a corresponding entry in
2560	# INDEX-OLD with type -.
2561	fetch_filter_unmodified_notpresent	\
2562	    INDEX-OLD INDEX-PRESENT INDEX-NEW tomerge-old
2563
2564	# For each entry in INDEX-PRESENT of type -, remove any corresponding
2565	# entry from INDEX-NEW if ${ALLOWADD} != "yes".  Remove all entries
2566	# of type - from INDEX-PRESENT.
2567	fetch_filter_allowadd INDEX-PRESENT INDEX-NEW
2568
2569	# If ${ALLOWDELETE} != "yes", then remove any entries from
2570	# INDEX-PRESENT which don't correspond to entries in INDEX-NEW.
2571	fetch_filter_allowdelete INDEX-PRESENT INDEX-NEW
2572
2573	# If ${KEEPMODIFIEDMETADATA} == "yes", then for each entry in
2574	# INDEX-PRESENT with metadata not matching any entry in INDEX-OLD,
2575	# replace the corresponding line of INDEX-NEW with one having the
2576	# same metadata as the entry in INDEX-PRESENT.
2577	fetch_filter_modified_metadata INDEX-OLD INDEX-PRESENT INDEX-NEW
2578
2579	# Remove lines from INDEX-PRESENT and INDEX-NEW which are identical;
2580	# no need to update a file if it isn't changing.
2581	fetch_filter_uptodate INDEX-PRESENT INDEX-NEW
2582
2583	# Fetch "clean" files from the old release for merging changes.
2584	fetch_files_premerge tomerge-old
2585
2586	# Prepare to fetch files: Generate a list of the files we need,
2587	# copy the unmodified files we have into /files/, and generate
2588	# a list of patches to download.
2589	fetch_files_prepare INDEX-OLD INDEX-PRESENT INDEX-NEW || return 1
2590
2591	# Fetch patches from to-${RELNUM}/${ARCH}/bp/
2592	PATCHDIR=to-${RELNUM}/${ARCH}/bp
2593	fetch_files || return 1
2594
2595	# Merge configuration file changes.
2596	upgrade_merge tomerge-old INDEX-PRESENT INDEX-NEW || return 1
2597
2598	# Create and populate install manifest directory; and report what
2599	# updates are available.
2600	fetch_create_manifest || return 1
2601
2602	# Leave a note behind to tell the "install" command that the kernel
2603	# needs to be installed before the world.
2604	touch ${BDHASH}-install/kernelfirst
2605
2606	# Remind the user that they need to run "freebsd-update install"
2607	# to install the downloaded bits, in case they didn't RTFM.
2608	echo "To install the downloaded upgrades, run \"$0 install\"."
2609}
2610
2611# Make sure that all the file hashes mentioned in $@ have corresponding
2612# gzipped files stored in /files/.
2613install_verify () {
2614	# Generate a list of hashes
2615	cat $@ |
2616	    cut -f 2,7 -d '|' |
2617	    grep -E '^f' |
2618	    cut -f 2 -d '|' |
2619	    sort -u > filelist
2620
2621	# Make sure all the hashes exist
2622	while read HASH; do
2623		if ! [ -f files/${HASH}.gz ]; then
2624			echo -n "Update files missing -- "
2625			echo "this should never happen."
2626			echo "Re-run '$0 fetch'."
2627			return 1
2628		fi
2629	done < filelist
2630
2631	# Clean up
2632	rm filelist
2633}
2634
2635# Remove the system immutable flag from files
2636install_unschg () {
2637	# Generate file list
2638	cat $@ |
2639	    cut -f 1 -d '|' > filelist
2640
2641	# Remove flags
2642	while read F; do
2643		if ! [ -e ${BASEDIR}/${F} ]; then
2644			continue
2645		else
2646			echo ${BASEDIR}/${F}
2647		fi
2648	done < filelist | xargs chflags noschg || return 1
2649
2650	# Clean up
2651	rm filelist
2652}
2653
2654# Decide which directory name to use for kernel backups.
2655backup_kernel_finddir () {
2656	CNT=0
2657	while true ; do
2658		# Pathname does not exist, so it is OK use that name
2659		# for backup directory.
2660		if [ ! -e $BASEDIR/$BACKUPKERNELDIR ]; then
2661			return 0
2662		fi
2663
2664		# If directory do exist, we only use if it has our
2665		# marker file.
2666		if [ -d $BASEDIR/$BACKUPKERNELDIR -a \
2667			-e $BASEDIR/$BACKUPKERNELDIR/.freebsd-update ]; then
2668			return 0
2669		fi
2670
2671		# We could not use current directory name, so add counter to
2672		# the end and try again.
2673		CNT=$((CNT + 1))
2674		if [ $CNT -gt 9 ]; then
2675			echo "Could not find valid backup dir ($BASEDIR/$BACKUPKERNELDIR)"
2676			exit 1
2677		fi
2678		BACKUPKERNELDIR="`echo $BACKUPKERNELDIR | sed -Ee 's/[0-9]\$//'`"
2679		BACKUPKERNELDIR="${BACKUPKERNELDIR}${CNT}"
2680	done
2681}
2682
2683# Backup the current kernel using hardlinks, if not disabled by user.
2684# Since we delete all files in the directory used for previous backups
2685# we create a marker file called ".freebsd-update" in the directory so
2686# we can determine on the next run that the directory was created by
2687# freebsd-update and we then do not accidentally remove user files in
2688# the unlikely case that the user has created a directory with a
2689# conflicting name.
2690backup_kernel () {
2691	# Only make kernel backup is so configured.
2692	if [ $BACKUPKERNEL != yes ]; then
2693		return 0
2694	fi
2695
2696	# Decide which directory name to use for kernel backups.
2697	backup_kernel_finddir
2698
2699	# Remove old kernel backup files.  If $BACKUPKERNELDIR was
2700	# "not ours", backup_kernel_finddir would have exited, so
2701	# deleting the directory content is as safe as we can make it.
2702	if [ -d $BASEDIR/$BACKUPKERNELDIR ]; then
2703		rm -fr $BASEDIR/$BACKUPKERNELDIR
2704	fi
2705
2706	# Create directories for backup.
2707	mkdir -p $BASEDIR/$BACKUPKERNELDIR
2708	mtree -cdn -p "${BASEDIR}/${KERNELDIR}" | \
2709	    mtree -Ue -p "${BASEDIR}/${BACKUPKERNELDIR}" > /dev/null
2710
2711	# Mark the directory as having been created by freebsd-update.
2712	touch $BASEDIR/$BACKUPKERNELDIR/.freebsd-update
2713	if [ $? -ne 0 ]; then
2714		echo "Could not create kernel backup directory"
2715		exit 1
2716	fi
2717
2718	# Disable pathname expansion to be sure *.symbols is not
2719	# expanded.
2720	set -f
2721
2722	# Use find to ignore symbol files, unless disabled by user.
2723	if [ $BACKUPKERNELSYMBOLFILES = yes ]; then
2724		FINDFILTER=""
2725	else
2726		FINDFILTER=-"a ! -name *.symbols"
2727	fi
2728
2729	# Backup all the kernel files using hardlinks.
2730	(cd ${BASEDIR}/${KERNELDIR} && find . -type f $FINDFILTER -exec \
2731	    cp -pl '{}' ${BASEDIR}/${BACKUPKERNELDIR}/'{}' \;)
2732
2733	# Re-enable patchname expansion.
2734	set +f
2735}
2736
2737# Install new files
2738install_from_index () {
2739	# First pass: Do everything apart from setting file flags.  We
2740	# can't set flags yet, because schg inhibits hard linking.
2741	sort -k 1,1 -t '|' $1 |
2742	    tr '|' ' ' |
2743	    while read FPATH TYPE OWNER GROUP PERM FLAGS HASH LINK; do
2744		case ${TYPE} in
2745		d)
2746			# Create a directory
2747			install -d -o ${OWNER} -g ${GROUP}		\
2748			    -m ${PERM} ${BASEDIR}/${FPATH}
2749			;;
2750		f)
2751			if [ -z "${LINK}" ]; then
2752				# Create a file, without setting flags.
2753				gunzip < files/${HASH}.gz > ${HASH}
2754				install -S -o ${OWNER} -g ${GROUP}	\
2755				    -m ${PERM} ${HASH} ${BASEDIR}/${FPATH}
2756				rm ${HASH}
2757			else
2758				# Create a hard link.
2759				ln -f ${BASEDIR}/${LINK} ${BASEDIR}/${FPATH}
2760			fi
2761			;;
2762		L)
2763			# Create a symlink
2764			ln -sfh ${HASH} ${BASEDIR}/${FPATH}
2765			;;
2766		esac
2767	    done
2768
2769	# Perform a second pass, adding file flags.
2770	tr '|' ' ' < $1 |
2771	    while read FPATH TYPE OWNER GROUP PERM FLAGS HASH LINK; do
2772		if [ ${TYPE} = "f" ] &&
2773		    ! [ ${FLAGS} = "0" ]; then
2774			chflags ${FLAGS} ${BASEDIR}/${FPATH}
2775		fi
2776	    done
2777}
2778
2779# Remove files which we want to delete
2780install_delete () {
2781	# Generate list of new files
2782	cut -f 1 -d '|' < $2 |
2783	    sort > newfiles
2784
2785	# Generate subindex of old files we want to nuke
2786	sort -k 1,1 -t '|' $1 |
2787	    join -t '|' -v 1 - newfiles |
2788	    sort -r -k 1,1 -t '|' |
2789	    cut -f 1,2 -d '|' |
2790	    tr '|' ' ' > killfiles
2791
2792	# Remove the offending bits
2793	while read FPATH TYPE; do
2794		case ${TYPE} in
2795		d)
2796			rmdir ${BASEDIR}/${FPATH}
2797			;;
2798		f)
2799			rm ${BASEDIR}/${FPATH}
2800			;;
2801		L)
2802			rm ${BASEDIR}/${FPATH}
2803			;;
2804		esac
2805	done < killfiles
2806
2807	# Clean up
2808	rm newfiles killfiles
2809}
2810
2811# Install new files, delete old files, and update linker.hints
2812install_files () {
2813	# If we haven't already dealt with the kernel, deal with it.
2814	if ! [ -f $1/kerneldone ]; then
2815		grep -E '^/boot/' $1/INDEX-OLD > INDEX-OLD
2816		grep -E '^/boot/' $1/INDEX-NEW > INDEX-NEW
2817
2818		# Backup current kernel before installing a new one
2819		backup_kernel || return 1
2820
2821		# Install new files
2822		install_from_index INDEX-NEW || return 1
2823
2824		# Remove files which need to be deleted
2825		install_delete INDEX-OLD INDEX-NEW || return 1
2826
2827		# Update linker.hints if necessary
2828		if [ -s INDEX-OLD -o -s INDEX-NEW ]; then
2829			kldxref -R ${BASEDIR}/boot/ 2>/dev/null
2830		fi
2831
2832		# We've finished updating the kernel.
2833		touch $1/kerneldone
2834
2835		# Do we need to ask for a reboot now?
2836		if [ -f $1/kernelfirst ] &&
2837		    [ -s INDEX-OLD -o -s INDEX-NEW ]; then
2838			cat <<-EOF
2839
2840Kernel updates have been installed.  Please reboot and run
2841"$0 install" again to finish installing updates.
2842			EOF
2843			exit 0
2844		fi
2845	fi
2846
2847	# If we haven't already dealt with the world, deal with it.
2848	if ! [ -f $1/worlddone ]; then
2849		# Create any necessary directories first
2850		grep -vE '^/boot/' $1/INDEX-NEW |
2851		    grep -E '^[^|]+\|d\|' > INDEX-NEW
2852		install_from_index INDEX-NEW || return 1
2853
2854		# Install new runtime linker
2855		grep -vE '^/boot/' $1/INDEX-NEW |
2856		    grep -vE '^[^|]+\|d\|' |
2857		    grep -E '^/libexec/ld-elf[^|]*\.so\.[0-9]+\|' > INDEX-NEW
2858		install_from_index INDEX-NEW || return 1
2859
2860		# Install new shared libraries next
2861		grep -vE '^/boot/' $1/INDEX-NEW |
2862		    grep -vE '^[^|]+\|d\|' |
2863		    grep -vE '^/libexec/ld-elf[^|]*\.so\.[0-9]+\|' |
2864		    grep -E '^[^|]*/lib/[^|]*\.so\.[0-9]+\|' > INDEX-NEW
2865		install_from_index INDEX-NEW || return 1
2866
2867		# Deal with everything else
2868		grep -vE '^/boot/' $1/INDEX-OLD |
2869		    grep -vE '^[^|]+\|d\|' |
2870		    grep -vE '^/libexec/ld-elf[^|]*\.so\.[0-9]+\|' |
2871		    grep -vE '^[^|]*/lib/[^|]*\.so\.[0-9]+\|' > INDEX-OLD
2872		grep -vE '^/boot/' $1/INDEX-NEW |
2873		    grep -vE '^[^|]+\|d\|' |
2874		    grep -vE '^/libexec/ld-elf[^|]*\.so\.[0-9]+\|' |
2875		    grep -vE '^[^|]*/lib/[^|]*\.so\.[0-9]+\|' > INDEX-NEW
2876		install_from_index INDEX-NEW || return 1
2877		install_delete INDEX-OLD INDEX-NEW || return 1
2878
2879		# Rebuild /etc/spwd.db and /etc/pwd.db if necessary.
2880		if [ ${BASEDIR}/etc/master.passwd -nt ${BASEDIR}/etc/spwd.db ] ||
2881		    [ ${BASEDIR}/etc/master.passwd -nt ${BASEDIR}/etc/pwd.db ]; then
2882			pwd_mkdb -d ${BASEDIR}/etc ${BASEDIR}/etc/master.passwd
2883		fi
2884
2885		# Rebuild /etc/login.conf.db if necessary.
2886		if [ ${BASEDIR}/etc/login.conf -nt ${BASEDIR}/etc/login.conf.db ]; then
2887			cap_mkdb ${BASEDIR}/etc/login.conf
2888		fi
2889
2890		# We've finished installing the world and deleting old files
2891		# which are not shared libraries.
2892		touch $1/worlddone
2893
2894		# Do we need to ask the user to portupgrade now?
2895		grep -vE '^/boot/' $1/INDEX-NEW |
2896		    grep -E '^[^|]*/lib/[^|]*\.so\.[0-9]+\|' |
2897		    cut -f 1 -d '|' |
2898		    sort > newfiles
2899		if grep -vE '^/boot/' $1/INDEX-OLD |
2900		    grep -E '^[^|]*/lib/[^|]*\.so\.[0-9]+\|' |
2901		    cut -f 1 -d '|' |
2902		    sort |
2903		    join -v 1 - newfiles |
2904		    grep -q .; then
2905			cat <<-EOF
2906
2907Completing this upgrade requires removing old shared object files.
2908Please rebuild all installed 3rd party software (e.g., programs
2909installed from the ports tree) and then run "$0 install"
2910again to finish installing updates.
2911			EOF
2912			rm newfiles
2913			exit 0
2914		fi
2915		rm newfiles
2916	fi
2917
2918	# Remove old shared libraries
2919	grep -vE '^/boot/' $1/INDEX-NEW |
2920	    grep -vE '^[^|]+\|d\|' |
2921	    grep -E '^[^|]*/lib/[^|]*\.so\.[0-9]+\|' > INDEX-NEW
2922	grep -vE '^/boot/' $1/INDEX-OLD |
2923	    grep -vE '^[^|]+\|d\|' |
2924	    grep -E '^[^|]*/lib/[^|]*\.so\.[0-9]+\|' > INDEX-OLD
2925	install_delete INDEX-OLD INDEX-NEW || return 1
2926
2927	# Remove old directories
2928	grep -vE '^/boot/' $1/INDEX-NEW |
2929	    grep -E '^[^|]+\|d\|' > INDEX-NEW
2930	grep -vE '^/boot/' $1/INDEX-OLD |
2931	    grep -E '^[^|]+\|d\|' > INDEX-OLD
2932	install_delete INDEX-OLD INDEX-NEW || return 1
2933
2934	# Remove temporary files
2935	rm INDEX-OLD INDEX-NEW
2936}
2937
2938# Rearrange bits to allow the installed updates to be rolled back
2939install_setup_rollback () {
2940	# Remove the "reboot after installing kernel", "kernel updated", and
2941	# "finished installing the world" flags if present -- they are
2942	# irrelevant when rolling back updates.
2943	if [ -f ${BDHASH}-install/kernelfirst ]; then
2944		rm ${BDHASH}-install/kernelfirst
2945		rm ${BDHASH}-install/kerneldone
2946	fi
2947	if [ -f ${BDHASH}-install/worlddone ]; then
2948		rm ${BDHASH}-install/worlddone
2949	fi
2950
2951	if [ -L ${BDHASH}-rollback ]; then
2952		mv ${BDHASH}-rollback ${BDHASH}-install/rollback
2953	fi
2954
2955	mv ${BDHASH}-install ${BDHASH}-rollback
2956}
2957
2958# Actually install updates
2959install_run () {
2960	echo -n "Installing updates..."
2961
2962	# Make sure we have all the files we should have
2963	install_verify ${BDHASH}-install/INDEX-OLD	\
2964	    ${BDHASH}-install/INDEX-NEW || return 1
2965
2966	# Remove system immutable flag from files
2967	install_unschg ${BDHASH}-install/INDEX-OLD	\
2968	    ${BDHASH}-install/INDEX-NEW || return 1
2969
2970	# Install new files, delete old files, and update linker.hints
2971	install_files ${BDHASH}-install || return 1
2972
2973	# Rearrange bits to allow the installed updates to be rolled back
2974	install_setup_rollback
2975
2976	echo " done."
2977}
2978
2979# Rearrange bits to allow the previous set of updates to be rolled back next.
2980rollback_setup_rollback () {
2981	if [ -L ${BDHASH}-rollback/rollback ]; then
2982		mv ${BDHASH}-rollback/rollback rollback-tmp
2983		rm -r ${BDHASH}-rollback/
2984		rm ${BDHASH}-rollback
2985		mv rollback-tmp ${BDHASH}-rollback
2986	else
2987		rm -r ${BDHASH}-rollback/
2988		rm ${BDHASH}-rollback
2989	fi
2990}
2991
2992# Install old files, delete new files, and update linker.hints
2993rollback_files () {
2994	# Install old shared library files which don't have the same path as
2995	# a new shared library file.
2996	grep -vE '^/boot/' $1/INDEX-NEW |
2997	    grep -E '/lib/.*\.so\.[0-9]+\|' |
2998	    cut -f 1 -d '|' |
2999	    sort > INDEX-NEW.libs.flist
3000	grep -vE '^/boot/' $1/INDEX-OLD |
3001	    grep -E '/lib/.*\.so\.[0-9]+\|' |
3002	    sort -k 1,1 -t '|' - |
3003	    join -t '|' -v 1 - INDEX-NEW.libs.flist > INDEX-OLD
3004	install_from_index INDEX-OLD || return 1
3005
3006	# Deal with files which are neither kernel nor shared library
3007	grep -vE '^/boot/' $1/INDEX-OLD |
3008	    grep -vE '/lib/.*\.so\.[0-9]+\|' > INDEX-OLD
3009	grep -vE '^/boot/' $1/INDEX-NEW |
3010	    grep -vE '/lib/.*\.so\.[0-9]+\|' > INDEX-NEW
3011	install_from_index INDEX-OLD || return 1
3012	install_delete INDEX-NEW INDEX-OLD || return 1
3013
3014	# Install any old shared library files which we didn't install above.
3015	grep -vE '^/boot/' $1/INDEX-OLD |
3016	    grep -E '/lib/.*\.so\.[0-9]+\|' |
3017	    sort -k 1,1 -t '|' - |
3018	    join -t '|' - INDEX-NEW.libs.flist > INDEX-OLD
3019	install_from_index INDEX-OLD || return 1
3020
3021	# Delete unneeded shared library files
3022	grep -vE '^/boot/' $1/INDEX-OLD |
3023	    grep -E '/lib/.*\.so\.[0-9]+\|' > INDEX-OLD
3024	grep -vE '^/boot/' $1/INDEX-NEW |
3025	    grep -E '/lib/.*\.so\.[0-9]+\|' > INDEX-NEW
3026	install_delete INDEX-NEW INDEX-OLD || return 1
3027
3028	# Deal with kernel files
3029	grep -E '^/boot/' $1/INDEX-OLD > INDEX-OLD
3030	grep -E '^/boot/' $1/INDEX-NEW > INDEX-NEW
3031	install_from_index INDEX-OLD || return 1
3032	install_delete INDEX-NEW INDEX-OLD || return 1
3033	if [ -s INDEX-OLD -o -s INDEX-NEW ]; then
3034		kldxref -R /boot/ 2>/dev/null
3035	fi
3036
3037	# Remove temporary files
3038	rm INDEX-OLD INDEX-NEW INDEX-NEW.libs.flist
3039}
3040
3041# Actually rollback updates
3042rollback_run () {
3043	echo -n "Uninstalling updates..."
3044
3045	# If there are updates waiting to be installed, remove them; we
3046	# want the user to re-run 'fetch' after rolling back updates.
3047	if [ -L ${BDHASH}-install ]; then
3048		rm -r ${BDHASH}-install/
3049		rm ${BDHASH}-install
3050	fi
3051
3052	# Make sure we have all the files we should have
3053	install_verify ${BDHASH}-rollback/INDEX-NEW	\
3054	    ${BDHASH}-rollback/INDEX-OLD || return 1
3055
3056	# Remove system immutable flag from files
3057	install_unschg ${BDHASH}-rollback/INDEX-NEW	\
3058	    ${BDHASH}-rollback/INDEX-OLD || return 1
3059
3060	# Install old files, delete new files, and update linker.hints
3061	rollback_files ${BDHASH}-rollback || return 1
3062
3063	# Remove the rollback directory and the symlink pointing to it; and
3064	# rearrange bits to allow the previous set of updates to be rolled
3065	# back next.
3066	rollback_setup_rollback
3067
3068	echo " done."
3069}
3070
3071# Compare INDEX-ALL and INDEX-PRESENT and print warnings about differences.
3072IDS_compare () {
3073	# Get all the lines which mismatch in something other than file
3074	# flags.  We ignore file flags because sysinstall doesn't seem to
3075	# set them when it installs FreeBSD; warning about these adds a
3076	# very large amount of noise.
3077	cut -f 1-5,7-8 -d '|' $1 > $1.noflags
3078	sort -k 1,1 -t '|' $1.noflags > $1.sorted
3079	cut -f 1-5,7-8 -d '|' $2 |
3080	    comm -13 $1.noflags - |
3081	    fgrep -v '|-|||||' |
3082	    sort -k 1,1 -t '|' |
3083	    join -t '|' $1.sorted - > INDEX-NOTMATCHING
3084
3085	# Ignore files which match IDSIGNOREPATHS.
3086	for X in ${IDSIGNOREPATHS}; do
3087		grep -E "^${X}" INDEX-NOTMATCHING
3088	done |
3089	    sort -u |
3090	    comm -13 - INDEX-NOTMATCHING > INDEX-NOTMATCHING.tmp
3091	mv INDEX-NOTMATCHING.tmp INDEX-NOTMATCHING
3092
3093	# Go through the lines and print warnings.
3094	while read LINE; do
3095		FPATH=`echo "${LINE}" | cut -f 1 -d '|'`
3096		TYPE=`echo "${LINE}" | cut -f 2 -d '|'`
3097		OWNER=`echo "${LINE}" | cut -f 3 -d '|'`
3098		GROUP=`echo "${LINE}" | cut -f 4 -d '|'`
3099		PERM=`echo "${LINE}" | cut -f 5 -d '|'`
3100		HASH=`echo "${LINE}" | cut -f 6 -d '|'`
3101		LINK=`echo "${LINE}" | cut -f 7 -d '|'`
3102		P_TYPE=`echo "${LINE}" | cut -f 8 -d '|'`
3103		P_OWNER=`echo "${LINE}" | cut -f 9 -d '|'`
3104		P_GROUP=`echo "${LINE}" | cut -f 10 -d '|'`
3105		P_PERM=`echo "${LINE}" | cut -f 11 -d '|'`
3106		P_HASH=`echo "${LINE}" | cut -f 12 -d '|'`
3107		P_LINK=`echo "${LINE}" | cut -f 13 -d '|'`
3108
3109		# Warn about different object types.
3110		if ! [ "${TYPE}" = "${P_TYPE}" ]; then
3111			echo -n "${FPATH} is a "
3112			case "${P_TYPE}" in
3113			f)	echo -n "regular file, "
3114				;;
3115			d)	echo -n "directory, "
3116				;;
3117			L)	echo -n "symlink, "
3118				;;
3119			esac
3120			echo -n "but should be a "
3121			case "${TYPE}" in
3122			f)	echo -n "regular file."
3123				;;
3124			d)	echo -n "directory."
3125				;;
3126			L)	echo -n "symlink."
3127				;;
3128			esac
3129			echo
3130
3131			# Skip other tests, since they don't make sense if
3132			# we're comparing different object types.
3133			continue
3134		fi
3135
3136		# Warn about different owners.
3137		if ! [ "${OWNER}" = "${P_OWNER}" ]; then
3138			echo -n "${FPATH} is owned by user id ${P_OWNER}, "
3139			echo "but should be owned by user id ${OWNER}."
3140		fi
3141
3142		# Warn about different groups.
3143		if ! [ "${GROUP}" = "${P_GROUP}" ]; then
3144			echo -n "${FPATH} is owned by group id ${P_GROUP}, "
3145			echo "but should be owned by group id ${GROUP}."
3146		fi
3147
3148		# Warn about different permissions.  We do not warn about
3149		# different permissions on symlinks, since some archivers
3150		# don't extract symlink permissions correctly and they are
3151		# ignored anyway.
3152		if ! [ "${PERM}" = "${P_PERM}" ] &&
3153		    ! [ "${TYPE}" = "L" ]; then
3154			echo -n "${FPATH} has ${P_PERM} permissions, "
3155			echo "but should have ${PERM} permissions."
3156		fi
3157
3158		# Warn about different file hashes / symlink destinations.
3159		if ! [ "${HASH}" = "${P_HASH}" ]; then
3160			if [ "${TYPE}" = "L" ]; then
3161				echo -n "${FPATH} is a symlink to ${P_HASH}, "
3162				echo "but should be a symlink to ${HASH}."
3163			fi
3164			if [ "${TYPE}" = "f" ]; then
3165				echo -n "${FPATH} has SHA256 hash ${P_HASH}, "
3166				echo "but should have SHA256 hash ${HASH}."
3167			fi
3168		fi
3169
3170		# We don't warn about different hard links, since some
3171		# some archivers break hard links, and as long as the
3172		# underlying data is correct they really don't matter.
3173	done < INDEX-NOTMATCHING
3174
3175	# Clean up
3176	rm $1 $1.noflags $1.sorted $2 INDEX-NOTMATCHING
3177}
3178
3179# Do the work involved in comparing the system to a "known good" index
3180IDS_run () {
3181	workdir_init || return 1
3182
3183	# Prepare the mirror list.
3184	fetch_pick_server_init && fetch_pick_server
3185
3186	# Try to fetch the public key until we run out of servers.
3187	while ! fetch_key; do
3188		fetch_pick_server || return 1
3189	done
3190 
3191	# Try to fetch the metadata index signature ("tag") until we run
3192	# out of available servers; and sanity check the downloaded tag.
3193	while ! fetch_tag; do
3194		fetch_pick_server || return 1
3195	done
3196	fetch_tagsanity || return 1
3197
3198	# Fetch INDEX-OLD and INDEX-ALL.
3199	fetch_metadata INDEX-OLD INDEX-ALL || return 1
3200
3201	# Generate filtered INDEX-OLD and INDEX-ALL files containing only
3202	# the components we want and without anything marked as "Ignore".
3203	fetch_filter_metadata INDEX-OLD || return 1
3204	fetch_filter_metadata INDEX-ALL || return 1
3205
3206	# Merge the INDEX-OLD and INDEX-ALL files into INDEX-ALL.
3207	sort INDEX-OLD INDEX-ALL > INDEX-ALL.tmp
3208	mv INDEX-ALL.tmp INDEX-ALL
3209	rm INDEX-OLD
3210
3211	# Translate /boot/${KERNCONF} to ${KERNELDIR}
3212	fetch_filter_kernel_names INDEX-ALL ${KERNCONF}
3213
3214	# Inspect the system and generate an INDEX-PRESENT file.
3215	fetch_inspect_system INDEX-ALL INDEX-PRESENT /dev/null || return 1
3216
3217	# Compare INDEX-ALL and INDEX-PRESENT and print warnings about any
3218	# differences.
3219	IDS_compare INDEX-ALL INDEX-PRESENT
3220}
3221
3222#### Main functions -- call parameter-handling and core functions
3223
3224# Using the command line, configuration file, and defaults,
3225# set all the parameters which are needed later.
3226get_params () {
3227	init_params
3228	parse_cmdline $@
3229	parse_conffile
3230	default_params
3231}
3232
3233# Fetch command.  Make sure that we're being called
3234# interactively, then run fetch_check_params and fetch_run
3235cmd_fetch () {
3236	if [ ! -t 0 -a $NOTTYOK -eq 0 ]; then
3237		echo -n "`basename $0` fetch should not "
3238		echo "be run non-interactively."
3239		echo "Run `basename $0` cron instead."
3240		exit 1
3241	fi
3242	fetch_check_params
3243	fetch_run || exit 1
3244}
3245
3246# Cron command.  Make sure the parameters are sensible; wait
3247# rand(3600) seconds; then fetch updates.  While fetching updates,
3248# send output to a temporary file; only print that file if the
3249# fetching failed.
3250cmd_cron () {
3251	fetch_check_params
3252	sleep `jot -r 1 0 3600`
3253
3254	TMPFILE=`mktemp /tmp/freebsd-update.XXXXXX` || exit 1
3255	if ! fetch_run >> ${TMPFILE} ||
3256	    ! grep -q "No updates needed" ${TMPFILE} ||
3257	    [ ${VERBOSELEVEL} = "debug" ]; then
3258		mail -s "`hostname` security updates" ${MAILTO} < ${TMPFILE}
3259	fi
3260
3261	rm ${TMPFILE}
3262}
3263
3264# Fetch files for upgrading to a new release.
3265cmd_upgrade () {
3266	upgrade_check_params
3267	upgrade_run || exit 1
3268}
3269
3270# Install downloaded updates.
3271cmd_install () {
3272	install_check_params
3273	install_run || exit 1
3274}
3275
3276# Rollback most recently installed updates.
3277cmd_rollback () {
3278	rollback_check_params
3279	rollback_run || exit 1
3280}
3281
3282# Compare system against a "known good" index.
3283cmd_IDS () {
3284	IDS_check_params
3285	IDS_run || exit 1
3286}
3287
3288#### Entry point
3289
3290# Make sure we find utilities from the base system
3291export PATH=/sbin:/bin:/usr/sbin:/usr/bin:${PATH}
3292
3293# Set a pager if the user doesn't
3294if [ -z "$PAGER" ]; then
3295	PAGER=/usr/bin/more
3296fi
3297
3298# Set LC_ALL in order to avoid problems with character ranges like [A-Z].
3299export LC_ALL=C
3300
3301get_params $@
3302for COMMAND in ${COMMANDS}; do
3303	cmd_${COMMAND}
3304done
3305