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