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