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