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