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