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