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