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