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