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