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