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