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