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