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