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