freebsd-update.sh revision 177601
1161748Scperciva#!/bin/sh
2161748Scperciva
3161748Scperciva#-
4173441Scperciva# Copyright 2004-2007 Colin Percival
5161748Scperciva# All rights reserved
6161748Scperciva#
7161748Scperciva# Redistribution and use in source and binary forms, with or without
8161748Scperciva# modification, are permitted providing that the following conditions 
9161748Scperciva# are met:
10161748Scperciva# 1. Redistributions of source code must retain the above copyright
11161748Scperciva#    notice, this list of conditions and the following disclaimer.
12161748Scperciva# 2. Redistributions in binary form must reproduce the above copyright
13161748Scperciva#    notice, this list of conditions and the following disclaimer in the
14161748Scperciva#    documentation and/or other materials provided with the distribution.
15161748Scperciva#
16161748Scperciva# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17161748Scperciva# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18161748Scperciva# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19161748Scperciva# ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
20161748Scperciva# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21161748Scperciva# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22161748Scperciva# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23161748Scperciva# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
24161748Scperciva# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
25161748Scperciva# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26161748Scperciva# POSSIBILITY OF SUCH DAMAGE.
27161748Scperciva
28161748Scperciva# $FreeBSD: head/usr.sbin/freebsd-update/freebsd-update.sh 177601 2008-03-25 11:31:16Z cperciva $
29161748Scperciva
30161748Scperciva#### Usage function -- called from command-line handling code.
31161748Scperciva
32161748Scperciva# Usage instructions.  Options not listed:
33161748Scperciva# --debug	-- don't filter output from utilities
34161748Scperciva# --no-stats	-- don't show progress statistics while fetching files
35161748Scpercivausage () {
36161748Scperciva	cat <<EOF
37161748Scpercivausage: `basename $0` [options] command ... [path]
38161748Scperciva
39161748ScpercivaOptions:
40161748Scperciva  -b basedir   -- Operate on a system mounted at basedir
41161748Scperciva                  (default: /)
42161748Scperciva  -d workdir   -- Store working files in workdir
43161748Scperciva                  (default: /var/db/freebsd-update/)
44161748Scperciva  -f conffile  -- Read configuration options from conffile
45161748Scperciva                  (default: /etc/freebsd-update.conf)
46161748Scperciva  -k KEY       -- Trust an RSA key with SHA256 hash of KEY
47173564Scperciva  -r release   -- Target for upgrade (e.g., 6.2-RELEASE)
48161748Scperciva  -s server    -- Server from which to fetch updates
49161748Scperciva                  (default: update.FreeBSD.org)
50161748Scperciva  -t address   -- Mail output of cron command, if any, to address
51161748Scperciva                  (default: root)
52161748ScpercivaCommands:
53161748Scperciva  fetch        -- Fetch updates from server
54161748Scperciva  cron         -- Sleep rand(3600) seconds, fetch updates, and send an
55161748Scperciva                  email if updates were found
56173564Scperciva  upgrade      -- Fetch upgrades to FreeBSD version specified via -r option
57173564Scperciva  install      -- Install downloaded updates or upgrades
58161748Scperciva  rollback     -- Uninstall most recently installed updates
59161748ScpercivaEOF
60161748Scperciva	exit 0
61161748Scperciva}
62161748Scperciva
63161748Scperciva#### Configuration processing functions
64161748Scperciva
65161748Scperciva#-
66161748Scperciva# Configuration options are set in the following order of priority:
67161748Scperciva# 1. Command line options
68161748Scperciva# 2. Configuration file options
69161748Scperciva# 3. Default options
70161748Scperciva# In addition, certain options (e.g., IgnorePaths) can be specified multiple
71161748Scperciva# times and (as long as these are all in the same place, e.g., inside the
72161748Scperciva# configuration file) they will accumulate.  Finally, because the path to the
73161748Scperciva# configuration file can be specified at the command line, the entire command
74161748Scperciva# line must be processed before we start reading the configuration file.
75161748Scperciva#
76161748Scperciva# Sound like a mess?  It is.  Here's how we handle this:
77161748Scperciva# 1. Initialize CONFFILE and all the options to "".
78161748Scperciva# 2. Process the command line.  Throw an error if a non-accumulating option
79161748Scperciva#    is specified twice.
80161748Scperciva# 3. If CONFFILE is "", set CONFFILE to /etc/freebsd-update.conf .
81161748Scperciva# 4. For all the configuration options X, set X_saved to X.
82161748Scperciva# 5. Initialize all the options to "".
83161748Scperciva# 6. Read CONFFILE line by line, parsing options.
84161748Scperciva# 7. For each configuration option X, set X to X_saved iff X_saved is not "".
85161748Scperciva# 8. Repeat steps 4-7, except setting options to their default values at (6).
86161748Scperciva
87161748ScpercivaCONFIGOPTIONS="KEYPRINT WORKDIR SERVERNAME MAILTO ALLOWADD ALLOWDELETE
88161748Scperciva    KEEPMODIFIEDMETADATA COMPONENTS IGNOREPATHS UPDATEIFUNMODIFIED
89173564Scperciva    BASEDIR VERBOSELEVEL TARGETRELEASE STRICTCOMPONENTS MERGECHANGES"
90161748Scperciva
91161748Scperciva# Set all the configuration options to "".
92161748Scpercivanullconfig () {
93161748Scperciva	for X in ${CONFIGOPTIONS}; do
94161748Scperciva		eval ${X}=""
95161748Scperciva	done
96161748Scperciva}
97161748Scperciva
98161748Scperciva# For each configuration option X, set X_saved to X.
99161748Scpercivasaveconfig () {
100161748Scperciva	for X in ${CONFIGOPTIONS}; do
101161748Scperciva		eval ${X}_saved=\$${X}
102161748Scperciva	done
103161748Scperciva}
104161748Scperciva
105161748Scperciva# For each configuration option X, set X to X_saved if X_saved is not "".
106161748Scpercivamergeconfig () {
107161748Scperciva	for X in ${CONFIGOPTIONS}; do
108161748Scperciva		eval _=\$${X}_saved
109161748Scperciva		if ! [ -z "${_}" ]; then
110161748Scperciva			eval ${X}=\$${X}_saved
111161748Scperciva		fi
112161748Scperciva	done
113161748Scperciva}
114161748Scperciva
115161748Scperciva# Set the trusted keyprint.
116161748Scpercivaconfig_KeyPrint () {
117161748Scperciva	if [ -z ${KEYPRINT} ]; then
118161748Scperciva		KEYPRINT=$1
119161748Scperciva	else
120161748Scperciva		return 1
121161748Scperciva	fi
122161748Scperciva}
123161748Scperciva
124161748Scperciva# Set the working directory.
125161748Scpercivaconfig_WorkDir () {
126161748Scperciva	if [ -z ${WORKDIR} ]; then
127161748Scperciva		WORKDIR=$1
128161748Scperciva	else
129161748Scperciva		return 1
130161748Scperciva	fi
131161748Scperciva}
132161748Scperciva
133161748Scperciva# Set the name of the server (pool) from which to fetch updates
134161748Scpercivaconfig_ServerName () {
135161748Scperciva	if [ -z ${SERVERNAME} ]; then
136161748Scperciva		SERVERNAME=$1
137161748Scperciva	else
138161748Scperciva		return 1
139161748Scperciva	fi
140161748Scperciva}
141161748Scperciva
142161748Scperciva# Set the address to which 'cron' output will be mailed.
143161748Scpercivaconfig_MailTo () {
144161748Scperciva	if [ -z ${MAILTO} ]; then
145161748Scperciva		MAILTO=$1
146161748Scperciva	else
147161748Scperciva		return 1
148161748Scperciva	fi
149161748Scperciva}
150161748Scperciva
151161748Scperciva# Set whether FreeBSD Update is allowed to add files (or directories, or
152161748Scperciva# symlinks) which did not previously exist.
153161748Scpercivaconfig_AllowAdd () {
154161748Scperciva	if [ -z ${ALLOWADD} ]; then
155161748Scperciva		case $1 in
156161748Scperciva		[Yy][Ee][Ss])
157161748Scperciva			ALLOWADD=yes
158161748Scperciva			;;
159161748Scperciva		[Nn][Oo])
160161748Scperciva			ALLOWADD=no
161161748Scperciva			;;
162161748Scperciva		*)
163161748Scperciva			return 1
164161748Scperciva			;;
165161748Scperciva		esac
166161748Scperciva	else
167161748Scperciva		return 1
168161748Scperciva	fi
169161748Scperciva}
170161748Scperciva
171161748Scperciva# Set whether FreeBSD Update is allowed to remove files/directories/symlinks.
172161748Scpercivaconfig_AllowDelete () {
173161748Scperciva	if [ -z ${ALLOWDELETE} ]; then
174161748Scperciva		case $1 in
175161748Scperciva		[Yy][Ee][Ss])
176161748Scperciva			ALLOWDELETE=yes
177161748Scperciva			;;
178161748Scperciva		[Nn][Oo])
179161748Scperciva			ALLOWDELETE=no
180161748Scperciva			;;
181161748Scperciva		*)
182161748Scperciva			return 1
183161748Scperciva			;;
184161748Scperciva		esac
185161748Scperciva	else
186161748Scperciva		return 1
187161748Scperciva	fi
188161748Scperciva}
189161748Scperciva
190161748Scperciva# Set whether FreeBSD Update should keep existing inode ownership,
191161748Scperciva# permissions, and flags, in the event that they have been modified locally
192161748Scperciva# after the release.
193161748Scpercivaconfig_KeepModifiedMetadata () {
194161748Scperciva	if [ -z ${KEEPMODIFIEDMETADATA} ]; then
195161748Scperciva		case $1 in
196161748Scperciva		[Yy][Ee][Ss])
197161748Scperciva			KEEPMODIFIEDMETADATA=yes
198161748Scperciva			;;
199161748Scperciva		[Nn][Oo])
200161748Scperciva			KEEPMODIFIEDMETADATA=no
201161748Scperciva			;;
202161748Scperciva		*)
203161748Scperciva			return 1
204161748Scperciva			;;
205161748Scperciva		esac
206161748Scperciva	else
207161748Scperciva		return 1
208161748Scperciva	fi
209161748Scperciva}
210161748Scperciva
211161748Scperciva# Add to the list of components which should be kept updated.
212161748Scpercivaconfig_Components () {
213161748Scperciva	for C in $@; do
214161748Scperciva		COMPONENTS="${COMPONENTS} ${C}"
215161748Scperciva	done
216161748Scperciva}
217161748Scperciva
218161748Scperciva# Add to the list of paths under which updates will be ignored.
219161748Scpercivaconfig_IgnorePaths () {
220161748Scperciva	for C in $@; do
221161748Scperciva		IGNOREPATHS="${IGNOREPATHS} ${C}"
222161748Scperciva	done
223161748Scperciva}
224161748Scperciva
225161748Scperciva# Add to the list of paths within which updates will be performed only if the
226161748Scperciva# file on disk has not been modified locally.
227161748Scpercivaconfig_UpdateIfUnmodified () {
228161748Scperciva	for C in $@; do
229161748Scperciva		UPDATEIFUNMODIFIED="${UPDATEIFUNMODIFIED} ${C}"
230161748Scperciva	done
231161748Scperciva}
232161748Scperciva
233173564Scperciva# Add to the list of paths within which updates to text files will be merged
234173564Scperciva# instead of overwritten.
235173564Scpercivaconfig_MergeChanges () {
236173564Scperciva	for C in $@; do
237173564Scperciva		MERGECHANGES="${MERGECHANGES} ${C}"
238173564Scperciva	done
239173564Scperciva}
240173564Scperciva
241161748Scperciva# Work on a FreeBSD installation mounted under $1
242161748Scpercivaconfig_BaseDir () {
243161748Scperciva	if [ -z ${BASEDIR} ]; then
244161748Scperciva		BASEDIR=$1
245161748Scperciva	else
246161748Scperciva		return 1
247161748Scperciva	fi
248161748Scperciva}
249161748Scperciva
250173564Scperciva# When fetching upgrades, should we assume the user wants exactly the
251173564Scperciva# components listed in COMPONENTS, rather than trying to guess based on
252173564Scperciva# what's currently installed?
253173564Scpercivaconfig_StrictComponents () {
254173564Scperciva	if [ -z ${STRICTCOMPONENTS} ]; then
255173564Scperciva		case $1 in
256173564Scperciva		[Yy][Ee][Ss])
257173564Scperciva			STRICTCOMPONENTS=yes
258173564Scperciva			;;
259173564Scperciva		[Nn][Oo])
260173564Scperciva			STRICTCOMPONENTS=no
261173564Scperciva			;;
262173564Scperciva		*)
263173564Scperciva			return 1
264173564Scperciva			;;
265173564Scperciva		esac
266173564Scperciva	else
267173564Scperciva		return 1
268173564Scperciva	fi
269173564Scperciva}
270173564Scperciva
271173564Scperciva# Upgrade to FreeBSD $1
272173564Scpercivaconfig_TargetRelease () {
273173564Scperciva	if [ -z ${TARGETRELEASE} ]; then
274173564Scperciva		TARGETRELEASE=$1
275173564Scperciva	else
276173564Scperciva		return 1
277173564Scperciva	fi
278173564Scperciva}
279173564Scperciva
280161748Scperciva# Define what happens to output of utilities
281161748Scpercivaconfig_VerboseLevel () {
282161748Scperciva	if [ -z ${VERBOSELEVEL} ]; then
283161748Scperciva		case $1 in
284161748Scperciva		[Dd][Ee][Bb][Uu][Gg])
285161748Scperciva			VERBOSELEVEL=debug
286161748Scperciva			;;
287161748Scperciva		[Nn][Oo][Ss][Tt][Aa][Tt][Ss])
288161748Scperciva			VERBOSELEVEL=nostats
289161748Scperciva			;;
290161748Scperciva		[Ss][Tt][Aa][Tt][Ss])
291161748Scperciva			VERBOSELEVEL=stats
292161748Scperciva			;;
293161748Scperciva		*)
294161748Scperciva			return 1
295161748Scperciva			;;
296161748Scperciva		esac
297161748Scperciva	else
298161748Scperciva		return 1
299161748Scperciva	fi
300161748Scperciva}
301161748Scperciva
302161748Scperciva# Handle one line of configuration
303161748Scpercivaconfigline () {
304161748Scperciva	if [ $# -eq 0 ]; then
305161748Scperciva		return
306161748Scperciva	fi
307161748Scperciva
308161748Scperciva	OPT=$1
309161748Scperciva	shift
310161748Scperciva	config_${OPT} $@
311161748Scperciva}
312161748Scperciva
313161748Scperciva#### Parameter handling functions.
314161748Scperciva
315161748Scperciva# Initialize parameters to null, just in case they're
316161748Scperciva# set in the environment.
317161748Scpercivainit_params () {
318161748Scperciva	# Configration settings
319161748Scperciva	nullconfig
320161748Scperciva
321161748Scperciva	# No configuration file set yet
322161748Scperciva	CONFFILE=""
323161748Scperciva
324161748Scperciva	# No commands specified yet
325161748Scperciva	COMMANDS=""
326161748Scperciva}
327161748Scperciva
328161748Scperciva# Parse the command line
329161748Scpercivaparse_cmdline () {
330161748Scperciva	while [ $# -gt 0 ]; do
331161748Scperciva		case "$1" in
332161748Scperciva		# Location of configuration file
333161748Scperciva		-f)
334161748Scperciva			if [ $# -eq 1 ]; then usage; fi
335161748Scperciva			if [ ! -z "${CONFFILE}" ]; then usage; fi
336161748Scperciva			shift; CONFFILE="$1"
337161748Scperciva			;;
338161748Scperciva
339161748Scperciva		# Configuration file equivalents
340161748Scperciva		-b)
341161748Scperciva			if [ $# -eq 1 ]; then usage; fi; shift
342161748Scperciva			config_BaseDir $1 || usage
343161748Scperciva			;;
344161748Scperciva		-d)
345161748Scperciva			if [ $# -eq 1 ]; then usage; fi; shift
346161748Scperciva			config_WorkDir $1 || usage
347161748Scperciva			;;
348161748Scperciva		-k)
349161748Scperciva			if [ $# -eq 1 ]; then usage; fi; shift
350161748Scperciva			config_KeyPrint $1 || usage
351161748Scperciva			;;
352161748Scperciva		-s)
353161748Scperciva			if [ $# -eq 1 ]; then usage; fi; shift
354161748Scperciva			config_ServerName $1 || usage
355161748Scperciva			;;
356173564Scperciva		-r)
357173564Scperciva			if [ $# -eq 1 ]; then usage; fi; shift
358173564Scperciva			config_TargetRelease $1 || usage
359173564Scperciva			;;
360161748Scperciva		-t)
361161748Scperciva			if [ $# -eq 1 ]; then usage; fi; shift
362161748Scperciva			config_MailTo $1 || usage
363161748Scperciva			;;
364161748Scperciva		-v)
365161748Scperciva			if [ $# -eq 1 ]; then usage; fi; shift
366161748Scperciva			config_VerboseLevel $1 || usage
367161748Scperciva			;;
368161748Scperciva
369161748Scperciva		# Aliases for "-v debug" and "-v nostats"
370161748Scperciva		--debug)
371161748Scperciva			config_VerboseLevel debug || usage
372161748Scperciva			;;
373161748Scperciva		--no-stats)
374161748Scperciva			config_VerboseLevel nostats || usage
375161748Scperciva			;;
376161748Scperciva
377161748Scperciva		# Commands
378173564Scperciva		cron | fetch | upgrade | install | rollback)
379161748Scperciva			COMMANDS="${COMMANDS} $1"
380161748Scperciva			;;
381161748Scperciva
382161748Scperciva		# Anything else is an error
383161748Scperciva		*)
384161748Scperciva			usage
385161748Scperciva			;;
386161748Scperciva		esac
387161748Scperciva		shift
388161748Scperciva	done
389161748Scperciva
390161748Scperciva	# Make sure we have at least one command
391161748Scperciva	if [ -z "${COMMANDS}" ]; then
392161748Scperciva		usage
393161748Scperciva	fi
394161748Scperciva}
395161748Scperciva
396161748Scperciva# Parse the configuration file
397161748Scpercivaparse_conffile () {
398161748Scperciva	# If a configuration file was specified on the command line, check
399161748Scperciva	# that it exists and is readable.
400161748Scperciva	if [ ! -z "${CONFFILE}" ] && [ ! -r "${CONFFILE}" ]; then
401161748Scperciva		echo -n "File does not exist "
402161748Scperciva		echo -n "or is not readable: "
403161748Scperciva		echo ${CONFFILE}
404161748Scperciva		exit 1
405161748Scperciva	fi
406161748Scperciva
407161748Scperciva	# If a configuration file was not specified on the command line,
408161748Scperciva	# use the default configuration file path.  If that default does
409161748Scperciva	# not exist, give up looking for any configuration.
410161748Scperciva	if [ -z "${CONFFILE}" ]; then
411161748Scperciva		CONFFILE="/etc/freebsd-update.conf"
412161748Scperciva		if [ ! -r "${CONFFILE}" ]; then
413161748Scperciva			return
414161748Scperciva		fi
415161748Scperciva	fi
416161748Scperciva
417161748Scperciva	# Save the configuration options specified on the command line, and
418161748Scperciva	# clear all the options in preparation for reading the config file.
419161748Scperciva	saveconfig
420161748Scperciva	nullconfig
421161748Scperciva
422161748Scperciva	# Read the configuration file.  Anything after the first '#' is
423161748Scperciva	# ignored, and any blank lines are ignored.
424161748Scperciva	L=0
425161748Scperciva	while read LINE; do
426161748Scperciva		L=$(($L + 1))
427161748Scperciva		LINEX=`echo "${LINE}" | cut -f 1 -d '#'`
428161748Scperciva		if ! configline ${LINEX}; then
429161748Scperciva			echo "Error processing configuration file, line $L:"
430161748Scperciva			echo "==> ${LINE}"
431161748Scperciva			exit 1
432161748Scperciva		fi
433161748Scperciva	done < ${CONFFILE}
434161748Scperciva
435161748Scperciva	# Merge the settings read from the configuration file with those
436161748Scperciva	# provided at the command line.
437161748Scperciva	mergeconfig
438161748Scperciva}
439161748Scperciva
440161748Scperciva# Provide some default parameters
441161748Scpercivadefault_params () {
442161748Scperciva	# Save any parameters already configured, and clear the slate
443161748Scperciva	saveconfig
444161748Scperciva	nullconfig
445161748Scperciva
446161748Scperciva	# Default configurations
447161748Scperciva	config_WorkDir /var/db/freebsd-update
448161748Scperciva	config_MailTo root
449161748Scperciva	config_AllowAdd yes
450161748Scperciva	config_AllowDelete yes
451161748Scperciva	config_KeepModifiedMetadata yes
452161748Scperciva	config_BaseDir /
453161748Scperciva	config_VerboseLevel stats
454173564Scperciva	config_StrictComponents no
455161748Scperciva
456161748Scperciva	# Merge these defaults into the earlier-configured settings
457161748Scperciva	mergeconfig
458161748Scperciva}
459161748Scperciva
460161748Scperciva# Set utility output filtering options, based on ${VERBOSELEVEL}
461161748Scpercivafetch_setup_verboselevel () {
462161748Scperciva	case ${VERBOSELEVEL} in
463161748Scperciva	debug)
464161748Scperciva		QUIETREDIR="/dev/stderr"
465161748Scperciva		QUIETFLAG=" "
466161748Scperciva		STATSREDIR="/dev/stderr"
467161748Scperciva		DDSTATS=".."
468161748Scperciva		XARGST="-t"
469161748Scperciva		NDEBUG=" "
470161748Scperciva		;;
471161748Scperciva	nostats)
472161748Scperciva		QUIETREDIR=""
473161748Scperciva		QUIETFLAG=""
474161748Scperciva		STATSREDIR="/dev/null"
475161748Scperciva		DDSTATS=".."
476161748Scperciva		XARGST=""
477161748Scperciva		NDEBUG=""
478161748Scperciva		;;
479161748Scperciva	stats)
480161748Scperciva		QUIETREDIR="/dev/null"
481161748Scperciva		QUIETFLAG="-q"
482161748Scperciva		STATSREDIR="/dev/stdout"
483161748Scperciva		DDSTATS=""
484161748Scperciva		XARGST=""
485161748Scperciva		NDEBUG="-n"
486161748Scperciva		;;
487161748Scperciva	esac
488161748Scperciva}
489161748Scperciva
490161748Scperciva# Perform sanity checks and set some final parameters
491161748Scperciva# in preparation for fetching files.  Figure out which
492161748Scperciva# set of updates should be downloaded: If the user is
493161748Scperciva# running *-p[0-9]+, strip off the last part; if the
494161748Scperciva# user is running -SECURITY, call it -RELEASE.  Chdir
495161748Scperciva# into the working directory.
496161748Scpercivafetch_check_params () {
497161748Scperciva	export HTTP_USER_AGENT="freebsd-update (${COMMAND}, `uname -r`)"
498161748Scperciva
499161748Scperciva	_SERVERNAME_z=\
500161748Scperciva"SERVERNAME must be given via command line or configuration file."
501161748Scperciva	_KEYPRINT_z="Key must be given via -k option or configuration file."
502161748Scperciva	_KEYPRINT_bad="Invalid key fingerprint: "
503161748Scperciva	_WORKDIR_bad="Directory does not exist or is not writable: "
504161748Scperciva
505161748Scperciva	if [ -z "${SERVERNAME}" ]; then
506161748Scperciva		echo -n "`basename $0`: "
507161748Scperciva		echo "${_SERVERNAME_z}"
508161748Scperciva		exit 1
509161748Scperciva	fi
510161748Scperciva	if [ -z "${KEYPRINT}" ]; then
511161748Scperciva		echo -n "`basename $0`: "
512161748Scperciva		echo "${_KEYPRINT_z}"
513161748Scperciva		exit 1
514161748Scperciva	fi
515161748Scperciva	if ! echo "${KEYPRINT}" | grep -qE "^[0-9a-f]{64}$"; then
516161748Scperciva		echo -n "`basename $0`: "
517161748Scperciva		echo -n "${_KEYPRINT_bad}"
518161748Scperciva		echo ${KEYPRINT}
519161748Scperciva		exit 1
520161748Scperciva	fi
521161748Scperciva	if ! [ -d "${WORKDIR}" -a -w "${WORKDIR}" ]; then
522161748Scperciva		echo -n "`basename $0`: "
523161748Scperciva		echo -n "${_WORKDIR_bad}"
524161748Scperciva		echo ${WORKDIR}
525161748Scperciva		exit 1
526161748Scperciva	fi
527161748Scperciva	cd ${WORKDIR} || exit 1
528161748Scperciva
529161748Scperciva	# Generate release number.  The s/SECURITY/RELEASE/ bit exists
530161748Scperciva	# to provide an upgrade path for FreeBSD Update 1.x users, since
531161748Scperciva	# the kernels provided by FreeBSD Update 1.x are always labelled
532161748Scperciva	# as X.Y-SECURITY.
533161748Scperciva	RELNUM=`uname -r |
534161748Scperciva	    sed -E 's,-p[0-9]+,,' |
535161748Scperciva	    sed -E 's,-SECURITY,-RELEASE,'`
536161748Scperciva	ARCH=`uname -m`
537161748Scperciva	FETCHDIR=${RELNUM}/${ARCH}
538173564Scperciva	PATCHDIR=${RELNUM}/${ARCH}/bp
539161748Scperciva
540161748Scperciva	# Figure out what directory contains the running kernel
541161748Scperciva	BOOTFILE=`sysctl -n kern.bootfile`
542161748Scperciva	KERNELDIR=${BOOTFILE%/kernel}
543161748Scperciva	if ! [ -d ${KERNELDIR} ]; then
544161748Scperciva		echo "Cannot identify running kernel"
545161748Scperciva		exit 1
546161748Scperciva	fi
547161748Scperciva
548167189Scperciva	# Figure out what kernel configuration is running.  We start with
549167189Scperciva	# the output of `uname -i`, and then make the following adjustments:
550167189Scperciva	# 1. Replace "SMP-GENERIC" with "SMP".  Why the SMP kernel config
551167189Scperciva	# file says "ident SMP-GENERIC", I don't know...
552167189Scperciva	# 2. If the kernel claims to be GENERIC _and_ ${ARCH} is "amd64"
553167189Scperciva	# _and_ `sysctl kern.version` contains a line which ends "/SMP", then
554167189Scperciva	# we're running an SMP kernel.  This mis-identification is a bug
555167189Scperciva	# which was fixed in 6.2-STABLE.
556167189Scperciva	KERNCONF=`uname -i`
557167189Scperciva	if [ ${KERNCONF} = "SMP-GENERIC" ]; then
558167189Scperciva		KERNCONF=SMP
559167189Scperciva	fi
560167189Scperciva	if [ ${KERNCONF} = "GENERIC" ] && [ ${ARCH} = "amd64" ]; then
561167189Scperciva		if sysctl kern.version | grep -qE '/SMP$'; then
562167189Scperciva			KERNCONF=SMP
563167189Scperciva		fi
564167189Scperciva	fi
565167189Scperciva
566161748Scperciva	# Define some paths
567161748Scperciva	BSPATCH=/usr/bin/bspatch
568161748Scperciva	SHA256=/sbin/sha256
569161748Scperciva	PHTTPGET=/usr/libexec/phttpget
570161748Scperciva
571161748Scperciva	# Set up variables relating to VERBOSELEVEL
572161748Scperciva	fetch_setup_verboselevel
573161748Scperciva
574161748Scperciva	# Construct a unique name from ${BASEDIR}
575161748Scperciva	BDHASH=`echo ${BASEDIR} | sha256 -q`
576161748Scperciva}
577161748Scperciva
578173564Scperciva# Perform sanity checks etc. before fetching upgrades.
579173564Scpercivaupgrade_check_params () {
580173564Scperciva	fetch_check_params
581173564Scperciva
582173564Scperciva	# Unless set otherwise, we're upgrading to the same kernel config.
583173564Scperciva	NKERNCONF=${KERNCONF}
584173564Scperciva
585173564Scperciva	# We need TARGETRELEASE set
586173564Scperciva	_TARGETRELEASE_z="Release target must be specified via -r option."
587173564Scperciva	if [ -z "${TARGETRELEASE}" ]; then
588173564Scperciva		echo -n "`basename $0`: "
589173564Scperciva		echo "${_TARGETRELEASE_z}"
590173564Scperciva		exit 1
591173564Scperciva	fi
592173564Scperciva
593173564Scperciva	# The target release should be != the current release.
594173564Scperciva	if [ "${TARGETRELEASE}" = "${RELNUM}" ]; then
595173564Scperciva		echo -n "`basename $0`: "
596173564Scperciva		echo "Cannot upgrade from ${RELNUM} to itself"
597173564Scperciva		exit 1
598173564Scperciva	fi
599173564Scperciva
600173564Scperciva	# Turning off AllowAdd or AllowDelete is a bad idea for upgrades.
601173564Scperciva	if [ "${ALLOWADD}" = "no" ]; then
602173564Scperciva		echo -n "`basename $0`: "
603173564Scperciva		echo -n "WARNING: \"AllowAdd no\" is a bad idea "
604173564Scperciva		echo "when upgrading between releases."
605173564Scperciva		echo
606173564Scperciva	fi
607173564Scperciva	if [ "${ALLOWDELETE}" = "no" ]; then
608173564Scperciva		echo -n "`basename $0`: "
609173564Scperciva		echo -n "WARNING: \"AllowDelete no\" is a bad idea "
610173564Scperciva		echo "when upgrading between releases."
611173564Scperciva		echo
612173564Scperciva	fi
613173564Scperciva
614173564Scperciva	# Set EDITOR to /usr/bin/vi if it isn't already set
615173564Scperciva	: ${EDITOR:='/usr/bin/vi'}
616173564Scperciva}
617173564Scperciva
618161748Scperciva# Perform sanity checks and set some final parameters in
619161748Scperciva# preparation for installing updates.
620161748Scpercivainstall_check_params () {
621161748Scperciva	# Check that we are root.  All sorts of things won't work otherwise.
622161748Scperciva	if [ `id -u` != 0 ]; then
623161748Scperciva		echo "You must be root to run this."
624161748Scperciva		exit 1
625161748Scperciva	fi
626161748Scperciva
627173441Scperciva	# Check that securelevel <= 0.  Otherwise we can't update schg files.
628173441Scperciva	if [ `sysctl -n kern.securelevel` -gt 0 ]; then
629173441Scperciva		echo "Updates cannot be installed when the system securelevel"
630173441Scperciva		echo "is greater than zero."
631173441Scperciva		exit 1
632173441Scperciva	fi
633173441Scperciva
634161748Scperciva	# Check that we have a working directory
635161748Scperciva	_WORKDIR_bad="Directory does not exist or is not writable: "
636161748Scperciva	if ! [ -d "${WORKDIR}" -a -w "${WORKDIR}" ]; then
637161748Scperciva		echo -n "`basename $0`: "
638161748Scperciva		echo -n "${_WORKDIR_bad}"
639161748Scperciva		echo ${WORKDIR}
640161748Scperciva		exit 1
641161748Scperciva	fi
642161748Scperciva	cd ${WORKDIR} || exit 1
643161748Scperciva
644161748Scperciva	# Construct a unique name from ${BASEDIR}
645161748Scperciva	BDHASH=`echo ${BASEDIR} | sha256 -q`
646161748Scperciva
647161748Scperciva	# Check that we have updates ready to install
648161748Scperciva	if ! [ -L ${BDHASH}-install ]; then
649161748Scperciva		echo "No updates are available to install."
650161748Scperciva		echo "Run '$0 fetch' first."
651161748Scperciva		exit 1
652161748Scperciva	fi
653161748Scperciva	if ! [ -f ${BDHASH}-install/INDEX-OLD ] ||
654161748Scperciva	    ! [ -f ${BDHASH}-install/INDEX-NEW ]; then
655161748Scperciva		echo "Update manifest is corrupt -- this should never happen."
656161748Scperciva		echo "Re-run '$0 fetch'."
657161748Scperciva		exit 1
658161748Scperciva	fi
659161748Scperciva}
660161748Scperciva
661161748Scperciva# Perform sanity checks and set some final parameters in
662161748Scperciva# preparation for UNinstalling updates.
663161748Scpercivarollback_check_params () {
664161748Scperciva	# Check that we are root.  All sorts of things won't work otherwise.
665161748Scperciva	if [ `id -u` != 0 ]; then
666161748Scperciva		echo "You must be root to run this."
667161748Scperciva		exit 1
668161748Scperciva	fi
669161748Scperciva
670161748Scperciva	# Check that we have a working directory
671161748Scperciva	_WORKDIR_bad="Directory does not exist or is not writable: "
672161748Scperciva	if ! [ -d "${WORKDIR}" -a -w "${WORKDIR}" ]; then
673161748Scperciva		echo -n "`basename $0`: "
674161748Scperciva		echo -n "${_WORKDIR_bad}"
675161748Scperciva		echo ${WORKDIR}
676161748Scperciva		exit 1
677161748Scperciva	fi
678161748Scperciva	cd ${WORKDIR} || exit 1
679161748Scperciva
680161748Scperciva	# Construct a unique name from ${BASEDIR}
681161748Scperciva	BDHASH=`echo ${BASEDIR} | sha256 -q`
682161748Scperciva
683161748Scperciva	# Check that we have updates ready to rollback
684161748Scperciva	if ! [ -L ${BDHASH}-rollback ]; then
685161748Scperciva		echo "No rollback directory found."
686161748Scperciva		exit 1
687161748Scperciva	fi
688161748Scperciva	if ! [ -f ${BDHASH}-rollback/INDEX-OLD ] ||
689161748Scperciva	    ! [ -f ${BDHASH}-rollback/INDEX-NEW ]; then
690161748Scperciva		echo "Update manifest is corrupt -- this should never happen."
691161748Scperciva		exit 1
692161748Scperciva	fi
693161748Scperciva}
694161748Scperciva
695161748Scperciva#### Core functionality -- the actual work gets done here
696161748Scperciva
697161748Scperciva# Use an SRV query to pick a server.  If the SRV query doesn't provide
698161748Scperciva# a useful answer, use the server name specified by the user.
699161748Scperciva# Put another way... look up _http._tcp.${SERVERNAME} and pick a server
700161748Scperciva# from that; or if no servers are returned, use ${SERVERNAME}.
701161748Scperciva# This allows a user to specify "portsnap.freebsd.org" (in which case
702161748Scperciva# portsnap will select one of the mirrors) or "portsnap5.tld.freebsd.org"
703161748Scperciva# (in which case portsnap will use that particular server, since there
704161748Scperciva# won't be an SRV entry for that name).
705161748Scperciva#
706161748Scperciva# We ignore the Port field, since we are always going to use port 80.
707161748Scperciva
708161748Scperciva# Fetch the mirror list, but do not pick a mirror yet.  Returns 1 if
709161748Scperciva# no mirrors are available for any reason.
710161748Scpercivafetch_pick_server_init () {
711161748Scperciva	: > serverlist_tried
712161748Scperciva
713161748Scperciva# Check that host(1) exists (i.e., that the system wasn't built with the
714161748Scperciva# WITHOUT_BIND set) and don't try to find a mirror if it doesn't exist.
715161748Scperciva	if ! which -s host; then
716161748Scperciva		: > serverlist_full
717161748Scperciva		return 1
718161748Scperciva	fi
719161748Scperciva
720161748Scperciva	echo -n "Looking up ${SERVERNAME} mirrors... "
721161748Scperciva
722161748Scperciva# Issue the SRV query and pull out the Priority, Weight, and Target fields.
723161748Scperciva# BIND 9 prints "$name has SRV record ..." while BIND 8 prints
724161748Scperciva# "$name server selection ..."; we allow either format.
725161748Scperciva	MLIST="_http._tcp.${SERVERNAME}"
726161748Scperciva	host -t srv "${MLIST}" |
727161748Scperciva	    sed -nE "s/${MLIST} (has SRV record|server selection) //p" |
728161748Scperciva	    cut -f 1,2,4 -d ' ' |
729161748Scperciva	    sed -e 's/\.$//' |
730161748Scperciva	    sort > serverlist_full
731161748Scperciva
732161748Scperciva# If no records, give up -- we'll just use the server name we were given.
733161748Scperciva	if [ `wc -l < serverlist_full` -eq 0 ]; then
734161748Scperciva		echo "none found."
735161748Scperciva		return 1
736161748Scperciva	fi
737161748Scperciva
738161748Scperciva# Report how many mirrors we found.
739161748Scperciva	echo `wc -l < serverlist_full` "mirrors found."
740161748Scperciva
741161748Scperciva# Generate a random seed for use in picking mirrors.  If HTTP_PROXY
742161748Scperciva# is set, this will be used to generate the seed; otherwise, the seed
743161748Scperciva# will be random.
744161748Scperciva	if [ -n "${HTTP_PROXY}${http_proxy}" ]; then
745161748Scperciva		RANDVALUE=`sha256 -qs "${HTTP_PROXY}${http_proxy}" |
746161748Scperciva		    tr -d 'a-f' |
747161748Scperciva		    cut -c 1-9`
748161748Scperciva	else
749161748Scperciva		RANDVALUE=`jot -r 1 0 999999999`
750161748Scperciva	fi
751161748Scperciva}
752161748Scperciva
753161748Scperciva# Pick a mirror.  Returns 1 if we have run out of mirrors to try.
754161748Scpercivafetch_pick_server () {
755161748Scperciva# Generate a list of not-yet-tried mirrors
756161748Scperciva	sort serverlist_tried |
757161748Scperciva	    comm -23 serverlist_full - > serverlist
758161748Scperciva
759161748Scperciva# Have we run out of mirrors?
760161748Scperciva	if [ `wc -l < serverlist` -eq 0 ]; then
761161748Scperciva		echo "No mirrors remaining, giving up."
762161748Scperciva		return 1
763161748Scperciva	fi
764161748Scperciva
765161748Scperciva# Find the highest priority level (lowest numeric value).
766161748Scperciva	SRV_PRIORITY=`cut -f 1 -d ' ' serverlist | sort -n | head -1`
767161748Scperciva
768161748Scperciva# Add up the weights of the response lines at that priority level.
769161748Scperciva	SRV_WSUM=0;
770161748Scperciva	while read X; do
771161748Scperciva		case "$X" in
772161748Scperciva		${SRV_PRIORITY}\ *)
773161748Scperciva			SRV_W=`echo $X | cut -f 2 -d ' '`
774161748Scperciva			SRV_WSUM=$(($SRV_WSUM + $SRV_W))
775161748Scperciva			;;
776161748Scperciva		esac
777161748Scperciva	done < serverlist
778161748Scperciva
779161748Scperciva# If all the weights are 0, pretend that they are all 1 instead.
780161748Scperciva	if [ ${SRV_WSUM} -eq 0 ]; then
781161748Scperciva		SRV_WSUM=`grep -E "^${SRV_PRIORITY} " serverlist | wc -l`
782161748Scperciva		SRV_W_ADD=1
783161748Scperciva	else
784161748Scperciva		SRV_W_ADD=0
785161748Scperciva	fi
786161748Scperciva
787161748Scperciva# Pick a value between 0 and the sum of the weights - 1
788161748Scperciva	SRV_RND=`expr ${RANDVALUE} % ${SRV_WSUM}`
789161748Scperciva
790161748Scperciva# Read through the list of mirrors and set SERVERNAME.  Write the line
791161748Scperciva# corresponding to the mirror we selected into serverlist_tried so that
792161748Scperciva# we won't try it again.
793161748Scperciva	while read X; do
794161748Scperciva		case "$X" in
795161748Scperciva		${SRV_PRIORITY}\ *)
796161748Scperciva			SRV_W=`echo $X | cut -f 2 -d ' '`
797161748Scperciva			SRV_W=$(($SRV_W + $SRV_W_ADD))
798161748Scperciva			if [ $SRV_RND -lt $SRV_W ]; then
799161748Scperciva				SERVERNAME=`echo $X | cut -f 3 -d ' '`
800161748Scperciva				echo "$X" >> serverlist_tried
801161748Scperciva				break
802161748Scperciva			else
803161748Scperciva				SRV_RND=$(($SRV_RND - $SRV_W))
804161748Scperciva			fi
805161748Scperciva			;;
806161748Scperciva		esac
807161748Scperciva	done < serverlist
808161748Scperciva}
809161748Scperciva
810161748Scperciva# Take a list of ${oldhash}|${newhash} and output a list of needed patches,
811161748Scperciva# i.e., those for which we have ${oldhash} and don't have ${newhash}.
812161748Scpercivafetch_make_patchlist () {
813161748Scperciva	grep -vE "^([0-9a-f]{64})\|\1$" |
814161748Scperciva	    tr '|' ' ' |
815161748Scperciva		while read X Y; do
816161748Scperciva			if [ -f "files/${Y}.gz" ] ||
817161748Scperciva			    [ ! -f "files/${X}.gz" ]; then
818161748Scperciva				continue
819161748Scperciva			fi
820161748Scperciva			echo "${X}|${Y}"
821161748Scperciva		done | uniq
822161748Scperciva}
823161748Scperciva
824161748Scperciva# Print user-friendly progress statistics
825161748Scpercivafetch_progress () {
826161748Scperciva	LNC=0
827161748Scperciva	while read x; do
828161748Scperciva		LNC=$(($LNC + 1))
829161748Scperciva		if [ $(($LNC % 10)) = 0 ]; then
830161748Scperciva			echo -n $LNC
831161748Scperciva		elif [ $(($LNC % 2)) = 0 ]; then
832161748Scperciva			echo -n .
833161748Scperciva		fi
834161748Scperciva	done
835161748Scperciva	echo -n " "
836161748Scperciva}
837161748Scperciva
838173564Scperciva# Function for asking the user if everything is ok
839173564Scpercivacontinuep () {
840173564Scperciva	while read -p "Does this look reasonable (y/n)? " CONTINUE; do
841173564Scperciva		case "${CONTINUE}" in
842173564Scperciva		y*)
843173564Scperciva			return 0
844173564Scperciva			;;
845173564Scperciva		n*)
846173564Scperciva			return 1
847173564Scperciva			;;
848173564Scperciva		esac
849173564Scperciva	done
850173564Scperciva}
851173564Scperciva
852161748Scperciva# Initialize the working directory
853161748Scpercivaworkdir_init () {
854161748Scperciva	mkdir -p files
855161748Scperciva	touch tINDEX.present
856161748Scperciva}
857161748Scperciva
858161748Scperciva# Check that we have a public key with an appropriate hash, or
859161748Scperciva# fetch the key if it doesn't exist.  Returns 1 if the key has
860161748Scperciva# not yet been fetched.
861161748Scpercivafetch_key () {
862161748Scperciva	if [ -r pub.ssl ] && [ `${SHA256} -q pub.ssl` = ${KEYPRINT} ]; then
863161748Scperciva		return 0
864161748Scperciva	fi
865161748Scperciva
866161748Scperciva	echo -n "Fetching public key from ${SERVERNAME}... "
867161748Scperciva	rm -f pub.ssl
868161748Scperciva	fetch ${QUIETFLAG} http://${SERVERNAME}/${FETCHDIR}/pub.ssl \
869161748Scperciva	    2>${QUIETREDIR} || true
870161748Scperciva	if ! [ -r pub.ssl ]; then
871161748Scperciva		echo "failed."
872161748Scperciva		return 1
873161748Scperciva	fi
874161748Scperciva	if ! [ `${SHA256} -q pub.ssl` = ${KEYPRINT} ]; then
875161748Scperciva		echo "key has incorrect hash."
876161748Scperciva		rm -f pub.ssl
877161748Scperciva		return 1
878161748Scperciva	fi
879161748Scperciva	echo "done."
880161748Scperciva}
881161748Scperciva
882161748Scperciva# Fetch metadata signature, aka "tag".
883161748Scpercivafetch_tag () {
884173564Scperciva	echo -n "Fetching metadata signature "
885173564Scperciva	echo ${NDEBUG} "for ${RELNUM} from ${SERVERNAME}... "
886161748Scperciva	rm -f latest.ssl
887161748Scperciva	fetch ${QUIETFLAG} http://${SERVERNAME}/${FETCHDIR}/latest.ssl	\
888161748Scperciva	    2>${QUIETREDIR} || true
889161748Scperciva	if ! [ -r latest.ssl ]; then
890161748Scperciva		echo "failed."
891161748Scperciva		return 1
892161748Scperciva	fi
893161748Scperciva
894161748Scperciva	openssl rsautl -pubin -inkey pub.ssl -verify		\
895161748Scperciva	    < latest.ssl > tag.new 2>${QUIETREDIR} || true
896161748Scperciva	rm latest.ssl
897161748Scperciva
898161748Scperciva	if ! [ `wc -l < tag.new` = 1 ] ||
899161748Scperciva	    ! grep -qE	\
900161748Scperciva    "^freebsd-update\|${ARCH}\|${RELNUM}\|[0-9]+\|[0-9a-f]{64}\|[0-9]{10}" \
901161748Scperciva		tag.new; then
902161748Scperciva		echo "invalid signature."
903161748Scperciva		return 1
904161748Scperciva	fi
905161748Scperciva
906161748Scperciva	echo "done."
907161748Scperciva
908161748Scperciva	RELPATCHNUM=`cut -f 4 -d '|' < tag.new`
909161748Scperciva	TINDEXHASH=`cut -f 5 -d '|' < tag.new`
910161748Scperciva	EOLTIME=`cut -f 6 -d '|' < tag.new`
911161748Scperciva}
912161748Scperciva
913161748Scperciva# Sanity-check the patch number in a tag, to make sure that we're not
914161748Scperciva# going to "update" backwards and to prevent replay attacks.
915161748Scpercivafetch_tagsanity () {
916161748Scperciva	# Check that we're not going to move from -pX to -pY with Y < X.
917161748Scperciva	RELPX=`uname -r | sed -E 's,.*-,,'`
918161748Scperciva	if echo ${RELPX} | grep -qE '^p[0-9]+$'; then
919161748Scperciva		RELPX=`echo ${RELPX} | cut -c 2-`
920161748Scperciva	else
921161748Scperciva		RELPX=0
922161748Scperciva	fi
923161748Scperciva	if [ "${RELPATCHNUM}" -lt "${RELPX}" ]; then
924161748Scperciva		echo
925161748Scperciva		echo -n "Files on mirror (${RELNUM}-p${RELPATCHNUM})"
926161748Scperciva		echo " appear older than what"
927161748Scperciva		echo "we are currently running (`uname -r`)!"
928161748Scperciva		echo "Cowardly refusing to proceed any further."
929161748Scperciva		return 1
930161748Scperciva	fi
931161748Scperciva
932161748Scperciva	# If "tag" exists and corresponds to ${RELNUM}, make sure that
933161748Scperciva	# it contains a patch number <= RELPATCHNUM, in order to protect
934161748Scperciva	# against rollback (replay) attacks.
935161748Scperciva	if [ -f tag ] &&
936161748Scperciva	    grep -qE	\
937161748Scperciva    "^freebsd-update\|${ARCH}\|${RELNUM}\|[0-9]+\|[0-9a-f]{64}\|[0-9]{10}" \
938161748Scperciva		tag; then
939161748Scperciva		LASTRELPATCHNUM=`cut -f 4 -d '|' < tag`
940161748Scperciva
941161748Scperciva		if [ "${RELPATCHNUM}" -lt "${LASTRELPATCHNUM}" ]; then
942161748Scperciva			echo
943161748Scperciva			echo -n "Files on mirror (${RELNUM}-p${RELPATCHNUM})"
944161748Scperciva			echo " are older than the"
945161748Scperciva			echo -n "most recently seen updates"
946161748Scperciva			echo " (${RELNUM}-p${LASTRELPATCHNUM})."
947161748Scperciva			echo "Cowardly refusing to proceed any further."
948161748Scperciva			return 1
949161748Scperciva		fi
950161748Scperciva	fi
951161748Scperciva}
952161748Scperciva
953161748Scperciva# Fetch metadata index file
954161748Scpercivafetch_metadata_index () {
955161748Scperciva	echo ${NDEBUG} "Fetching metadata index... "
956161748Scperciva	rm -f ${TINDEXHASH}
957161748Scperciva	fetch ${QUIETFLAG} http://${SERVERNAME}/${FETCHDIR}/t/${TINDEXHASH}
958161748Scperciva	    2>${QUIETREDIR}
959161748Scperciva	if ! [ -f ${TINDEXHASH} ]; then
960161748Scperciva		echo "failed."
961161748Scperciva		return 1
962161748Scperciva	fi
963161748Scperciva	if [ `${SHA256} -q ${TINDEXHASH}` != ${TINDEXHASH} ]; then
964161748Scperciva		echo "update metadata index corrupt."
965161748Scperciva		return 1
966161748Scperciva	fi
967161748Scperciva	echo "done."
968161748Scperciva}
969161748Scperciva
970161748Scperciva# Print an error message about signed metadata being bogus.
971161748Scpercivafetch_metadata_bogus () {
972161748Scperciva	echo
973161748Scperciva	echo "The update metadata$1 is correctly signed, but"
974161748Scperciva	echo "failed an integrity check."
975161748Scperciva	echo "Cowardly refusing to proceed any further."
976161748Scperciva	return 1
977161748Scperciva}
978161748Scperciva
979161748Scperciva# Construct tINDEX.new by merging the lines named in $1 from ${TINDEXHASH}
980161748Scperciva# with the lines not named in $@ from tINDEX.present (if that file exists).
981161748Scpercivafetch_metadata_index_merge () {
982161748Scperciva	for METAFILE in $@; do
983161748Scperciva		if [ `grep -E "^${METAFILE}\|" ${TINDEXHASH} | wc -l`	\
984161748Scperciva		    -ne 1 ]; then
985161748Scperciva			fetch_metadata_bogus " index"
986161748Scperciva			return 1
987161748Scperciva		fi
988161748Scperciva
989161748Scperciva		grep -E "${METAFILE}\|" ${TINDEXHASH}
990161748Scperciva	done |
991161748Scperciva	    sort > tINDEX.wanted
992161748Scperciva
993161748Scperciva	if [ -f tINDEX.present ]; then
994161748Scperciva		join -t '|' -v 2 tINDEX.wanted tINDEX.present |
995161748Scperciva		    sort -m - tINDEX.wanted > tINDEX.new
996161748Scperciva		rm tINDEX.wanted
997161748Scperciva	else
998161748Scperciva		mv tINDEX.wanted tINDEX.new
999161748Scperciva	fi
1000161748Scperciva}
1001161748Scperciva
1002161748Scperciva# Sanity check all the lines of tINDEX.new.  Even if more metadata lines
1003161748Scperciva# are added by future versions of the server, this won't cause problems,
1004161748Scperciva# since the only lines which appear in tINDEX.new are the ones which we
1005161748Scperciva# specifically grepped out of ${TINDEXHASH}.
1006161748Scpercivafetch_metadata_index_sanity () {
1007161748Scperciva	if grep -qvE '^[0-9A-Z.-]+\|[0-9a-f]{64}$' tINDEX.new; then
1008161748Scperciva		fetch_metadata_bogus " index"
1009161748Scperciva		return 1
1010161748Scperciva	fi
1011161748Scperciva}
1012161748Scperciva
1013161748Scperciva# Sanity check the metadata file $1.
1014161748Scpercivafetch_metadata_sanity () {
1015161748Scperciva	# Some aliases to save space later: ${P} is a character which can
1016161748Scperciva	# appear in a path; ${M} is the four numeric metadata fields; and
1017161748Scperciva	# ${H} is a sha256 hash.
1018161748Scperciva	P="[-+./:=_[[:alnum:]]"
1019161748Scperciva	M="[0-9]+\|[0-9]+\|[0-9]+\|[0-9]+"
1020161748Scperciva	H="[0-9a-f]{64}"
1021161748Scperciva
1022161748Scperciva	# Check that the first four fields make sense.
1023161748Scperciva	if gunzip -c < files/$1.gz |
1024161748Scperciva	    grep -qvE "^[a-z]+\|[0-9a-z]+\|${P}+\|[fdL-]\|"; then
1025161748Scperciva		fetch_metadata_bogus ""
1026161748Scperciva		return 1
1027161748Scperciva	fi
1028161748Scperciva
1029161748Scperciva	# Remove the first three fields.
1030161748Scperciva	gunzip -c < files/$1.gz |
1031161748Scperciva	    cut -f 4- -d '|' > sanitycheck.tmp
1032161748Scperciva
1033161748Scperciva	# Sanity check entries with type 'f'
1034161748Scperciva	if grep -E '^f' sanitycheck.tmp |
1035161748Scperciva	    grep -qvE "^f\|${M}\|${H}\|${P}*\$"; then
1036161748Scperciva		fetch_metadata_bogus ""
1037161748Scperciva		return 1
1038161748Scperciva	fi
1039161748Scperciva
1040161748Scperciva	# Sanity check entries with type 'd'
1041161748Scperciva	if grep -E '^d' sanitycheck.tmp |
1042161748Scperciva	    grep -qvE "^d\|${M}\|\|\$"; then
1043161748Scperciva		fetch_metadata_bogus ""
1044161748Scperciva		return 1
1045161748Scperciva	fi
1046161748Scperciva
1047161748Scperciva	# Sanity check entries with type 'L'
1048161748Scperciva	if grep -E '^L' sanitycheck.tmp |
1049161748Scperciva	    grep -qvE "^L\|${M}\|${P}*\|\$"; then
1050161748Scperciva		fetch_metadata_bogus ""
1051161748Scperciva		return 1
1052161748Scperciva	fi
1053161748Scperciva
1054161748Scperciva	# Sanity check entries with type '-'
1055161748Scperciva	if grep -E '^-' sanitycheck.tmp |
1056161748Scperciva	    grep -qvE "^-\|\|\|\|\|\|"; then
1057161748Scperciva		fetch_metadata_bogus ""
1058161748Scperciva		return 1
1059161748Scperciva	fi
1060161748Scperciva
1061161748Scperciva	# Clean up
1062161748Scperciva	rm sanitycheck.tmp
1063161748Scperciva}
1064161748Scperciva
1065161748Scperciva# Fetch the metadata index and metadata files listed in $@,
1066161748Scperciva# taking advantage of metadata patches where possible.
1067161748Scpercivafetch_metadata () {
1068161748Scperciva	fetch_metadata_index || return 1
1069161748Scperciva	fetch_metadata_index_merge $@ || return 1
1070161748Scperciva	fetch_metadata_index_sanity || return 1
1071161748Scperciva
1072161748Scperciva	# Generate a list of wanted metadata patches
1073161748Scperciva	join -t '|' -o 1.2,2.2 tINDEX.present tINDEX.new |
1074161748Scperciva	    fetch_make_patchlist > patchlist
1075161748Scperciva
1076161748Scperciva	if [ -s patchlist ]; then
1077161748Scperciva		# Attempt to fetch metadata patches
1078161748Scperciva		echo -n "Fetching `wc -l < patchlist | tr -d ' '` "
1079161748Scperciva		echo ${NDEBUG} "metadata patches.${DDSTATS}"
1080161748Scperciva		tr '|' '-' < patchlist |
1081161748Scperciva		    lam -s "${FETCHDIR}/tp/" - -s ".gz" |
1082161748Scperciva		    xargs ${XARGST} ${PHTTPGET} ${SERVERNAME}	\
1083161748Scperciva			2>${STATSREDIR} | fetch_progress
1084161748Scperciva		echo "done."
1085161748Scperciva
1086161748Scperciva		# Attempt to apply metadata patches
1087161748Scperciva		echo -n "Applying metadata patches... "
1088161748Scperciva		tr '|' ' ' < patchlist |
1089161748Scperciva		    while read X Y; do
1090161748Scperciva			if [ ! -f "${X}-${Y}.gz" ]; then continue; fi
1091161748Scperciva			gunzip -c < ${X}-${Y}.gz > diff
1092161748Scperciva			gunzip -c < files/${X}.gz > diff-OLD
1093161748Scperciva
1094161748Scperciva			# Figure out which lines are being added and removed
1095161748Scperciva			grep -E '^-' diff |
1096161748Scperciva			    cut -c 2- |
1097161748Scperciva			    while read PREFIX; do
1098161748Scperciva				look "${PREFIX}" diff-OLD
1099161748Scperciva			    done |
1100161748Scperciva			    sort > diff-rm
1101161748Scperciva			grep -E '^\+' diff |
1102161748Scperciva			    cut -c 2- > diff-add
1103161748Scperciva
1104161748Scperciva			# Generate the new file
1105161748Scperciva			comm -23 diff-OLD diff-rm |
1106161748Scperciva			    sort - diff-add > diff-NEW
1107161748Scperciva
1108161748Scperciva			if [ `${SHA256} -q diff-NEW` = ${Y} ]; then
1109161748Scperciva				mv diff-NEW files/${Y}
1110161748Scperciva				gzip -n files/${Y}
1111161748Scperciva			else
1112161748Scperciva				mv diff-NEW ${Y}.bad
1113161748Scperciva			fi
1114161748Scperciva			rm -f ${X}-${Y}.gz diff
1115161748Scperciva			rm -f diff-OLD diff-NEW diff-add diff-rm
1116161748Scperciva		done 2>${QUIETREDIR}
1117161748Scperciva		echo "done."
1118161748Scperciva	fi
1119161748Scperciva
1120161748Scperciva	# Update metadata without patches
1121161748Scperciva	cut -f 2 -d '|' < tINDEX.new |
1122161748Scperciva	    while read Y; do
1123161748Scperciva		if [ ! -f "files/${Y}.gz" ]; then
1124161748Scperciva			echo ${Y};
1125161748Scperciva		fi
1126164600Scperciva	    done |
1127164600Scperciva	    sort -u > filelist
1128161748Scperciva
1129161748Scperciva	if [ -s filelist ]; then
1130161748Scperciva		echo -n "Fetching `wc -l < filelist | tr -d ' '` "
1131161748Scperciva		echo ${NDEBUG} "metadata files... "
1132161748Scperciva		lam -s "${FETCHDIR}/m/" - -s ".gz" < filelist |
1133161748Scperciva		    xargs ${XARGST} ${PHTTPGET} ${SERVERNAME}	\
1134161748Scperciva		    2>${QUIETREDIR}
1135161748Scperciva
1136161748Scperciva		while read Y; do
1137161748Scperciva			if ! [ -f ${Y}.gz ]; then
1138161748Scperciva				echo "failed."
1139161748Scperciva				return 1
1140161748Scperciva			fi
1141161748Scperciva			if [ `gunzip -c < ${Y}.gz |
1142161748Scperciva			    ${SHA256} -q` = ${Y} ]; then
1143161748Scperciva				mv ${Y}.gz files/${Y}.gz
1144161748Scperciva			else
1145161748Scperciva				echo "metadata is corrupt."
1146161748Scperciva				return 1
1147161748Scperciva			fi
1148161748Scperciva		done < filelist
1149161748Scperciva		echo "done."
1150161748Scperciva	fi
1151161748Scperciva
1152161748Scperciva# Sanity-check the metadata files.
1153161748Scperciva	cut -f 2 -d '|' tINDEX.new > filelist
1154161748Scperciva	while read X; do
1155161748Scperciva		fetch_metadata_sanity ${X} || return 1
1156161748Scperciva	done < filelist
1157161748Scperciva
1158161748Scperciva# Remove files which are no longer needed
1159161748Scperciva	cut -f 2 -d '|' tINDEX.present |
1160161748Scperciva	    sort > oldfiles
1161161748Scperciva	cut -f 2 -d '|' tINDEX.new |
1162161748Scperciva	    sort |
1163161748Scperciva	    comm -13 - oldfiles |
1164161748Scperciva	    lam -s "files/" - -s ".gz" |
1165161748Scperciva	    xargs rm -f
1166161748Scperciva	rm patchlist filelist oldfiles
1167161748Scperciva	rm ${TINDEXHASH}
1168161748Scperciva
1169161748Scperciva# We're done!
1170161748Scperciva	mv tINDEX.new tINDEX.present
1171161748Scperciva	mv tag.new tag
1172161748Scperciva
1173161748Scperciva	return 0
1174161748Scperciva}
1175161748Scperciva
1176173564Scperciva# Extract a subset of a downloaded metadata file containing only the parts
1177173564Scperciva# which are listed in COMPONENTS.
1178173564Scpercivafetch_filter_metadata_components () {
1179173564Scperciva	METAHASH=`look "$1|" tINDEX.present | cut -f 2 -d '|'`
1180173564Scperciva	gunzip -c < files/${METAHASH}.gz > $1.all
1181173564Scperciva
1182173564Scperciva	# Fish out the lines belonging to components we care about.
1183173564Scperciva	for C in ${COMPONENTS}; do
1184173564Scperciva		look "`echo ${C} | tr '/' '|'`|" $1.all
1185173564Scperciva	done > $1
1186173564Scperciva
1187173564Scperciva	# Remove temporary file.
1188173564Scperciva	rm $1.all
1189173564Scperciva}
1190173564Scperciva
1191161869Scperciva# Generate a filtered version of the metadata file $1 from the downloaded
1192161748Scperciva# file, by fishing out the lines corresponding to components we're trying
1193161748Scperciva# to keep updated, and then removing lines corresponding to paths we want
1194161748Scperciva# to ignore.
1195161748Scpercivafetch_filter_metadata () {
1196173564Scperciva	# Fish out the lines belonging to components we care about.
1197173564Scperciva	fetch_filter_metadata_components $1
1198161748Scperciva
1199161748Scperciva	# Canonicalize directory names by removing any trailing / in
1200161748Scperciva	# order to avoid listing directories multiple times if they
1201161748Scperciva	# belong to multiple components.  Turning "/" into "" doesn't
1202161748Scperciva	# matter, since we add a leading "/" when we use paths later.
1203173564Scperciva	cut -f 3- -d '|' $1 |
1204161748Scperciva	    sed -e 's,/|d|,|d|,' |
1205161748Scperciva	    sort -u > $1.tmp
1206161748Scperciva
1207161748Scperciva	# Figure out which lines to ignore and remove them.
1208161748Scperciva	for X in ${IGNOREPATHS}; do
1209161748Scperciva		grep -E "^${X}" $1.tmp
1210161748Scperciva	done |
1211161748Scperciva	    sort -u |
1212161748Scperciva	    comm -13 - $1.tmp > $1
1213161748Scperciva
1214161748Scperciva	# Remove temporary files.
1215173564Scperciva	rm $1.tmp
1216161748Scperciva}
1217161748Scperciva
1218173564Scperciva# Filter the metadata file $1 by adding lines with "/boot/$2"
1219164600Scperciva# replaced by ${KERNELDIR} (which is `sysctl -n kern.bootfile` minus the
1220173564Scperciva# trailing "/kernel"); and if "/boot/$2" does not exist, remove
1221164600Scperciva# the original lines which start with that.
1222164600Scperciva# Put another way: Deal with the fact that the FOO kernel is sometimes
1223164600Scperciva# installed in /boot/FOO/ and is sometimes installed elsewhere.
1224161748Scpercivafetch_filter_kernel_names () {
1225173564Scperciva	grep ^/boot/$2 $1 |
1226173564Scperciva	    sed -e "s,/boot/$2,${KERNELDIR},g" |
1227161748Scperciva	    sort - $1 > $1.tmp
1228161748Scperciva	mv $1.tmp $1
1229164600Scperciva
1230173564Scperciva	if ! [ -d /boot/$2 ]; then
1231173564Scperciva		grep -v ^/boot/$2 $1 > $1.tmp
1232164600Scperciva		mv $1.tmp $1
1233164600Scperciva	fi
1234161748Scperciva}
1235161748Scperciva
1236161748Scperciva# For all paths appearing in $1 or $3, inspect the system
1237161748Scperciva# and generate $2 describing what is currently installed.
1238161748Scpercivafetch_inspect_system () {
1239161748Scperciva	# No errors yet...
1240161748Scperciva	rm -f .err
1241161748Scperciva
1242161748Scperciva	# Tell the user why his disk is suddenly making lots of noise
1243161748Scperciva	echo -n "Inspecting system... "
1244161748Scperciva
1245161748Scperciva	# Generate list of files to inspect
1246161748Scperciva	cat $1 $3 |
1247161748Scperciva	    cut -f 1 -d '|' |
1248161748Scperciva	    sort -u > filelist
1249161748Scperciva
1250161748Scperciva	# Examine each file and output lines of the form
1251161748Scperciva	# /path/to/file|type|device-inum|user|group|perm|flags|value
1252161748Scperciva	# sorted by device and inode number.
1253161748Scperciva	while read F; do
1254161748Scperciva		# If the symlink/file/directory does not exist, record this.
1255161748Scperciva		if ! [ -e ${BASEDIR}/${F} ]; then
1256161748Scperciva			echo "${F}|-||||||"
1257161748Scperciva			continue
1258161748Scperciva		fi
1259161748Scperciva		if ! [ -r ${BASEDIR}/${F} ]; then
1260161748Scperciva			echo "Cannot read file: ${BASEDIR}/${F}"	\
1261161748Scperciva			    >/dev/stderr
1262161748Scperciva			touch .err
1263161748Scperciva			return 1
1264161748Scperciva		fi
1265161748Scperciva
1266161748Scperciva		# Otherwise, output an index line.
1267161748Scperciva		if [ -L ${BASEDIR}/${F} ]; then
1268161748Scperciva			echo -n "${F}|L|"
1269161748Scperciva			stat -n -f '%d-%i|%u|%g|%Mp%Lp|%Of|' ${BASEDIR}/${F};
1270161748Scperciva			readlink ${BASEDIR}/${F};
1271161748Scperciva		elif [ -f ${BASEDIR}/${F} ]; then
1272161748Scperciva			echo -n "${F}|f|"
1273161748Scperciva			stat -n -f '%d-%i|%u|%g|%Mp%Lp|%Of|' ${BASEDIR}/${F};
1274161748Scperciva			sha256 -q ${BASEDIR}/${F};
1275161748Scperciva		elif [ -d ${BASEDIR}/${F} ]; then
1276161748Scperciva			echo -n "${F}|d|"
1277161748Scperciva			stat -f '%d-%i|%u|%g|%Mp%Lp|%Of|' ${BASEDIR}/${F};
1278161748Scperciva		else
1279161748Scperciva			echo "Unknown file type: ${BASEDIR}/${F}"	\
1280161748Scperciva			    >/dev/stderr
1281161748Scperciva			touch .err
1282161748Scperciva			return 1
1283161748Scperciva		fi
1284161748Scperciva	done < filelist |
1285161748Scperciva	    sort -k 3,3 -t '|' > $2.tmp
1286161748Scperciva	rm filelist
1287161748Scperciva
1288161748Scperciva	# Check if an error occured during system inspection
1289161748Scperciva	if [ -f .err ]; then
1290161748Scperciva		return 1
1291161748Scperciva	fi
1292161748Scperciva
1293161748Scperciva	# Convert to the form
1294161748Scperciva	# /path/to/file|type|user|group|perm|flags|value|hlink
1295161748Scperciva	# by resolving identical device and inode numbers into hard links.
1296161748Scperciva	cut -f 1,3 -d '|' $2.tmp |
1297161748Scperciva	    sort -k 1,1 -t '|' |
1298161748Scperciva	    sort -s -u -k 2,2 -t '|' |
1299161748Scperciva	    join -1 2 -2 3 -t '|' - $2.tmp |
1300161748Scperciva	    awk -F \| -v OFS=\|		\
1301161748Scperciva		'{
1302161748Scperciva		    if (($2 == $3) || ($4 == "-"))
1303161748Scperciva			print $3,$4,$5,$6,$7,$8,$9,""
1304161748Scperciva		    else
1305161748Scperciva			print $3,$4,$5,$6,$7,$8,$9,$2
1306161748Scperciva		}' |
1307161748Scperciva	    sort > $2
1308161748Scperciva	rm $2.tmp
1309161748Scperciva
1310161748Scperciva	# We're finished looking around
1311161748Scperciva	echo "done."
1312161748Scperciva}
1313161748Scperciva
1314173564Scperciva# For any paths matching ${MERGECHANGES}, compare $1 and $2 and find any
1315173564Scperciva# files which differ; generate $3 containing these paths and the old hashes.
1316173564Scpercivafetch_filter_mergechanges () {
1317173564Scperciva	# Pull out the paths and hashes of the files matching ${MERGECHANGES}.
1318173564Scperciva	for F in $1 $2; do
1319173564Scperciva		for X in ${MERGECHANGES}; do
1320173564Scperciva			grep -E "^${X}" ${F}
1321173564Scperciva		done |
1322173564Scperciva		    cut -f 1,2,7 -d '|' |
1323173564Scperciva		    sort > ${F}-values
1324173564Scperciva	done
1325173564Scperciva
1326173564Scperciva	# Any line in $2-values which doesn't appear in $1-values and is a
1327173564Scperciva	# file means that we should list the path in $3.
1328173564Scperciva	comm -13 $1-values $2-values |
1329173564Scperciva	    fgrep '|f|' |
1330173564Scperciva	    cut -f 1 -d '|' > $2-paths
1331173564Scperciva
1332173564Scperciva	# For each path, pull out one (and only one!) entry from $1-values.
1333173564Scperciva	# Note that we cannot distinguish which "old" version the user made
1334173564Scperciva	# changes to; but hopefully any changes which occur due to security
1335173564Scperciva	# updates will exist in both the "new" version and the version which
1336173564Scperciva	# the user has installed, so the merging will still work.
1337173564Scperciva	while read X; do
1338173564Scperciva		look "${X}|" $1-values |
1339173564Scperciva		    head -1
1340173564Scperciva	done < $2-paths > $3
1341173564Scperciva
1342173564Scperciva	# Clean up
1343173564Scperciva	rm $1-values $2-values $2-paths
1344173564Scperciva}
1345173564Scperciva
1346161748Scperciva# For any paths matching ${UPDATEIFUNMODIFIED}, remove lines from $[123]
1347173564Scperciva# which correspond to lines in $2 with hashes not matching $1 or $3, unless
1348173564Scperciva# the paths are listed in $4.  For entries in $2 marked "not present"
1349173564Scperciva# (aka. type -), remove lines from $[123] unless there is a corresponding
1350173564Scperciva# entry in $1.
1351161748Scpercivafetch_filter_unmodified_notpresent () {
1352161748Scperciva	# Figure out which lines of $1 and $3 correspond to bits which
1353161748Scperciva	# should only be updated if they haven't changed, and fish out
1354161748Scperciva	# the (path, type, value) tuples.
1355161748Scperciva	# NOTE: We don't consider a file to be "modified" if it matches
1356161748Scperciva	# the hash from $3.
1357161748Scperciva	for X in ${UPDATEIFUNMODIFIED}; do
1358161748Scperciva		grep -E "^${X}" $1
1359161748Scperciva		grep -E "^${X}" $3
1360161748Scperciva	done |
1361161748Scperciva	    cut -f 1,2,7 -d '|' |
1362161748Scperciva	    sort > $1-values
1363161748Scperciva
1364161748Scperciva	# Do the same for $2.
1365161748Scperciva	for X in ${UPDATEIFUNMODIFIED}; do
1366161748Scperciva		grep -E "^${X}" $2
1367161748Scperciva	done |
1368161748Scperciva	    cut -f 1,2,7 -d '|' |
1369161748Scperciva	    sort > $2-values
1370161748Scperciva
1371161748Scperciva	# Any entry in $2-values which is not in $1-values corresponds to
1372173564Scperciva	# a path which we need to remove from $1, $2, and $3, unless it
1373173564Scperciva	# that path appears in $4.
1374173564Scperciva	comm -13 $1-values $2-values |
1375173564Scperciva	    sort -t '|' -k 1,1 > mlines.tmp
1376173564Scperciva	cut -f 1 -d '|' $4 |
1377173564Scperciva	    sort |
1378173564Scperciva	    join -v 2 -t '|' - mlines.tmp |
1379173564Scperciva	    sort > mlines
1380173564Scperciva	rm $1-values $2-values mlines.tmp
1381161748Scperciva
1382161748Scperciva	# Any lines in $2 which are not in $1 AND are "not present" lines
1383161748Scperciva	# also belong in mlines.
1384161748Scperciva	comm -13 $1 $2 |
1385161748Scperciva	    cut -f 1,2,7 -d '|' |
1386161748Scperciva	    fgrep '|-|' >> mlines
1387161748Scperciva
1388161748Scperciva	# Remove lines from $1, $2, and $3
1389161748Scperciva	for X in $1 $2 $3; do
1390161748Scperciva		sort -t '|' -k 1,1 ${X} > ${X}.tmp
1391161748Scperciva		cut -f 1 -d '|' < mlines |
1392161748Scperciva		    sort |
1393161748Scperciva		    join -v 2 -t '|' - ${X}.tmp |
1394161748Scperciva		    sort > ${X}
1395161748Scperciva		rm ${X}.tmp
1396161748Scperciva	done
1397161748Scperciva
1398161748Scperciva	# Store a list of the modified files, for future reference
1399161748Scperciva	fgrep -v '|-|' mlines |
1400161748Scperciva	    cut -f 1 -d '|' > modifiedfiles
1401161748Scperciva	rm mlines
1402161748Scperciva}
1403161748Scperciva
1404161748Scperciva# For each entry in $1 of type -, remove any corresponding
1405161748Scperciva# entry from $2 if ${ALLOWADD} != "yes".  Remove all entries
1406161748Scperciva# of type - from $1.
1407161748Scpercivafetch_filter_allowadd () {
1408161748Scperciva	cut -f 1,2 -d '|' < $1 |
1409161748Scperciva	    fgrep '|-' |
1410161748Scperciva	    cut -f 1 -d '|' > filesnotpresent
1411161748Scperciva
1412161748Scperciva	if [ ${ALLOWADD} != "yes" ]; then
1413161748Scperciva		sort < $2 |
1414161748Scperciva		    join -v 1 -t '|' - filesnotpresent |
1415161748Scperciva		    sort > $2.tmp
1416161748Scperciva		mv $2.tmp $2
1417161748Scperciva	fi
1418161748Scperciva
1419161748Scperciva	sort < $1 |
1420161748Scperciva	    join -v 1 -t '|' - filesnotpresent |
1421161748Scperciva	    sort > $1.tmp
1422161748Scperciva	mv $1.tmp $1
1423161748Scperciva	rm filesnotpresent
1424161748Scperciva}
1425161748Scperciva
1426161748Scperciva# If ${ALLOWDELETE} != "yes", then remove any entries from $1
1427161748Scperciva# which don't correspond to entries in $2.
1428161748Scpercivafetch_filter_allowdelete () {
1429161748Scperciva	# Produce a lists ${PATH}|${TYPE}
1430161748Scperciva	for X in $1 $2; do
1431161748Scperciva		cut -f 1-2 -d '|' < ${X} |
1432161748Scperciva		    sort -u > ${X}.nodes
1433161748Scperciva	done
1434161748Scperciva
1435161748Scperciva	# Figure out which lines need to be removed from $1.
1436161748Scperciva	if [ ${ALLOWDELETE} != "yes" ]; then
1437161748Scperciva		comm -23 $1.nodes $2.nodes > $1.badnodes
1438161748Scperciva	else
1439161748Scperciva		: > $1.badnodes
1440161748Scperciva	fi
1441161748Scperciva
1442161748Scperciva	# Remove the relevant lines from $1
1443161748Scperciva	while read X; do
1444161748Scperciva		look "${X}|" $1
1445161748Scperciva	done < $1.badnodes |
1446161748Scperciva	    comm -13 - $1 > $1.tmp
1447161748Scperciva	mv $1.tmp $1
1448161748Scperciva
1449161748Scperciva	rm $1.badnodes $1.nodes $2.nodes
1450161748Scperciva}
1451161748Scperciva
1452161748Scperciva# If ${KEEPMODIFIEDMETADATA} == "yes", then for each entry in $2
1453161748Scperciva# with metadata not matching any entry in $1, replace the corresponding
1454161748Scperciva# line of $3 with one having the same metadata as the entry in $2.
1455161748Scpercivafetch_filter_modified_metadata () {
1456161748Scperciva	# Fish out the metadata from $1 and $2
1457161748Scperciva	for X in $1 $2; do
1458161748Scperciva		cut -f 1-6 -d '|' < ${X} > ${X}.metadata
1459161748Scperciva	done
1460161748Scperciva
1461161748Scperciva	# Find the metadata we need to keep
1462161748Scperciva	if [ ${KEEPMODIFIEDMETADATA} = "yes" ]; then
1463161748Scperciva		comm -13 $1.metadata $2.metadata > keepmeta
1464161748Scperciva	else
1465161748Scperciva		: > keepmeta
1466161748Scperciva	fi
1467161748Scperciva
1468161748Scperciva	# Extract the lines which we need to remove from $3, and
1469161748Scperciva	# construct the lines which we need to add to $3.
1470161748Scperciva	: > $3.remove
1471161748Scperciva	: > $3.add
1472161748Scperciva	while read LINE; do
1473161748Scperciva		NODE=`echo "${LINE}" | cut -f 1-2 -d '|'`
1474161748Scperciva		look "${NODE}|" $3 >> $3.remove
1475161748Scperciva		look "${NODE}|" $3 |
1476161748Scperciva		    cut -f 7- -d '|' |
1477161748Scperciva		    lam -s "${LINE}|" - >> $3.add
1478161748Scperciva	done < keepmeta
1479161748Scperciva
1480161748Scperciva	# Remove the specified lines and add the new lines.
1481161748Scperciva	sort $3.remove |
1482161748Scperciva	    comm -13 - $3 |
1483161748Scperciva	    sort -u - $3.add > $3.tmp
1484161748Scperciva	mv $3.tmp $3
1485161748Scperciva
1486161748Scperciva	rm keepmeta $1.metadata $2.metadata $3.add $3.remove
1487161748Scperciva}
1488161748Scperciva
1489161748Scperciva# Remove lines from $1 and $2 which are identical;
1490161748Scperciva# no need to update a file if it isn't changing.
1491161748Scpercivafetch_filter_uptodate () {
1492161748Scperciva	comm -23 $1 $2 > $1.tmp
1493161748Scperciva	comm -13 $1 $2 > $2.tmp
1494161748Scperciva
1495161748Scperciva	mv $1.tmp $1
1496161748Scperciva	mv $2.tmp $2
1497161748Scperciva}
1498161748Scperciva
1499173564Scperciva# Fetch any "clean" old versions of files we need for merging changes.
1500173564Scpercivafetch_files_premerge () {
1501173564Scperciva	# We only need to do anything if $1 is non-empty.
1502173564Scperciva	if [ -s $1 ]; then
1503173564Scperciva		# Tell the user what we're doing
1504173564Scperciva		echo -n "Fetching files from ${OLDRELNUM} for merging... "
1505173564Scperciva
1506173564Scperciva		# List of files wanted
1507173564Scperciva		fgrep '|f|' < $1 |
1508173564Scperciva		    cut -f 3 -d '|' |
1509173564Scperciva		    sort -u > files.wanted
1510173564Scperciva
1511173564Scperciva		# Only fetch the files we don't already have
1512173564Scperciva		while read Y; do
1513173564Scperciva			if [ ! -f "files/${Y}.gz" ]; then
1514173564Scperciva				echo ${Y};
1515173564Scperciva			fi
1516173564Scperciva		done < files.wanted > filelist
1517173564Scperciva
1518173564Scperciva		# Actually fetch them
1519173564Scperciva		lam -s "${OLDFETCHDIR}/f/" - -s ".gz" < filelist |
1520173564Scperciva		    xargs ${XARGST} ${PHTTPGET} ${SERVERNAME}	\
1521173564Scperciva		    2>${QUIETREDIR}
1522173564Scperciva
1523173564Scperciva		# Make sure we got them all, and move them into /files/
1524173564Scperciva		while read Y; do
1525173564Scperciva			if ! [ -f ${Y}.gz ]; then
1526173564Scperciva				echo "failed."
1527173564Scperciva				return 1
1528173564Scperciva			fi
1529173564Scperciva			if [ `gunzip -c < ${Y}.gz |
1530173564Scperciva			    ${SHA256} -q` = ${Y} ]; then
1531173564Scperciva				mv ${Y}.gz files/${Y}.gz
1532173564Scperciva			else
1533173564Scperciva				echo "${Y} has incorrect hash."
1534173564Scperciva				return 1
1535173564Scperciva			fi
1536173564Scperciva		done < filelist
1537173564Scperciva		echo "done."
1538173564Scperciva
1539173564Scperciva		# Clean up
1540173564Scperciva		rm filelist files.wanted
1541173564Scperciva	fi
1542173564Scperciva}
1543173564Scperciva
1544161748Scperciva# Prepare to fetch files: Generate a list of the files we need,
1545161748Scperciva# copy the unmodified files we have into /files/, and generate
1546161748Scperciva# a list of patches to download.
1547161748Scpercivafetch_files_prepare () {
1548161748Scperciva	# Tell the user why his disk is suddenly making lots of noise
1549161748Scperciva	echo -n "Preparing to download files... "
1550161748Scperciva
1551161748Scperciva	# Reduce indices to ${PATH}|${HASH} pairs
1552161748Scperciva	for X in $1 $2 $3; do
1553161748Scperciva		cut -f 1,2,7 -d '|' < ${X} |
1554161748Scperciva		    fgrep '|f|' |
1555161748Scperciva		    cut -f 1,3 -d '|' |
1556161748Scperciva		    sort > ${X}.hashes
1557161748Scperciva	done
1558161748Scperciva
1559161748Scperciva	# List of files wanted
1560161748Scperciva	cut -f 2 -d '|' < $3.hashes |
1561173441Scperciva	    sort -u |
1562173441Scperciva	    while read HASH; do
1563173441Scperciva		if ! [ -f files/${HASH}.gz ]; then
1564173441Scperciva			echo ${HASH}
1565173441Scperciva		fi
1566173441Scperciva	done > files.wanted
1567161748Scperciva
1568161748Scperciva	# Generate a list of unmodified files
1569161748Scperciva	comm -12 $1.hashes $2.hashes |
1570161748Scperciva	    sort -k 1,1 -t '|' > unmodified.files
1571161748Scperciva
1572161748Scperciva	# Copy all files into /files/.  We only need the unmodified files
1573161748Scperciva	# for use in patching; but we'll want all of them if the user asks
1574161748Scperciva	# to rollback the updates later.
1575171784Scperciva	while read LINE; do
1576171784Scperciva		F=`echo "${LINE}" | cut -f 1 -d '|'`
1577171784Scperciva		HASH=`echo "${LINE}" | cut -f 2 -d '|'`
1578171784Scperciva
1579171784Scperciva		# Skip files we already have.
1580171784Scperciva		if [ -f files/${HASH}.gz ]; then
1581171784Scperciva			continue
1582171784Scperciva		fi
1583171784Scperciva
1584171784Scperciva		# Make sure the file hasn't changed.
1585161748Scperciva		cp "${BASEDIR}/${F}" tmpfile
1586171784Scperciva		if [ `sha256 -q tmpfile` != ${HASH} ]; then
1587171784Scperciva			echo
1588171784Scperciva			echo "File changed while FreeBSD Update running: ${F}"
1589171784Scperciva			return 1
1590171784Scperciva		fi
1591171784Scperciva
1592171784Scperciva		# Place the file into storage.
1593171784Scperciva		gzip -c < tmpfile > files/${HASH}.gz
1594161748Scperciva		rm tmpfile
1595171784Scperciva	done < $2.hashes
1596161748Scperciva
1597161748Scperciva	# Produce a list of patches to download
1598161748Scperciva	sort -k 1,1 -t '|' $3.hashes |
1599161748Scperciva	    join -t '|' -o 2.2,1.2 - unmodified.files |
1600161748Scperciva	    fetch_make_patchlist > patchlist
1601161748Scperciva
1602161748Scperciva	# Garbage collect
1603161748Scperciva	rm unmodified.files $1.hashes $2.hashes $3.hashes
1604161748Scperciva
1605161748Scperciva	# We don't need the list of possible old files any more.
1606161748Scperciva	rm $1
1607161748Scperciva
1608161748Scperciva	# We're finished making noise
1609161748Scperciva	echo "done."
1610161748Scperciva}
1611161748Scperciva
1612161748Scperciva# Fetch files.
1613161748Scpercivafetch_files () {
1614161748Scperciva	# Attempt to fetch patches
1615161748Scperciva	if [ -s patchlist ]; then
1616161748Scperciva		echo -n "Fetching `wc -l < patchlist | tr -d ' '` "
1617161748Scperciva		echo ${NDEBUG} "patches.${DDSTATS}"
1618161748Scperciva		tr '|' '-' < patchlist |
1619173564Scperciva		    lam -s "${PATCHDIR}/" - |
1620161748Scperciva		    xargs ${XARGST} ${PHTTPGET} ${SERVERNAME}	\
1621161748Scperciva			2>${STATSREDIR} | fetch_progress
1622161748Scperciva		echo "done."
1623161748Scperciva
1624161748Scperciva		# Attempt to apply patches
1625161748Scperciva		echo -n "Applying patches... "
1626161748Scperciva		tr '|' ' ' < patchlist |
1627161748Scperciva		    while read X Y; do
1628161748Scperciva			if [ ! -f "${X}-${Y}" ]; then continue; fi
1629161748Scperciva			gunzip -c < files/${X}.gz > OLD
1630161748Scperciva
1631161748Scperciva			bspatch OLD NEW ${X}-${Y}
1632161748Scperciva
1633161748Scperciva			if [ `${SHA256} -q NEW` = ${Y} ]; then
1634161748Scperciva				mv NEW files/${Y}
1635161748Scperciva				gzip -n files/${Y}
1636161748Scperciva			fi
1637161748Scperciva			rm -f diff OLD NEW ${X}-${Y}
1638161748Scperciva		done 2>${QUIETREDIR}
1639161748Scperciva		echo "done."
1640161748Scperciva	fi
1641161748Scperciva
1642161748Scperciva	# Download files which couldn't be generate via patching
1643161748Scperciva	while read Y; do
1644161748Scperciva		if [ ! -f "files/${Y}.gz" ]; then
1645161748Scperciva			echo ${Y};
1646161748Scperciva		fi
1647161748Scperciva	done < files.wanted > filelist
1648161748Scperciva
1649161748Scperciva	if [ -s filelist ]; then
1650161748Scperciva		echo -n "Fetching `wc -l < filelist | tr -d ' '` "
1651161748Scperciva		echo ${NDEBUG} "files... "
1652161748Scperciva		lam -s "${FETCHDIR}/f/" - -s ".gz" < filelist |
1653161748Scperciva		    xargs ${XARGST} ${PHTTPGET} ${SERVERNAME}	\
1654161748Scperciva		    2>${QUIETREDIR}
1655161748Scperciva
1656161748Scperciva		while read Y; do
1657161748Scperciva			if ! [ -f ${Y}.gz ]; then
1658161748Scperciva				echo "failed."
1659161748Scperciva				return 1
1660161748Scperciva			fi
1661161748Scperciva			if [ `gunzip -c < ${Y}.gz |
1662161748Scperciva			    ${SHA256} -q` = ${Y} ]; then
1663161748Scperciva				mv ${Y}.gz files/${Y}.gz
1664161748Scperciva			else
1665161748Scperciva				echo "${Y} has incorrect hash."
1666161748Scperciva				return 1
1667161748Scperciva			fi
1668161748Scperciva		done < filelist
1669161748Scperciva		echo "done."
1670161748Scperciva	fi
1671161748Scperciva
1672161748Scperciva	# Clean up
1673161748Scperciva	rm files.wanted filelist patchlist
1674161748Scperciva}
1675161748Scperciva
1676161748Scperciva# Create and populate install manifest directory; and report what updates
1677161748Scperciva# are available.
1678161748Scpercivafetch_create_manifest () {
1679161748Scperciva	# If we have an existing install manifest, nuke it.
1680161748Scperciva	if [ -L "${BDHASH}-install" ]; then
1681161748Scperciva		rm -r ${BDHASH}-install/
1682161748Scperciva		rm ${BDHASH}-install
1683161748Scperciva	fi
1684161748Scperciva
1685161748Scperciva	# Report to the user if any updates were avoided due to local changes
1686161748Scperciva	if [ -s modifiedfiles ]; then
1687161748Scperciva		echo
1688161748Scperciva		echo -n "The following files are affected by updates, "
1689161748Scperciva		echo "but no changes have"
1690161748Scperciva		echo -n "been downloaded because the files have been "
1691161748Scperciva		echo "modified locally:"
1692161748Scperciva		cat modifiedfiles
1693173564Scperciva	fi | more
1694161748Scperciva	rm modifiedfiles
1695161748Scperciva
1696161748Scperciva	# If no files will be updated, tell the user and exit
1697161748Scperciva	if ! [ -s INDEX-PRESENT ] &&
1698161748Scperciva	    ! [ -s INDEX-NEW ]; then
1699161748Scperciva		rm INDEX-PRESENT INDEX-NEW
1700161748Scperciva		echo
1701161748Scperciva		echo -n "No updates needed to update system to "
1702161748Scperciva		echo "${RELNUM}-p${RELPATCHNUM}."
1703161748Scperciva		return
1704161748Scperciva	fi
1705161748Scperciva
1706161748Scperciva	# Divide files into (a) removed files, (b) added files, and
1707161748Scperciva	# (c) updated files.
1708161748Scperciva	cut -f 1 -d '|' < INDEX-PRESENT |
1709161748Scperciva	    sort > INDEX-PRESENT.flist
1710161748Scperciva	cut -f 1 -d '|' < INDEX-NEW |
1711161748Scperciva	    sort > INDEX-NEW.flist
1712161748Scperciva	comm -23 INDEX-PRESENT.flist INDEX-NEW.flist > files.removed
1713161748Scperciva	comm -13 INDEX-PRESENT.flist INDEX-NEW.flist > files.added
1714161748Scperciva	comm -12 INDEX-PRESENT.flist INDEX-NEW.flist > files.updated
1715161748Scperciva	rm INDEX-PRESENT.flist INDEX-NEW.flist
1716161748Scperciva
1717161748Scperciva	# Report removed files, if any
1718161748Scperciva	if [ -s files.removed ]; then
1719161748Scperciva		echo
1720161748Scperciva		echo -n "The following files will be removed "
1721161748Scperciva		echo "as part of updating to ${RELNUM}-p${RELPATCHNUM}:"
1722161748Scperciva		cat files.removed
1723173564Scperciva	fi | more
1724161748Scperciva	rm files.removed
1725161748Scperciva
1726161748Scperciva	# Report added files, if any
1727161748Scperciva	if [ -s files.added ]; then
1728161748Scperciva		echo
1729161748Scperciva		echo -n "The following files will be added "
1730161748Scperciva		echo "as part of updating to ${RELNUM}-p${RELPATCHNUM}:"
1731161748Scperciva		cat files.added
1732173564Scperciva	fi | more
1733161748Scperciva	rm files.added
1734161748Scperciva
1735161748Scperciva	# Report updated files, if any
1736161748Scperciva	if [ -s files.updated ]; then
1737161748Scperciva		echo
1738161748Scperciva		echo -n "The following files will be updated "
1739161748Scperciva		echo "as part of updating to ${RELNUM}-p${RELPATCHNUM}:"
1740161748Scperciva
1741161748Scperciva		cat files.updated
1742173564Scperciva	fi | more
1743161748Scperciva	rm files.updated
1744161748Scperciva
1745161748Scperciva	# Create a directory for the install manifest.
1746161748Scperciva	MDIR=`mktemp -d install.XXXXXX` || return 1
1747161748Scperciva
1748161748Scperciva	# Populate it
1749161748Scperciva	mv INDEX-PRESENT ${MDIR}/INDEX-OLD
1750161748Scperciva	mv INDEX-NEW ${MDIR}/INDEX-NEW
1751161748Scperciva
1752161748Scperciva	# Link it into place
1753161748Scperciva	ln -s ${MDIR} ${BDHASH}-install
1754161748Scperciva}
1755161748Scperciva
1756161748Scperciva# Warn about any upcoming EoL
1757161748Scpercivafetch_warn_eol () {
1758161748Scperciva	# What's the current time?
1759161748Scperciva	NOWTIME=`date "+%s"`
1760161748Scperciva
1761161748Scperciva	# When did we last warn about the EoL date?
1762161748Scperciva	if [ -f lasteolwarn ]; then
1763161748Scperciva		LASTWARN=`cat lasteolwarn`
1764161748Scperciva	else
1765161748Scperciva		LASTWARN=`expr ${NOWTIME} - 63072000`
1766161748Scperciva	fi
1767161748Scperciva
1768161748Scperciva	# If the EoL time is past, warn.
1769161748Scperciva	if [ ${EOLTIME} -lt ${NOWTIME} ]; then
1770161748Scperciva		echo
1771161748Scperciva		cat <<-EOF
1772161869Scperciva		WARNING: `uname -sr` HAS PASSED ITS END-OF-LIFE DATE.
1773161748Scperciva		Any security issues discovered after `date -r ${EOLTIME}`
1774161748Scperciva		will not have been corrected.
1775161748Scperciva		EOF
1776161748Scperciva		return 1
1777161748Scperciva	fi
1778161748Scperciva
1779161748Scperciva	# Figure out how long it has been since we last warned about the
1780161748Scperciva	# upcoming EoL, and how much longer we have left.
1781161748Scperciva	SINCEWARN=`expr ${NOWTIME} - ${LASTWARN}`
1782161748Scperciva	TIMELEFT=`expr ${EOLTIME} - ${NOWTIME}`
1783161748Scperciva
1784171838Scperciva	# Don't warn if the EoL is more than 3 months away
1785171838Scperciva	if [ ${TIMELEFT} -gt 7884000 ]; then
1786161748Scperciva		return 0
1787161748Scperciva	fi
1788161748Scperciva
1789161748Scperciva	# Don't warn if the time remaining is more than 3 times the time
1790161748Scperciva	# since the last warning.
1791161748Scperciva	if [ ${TIMELEFT} -gt `expr ${SINCEWARN} \* 3` ]; then
1792161748Scperciva		return 0
1793161748Scperciva	fi
1794161748Scperciva
1795161748Scperciva	# Figure out what time units to use.
1796161748Scperciva	if [ ${TIMELEFT} -lt 604800 ]; then
1797161748Scperciva		UNIT="day"
1798161748Scperciva		SIZE=86400
1799161748Scperciva	elif [ ${TIMELEFT} -lt 2678400 ]; then
1800161748Scperciva		UNIT="week"
1801161748Scperciva		SIZE=604800
1802161748Scperciva	else
1803161748Scperciva		UNIT="month"
1804161748Scperciva		SIZE=2678400
1805161748Scperciva	fi
1806161748Scperciva
1807161748Scperciva	# Compute the right number of units
1808161748Scperciva	NUM=`expr ${TIMELEFT} / ${SIZE}`
1809161748Scperciva	if [ ${NUM} != 1 ]; then
1810161748Scperciva		UNIT="${UNIT}s"
1811161748Scperciva	fi
1812161748Scperciva
1813161748Scperciva	# Print the warning
1814161748Scperciva	echo
1815161748Scperciva	cat <<-EOF
1816161748Scperciva		WARNING: `uname -sr` is approaching its End-of-Life date.
1817161748Scperciva		It is strongly recommended that you upgrade to a newer
1818161748Scperciva		release within the next ${NUM} ${UNIT}.
1819161748Scperciva	EOF
1820161748Scperciva
1821161748Scperciva	# Update the stored time of last warning
1822161748Scperciva	echo ${NOWTIME} > lasteolwarn
1823161748Scperciva}
1824161748Scperciva
1825161748Scperciva# Do the actual work involved in "fetch" / "cron".
1826161748Scpercivafetch_run () {
1827161748Scperciva	workdir_init || return 1
1828161748Scperciva
1829161748Scperciva	# Prepare the mirror list.
1830161748Scperciva	fetch_pick_server_init && fetch_pick_server
1831161748Scperciva
1832161748Scperciva	# Try to fetch the public key until we run out of servers.
1833161748Scperciva	while ! fetch_key; do
1834161748Scperciva		fetch_pick_server || return 1
1835161748Scperciva	done
1836161748Scperciva
1837161748Scperciva	# Try to fetch the metadata index signature ("tag") until we run
1838161748Scperciva	# out of available servers; and sanity check the downloaded tag.
1839161748Scperciva	while ! fetch_tag; do
1840161748Scperciva		fetch_pick_server || return 1
1841161748Scperciva	done
1842161748Scperciva	fetch_tagsanity || return 1
1843161748Scperciva
1844161748Scperciva	# Fetch the latest INDEX-NEW and INDEX-OLD files.
1845161748Scperciva	fetch_metadata INDEX-NEW INDEX-OLD || return 1
1846161748Scperciva
1847161748Scperciva	# Generate filtered INDEX-NEW and INDEX-OLD files containing only
1848161748Scperciva	# the lines which (a) belong to components we care about, and (b)
1849161748Scperciva	# don't correspond to paths we're explicitly ignoring.
1850161748Scperciva	fetch_filter_metadata INDEX-NEW || return 1
1851161748Scperciva	fetch_filter_metadata INDEX-OLD || return 1
1852161748Scperciva
1853173564Scperciva	# Translate /boot/${KERNCONF} into ${KERNELDIR}
1854173564Scperciva	fetch_filter_kernel_names INDEX-NEW ${KERNCONF}
1855173564Scperciva	fetch_filter_kernel_names INDEX-OLD ${KERNCONF}
1856161748Scperciva
1857161748Scperciva	# For all paths appearing in INDEX-OLD or INDEX-NEW, inspect the
1858161748Scperciva	# system and generate an INDEX-PRESENT file.
1859161748Scperciva	fetch_inspect_system INDEX-OLD INDEX-PRESENT INDEX-NEW || return 1
1860161748Scperciva
1861161748Scperciva	# Based on ${UPDATEIFUNMODIFIED}, remove lines from INDEX-* which
1862161748Scperciva	# correspond to lines in INDEX-PRESENT with hashes not appearing
1863161748Scperciva	# in INDEX-OLD or INDEX-NEW.  Also remove lines where the entry in
1864161748Scperciva	# INDEX-PRESENT has type - and there isn't a corresponding entry in
1865161748Scperciva	# INDEX-OLD with type -.
1866173564Scperciva	fetch_filter_unmodified_notpresent	\
1867173564Scperciva	    INDEX-OLD INDEX-PRESENT INDEX-NEW /dev/null
1868161748Scperciva
1869161748Scperciva	# For each entry in INDEX-PRESENT of type -, remove any corresponding
1870161748Scperciva	# entry from INDEX-NEW if ${ALLOWADD} != "yes".  Remove all entries
1871161748Scperciva	# of type - from INDEX-PRESENT.
1872161748Scperciva	fetch_filter_allowadd INDEX-PRESENT INDEX-NEW
1873161748Scperciva
1874161748Scperciva	# If ${ALLOWDELETE} != "yes", then remove any entries from
1875161748Scperciva	# INDEX-PRESENT which don't correspond to entries in INDEX-NEW.
1876161748Scperciva	fetch_filter_allowdelete INDEX-PRESENT INDEX-NEW
1877161748Scperciva
1878161748Scperciva	# If ${KEEPMODIFIEDMETADATA} == "yes", then for each entry in
1879161748Scperciva	# INDEX-PRESENT with metadata not matching any entry in INDEX-OLD,
1880161748Scperciva	# replace the corresponding line of INDEX-NEW with one having the
1881161748Scperciva	# same metadata as the entry in INDEX-PRESENT.
1882161748Scperciva	fetch_filter_modified_metadata INDEX-OLD INDEX-PRESENT INDEX-NEW
1883161748Scperciva
1884161748Scperciva	# Remove lines from INDEX-PRESENT and INDEX-NEW which are identical;
1885161748Scperciva	# no need to update a file if it isn't changing.
1886161748Scperciva	fetch_filter_uptodate INDEX-PRESENT INDEX-NEW
1887161748Scperciva
1888161748Scperciva	# Prepare to fetch files: Generate a list of the files we need,
1889161748Scperciva	# copy the unmodified files we have into /files/, and generate
1890161748Scperciva	# a list of patches to download.
1891171784Scperciva	fetch_files_prepare INDEX-OLD INDEX-PRESENT INDEX-NEW || return 1
1892161748Scperciva
1893161748Scperciva	# Fetch files.
1894161748Scperciva	fetch_files || return 1
1895161748Scperciva
1896161748Scperciva	# Create and populate install manifest directory; and report what
1897161748Scperciva	# updates are available.
1898161748Scperciva	fetch_create_manifest || return 1
1899161748Scperciva
1900161748Scperciva	# Warn about any upcoming EoL
1901161748Scperciva	fetch_warn_eol || return 1
1902161748Scperciva}
1903161748Scperciva
1904173564Scperciva# If StrictComponents is not "yes", generate a new components list
1905173564Scperciva# with only the components which appear to be installed.
1906173564Scpercivaupgrade_guess_components () {
1907173564Scperciva	if [ "${STRICTCOMPONENTS}" = "no" ]; then
1908173564Scperciva		# Generate filtered INDEX-ALL with only the components listed
1909173564Scperciva		# in COMPONENTS.
1910173564Scperciva		fetch_filter_metadata_components $1 || return 1
1911173564Scperciva
1912173564Scperciva		# Tell the user why his disk is suddenly making lots of noise
1913173564Scperciva		echo -n "Inspecting system... "
1914173564Scperciva
1915173564Scperciva		# Look at the files on disk, and assume that a component is
1916173564Scperciva		# supposed to be present if it is more than half-present.
1917173564Scperciva		cut -f 1-3 -d '|' < INDEX-ALL |
1918173564Scperciva		    tr '|' ' ' |
1919173564Scperciva		    while read C S F; do
1920173564Scperciva			if [ -e ${BASEDIR}/${F} ]; then
1921173564Scperciva				echo "+ ${C}|${S}"
1922173564Scperciva			fi
1923173564Scperciva			echo "= ${C}|${S}"
1924173564Scperciva		    done |
1925173564Scperciva		    sort |
1926173564Scperciva		    uniq -c |
1927173564Scperciva		    sed -E 's,^ +,,' > compfreq
1928173564Scperciva		grep ' = ' compfreq |
1929173564Scperciva		    cut -f 1,3 -d ' ' |
1930173564Scperciva		    sort -k 2,2 -t ' ' > compfreq.total
1931173564Scperciva		grep ' + ' compfreq |
1932173564Scperciva		    cut -f 1,3 -d ' ' |
1933173564Scperciva		    sort -k 2,2 -t ' ' > compfreq.present
1934173564Scperciva		join -t ' ' -1 2 -2 2 compfreq.present compfreq.total |
1935173564Scperciva		    while read S P T; do
1936173564Scperciva			if [ ${P} -gt `expr ${T} / 2` ]; then
1937173564Scperciva				echo ${S}
1938173564Scperciva			fi
1939173564Scperciva		    done > comp.present
1940173564Scperciva		cut -f 2 -d ' ' < compfreq.total > comp.total
1941173564Scperciva		rm INDEX-ALL compfreq compfreq.total compfreq.present
1942173564Scperciva
1943173564Scperciva		# We're done making noise.
1944173564Scperciva		echo "done."
1945173564Scperciva
1946173564Scperciva		# Sometimes the kernel isn't installed where INDEX-ALL
1947173564Scperciva		# thinks that it should be: In particular, it is often in
1948173564Scperciva		# /boot/kernel instead of /boot/GENERIC or /boot/SMP.  To
1949173564Scperciva		# deal with this, if "kernel|X" is listed in comp.total
1950173564Scperciva		# (i.e., is a component which would be upgraded if it is
1951173564Scperciva		# found to be present) we will add it to comp.present.
1952173564Scperciva		# If "kernel|<anything>" is in comp.total but "kernel|X" is
1953173564Scperciva		# not, we print a warning -- the user is running a kernel
1954173564Scperciva		# which isn't part of the release.
1955173564Scperciva		KCOMP=`echo ${KERNCONF} | tr 'A-Z' 'a-z'`
1956173564Scperciva		grep -E "^kernel\|${KCOMP}\$" comp.total >> comp.present
1957173564Scperciva
1958173564Scperciva		if grep -qE "^kernel\|" comp.total &&
1959173564Scperciva		    ! grep -qE "^kernel\|${KCOMP}\$" comp.total; then
1960173564Scperciva			cat <<-EOF
1961173564Scperciva
1962173564ScpercivaWARNING: This system is running a "${KCOMP}" kernel, which is not a
1963173564Scpercivakernel configuration distributed as part of FreeBSD ${RELNUM}.
1964173564ScpercivaThis kernel will not be updated: you MUST update the kernel manually
1965173564Scpercivabefore running "$0 install".
1966173564Scperciva			EOF
1967173564Scperciva		fi
1968173564Scperciva
1969173564Scperciva		# Re-sort the list of installed components and generate
1970173564Scperciva		# the list of non-installed components.
1971173564Scperciva		sort -u < comp.present > comp.present.tmp
1972173564Scperciva		mv comp.present.tmp comp.present
1973173564Scperciva		comm -13 comp.present comp.total > comp.absent
1974173564Scperciva
1975173564Scperciva		# Ask the user to confirm that what we have is correct.  To
1976173564Scperciva		# reduce user confusion, translate "X|Y" back to "X/Y" (as
1977173564Scperciva		# subcomponents must be listed in the configuration file).
1978173564Scperciva		echo
1979173564Scperciva		echo -n "The following components of FreeBSD "
1980173564Scperciva		echo "seem to be installed:"
1981173564Scperciva		tr '|' '/' < comp.present |
1982173564Scperciva		    fmt -72
1983173564Scperciva		echo
1984173564Scperciva		echo -n "The following components of FreeBSD "
1985173564Scperciva		echo "do not seem to be installed:"
1986173564Scperciva		tr '|' '/' < comp.absent |
1987173564Scperciva		    fmt -72
1988173564Scperciva		echo
1989173564Scperciva		continuep || return 1
1990173564Scperciva		echo
1991173564Scperciva
1992173564Scperciva		# Suck the generated list of components into ${COMPONENTS}.
1993173564Scperciva		# Note that comp.present.tmp is used due to issues with
1994173564Scperciva		# pipelines and setting variables.
1995173564Scperciva		COMPONENTS=""
1996173564Scperciva		tr '|' '/' < comp.present > comp.present.tmp
1997173564Scperciva		while read C; do
1998173564Scperciva			COMPONENTS="${COMPONENTS} ${C}"
1999173564Scperciva		done < comp.present.tmp
2000173564Scperciva
2001173564Scperciva		# Delete temporary files
2002173564Scperciva		rm comp.present comp.present.tmp comp.absent comp.total
2003173564Scperciva	fi
2004173564Scperciva}
2005173564Scperciva
2006173564Scperciva# If StrictComponents is not "yes", COMPONENTS contains an entry
2007173564Scperciva# corresponding to the currently running kernel, and said kernel
2008173564Scperciva# does not exist in the new release, add "kernel/generic" to the
2009173564Scperciva# list of components.
2010173564Scpercivaupgrade_guess_new_kernel () {
2011173564Scperciva	if [ "${STRICTCOMPONENTS}" = "no" ]; then
2012173564Scperciva		# Grab the unfiltered metadata file.
2013173564Scperciva		METAHASH=`look "$1|" tINDEX.present | cut -f 2 -d '|'`
2014173564Scperciva		gunzip -c < files/${METAHASH}.gz > $1.all
2015173564Scperciva
2016173564Scperciva		# If "kernel/${KCOMP}" is in ${COMPONENTS} and that component
2017173564Scperciva		# isn't in $1.all, we need to add kernel/generic.
2018173564Scperciva		for C in ${COMPONENTS}; do
2019173564Scperciva			if [ ${C} = "kernel/${KCOMP}" ] &&
2020173564Scperciva			    ! grep -qE "^kernel\|${KCOMP}\|" $1.all; then
2021173564Scperciva				COMPONENTS="${COMPONENTS} kernel/generic"
2022173564Scperciva				NKERNCONF="GENERIC"
2023173564Scperciva				cat <<-EOF
2024173564Scperciva
2025173564ScpercivaWARNING: This system is running a "${KCOMP}" kernel, which is not a
2026173564Scpercivakernel configuration distributed as part of FreeBSD ${RELNUM}.
2027173564ScpercivaAs part of upgrading to FreeBSD ${RELNUM}, this kernel will be
2028173564Scpercivareplaced with a "generic" kernel.
2029173564Scperciva				EOF
2030173564Scperciva				continuep || return 1
2031173564Scperciva			fi
2032173564Scperciva		done
2033173564Scperciva
2034173564Scperciva		# Don't need this any more...
2035173564Scperciva		rm $1.all
2036173564Scperciva	fi
2037173564Scperciva}
2038173564Scperciva
2039173564Scperciva# Convert INDEX-OLD (last release) and INDEX-ALL (new release) into
2040173564Scperciva# INDEX-OLD and INDEX-NEW files (in the sense of normal upgrades).
2041173564Scpercivaupgrade_oldall_to_oldnew () {
2042173564Scperciva	# For each ${F}|... which appears in INDEX-ALL but does not appear
2043173564Scperciva	# in INDEX-OLD, add ${F}|-|||||| to INDEX-OLD.
2044173564Scperciva	cut -f 1 -d '|' < $1 |
2045173564Scperciva	    sort -u > $1.paths
2046173564Scperciva	cut -f 1 -d '|' < $2 |
2047173564Scperciva	    sort -u |
2048173564Scperciva	    comm -13 $1.paths - |
2049173564Scperciva	    lam - -s "|-||||||" |
2050173564Scperciva	    sort - $1 > $1.tmp
2051173564Scperciva	mv $1.tmp $1
2052173564Scperciva
2053173564Scperciva	# Remove lines from INDEX-OLD which also appear in INDEX-ALL
2054173564Scperciva	comm -23 $1 $2 > $1.tmp
2055173564Scperciva	mv $1.tmp $1
2056173564Scperciva
2057173564Scperciva	# Remove lines from INDEX-ALL which have a file name not appearing
2058173564Scperciva	# anywhere in INDEX-OLD (since these must be files which haven't
2059173564Scperciva	# changed -- if they were new, there would be an entry of type "-").
2060173564Scperciva	cut -f 1 -d '|' < $1 |
2061173564Scperciva	    sort -u > $1.paths
2062173564Scperciva	sort -k 1,1 -t '|' < $2 |
2063173564Scperciva	    join -t '|' - $1.paths |
2064173564Scperciva	    sort > $2.tmp
2065173564Scperciva	rm $1.paths
2066173564Scperciva	mv $2.tmp $2
2067173564Scperciva
2068173564Scperciva	# Rename INDEX-ALL to INDEX-NEW.
2069173564Scperciva	mv $2 $3
2070173564Scperciva}
2071173564Scperciva
2072173564Scperciva# From the list of "old" files in $1, merge changes in $2 with those in $3,
2073173564Scperciva# and update $3 to reflect the hashes of merged files.
2074173564Scpercivaupgrade_merge () {
2075173564Scperciva	# We only need to do anything if $1 is non-empty.
2076173564Scperciva	if [ -s $1 ]; then
2077173564Scperciva		cut -f 1 -d '|' $1 |
2078173564Scperciva		    sort > $1-paths
2079173564Scperciva
2080173564Scperciva		# Create staging area for merging files
2081173564Scperciva		rm -rf merge/
2082173564Scperciva		while read F; do
2083173564Scperciva			D=`dirname ${F}`
2084173564Scperciva			mkdir -p merge/old/${D}
2085173564Scperciva			mkdir -p merge/${OLDRELNUM}/${D}
2086173564Scperciva			mkdir -p merge/${RELNUM}/${D}
2087173564Scperciva			mkdir -p merge/new/${D}
2088173564Scperciva		done < $1-paths
2089173564Scperciva
2090173564Scperciva		# Copy in files
2091173564Scperciva		while read F; do
2092173564Scperciva			# Currently installed file
2093173564Scperciva			V=`look "${F}|" $2 | cut -f 7 -d '|'`
2094173564Scperciva			gunzip < files/${V}.gz > merge/old/${F}
2095173564Scperciva
2096173564Scperciva			# Old release
2097173564Scperciva			if look "${F}|" $1 | fgrep -q "|f|"; then
2098173564Scperciva				V=`look "${F}|" $1 | cut -f 3 -d '|'`
2099173564Scperciva				gunzip < files/${V}.gz		\
2100173564Scperciva				    > merge/${OLDRELNUM}/${F}
2101173564Scperciva			fi
2102173564Scperciva
2103173564Scperciva			# New release
2104173564Scperciva			if look "${F}|" $3 | cut -f 1,2,7 -d '|' |
2105173564Scperciva			    fgrep -q "|f|"; then
2106173564Scperciva				V=`look "${F}|" $3 | cut -f 7 -d '|'`
2107173564Scperciva				gunzip < files/${V}.gz		\
2108173564Scperciva				    > merge/${RELNUM}/${F}
2109173564Scperciva			fi
2110173564Scperciva		done < $1-paths
2111173564Scperciva
2112173564Scperciva		# Attempt to automatically merge changes
2113173564Scperciva		echo -n "Attempting to automatically merge "
2114173564Scperciva		echo -n "changes in files..."
2115173564Scperciva		: > failed.merges
2116173564Scperciva		while read F; do
2117173564Scperciva			# If the file doesn't exist in the new release,
2118173564Scperciva			# the result of "merging changes" is having the file
2119173564Scperciva			# not exist.
2120173564Scperciva			if ! [ -f merge/${RELNUM}/${F} ]; then
2121173564Scperciva				continue
2122173564Scperciva			fi
2123173564Scperciva
2124173564Scperciva			# If the file didn't exist in the old release, we're
2125173564Scperciva			# going to throw away the existing file and hope that
2126173564Scperciva			# the version from the new release is what we want.
2127173564Scperciva			if ! [ -f merge/${OLDRELNUM}/${F} ]; then
2128173564Scperciva				cp merge/${RELNUM}/${F} merge/new/${F}
2129173564Scperciva				continue
2130173564Scperciva			fi
2131173564Scperciva
2132173564Scperciva			# Some files need special treatment.
2133173564Scperciva			case ${F} in
2134173564Scperciva			/etc/spwd.db | /etc/pwd.db | /etc/login.conf.db)
2135173564Scperciva				# Don't merge these -- we're rebuild them
2136173564Scperciva				# after updates are installed.
2137173564Scperciva				cp merge/old/${F} merge/new/${F}
2138173564Scperciva				;;
2139173564Scperciva			*)
2140173564Scperciva				if ! merge -p -L "current version"	\
2141173564Scperciva				    -L "${OLDRELNUM}" -L "${RELNUM}"	\
2142173564Scperciva				    merge/old/${F}			\
2143173564Scperciva				    merge/${OLDRELNUM}/${F}		\
2144173564Scperciva				    merge/${RELNUM}/${F}		\
2145173564Scperciva				    > merge/new/${F} 2>/dev/null; then
2146173564Scperciva					echo ${F} >> failed.merges
2147173564Scperciva				fi
2148173564Scperciva				;;
2149173564Scperciva			esac
2150173564Scperciva		done < $1-paths
2151173564Scperciva		echo " done."
2152173564Scperciva
2153173564Scperciva		# Ask the user to handle any files which didn't merge.
2154173564Scperciva		while read F; do
2155173564Scperciva			cat <<-EOF
2156173564Scperciva
2157173564ScpercivaThe following file could not be merged automatically: ${F}
2158173564ScpercivaPress Enter to edit this file in ${EDITOR} and resolve the conflicts
2159173564Scpercivamanually...
2160173564Scperciva			EOF
2161173564Scperciva			read dummy </dev/tty
2162173564Scperciva			${EDITOR} `pwd`/merge/new/${F} < /dev/tty
2163173564Scperciva		done < failed.merges
2164173564Scperciva		rm failed.merges
2165173564Scperciva
2166173564Scperciva		# Ask the user to confirm that he likes how the result
2167173564Scperciva		# of merging files.
2168173564Scperciva		while read F; do
2169173564Scperciva			# Skip files which haven't changed.
2170173564Scperciva			if [ -f merge/new/${F} ] &&
2171173564Scperciva			    cmp -s merge/old/${F} merge/new/${F}; then
2172173564Scperciva				continue
2173173564Scperciva			fi
2174173564Scperciva
2175173564Scperciva			# Warn about files which are ceasing to exist.
2176173564Scperciva			if ! [ -f merge/new/${F} ]; then
2177173564Scperciva				cat <<-EOF
2178173564Scperciva
2179173564ScpercivaThe following file will be removed, as it no longer exists in
2180173564ScpercivaFreeBSD ${RELNUM}: ${F}
2181173564Scperciva				EOF
2182173564Scperciva				continuep < /dev/tty || return 1
2183173564Scperciva				continue
2184173564Scperciva			fi
2185173564Scperciva
2186173564Scperciva			# Print changes for the user's approval.
2187173564Scperciva			cat <<-EOF
2188173564Scperciva
2189173564ScpercivaThe following changes, which occurred between FreeBSD ${OLDRELNUM} and
2190173564ScpercivaFreeBSD ${RELNUM} have been merged into ${F}:
2191173564ScpercivaEOF
2192173564Scperciva			diff -U 5 -L "current version" -L "new version"	\
2193173564Scperciva			    merge/old/${F} merge/new/${F} || true
2194173564Scperciva			continuep < /dev/tty || return 1
2195173564Scperciva		done < $1-paths
2196173564Scperciva
2197173564Scperciva		# Store merged files.
2198173564Scperciva		while read F; do
2199177527Scperciva			if [ -f merge/new/${F} ]; then
2200177527Scperciva				V=`${SHA256} -q merge/new/${F}`
2201173564Scperciva
2202173564Scperciva				gzip -c < merge/new/${F} > files/${V}.gz
2203173564Scperciva				echo "${F}|${V}"
2204173564Scperciva			fi
2205173564Scperciva		done < $1-paths > newhashes
2206173564Scperciva
2207173564Scperciva		# Pull lines out from $3 which need to be updated to
2208173564Scperciva		# reflect merged files.
2209173564Scperciva		while read F; do
2210173564Scperciva			look "${F}|" $3
2211173564Scperciva		done < $1-paths > $3-oldlines
2212173564Scperciva
2213173564Scperciva		# Update lines to reflect merged files
2214173564Scperciva		join -t '|' -o 1.1,1.2,1.3,1.4,1.5,1.6,2.2,1.8		\
2215173564Scperciva		    $3-oldlines newhashes > $3-newlines
2216173564Scperciva
2217173564Scperciva		# Remove old lines from $3 and add new lines.
2218173564Scperciva		sort $3-oldlines |
2219173564Scperciva		    comm -13 - $3 |
2220173564Scperciva		    sort - $3-newlines > $3.tmp
2221173564Scperciva		mv $3.tmp $3
2222173564Scperciva
2223173564Scperciva		# Clean up
2224173564Scperciva		rm $1-paths newhashes $3-oldlines $3-newlines
2225173564Scperciva		rm -rf merge/
2226173564Scperciva	fi
2227173564Scperciva
2228173564Scperciva	# We're done with merging files.
2229173564Scperciva	rm $1
2230173564Scperciva}
2231173564Scperciva
2232173564Scperciva# Do the work involved in fetching upgrades to a new release
2233173564Scpercivaupgrade_run () {
2234173564Scperciva	workdir_init || return 1
2235173564Scperciva
2236173564Scperciva	# Prepare the mirror list.
2237173564Scperciva	fetch_pick_server_init && fetch_pick_server
2238173564Scperciva
2239173564Scperciva	# Try to fetch the public key until we run out of servers.
2240173564Scperciva	while ! fetch_key; do
2241173564Scperciva		fetch_pick_server || return 1
2242173564Scperciva	done
2243173564Scperciva 
2244173564Scperciva	# Try to fetch the metadata index signature ("tag") until we run
2245173564Scperciva	# out of available servers; and sanity check the downloaded tag.
2246173564Scperciva	while ! fetch_tag; do
2247173564Scperciva		fetch_pick_server || return 1
2248173564Scperciva	done
2249173564Scperciva	fetch_tagsanity || return 1
2250173564Scperciva
2251173564Scperciva	# Fetch the INDEX-OLD and INDEX-ALL.
2252173564Scperciva	fetch_metadata INDEX-OLD INDEX-ALL || return 1
2253173564Scperciva
2254173564Scperciva	# If StrictComponents is not "yes", generate a new components list
2255173564Scperciva	# with only the components which appear to be installed.
2256173564Scperciva	upgrade_guess_components INDEX-ALL || return 1
2257173564Scperciva
2258173564Scperciva	# Generate filtered INDEX-OLD and INDEX-ALL files containing only
2259173564Scperciva	# the components we want and without anything marked as "Ignore".
2260173564Scperciva	fetch_filter_metadata INDEX-OLD || return 1
2261173564Scperciva	fetch_filter_metadata INDEX-ALL || return 1
2262173564Scperciva
2263173564Scperciva	# Merge the INDEX-OLD and INDEX-ALL files into INDEX-OLD.
2264173564Scperciva	sort INDEX-OLD INDEX-ALL > INDEX-OLD.tmp
2265173564Scperciva	mv INDEX-OLD.tmp INDEX-OLD
2266173564Scperciva	rm INDEX-ALL
2267173564Scperciva
2268173564Scperciva	# Adjust variables for fetching files from the new release.
2269173564Scperciva	OLDRELNUM=${RELNUM}
2270173564Scperciva	RELNUM=${TARGETRELEASE}
2271173564Scperciva	OLDFETCHDIR=${FETCHDIR}
2272173564Scperciva	FETCHDIR=${RELNUM}/${ARCH}
2273173564Scperciva
2274173564Scperciva	# Try to fetch the NEW metadata index signature ("tag") until we run
2275173564Scperciva	# out of available servers; and sanity check the downloaded tag.
2276173564Scperciva	while ! fetch_tag; do
2277173564Scperciva		fetch_pick_server || return 1
2278173564Scperciva	done
2279173564Scperciva
2280173564Scperciva	# Fetch the new INDEX-ALL.
2281173564Scperciva	fetch_metadata INDEX-ALL || return 1
2282173564Scperciva
2283173564Scperciva	# If StrictComponents is not "yes", COMPONENTS contains an entry
2284173564Scperciva	# corresponding to the currently running kernel, and said kernel
2285173564Scperciva	# does not exist in the new release, add "kernel/generic" to the
2286173564Scperciva	# list of components.
2287173564Scperciva	upgrade_guess_new_kernel INDEX-ALL || return 1
2288173564Scperciva
2289173564Scperciva	# Filter INDEX-ALL to contain only the components we want and without
2290173564Scperciva	# anything marked as "Ignore".
2291173564Scperciva	fetch_filter_metadata INDEX-ALL || return 1
2292173564Scperciva
2293173564Scperciva	# Convert INDEX-OLD (last release) and INDEX-ALL (new release) into
2294173564Scperciva	# INDEX-OLD and INDEX-NEW files (in the sense of normal upgrades).
2295173564Scperciva	upgrade_oldall_to_oldnew INDEX-OLD INDEX-ALL INDEX-NEW
2296173564Scperciva
2297173564Scperciva	# Translate /boot/${KERNCONF} or /boot/${NKERNCONF} into ${KERNELDIR}
2298173564Scperciva	fetch_filter_kernel_names INDEX-NEW ${NKERNCONF}
2299173564Scperciva	fetch_filter_kernel_names INDEX-OLD ${KERNCONF}
2300173564Scperciva
2301173564Scperciva	# For all paths appearing in INDEX-OLD or INDEX-NEW, inspect the
2302173564Scperciva	# system and generate an INDEX-PRESENT file.
2303173564Scperciva	fetch_inspect_system INDEX-OLD INDEX-PRESENT INDEX-NEW || return 1
2304173564Scperciva
2305173564Scperciva	# Based on ${MERGECHANGES}, generate a file tomerge-old with the
2306173564Scperciva	# paths and hashes of old versions of files to merge.
2307173564Scperciva	fetch_filter_mergechanges INDEX-OLD INDEX-PRESENT tomerge-old
2308173564Scperciva
2309173564Scperciva	# Based on ${UPDATEIFUNMODIFIED}, remove lines from INDEX-* which
2310173564Scperciva	# correspond to lines in INDEX-PRESENT with hashes not appearing
2311173564Scperciva	# in INDEX-OLD or INDEX-NEW.  Also remove lines where the entry in
2312173564Scperciva	# INDEX-PRESENT has type - and there isn't a corresponding entry in
2313173564Scperciva	# INDEX-OLD with type -.
2314173564Scperciva	fetch_filter_unmodified_notpresent	\
2315173564Scperciva	    INDEX-OLD INDEX-PRESENT INDEX-NEW tomerge-old
2316173564Scperciva
2317173564Scperciva	# For each entry in INDEX-PRESENT of type -, remove any corresponding
2318173564Scperciva	# entry from INDEX-NEW if ${ALLOWADD} != "yes".  Remove all entries
2319173564Scperciva	# of type - from INDEX-PRESENT.
2320173564Scperciva	fetch_filter_allowadd INDEX-PRESENT INDEX-NEW
2321173564Scperciva
2322173564Scperciva	# If ${ALLOWDELETE} != "yes", then remove any entries from
2323173564Scperciva	# INDEX-PRESENT which don't correspond to entries in INDEX-NEW.
2324173564Scperciva	fetch_filter_allowdelete INDEX-PRESENT INDEX-NEW
2325173564Scperciva
2326173564Scperciva	# If ${KEEPMODIFIEDMETADATA} == "yes", then for each entry in
2327173564Scperciva	# INDEX-PRESENT with metadata not matching any entry in INDEX-OLD,
2328173564Scperciva	# replace the corresponding line of INDEX-NEW with one having the
2329173564Scperciva	# same metadata as the entry in INDEX-PRESENT.
2330173564Scperciva	fetch_filter_modified_metadata INDEX-OLD INDEX-PRESENT INDEX-NEW
2331173564Scperciva
2332173564Scperciva	# Remove lines from INDEX-PRESENT and INDEX-NEW which are identical;
2333173564Scperciva	# no need to update a file if it isn't changing.
2334173564Scperciva	fetch_filter_uptodate INDEX-PRESENT INDEX-NEW
2335173564Scperciva
2336173564Scperciva	# Fetch "clean" files from the old release for merging changes.
2337173564Scperciva	fetch_files_premerge tomerge-old
2338173564Scperciva
2339173564Scperciva	# Prepare to fetch files: Generate a list of the files we need,
2340173564Scperciva	# copy the unmodified files we have into /files/, and generate
2341173564Scperciva	# a list of patches to download.
2342173564Scperciva	fetch_files_prepare INDEX-OLD INDEX-PRESENT INDEX-NEW || return 1
2343173564Scperciva
2344173564Scperciva	# Fetch patches from to-${RELNUM}/${ARCH}/bp/
2345173564Scperciva	PATCHDIR=to-${RELNUM}/${ARCH}/bp
2346173564Scperciva	fetch_files || return 1
2347173564Scperciva
2348173564Scperciva	# Merge configuration file changes.
2349173564Scperciva	upgrade_merge tomerge-old INDEX-PRESENT INDEX-NEW || return 1
2350173564Scperciva
2351173564Scperciva	# Create and populate install manifest directory; and report what
2352173564Scperciva	# updates are available.
2353173564Scperciva	fetch_create_manifest || return 1
2354173564Scperciva
2355173564Scperciva	# Leave a note behind to tell the "install" command that the kernel
2356173564Scperciva	# needs to be installed before the world.
2357173564Scperciva	touch ${BDHASH}-install/kernelfirst
2358173564Scperciva}
2359173564Scperciva
2360161748Scperciva# Make sure that all the file hashes mentioned in $@ have corresponding
2361161748Scperciva# gzipped files stored in /files/.
2362161748Scpercivainstall_verify () {
2363161748Scperciva	# Generate a list of hashes
2364161748Scperciva	cat $@ |
2365161748Scperciva	    cut -f 2,7 -d '|' |
2366161748Scperciva	    grep -E '^f' |
2367161748Scperciva	    cut -f 2 -d '|' |
2368161748Scperciva	    sort -u > filelist
2369161748Scperciva
2370161748Scperciva	# Make sure all the hashes exist
2371161748Scperciva	while read HASH; do
2372161748Scperciva		if ! [ -f files/${HASH}.gz ]; then
2373161748Scperciva			echo -n "Update files missing -- "
2374161748Scperciva			echo "this should never happen."
2375161748Scperciva			echo "Re-run '$0 fetch'."
2376161748Scperciva			return 1
2377161748Scperciva		fi
2378161748Scperciva	done < filelist
2379161748Scperciva
2380161748Scperciva	# Clean up
2381161748Scperciva	rm filelist
2382161748Scperciva}
2383161748Scperciva
2384161748Scperciva# Remove the system immutable flag from files
2385161748Scpercivainstall_unschg () {
2386161748Scperciva	# Generate file list
2387161748Scperciva	cat $@ |
2388161748Scperciva	    cut -f 1 -d '|' > filelist
2389161748Scperciva
2390161748Scperciva	# Remove flags
2391161748Scperciva	while read F; do
2392169603Scperciva		if ! [ -e ${BASEDIR}/${F} ]; then
2393161748Scperciva			continue
2394161748Scperciva		fi
2395161748Scperciva
2396169603Scperciva		chflags noschg ${BASEDIR}/${F} || return 1
2397161748Scperciva	done < filelist
2398161748Scperciva
2399161748Scperciva	# Clean up
2400161748Scperciva	rm filelist
2401161748Scperciva}
2402161748Scperciva
2403161748Scperciva# Install new files
2404161748Scpercivainstall_from_index () {
2405161748Scperciva	# First pass: Do everything apart from setting file flags.  We
2406161748Scperciva	# can't set flags yet, because schg inhibits hard linking.
2407161748Scperciva	sort -k 1,1 -t '|' $1 |
2408161748Scperciva	    tr '|' ' ' |
2409161748Scperciva	    while read FPATH TYPE OWNER GROUP PERM FLAGS HASH LINK; do
2410161748Scperciva		case ${TYPE} in
2411161748Scperciva		d)
2412161748Scperciva			# Create a directory
2413161748Scperciva			install -d -o ${OWNER} -g ${GROUP}		\
2414161748Scperciva			    -m ${PERM} ${BASEDIR}/${FPATH}
2415161748Scperciva			;;
2416161748Scperciva		f)
2417161748Scperciva			if [ -z "${LINK}" ]; then
2418161748Scperciva				# Create a file, without setting flags.
2419161748Scperciva				gunzip < files/${HASH}.gz > ${HASH}
2420161748Scperciva				install -S -o ${OWNER} -g ${GROUP}	\
2421161748Scperciva				    -m ${PERM} ${HASH} ${BASEDIR}/${FPATH}
2422161748Scperciva				rm ${HASH}
2423161748Scperciva			else
2424161748Scperciva				# Create a hard link.
2425169603Scperciva				ln -f ${BASEDIR}/${LINK} ${BASEDIR}/${FPATH}
2426161748Scperciva			fi
2427161748Scperciva			;;
2428161748Scperciva		L)
2429161748Scperciva			# Create a symlink
2430161748Scperciva			ln -sfh ${HASH} ${BASEDIR}/${FPATH}
2431161748Scperciva			;;
2432161748Scperciva		esac
2433161748Scperciva	    done
2434161748Scperciva
2435161748Scperciva	# Perform a second pass, adding file flags.
2436161748Scperciva	tr '|' ' ' < $1 |
2437161748Scperciva	    while read FPATH TYPE OWNER GROUP PERM FLAGS HASH LINK; do
2438161748Scperciva		if [ ${TYPE} = "f" ] &&
2439161748Scperciva		    ! [ ${FLAGS} = "0" ]; then
2440161748Scperciva			chflags ${FLAGS} ${BASEDIR}/${FPATH}
2441161748Scperciva		fi
2442161748Scperciva	    done
2443161748Scperciva}
2444161748Scperciva
2445161748Scperciva# Remove files which we want to delete
2446161748Scpercivainstall_delete () {
2447161748Scperciva	# Generate list of new files
2448161748Scperciva	cut -f 1 -d '|' < $2 |
2449161748Scperciva	    sort > newfiles
2450161748Scperciva
2451161748Scperciva	# Generate subindex of old files we want to nuke
2452161748Scperciva	sort -k 1,1 -t '|' $1 |
2453161748Scperciva	    join -t '|' -v 1 - newfiles |
2454164600Scperciva	    sort -r -k 1,1 -t '|' |
2455161748Scperciva	    cut -f 1,2 -d '|' |
2456161748Scperciva	    tr '|' ' ' > killfiles
2457161748Scperciva
2458161748Scperciva	# Remove the offending bits
2459161748Scperciva	while read FPATH TYPE; do
2460161748Scperciva		case ${TYPE} in
2461161748Scperciva		d)
2462161748Scperciva			rmdir ${BASEDIR}/${FPATH}
2463161748Scperciva			;;
2464161748Scperciva		f)
2465161748Scperciva			rm ${BASEDIR}/${FPATH}
2466161748Scperciva			;;
2467161748Scperciva		L)
2468161748Scperciva			rm ${BASEDIR}/${FPATH}
2469161748Scperciva			;;
2470161748Scperciva		esac
2471161748Scperciva	done < killfiles
2472161748Scperciva
2473161748Scperciva	# Clean up
2474161748Scperciva	rm newfiles killfiles
2475161748Scperciva}
2476161748Scperciva
2477173564Scperciva# Install new files, delete old files, and update linker.hints
2478173564Scpercivainstall_files () {
2479173564Scperciva	# If we haven't already dealt with the kernel, deal with it.
2480173564Scperciva	if ! [ -f $1/kerneldone ]; then
2481173564Scperciva		grep -E '^/boot/' $1/INDEX-OLD > INDEX-OLD
2482173564Scperciva		grep -E '^/boot/' $1/INDEX-NEW > INDEX-NEW
2483173564Scperciva
2484173564Scperciva		# Install new files
2485173564Scperciva		install_from_index INDEX-NEW || return 1
2486173564Scperciva
2487173564Scperciva		# Remove files which need to be deleted
2488173564Scperciva		install_delete INDEX-OLD INDEX-NEW || return 1
2489173564Scperciva
2490173564Scperciva		# Update linker.hints if necessary
2491173564Scperciva		if [ -s INDEX-OLD -o -s INDEX-NEW ]; then
2492173564Scperciva			kldxref -R /boot/ 2>/dev/null
2493173564Scperciva		fi
2494173564Scperciva
2495173564Scperciva		# We've finished updating the kernel.
2496173564Scperciva		touch $1/kerneldone
2497173564Scperciva
2498173564Scperciva		# Do we need to ask for a reboot now?
2499173564Scperciva		if [ -f $1/kernelfirst ] &&
2500173564Scperciva		    [ -s INDEX-OLD -o -s INDEX-NEW ]; then
2501173564Scperciva			cat <<-EOF
2502173564Scperciva
2503173564ScpercivaKernel updates have been installed.  Please reboot and run
2504173564Scperciva"$0 install" again to finish installing updates.
2505173564Scperciva			EOF
2506173564Scperciva			exit 0
2507173564Scperciva		fi
2508161748Scperciva	fi
2509173564Scperciva
2510173564Scperciva	# If we haven't already dealt with the world, deal with it.
2511173564Scperciva	if ! [ -f $1/worlddone ]; then
2512173564Scperciva		# Install new shared libraries next
2513173564Scperciva		grep -vE '^/boot/' $1/INDEX-NEW |
2514177601Scperciva		    grep -E '/lib/.*\.so\.[0-9]+\|' > INDEX-NEW
2515173564Scperciva		install_from_index INDEX-NEW || return 1
2516173564Scperciva
2517173564Scperciva		# Deal with everything else
2518173564Scperciva		grep -vE '^/boot/' $1/INDEX-OLD |
2519177601Scperciva		    grep -vE '/lib/.*\.so\.[0-9]+\|' > INDEX-OLD
2520173564Scperciva		grep -vE '^/boot/' $1/INDEX-NEW |
2521177601Scperciva		    grep -vE '/lib/.*\.so\.[0-9]+\|' > INDEX-NEW
2522173564Scperciva		install_from_index INDEX-NEW || return 1
2523173564Scperciva		install_delete INDEX-OLD INDEX-NEW || return 1
2524173564Scperciva
2525173564Scperciva		# Rebuild /etc/spwd.db and /etc/pwd.db if necessary.
2526173564Scperciva		if [ /etc/master.passwd -nt /etc/spwd.db ] ||
2527173564Scperciva		    [ /etc/master.passwd -nt /etc/pwd.db ]; then
2528173564Scperciva			pwd_mkdb /etc/master.passwd
2529173564Scperciva		fi
2530173564Scperciva
2531173564Scperciva		# Rebuild /etc/login.conf.db if necessary.
2532173564Scperciva		if [ /etc/login.conf -nt /etc/login.conf.db ]; then
2533173564Scperciva			cap_mkdb /etc/login.conf
2534173564Scperciva		fi
2535173564Scperciva
2536173564Scperciva		# We've finished installing the world and deleting old files
2537173564Scperciva		# which are not shared libraries.
2538173564Scperciva		touch $1/worlddone
2539173564Scperciva
2540173564Scperciva		# Do we need to ask the user to portupgrade now?
2541173564Scperciva		grep -vE '^/boot/' $1/INDEX-NEW |
2542177601Scperciva		    grep -E '/lib/.*\.so\.[0-9]+\|' |
2543173564Scperciva		    cut -f 1 -d '|' |
2544173564Scperciva		    sort > newfiles
2545173564Scperciva		if grep -vE '^/boot/' $1/INDEX-OLD |
2546177601Scperciva		    grep -E '/lib/.*\.so\.[0-9]+\|' |
2547173564Scperciva		    cut -f 1 -d '|' |
2548173564Scperciva		    sort |
2549173564Scperciva		    join -v 1 - newfiles |
2550173564Scperciva		    grep -q .; then
2551173564Scperciva			cat <<-EOF
2552173564Scperciva
2553173564ScpercivaCompleting this upgrade requires removing old shared object files.
2554173564ScpercivaPlease rebuild all installed 3rd party software (e.g., programs
2555173564Scpercivainstalled from the ports tree) and then run "$0 install"
2556173564Scpercivaagain to finish installing updates.
2557173564Scperciva			EOF
2558173564Scperciva			rm newfiles
2559173564Scperciva			exit 0
2560173564Scperciva		fi
2561173564Scperciva		rm newfiles
2562173564Scperciva	fi
2563173564Scperciva
2564173564Scperciva	# Remove old shared libraries
2565173564Scperciva	grep -vE '^/boot/' $1/INDEX-NEW |
2566177601Scperciva	    grep -E '/lib/.*\.so\.[0-9]+\|' > INDEX-NEW
2567173564Scperciva	grep -vE '^/boot/' $1/INDEX-OLD |
2568177601Scperciva	    grep -E '/lib/.*\.so\.[0-9]+\|' > INDEX-OLD
2569173564Scperciva	install_delete INDEX-OLD INDEX-NEW || return 1
2570173564Scperciva
2571173564Scperciva	# Remove temporary files
2572173564Scperciva	rm INDEX-OLD INDEX-NEW
2573161748Scperciva}
2574161748Scperciva
2575161748Scperciva# Rearrange bits to allow the installed updates to be rolled back
2576161748Scpercivainstall_setup_rollback () {
2577173564Scperciva	# Remove the "reboot after installing kernel", "kernel updated", and
2578173564Scperciva	# "finished installing the world" flags if present -- they are
2579173564Scperciva	# irrelevant when rolling back updates.
2580173564Scperciva	if [ -f ${BDHASH}-install/kernelfirst ]; then
2581173564Scperciva		rm ${BDHASH}-install/kernelfirst
2582173564Scperciva		rm ${BDHASH}-install/kerneldone
2583173564Scperciva	fi
2584173564Scperciva	if [ -f ${BDHASH}-install/worlddone ]; then
2585173564Scperciva		rm ${BDHASH}-install/worlddone
2586173564Scperciva	fi
2587173564Scperciva
2588161748Scperciva	if [ -L ${BDHASH}-rollback ]; then
2589161748Scperciva		mv ${BDHASH}-rollback ${BDHASH}-install/rollback
2590161748Scperciva	fi
2591161748Scperciva
2592161748Scperciva	mv ${BDHASH}-install ${BDHASH}-rollback
2593161748Scperciva}
2594161748Scperciva
2595161748Scperciva# Actually install updates
2596161748Scpercivainstall_run () {
2597161748Scperciva	echo -n "Installing updates..."
2598161748Scperciva
2599161748Scperciva	# Make sure we have all the files we should have
2600161748Scperciva	install_verify ${BDHASH}-install/INDEX-OLD	\
2601161748Scperciva	    ${BDHASH}-install/INDEX-NEW || return 1
2602161748Scperciva
2603161748Scperciva	# Remove system immutable flag from files
2604161748Scperciva	install_unschg ${BDHASH}-install/INDEX-OLD	\
2605161748Scperciva	    ${BDHASH}-install/INDEX-NEW || return 1
2606161748Scperciva
2607173564Scperciva	# Install new files, delete old files, and update linker.hints
2608173564Scperciva	install_files ${BDHASH}-install || return 1
2609161748Scperciva
2610161748Scperciva	# Rearrange bits to allow the installed updates to be rolled back
2611161748Scperciva	install_setup_rollback
2612161748Scperciva
2613161748Scperciva	echo " done."
2614161748Scperciva}
2615161748Scperciva
2616161748Scperciva# Rearrange bits to allow the previous set of updates to be rolled back next.
2617161748Scpercivarollback_setup_rollback () {
2618161748Scperciva	if [ -L ${BDHASH}-rollback/rollback ]; then
2619161748Scperciva		mv ${BDHASH}-rollback/rollback rollback-tmp
2620161748Scperciva		rm -r ${BDHASH}-rollback/
2621161748Scperciva		rm ${BDHASH}-rollback
2622161748Scperciva		mv rollback-tmp ${BDHASH}-rollback
2623161748Scperciva	else
2624161748Scperciva		rm -r ${BDHASH}-rollback/
2625161748Scperciva		rm ${BDHASH}-rollback
2626161748Scperciva	fi
2627161748Scperciva}
2628161748Scperciva
2629173564Scperciva# Install old files, delete new files, and update linker.hints
2630173564Scpercivarollback_files () {
2631173671Scperciva	# Install old shared library files which don't have the same path as
2632173671Scperciva	# a new shared library file.
2633173671Scperciva	grep -vE '^/boot/' $1/INDEX-NEW |
2634177601Scperciva	    grep -E '/lib/.*\.so\.[0-9]+\|' |
2635173671Scperciva	    cut -f 1 -d '|' |
2636173671Scperciva	    sort > INDEX-NEW.libs.flist
2637173564Scperciva	grep -vE '^/boot/' $1/INDEX-OLD |
2638177601Scperciva	    grep -E '/lib/.*\.so\.[0-9]+\|' |
2639173671Scperciva	    sort -k 1,1 -t '|' - |
2640173671Scperciva	    join -t '|' -v 1 - INDEX-NEW.libs.flist > INDEX-OLD
2641173564Scperciva	install_from_index INDEX-OLD || return 1
2642173564Scperciva
2643173564Scperciva	# Deal with files which are neither kernel nor shared library
2644173564Scperciva	grep -vE '^/boot/' $1/INDEX-OLD |
2645177601Scperciva	    grep -vE '/lib/.*\.so\.[0-9]+\|' > INDEX-OLD
2646173564Scperciva	grep -vE '^/boot/' $1/INDEX-NEW |
2647177601Scperciva	    grep -vE '/lib/.*\.so\.[0-9]+\|' > INDEX-NEW
2648173564Scperciva	install_from_index INDEX-OLD || return 1
2649173564Scperciva	install_delete INDEX-NEW INDEX-OLD || return 1
2650173564Scperciva
2651173671Scperciva	# Install any old shared library files which we didn't install above.
2652173671Scperciva	grep -vE '^/boot/' $1/INDEX-OLD |
2653177601Scperciva	    grep -E '/lib/.*\.so\.[0-9]+\|' |
2654173671Scperciva	    sort -k 1,1 -t '|' - |
2655173671Scperciva	    join -t '|' - INDEX-NEW.libs.flist > INDEX-OLD
2656173671Scperciva	install_from_index INDEX-OLD || return 1
2657173671Scperciva
2658173564Scperciva	# Delete unneeded shared library files
2659173564Scperciva	grep -vE '^/boot/' $1/INDEX-OLD |
2660177601Scperciva	    grep -E '/lib/.*\.so\.[0-9]+\|' > INDEX-OLD
2661173564Scperciva	grep -vE '^/boot/' $1/INDEX-NEW |
2662177601Scperciva	    grep -E '/lib/.*\.so\.[0-9]+\|' > INDEX-NEW
2663173564Scperciva	install_delete INDEX-NEW INDEX-OLD || return 1
2664173564Scperciva
2665173564Scperciva	# Deal with kernel files
2666173564Scperciva	grep -E '^/boot/' $1/INDEX-OLD > INDEX-OLD
2667173564Scperciva	grep -E '^/boot/' $1/INDEX-NEW > INDEX-NEW
2668173564Scperciva	install_from_index INDEX-OLD || return 1
2669173564Scperciva	install_delete INDEX-NEW INDEX-OLD || return 1
2670173564Scperciva	if [ -s INDEX-OLD -o -s INDEX-NEW ]; then
2671173564Scperciva		kldxref -R /boot/ 2>/dev/null
2672173564Scperciva	fi
2673173564Scperciva
2674173564Scperciva	# Remove temporary files
2675173672Scperciva	rm INDEX-OLD INDEX-NEW INDEX-NEW.libs.flist
2676173564Scperciva}
2677173564Scperciva
2678161748Scperciva# Actually rollback updates
2679161748Scpercivarollback_run () {
2680161748Scperciva	echo -n "Uninstalling updates..."
2681161748Scperciva
2682161748Scperciva	# If there are updates waiting to be installed, remove them; we
2683161748Scperciva	# want the user to re-run 'fetch' after rolling back updates.
2684161748Scperciva	if [ -L ${BDHASH}-install ]; then
2685161748Scperciva		rm -r ${BDHASH}-install/
2686161748Scperciva		rm ${BDHASH}-install
2687161748Scperciva	fi
2688161748Scperciva
2689161748Scperciva	# Make sure we have all the files we should have
2690161748Scperciva	install_verify ${BDHASH}-rollback/INDEX-NEW	\
2691161748Scperciva	    ${BDHASH}-rollback/INDEX-OLD || return 1
2692161748Scperciva
2693161748Scperciva	# Remove system immutable flag from files
2694161748Scperciva	install_unschg ${BDHASH}-rollback/INDEX-NEW	\
2695161748Scperciva	    ${BDHASH}-rollback/INDEX-OLD || return 1
2696161748Scperciva
2697173564Scperciva	# Install old files, delete new files, and update linker.hints
2698173564Scperciva	rollback_files ${BDHASH}-rollback || return 1
2699161748Scperciva
2700161748Scperciva	# Remove the rollback directory and the symlink pointing to it; and
2701161748Scperciva	# rearrange bits to allow the previous set of updates to be rolled
2702161748Scperciva	# back next.
2703161748Scperciva	rollback_setup_rollback
2704161748Scperciva
2705161748Scperciva	echo " done."
2706161748Scperciva}
2707161748Scperciva
2708161748Scperciva#### Main functions -- call parameter-handling and core functions
2709161748Scperciva
2710161748Scperciva# Using the command line, configuration file, and defaults,
2711161748Scperciva# set all the parameters which are needed later.
2712161748Scpercivaget_params () {
2713161748Scperciva	init_params
2714161748Scperciva	parse_cmdline $@
2715161748Scperciva	parse_conffile
2716161748Scperciva	default_params
2717161748Scperciva}
2718161748Scperciva
2719161748Scperciva# Fetch command.  Make sure that we're being called
2720161748Scperciva# interactively, then run fetch_check_params and fetch_run
2721161748Scpercivacmd_fetch () {
2722161748Scperciva	if [ ! -t 0 ]; then
2723161748Scperciva		echo -n "`basename $0` fetch should not "
2724161748Scperciva		echo "be run non-interactively."
2725161748Scperciva		echo "Run `basename $0` cron instead."
2726161748Scperciva		exit 1
2727161748Scperciva	fi
2728161748Scperciva	fetch_check_params
2729161748Scperciva	fetch_run || exit 1
2730161748Scperciva}
2731161748Scperciva
2732161748Scperciva# Cron command.  Make sure the parameters are sensible; wait
2733161748Scperciva# rand(3600) seconds; then fetch updates.  While fetching updates,
2734161748Scperciva# send output to a temporary file; only print that file if the
2735161748Scperciva# fetching failed.
2736161748Scpercivacmd_cron () {
2737161748Scperciva	fetch_check_params
2738161748Scperciva	sleep `jot -r 1 0 3600`
2739161748Scperciva
2740161748Scperciva	TMPFILE=`mktemp /tmp/freebsd-update.XXXXXX` || exit 1
2741161748Scperciva	if ! fetch_run >> ${TMPFILE} ||
2742161748Scperciva	    ! grep -q "No updates needed" ${TMPFILE} ||
2743161748Scperciva	    [ ${VERBOSELEVEL} = "debug" ]; then
2744161748Scperciva		mail -s "`hostname` security updates" ${MAILTO} < ${TMPFILE}
2745161748Scperciva	fi
2746161748Scperciva
2747161748Scperciva	rm ${TMPFILE}
2748161748Scperciva}
2749161748Scperciva
2750173564Scperciva# Fetch files for upgrading to a new release.
2751173564Scpercivacmd_upgrade () {
2752173564Scperciva	upgrade_check_params
2753173564Scperciva	upgrade_run || exit 1
2754173564Scperciva}
2755173564Scperciva
2756161748Scperciva# Install downloaded updates.
2757161748Scpercivacmd_install () {
2758161748Scperciva	install_check_params
2759161748Scperciva	install_run || exit 1
2760161748Scperciva}
2761161748Scperciva
2762161748Scperciva# Rollback most recently installed updates.
2763161748Scpercivacmd_rollback () {
2764161748Scperciva	rollback_check_params
2765161748Scperciva	rollback_run || exit 1
2766161748Scperciva}
2767161748Scperciva
2768161748Scperciva#### Entry point
2769161748Scperciva
2770161748Scperciva# Make sure we find utilities from the base system
2771161748Scpercivaexport PATH=/sbin:/bin:/usr/sbin:/usr/bin:${PATH}
2772161748Scperciva
2773163564Scperciva# Set LC_ALL in order to avoid problems with character ranges like [A-Z].
2774163564Scpercivaexport LC_ALL=C
2775163564Scperciva
2776161748Scpercivaget_params $@
2777161748Scpercivafor COMMAND in ${COMMANDS}; do
2778161748Scperciva	cmd_${COMMAND}
2779161748Scpercivadone
2780