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