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