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